Context Engineering 实战:别再往 context 里塞东西了
Context Engineering 实战别再往 context 里塞东西了为什么 token 塞满反而让 LLM 变蠢四种核心策略 Python 代码实现Agent 跑到第 15 步突然开始做蠢事。它把已经检查过的文件又检查了一遍给出了和第 3 步完全矛盾的结论还重复调用了两次同一个工具。我以为是模型问题换了更贵的模型没用。以为是 prompt 问题把 system prompt 改得更详细更没用。最后发现根本原因context 里塞了太多东西。那 15 步里积累的工具调用历史、中间状态、重复输出——把真正重要的信息彻底淹没了。这就是 context rot一个你在教程里很少听到但在生产环境里天天遇到的问题。本文说的就是怎么系统性地解决它。一、Context 不是暂存区是注意力预算很多工程师把 context window 当暂存区用——把所有可能有用的信息都塞进去让模型自己判断哪个重要。这个直觉是错的。1.1 为什么 context 越长模型越蠢Transformer 的注意力机制计算的是每个 token 对其他所有 token 的关系复杂度是 O(n²)。这意味着当 context 变长时模型需要在更多的 token pair 之间分配有限的注意力。Chroma 做了一个实测对 18 个主流 LLM含 GPT-4.1、Claude 3.7、Gemini 2.5做了 context 长度对准确率的影响测试。结论所有模型在 context 增长时性能都会下降没有例外。GPT-4.1 在 1K token 的 context 里准确率约 95%放大到 128K 时降到约 60%。这不是模型的 bug是注意力机制的数学决定的。1.2 Lost in the Middle中间位置最危险Stanford 的研究发现了一个规律模型对 context 开头和结尾的信息记忆更准中间部分最容易丢失。这个现象叫 “lost in the middle”。实际影响如果你把最重要的约束或状态放在对话历史的中间模型在长 context 下大概率会忽视它。1.3 四种典型的 context 失败模式Context Poisoning错误信息进入了 context。最危险的是工具调用返回了错误结果Agent 把它当作事实用于后续推理越跑越偏。Context Distraction过多的历史信息让 Agent 倾向于重复历史模式而不是根据当前状态重新推理。你会看到 Agent 在第 10 步记起第 2 步的做法并重复即使当时的状态已经完全不同了。Context Confusion塞了太多工具 schema 或文档内容模型在选工具时开始犹豫选错率上升。Context Clashcontext 里存在互相矛盾的信息比如第 3 步的结论和第 9 步的结论相反模型行为变得不可预测。二、四种 Context 管理策略LangChain 的 context engineering 博客里把策略分成四类我觉得这个框架非常实用Write、Select、Compress、Isolate。每种策略解决不同类型的问题。2.1 Write控制写进 context 的内容质量System prompt 的 altitude 问题Anthropic 的工程师用了一个好词altitude高度。太低了——system prompt 列了一堆具体规则Agent 在规则没覆盖到的边缘场景完全不知道怎么办。太高了——只写了你是一个有用的助手Agent 行为过于随意。工具返回结果的控制工具调用是 context 膨胀的最大来源。一次 API 调用可能返回 5000 字但 Agent 真正需要的可能只有 3 个字段。fromtypingimportAnydefcompress_tool_result(result:Any,tool_name:str)-str: 把工具返回结果压缩成 Agent 真正需要的内容 不要把原始 JSON 塞进 context iftool_namesearch_files:# 只保留文件路径和关键摘要丢弃原始内容ifisinstance(result,list):return\n.join([f-{item[path]}:{item.get(summary,item.get(content,)[:100])}foriteminresult[:10]# 最多 10 条])iftool_namerun_code:# 只保留最后 50 行输出截断中间linesstr(result).split(\n)iflen(lines)50:returnf[前{len(lines)-50}行已截断]\n\n.join(lines[-50:])returnstr(result)iftool_nameread_file:# 大文件只返回前后各 500 字符 行数contentstr(result)iflen(content)2000:return(f[文件共{len(content)}字符]\nf[前 500 字符]\n{content[:500]}\nf...\nf[后 500 字符]\n{content[-500:]})returncontent# 默认字符串化并截断returnstr(result)[:2000] 这个函数做的事很简单在工具结果进入 context 之前先过滤一遍。大多数教程里的 Agent 代码直接 str(tool_result) 塞进去这是 context 膨胀最常见的原因。### 2.2 Select决定什么放进 context不是所有历史对话都有用不是所有 few-shot 例子都需要。**动态 few-shot 选择器**静态 few-shot 的问题你放了5个例子实际上对当前请求有用的可能只有1个另外4个在白占 token。 pythonimportnumpyasnpfromsentence_transformersimportSentenceTransformerfromsklearn.metrics.pairwiseimportcosine_similarityclassDynamicFewShotSelector: 根据当前请求动态选择最相关的 few-shot 例子 用 embedding 相似度替代静态选择 def__init__(self,examples:list[dict],model_name:strall-MiniLM-L6-v2):self.examplesexamples self.modelSentenceTransformer(model_name)# 预计算所有例子的 embeddingtexts[ex[input]forexinexamples]self.embeddingsself.model.encode(texts)defselect(self,query:str,top_k:int3)-list[dict]:选择与 query 最相关的 top_k 个例子query_embeddingself.model.encode([query])similaritiescosine_similarity(query_embedding,self.embeddings)[0]# 按相似度排序top_indicesnp.argsort(similarities)[::-1][:top_k]return[self.examples[i]foriintop_indices]defformat_for_context(self,query:str,top_k:int3)-str:直接返回格式化好的 few-shot 文本selectedself.select(query,top_k)parts[]forexinselected:parts.append(fInput:{ex[input]}\nOutput:{ex[output]})return\n\n.join(parts) 实际上大多数场景用不到复杂的 embeddingBM25 检索已经够用了。关键是不要用静态的 few-shot 列表。### 2.3 Compress压缩现有 context这是实际工程里用得最多的策略。三种主要手段-**Summarization**用 LLM 把对话历史摘要成更短的文本--**Truncation**直接丢弃最早的消息最简单但会丢失重要状态--**Distillation**提取关键事实丢弃推理过程最精确成本最高 关键问题不是选哪种而是**什么时候触发压缩到多长**。应该基于 context 使用率触发而不是每 N 轮触发一次。 pythonimportanthropicfromdataclassesimportdataclassdataclassclassContextConfig:max_tokens:int100_000# context window 上限compress_threshold:float0.65# 超过 65% 时触发压缩target_ratio:float0.40# 压缩后目标是 40%summary_max_tokens:int400# summary 本身不超过 400 tokenpreserve_last_n:int6# 最近 N 条消息不压缩classContextManager: 生产可用的 Context 管理器 基于 token 使用率自动触发压缩 def__init__(self,config:ContextConfigNone):self.configconfigorContextConfig()self.clientanthropic.Anthropic()self.messages:list[dict][]self.current_token_count:int0defadd_message(self,role:str,content:str)-None:self.messages.append({role:role,content:content})# 粗估每个字符约 0.5 tokenself.current_token_countint(len(content)*0.5)# 检查是否需要压缩usage_ratioself.current_token_count/self.config.max_tokensifusage_ratioself.config.compress_threshold:self._compress()def_compress(self)-None:执行 sliding window summarizationiflen(self.messages)self.config.preserve_last_n:return# 分离出需要压缩的历史和需要保留的最近消息split_pointlen(self.messages)-self.config.preserve_last_n to_compressself.messages[:split_point]to_keepself.messages[split_point:]# 调用 LLM 生成摘要用便宜的模型compress_prompt(以下是对话历史请提取关键事实、决策和状态f压缩成不超过{self.config.summary_max_tokens}token 的摘要。只保留后续推理必需的信息丢弃推理过程。\n\n)history_text\n.join([f{m[role]}:{m[content]}forminto_compress])responseself.client.messages.create(modelclaude-3-5-haiku-20241022,# 用便宜的模型做压缩max_tokensself.config.summary_max_tokens,messages[{role:user,content:compress_prompthistory_text}])summaryresponse.content[0].text# 重建 messagessummary 最近 N 条self.messages[{role:system,content:f[对话历史摘要]\n{summary}},*to_keep]# 重新估算 token 数total_content .join([m[content]forminself.messages])self.current_token_countint(len(total_content)*0.5)defget_messages(self)-list[dict]:returnself.messages 有几个细节值得注意1.**用便宜模型做压缩**。claude-3-5-haiku 做 summarization 完全够用不需要用 Opus。2.2.**summary 的 token 上限**要控制严格否则你会发现压缩完了 summary 比原始内容还长。3.3.**最近 N 条不压缩**。刚发生的对话对当前推理最重要不要动它。### 2.4 Isolate给子任务一个干净的 context当 Agent 需要处理相对独立的子任务时最好给它一个全新的 context而不是带着父 Agent 的全部历史。 pythonimportasyncioimportanthropicclassIsolatedSubAgent: 每个子任务运行在独立的 context 中 父 Agent 只传递必要的任务描述不传递完整历史 def__init__(self,task_description:str,tools:listNone):self.clientanthropic.Anthropic()self.tasktask_description self.toolstoolsor[]# 子 Agent 有自己干净的 contextself.messages:list[dict][]asyncdefrun(self,max_steps:int10)-str: 运行子任务返回结果摘要 子 Agent 的全部历史不会泄漏到父 Agent self.messages[{role:user,content:self.task}]forstepinrange(max_steps):responseself.client.messages.create(modelclaude-opus-4-5,max_tokens4096,toolsself.tools,messagesself.messages)ifresponse.stop_reasonend_turn:# 只返回最终结果不是整个对话历史returnself._extract_result(response)# 处理工具调用...简化示意self.messages.append({role:assistant,content:response.content})return子任务达到步数上限def_extract_result(self,response)-str:从响应中提取关键结果丢弃推理过程text_blocks[b.textforbinresponse.contentifhasattr(b,text)]return\n.join(text_blocks)asyncdefrun_parallel_subtasks(parent_context:str,subtasks:list[str])-list[str]: 并行运行多个子任务每个都有独立的 context 父 Agent 的 context 不会传给子任务只传任务描述 agents[IsolatedSubAgent(task_descriptionf任务背景{parent_context}\n\n具体任务{task})fortaskinsubtasks]resultsawaitasyncio.gather(*[agent.run()foragentinagents])returnlist(results) Isolate 策略的核心思想子任务不需要知道父 Agent 的全部历史只需要知道它要做什么。把父 Agent 的全部 context 传给子 Agent 是最常见的架构错误之一。---## 三、一个决策树什么时候用哪种策略理论讲完了实际工程里你面对的问题是**现在应该做什么**当前 context 使用率 65%├── 否 → 继续监控└── 是 → 最近 N 步是否有重复/冗余工具调用├── 是 → Write 策略改进 compress_tool_result减少单次写入└── 否 → 任务是否可以分解成独立子任务├── 是 → Isolate 策略用 IsolatedSubAgent└── 否 → 历史消息是否超过 20 条├── 是 → Compress 策略触发 summarization└── 否 → Select 策略减少 few-shot / RAG 召回数量65% 这个阈值不是拍脑袋的——留 35% 的余量是为了给模型的推理和输出留空间。很多工程师等到 90% 才触发压缩这时候 context rot 已经发生了。四、生产环境的几个工程坑坑 1把压缩 token 当压缩信息Summary 写得太短会丢失关键状态。一个 Agent 在 30 步任务里可能有很多中间决策“决定不用方案 A 因为 X”、“发现文件 Y 已存在”如果 summary 只有 50 字这些状态全丢了。经验值每 10 条对话历史summary 保留 150-300 token。少于这个信息损失太大。坑 2忘记工具 schema 也占 token很多教程只统计对话消息的 token但工具定义本身也会占用大量 context。如果你注册了 20 个工具每个 schema 200 token光工具定义就占了 4000 token。做法动态注册工具。Agent 在不同阶段只激活当前阶段需要的工具而不是一次性注册所有工具。坑 3sub-agent 的 context 隔离破了常见错误父 Agent 把整个self.messages列表直接传给子 Agent。子 Agent 带着父 Agent 几千 token 的历史跑隔离完全没有意义。正确做法只传任务描述必要的输入数据任务描述用自然语言写清楚不要把对话历史序列化进去。坑 4Long-term Memory 的写时机错了很多实现是在每轮对话结束后都往 long-term memory 里写一遍导致 memory 里全是重复和噪音。写入时机应该是有新的、值得记住的状态变化时才写。比如发现了一个新的 API 端点、“确认了文件结构”而不是每次对话都写。小结Context Engineering 这个词最近很流行但它说的不是什么新技术本质是一种工程习惯像管理内存一样管理 context。具体到行动Write工具返回结果在进 context 前先过滤不要裸str(result)Selectfew-shot 用动态选择不用静态列表RAG 召回数量保守点Compress在 65% 使用率触发用便宜模型做 summarizesummary 保留 150-400 tokenIsolate子任务用独立 context只传任务描述不传历史最后一点不要等到 context 爆了再处理。context rot 是渐进的模型不会突然失效它会越来越蠢而你可能以为是别的问题。主动监控 context 健康度是 LLM 应用生产化里最容易被忽视的一个环节。参考资料Anthropic: Effective context engineering for AI agentsLangChain: Context Engineering for AgentsWeaviate: Context EngineeringJetBrains Research: Efficient Context Management (NeurIPS 2025)Karpathy, A. (2025). “Context Engineering is the new Prompt Engineering.” [Twitter/X]
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2631875.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!