上下文窗口动态调整的核心是在不超过模型token限制的前提下,最大化保留上下文的关键信息,避免因输入过长导致模型报错或回答质量下降。具体做法可分为“token监控与计算”“动态截断”“智能压缩”“模型适配”四大类,每类都有明确的实现逻辑和工具支持,以下详细说明:

一、基础:精准Token计数与实时监控

核心目标:实时计算上下文(对话历史、文档片段等)的token数,为后续调整提供依据。
具体做法

  1. 选择Token计算工具

    • 用模型原生的tokenizer(如OpenAI的tiktoken、Hugging Face的transformers库),确保计数与模型实际消耗一致。
    • 例:OpenAI模型用tiktoken计算token:
      import tiktoken
      
      def count_tokens(text: str, model: str = "gpt-3.5-turbo") -> int:
          encoder = tiktoken.encoding_for_model(model)
          return len(encoder.encode(text))
      
  2. 监控上下文总token

    • 对话场景:实时累加“用户输入+模型回复”的token数;
    • 文档场景:累加检索到的所有文档片段的token数;
    • 设置“安全阈值”(如模型最大token的80%,避免接近上限时的精度下降),例如GPT-3.5-turbo(4k token)的安全阈值设为3200。

二、动态截断:超限时优先保留关键信息

当上下文总token超过模型限制时,需按“重要性优先级”截断,确保核心信息不丢失。

1. 按“时间+重要性”混合截断(对话场景)

原理:最近的对话通常更重要,同时优先保留包含关键实体(如用户ID、任务目标)的历史。
具体操作

  • 用LangChain的ConversationTokenBufferMemory,它会:
    ① 按时间顺序保留对话历史;
    ② 当总token接近限制时,从最早的对话开始截断,直到符合token要求;
    ③ 可通过memory_key指定需优先保留的关键信息(如用户提到的“订单号”“预算”)。
  • 示例代码:
    from langchain.memory import ConversationTokenBufferMemory
    from langchain.chat_models import ChatOpenAI
    
    llm = ChatOpenAI(model_name="gpt-3.5-turbo", max_tokens=4096)
    memory = ConversationTokenBufferMemory(
        llm=llm,
        max_token_limit=3000,  # 保留3000 token(低于模型4k上限)
        memory_key="chat_history"  # 上下文键名
    )
    
    # 累加对话历史
    memory.save_context({"input": "我要订明天去北京的机票"}, {"output": "好的,请问乘客姓名?"})
    memory.save_context({"input": "张三,身份证110..."}, {"output": "已记录,需要靠窗位吗?"})
    # 当历史超过3000 token时,自动截断最早的对话
    
2. 按“语义相关性”截断(文档场景)

原理:从检索到的文档片段中,移除与用户问题相关性最低的块,保留高相关内容。
具体操作

  • 先按相似度排序检索到的文档块(Top-K);
  • 计算所有块的总token,若超限,从相似度最低的块开始移除,直到总token符合限制;
  • 示例:检索到5个文档块(总token 4500,模型上限4000),移除相似度最低的第5块(500 token),保留前4块(4000 token)。
3. 滑动窗口截断(固定保留最近N轮)

原理:只保留最近N轮对话或N个文档块,彻底截断更早的内容,适合对历史依赖低的场景。
具体操作

  • 对话场景:用ConversationBufferWindowMemory,通过k参数设置保留轮次(如k=5保留最近5轮);
  • 文档场景:检索时只取最近相关的N个块(如k=3),忽略更早的检索结果;
  • 示例:
    from langchain.memory import ConversationBufferWindowMemory
    memory = ConversationBufferWindowMemory(k=3)  # 只保留最近3轮对话
    

三、智能压缩:用摘要/提取减少token消耗

当截断会丢失关键信息时,通过压缩上下文(如生成摘要、提取关键实体)减少token数,同时保留核心内容。

1. 对话历史摘要压缩

原理:用LLM对早期对话生成摘要,替代原始长对话,大幅减少token。
具体操作

  • 用LangChain的ConversationSummaryMemory,它会:
    ① 实时将新对话与历史摘要拼接;
    ② 当总token接近限制时,调用LLM生成新摘要(覆盖早期历史);
  • 示例:
    from langchain.memory import ConversationSummaryMemory
    memory = ConversationSummaryMemory(llm=llm)  # 用llm生成摘要
    
    # 多轮对话后,memory中的历史会自动压缩为摘要
    memory.save_context({"input": "我要订机票..."}, {"output": "已记录..."})
    memory.save_context({"input": "修改日期..."}, {"output": "已修改..."})
    print(memory.load_memory_variables({})["history"])  # 输出:"用户最初要订机票,后修改了日期..."(摘要,token数远低于原始对话)
    
2. 文档块核心信息提取

原理:对长文档块,用LLM提取与问题相关的核心句子(如“故障原因”“解决步骤”),丢弃冗余描述。
具体操作

  • MapReduceDocumentsChainStuffDocumentsChain对文档块做精简:
    from langchain.chains.summarize import load_summarize_chain
    from langchain.docstore.document import Document
    
    # 假设检索到的长文档块
    long_chunks = [Document(page_content="...(500 token的故障排查内容)...")]
    # 提取核心信息(压缩为200 token)
    chain = load_summarize_chain(llm, chain_type="map_reduce", return_intermediate_steps=False)
    compressed_chunk = chain.run(long_chunks)  # 输出:"故障原因:1.传感器故障;2.散热堵塞。解决步骤:..."
    

四、模型适配:根据上下文长度动态切换模型

当上下文过长(如超过10k token),单一模型无法处理时,切换到更大窗口的模型,或拆分任务到多模型协作。

1. 按token长度自动切换模型

原理:预设模型切换阈值,短上下文用小模型(低成本),长上下文用大模型(高容量)。
具体操作

  • 示例逻辑:
    def get_llm_by_tokens(context_tokens: int):
        if context_tokens <= 4000:
            return ChatOpenAI(model_name="gpt-3.5-turbo", max_tokens=4096)  # 小模型
        elif context_tokens <= 32000:
            return ChatOpenAI(model_name="gpt-4-32k", max_tokens=32768)  # 中模型
        else:
            return ChatOpenAI(model_name="gpt-4-128k", max_tokens=131072)  # 大模型
    
    # 计算上下文token后切换模型
    context = "..."  # 拼接的对话历史+文档块
    context_tokens = count_tokens(context)
    llm = get_llm_by_tokens(context_tokens)
    
2. 长上下文拆分处理(超超大模型窗口)

原理:当上下文远超所有模型窗口(如100k token),将其拆分为多个子任务,分别处理后合并结果。
具体操作

  • RefineDocumentsChain分阶段处理:
    ① 先处理前N个块,生成初步结果;
    ② 用后续块“优化”初步结果,逐步累加信息;
  • 示例:处理100k token的书籍摘要,先处理前10k token生成部分摘要,再用接下来的10k token优化,直到覆盖全书。

五、兜底策略:用户交互与动态确认

当自动调整仍可能丢失关键信息时,通过用户确认决定保留内容,平衡准确性与token限制。
具体操作

  • 当上下文接近阈值时,弹出提示:“检测到您的对话历史较长,是否保留以下关键信息?[ ] 订单号 [ ] 预算 [ ] 之前的解决方案”;
  • 根据用户选择保留信息,截断未选中内容,确保用户主导关键信息的留存。

总结:动态调整的完整流程

  1. 监控:用tiktoken实时计算上下文总token;
  2. 判断:若低于安全阈值,直接输入模型;
  3. 调整:若超限,优先用“摘要压缩”保留核心,其次用“按重要性截断”,必要时“切换大模型”;
  4. 兜底:关键信息存疑时,通过用户确认补充。

通过这套策略,既能严格遵守模型的token限制,又能最大化保留上下文的价值,避免“因长度牺牲准确性”或“因超限导致失败”。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐