langchain 之 上下文窗口动态调整:适配模型 token 限制
上下文窗口动态调整的核心是在不超过模型token限制的前提下,最大化保留上下文的关键信息,避免因输入过长导致模型报错或回答质量下降。具体做法可分为“token监控与计算”“动态截断”“智能压缩”“模型适配”四大类,每类都有明确的实现逻辑和工具支持,以下详细说明:
一、基础:精准Token计数与实时监控
核心目标:实时计算上下文(对话历史、文档片段等)的token数,为后续调整提供依据。
具体做法:
-
选择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))
- 用模型原生的tokenizer(如OpenAI的
-
监控上下文总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提取与问题相关的核心句子(如“故障原因”“解决步骤”),丢弃冗余描述。
具体操作:
- 用
MapReduceDocumentsChain或StuffDocumentsChain对文档块做精简: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限制。
具体操作:
- 当上下文接近阈值时,弹出提示:“检测到您的对话历史较长,是否保留以下关键信息?[ ] 订单号 [ ] 预算 [ ] 之前的解决方案”;
- 根据用户选择保留信息,截断未选中内容,确保用户主导关键信息的留存。
总结:动态调整的完整流程
- 监控:用
tiktoken实时计算上下文总token; - 判断:若低于安全阈值,直接输入模型;
- 调整:若超限,优先用“摘要压缩”保留核心,其次用“按重要性截断”,必要时“切换大模型”;
- 兜底:关键信息存疑时,通过用户确认补充。
通过这套策略,既能严格遵守模型的token限制,又能最大化保留上下文的价值,避免“因长度牺牲准确性”或“因超限导致失败”。
更多推荐

所有评论(0)