上下文token消耗优化方案

随着用户聊天越来越多,可能一个对话能聊上上千条,那就不得不优化一下历史会话记录的输入了,不然太费token了。

这里只做滑动窗口方案和摘要提取方案,向量数据库方案等学到了补充。

把过程摘抄出来,方便复习

基础方案-滑动窗口

两种方案都是从最近的消息中截取n条,截取之后都要对数据的头部和尾部做合法清洗。
  1. 按照消息的长度截取
  2. 按照token的长度截取
js 复制代码
// 工具函数处理上下文截取
const MAX_HISTORY_LENGTH = 10;
export const sliceMessages = (
  messages,
  maxHistoryLength = MAX_HISTORY_LENGTH,
) => {
  let result = messages.slice(-maxHistoryLength);

  // 处理头部
  while (result.length > 0 && result[0].role === "tool") {
    result.shift(); // 移除数组第一个元素
  }

  if (result.length === 0) return result;

  // 处理尾部:openai要求的上下文格式,tool_calls后面必须跟着tool_calls长度个数的tool调用结果,不然调用大模型会报错400
  const lastOne = result[result.length - 1];

  const getRecentAssistant = (list, index) => {
    while (index >= 0) {
      if (list[index].role === "assistant") return index;
      index--;
    }
    return -1;
  };

  if (lastOne.role === "tool") {
    const assistantIdx = getRecentAssistant(result, result.length - 1);
    if (assistantIdx === -1) {
      while (result.length > 0 && result[result.length - 1].role === "tool") {
        result.pop();
      }
      return result;
    }
    const { tool_calls } = result[assistantIdx];
    if (tool_calls.length === result.length - 1 - assistantIdx) {
      return result;
    } else {
      return assistantIdx > 1 ? result.slice(0, assistantIdx - 1) : [];
    }
  } else if (lastOne.role === "assistant" && lastOne.tool_calls) {
    return result.slice(0, -1);
  }
  return result;
};
js 复制代码
// route文件中使用
const messages = [
    {
      role: "system",
      content: finalSystemPrompt,
    },
    ...sliceMessages(sessionHistory),
    {
      role: "user",
      content: message,
    },
  ];
// ...调用ai发起会话

中级方案-摘要提取

当历史记录条数超过阈值,截取前n条数据调用ai生成摘要,将摘要作为systemPrompt输入,剩下的记录作为历史消息喂给ai
js 复制代码
// controller 
// 生成或更新记忆摘要
const generateMemorySummary = async (oldSummary, messagesToCompress) => {
  const conversationText = messagesToCompress
    .map((item) => `${item.role}:${item.content || "调用了工具"}`)
    .join("\n");
  const summaryPrompt = `
    你是一个负责管理对话上下文的记忆助手。
    XXXXXXXXXXXXX 输入你的定制化需求
    
    【之前的摘要】: ${oldSummary || "无"}
    【最新对话记录】:
    ${conversationText}
  `;
  try {
    const response = await client.chat.completions.create({
      model: "deepseek-chat",
      messages: [{ role: "system", content: summaryPrompt }],
      temperature: 0.3, // 降低温度,保证摘要客观
    });
    return response.choices[0].message.content;
  } catch (err) {
    console.error("摘要生成失败:", err);
    return oldSummary;
  }
};
js 复制代码
// route 中使用
// 当历史消息超过20条时,压缩前10条
  const COMPRESS_THRESHOLD = 20;
  const COMPRESS_COUNT = 10;
  if (sessionHistory.length > COMPRESS_THRESHOLD) {

      // 截取需要压缩的历史消息
      const messagesToCompress = sessionHistory.slice(0, COMPRESS_COUNT); 

      // 调用ai生成新的摘要
      currentSummary = await generateMemorySummary(
        currentSummary,
        messagesToCompress,
      );
      // 将新的摘要写入数据库
      await updateSessionSummary(sessionId, currentSummary);
      // 删除已压缩的历史记录,异步操作,不要阻塞对话
      deleteCompressedMessages(
        sessionId,
        messagesToCompress.map((item) => item.id),
      ).catch((err) => {
        console.error(`异步删除压缩消息失败:${sessionId}: `, err);
      });
      // 更新当前内存里的历史记录
      sessionHistory = sessionHistory.slice(COMPRESS_COUNT);
  }

  // 摘要作为长期记忆,拼接到系统提示词
  const finalSystemPrompt = currentSummary
    ? `${systemPrompt}\n\n【关于用户的长期记忆】:\n${currentSummary}`
    : systemPrompt;
    
    
  const slicedMessages = sliceMessages(sessionHistory).map((item) => {
    const { id, ...rest } = item;
    return rest;
  });

  const messages = [
    {
      role: "system",
      content: finalSystemPrompt,
    },
    ...slicedMessages,
    {
      role: "user",
      content: message,
    },
  ];

  // xxxx会话内容
  

中级方案优化-摘要提取

当你提前截取前m条去提取摘要了,剩下的n-m条就要做数据合法清洗,那么中间必然要漏掉重要的业务数据,因此对截取索引做优化,保证信息不会遗漏
js 复制代码
// tools
// 动态寻找安全切割点, 防止一刀切,漏掉中间重要业务信息
export const getSafeSplitIndex = (messages, targetIndex) => {
  let safeIndex = targetIndex;

  if (safeIndex >= messages.length || safeIndex <= 0) return safeIndex;

  while (
    safeIndex >= 0 &&
    (messages[safeIndex].role === "tool" ||
      (messages[safeIndex - 1] &&
        messages[safeIndex - 1].role === "assistant" &&
        messages[safeIndex - 1].tool_calls))
  ) {
    safeIndex--;
  }

  return safeIndex;
};
js 复制代码
// route中使用
// 当历史消息超过20条时,压缩前10条
  const COMPRESS_THRESHOLD = 20;
  const COMPRESS_COUNT = 10;
  if (sessionHistory.length > COMPRESS_THRESHOLD) {
    // 获取安全的切割点
    const safeSplitIndex = getSafeSplitIndex(sessionHistory, COMPRESS_COUNT);

    if (safeSplitIndex > 0) {
      // 截取需要压缩的历史消息
      const messagesToCompress = sessionHistory.slice(0, safeSplitIndex);

      // 调用ai生成新的摘要
      currentSummary = await generateMemorySummary(
        currentSummary,
        messagesToCompress,
      );
      // 将新的摘要写入数据库
      await updateSessionSummary(sessionId, currentSummary);
      // 删除已压缩的历史记录
      deleteCompressedMessages(
        sessionId,
        messagesToCompress.map((item) => item.id),
      ).catch((err) => {
        console.error(`异步删除压缩消息失败:${sessionId}: `, err);
      });
      // 更新当前内存里的历史记录
      sessionHistory = sessionHistory.slice(safeSplitIndex);
    }
  }

  // 摘要作为长期记忆,拼接到系统提示词
  const finalSystemPrompt = currentSummary
    ? `${systemPrompt}\n\n【关于用户的长期记忆】:\n${currentSummary}`
    : systemPrompt;
 
  const messages = [
    {
      role: "system",
      content: finalSystemPrompt,
    },
    ...sessionHistory.map((item) => {
      const { id, ...rest } = item;
      return rest;
    }),
    {
      role: "user",
      content: message,
    },
  ];

终级方案-向量数据库增强检索

未完待续
相关推荐
程序员陆业聪7 小时前
今年春节AI圈很热闹,但我还是怀念去年DeepSeek炸场的那个夜晚
openai
win4r1 天前
🚀OpenClaw高级进阶技巧分享!模型精选策略+记忆系统优化经验+深度搜索集成+Gateway崩溃自动修复!Claude Code自动读日志修Bug重启验证
aigc·openai·ai编程
玹外之音1 天前
Spring AI MCP 之 SSE WebFlux 实战:从零构建 AI 天气助手
spring·openai
lhxcc_fly1 天前
0.LangChain--大模型篇
langchain·大模型·llm·openai·deepseek
玹外之音2 天前
Spring AI 工具调用实战:手把手教你让 AI 拥有"超能力"
spring·openai
玹外之音5 天前
Spring AI 结构化输出转换器实战:告别字符串解析,拥抱类型安全
spring·openai
树獭叔叔5 天前
📉 大模型量化 (Quantization) 全维度解析:从哲学到算力
后端·aigc·openai
Jing_Rainbow5 天前
【AI-27 React-13/Lesson98(2026-01-07)】Ollama 本地大模型部署与前端集成指南🤖
aigc·openai·ai编程
泯泷6 天前
提示工程的悖论:为什么与 AI 对话比你想象的更难
人工智能·后端·openai