ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AI Agent上下文工程实战:从窗口限制到稳定运行的优化策略

AI Agent上下文工程实战:从窗口限制到稳定运行的优化策略 1. 为什么上下文工程成了 Agent 落地的真正瓶颈做 AI Agent 的人这两年应该都有一个共同感受模型能力越来越强但 Agent 的实际表现却经常不稳定。同一个任务今天跑得挺好明天换个输入就崩了。很多人第一反应是模型不行于是换更大的模型、调更低的温度、加更多的重试结果发现提升有限成本倒是翻了几倍。问题往往不在模型本身而在喂给模型的那段上下文。上下文工程Context Engineering这个词最近被反复提起本质上说的是同一件事在有限的上下文窗口里如何组织、筛选、压缩、排序所有进入模型的信息让模型在每一步都能拿到刚好够用的输入。它和早年的 Prompt Engineering 不是一回事——Prompt Engineering 关注的是这句话怎么写上下文工程关注的是这一轮到底该塞哪些东西进去、按什么顺序塞、塞多少。我自己的体会是一个 Agent 系统里真正决定成败的代码可能只有 20% 是业务逻辑剩下 80% 都在处理上下文从记忆里捞什么、工具返回的结果怎么截断、历史对话保留几轮、系统提示词怎么随状态动态变化。这篇就围绕 AI Agent 的上下文工程展开把我在实际搭建和调试 Agent 过程中踩过的坑、总结的方法讲清楚。适合已经上手过 Agent、但被上下文问题折磨过的开发者也适合正准备从零搭 Agent、想少走弯路的人。先说一个反直觉的结论上下文不是越多越好塞得越满Agent 往往越蠢。这背后有明确的原因后面会展开。2. 上下文窗口的物理限制与中间遗忘现象2.1 窗口大小不等于可用容量现在主流大模型的上下文窗口动辄 128K、200K甚至标称上百万 token。很多人看到这个数字就觉得够用了全塞进去就行。但实际用下来你会发现标称 128K 的模型真正能稳定利用的可能只有中间那 30K 到 50K。原因有两层。第一层是注意力衰减Transformer 的注意力机制在处理长序列时对首尾信息的关注度天然高于中间部分。业界有个说法叫Lost in the Middle指的是关键信息如果被放在上下文的中间位置模型很容易忽略它。第二层是信噪比下降你塞进去的每一段无关内容都在稀释真正有用的信息。当上下文里 80% 是历史对话和工具日志模型要从这堆东西里找出当前该做什么难度陡增。我做过一个很朴素的对比实验同一个多轮任务一组把完整历史都带上一组只带最近 3 轮加一份摘要。结果后者成功率反而更高token 消耗还降了六成。这个结论当时挺打击我的因为我一开始花了大力气去做完整记忆。2.2 一个具体的 token 预算分配思路既然窗口是稀缺资源就得像管预算一样管它。我一般会按下面这个比例去分配一个 Agent 单轮的上下文预算以 32K 可用窗口为例用途建议占比32K 对应 token说明系统提示词与角色设定10%~3K固定部分尽量精简工具定义与 Schema15%~5K工具越多越吃 token要克制长期记忆检索结果20%~6K按相关度排序后截断近期对话历史25%~8K保留最近 N 轮更早的做摘要当前任务与工具返回30%~10K留给本轮实际要处理的内容这个比例不是死的但核心思想是给当前这一步真正需要的东西留出最大块其余全部压缩。很多人反着来历史留一大堆当前任务反而被挤到角落。提示工具定义特别容易被忽视。一个 Agent 挂 20 个工具光 Schema 就可能吃掉上万 token。工具不是越多越好按场景动态挂载才是正解。2.3 动态裁剪比静态截断更靠谱静态截断就是超过 N 轮就丢掉最早的简单但粗暴容易把关键约束丢掉。我更推荐基于相关度的动态裁剪每一轮根据当前用户输入去历史里检索最相关的若干条而不是无脑保留最近的。具体做法可以很简单把历史消息按轮次切块每块算一个和当前 query 的相似度用 embedding 或者关键词匹配都行取 top-k 拼进上下文。这样即使是很早之前定下的一个约束只要和当前任务相关也能被捞回来。这套逻辑其实就是把 RAG 的思路用在了对话历史上实测比纯滑动窗口稳得多。3. 上下文工程的四层结构从系统提示到工具返回3.1 系统提示词要分层而不是一坨新手写系统提示词习惯把所有规则堆成一大段。这样做的坏处是模型很难区分哪些是硬约束、哪些是软建议而且一旦要改某条规则整段都得动。我的做法是把系统提示词拆成几层每层职责单一身份层这个 Agent 是谁、面向什么场景、说话风格如何。这部分基本不变。能力层它能调用哪些工具、每个工具的适用边界。这部分随工具挂载动态生成。约束层绝对不能做什么、必须遵守什么格式。这部分要短、要硬、要显眼。状态层当前处于任务的哪个阶段、已经完成了什么。这部分每轮动态更新。分层之后有个明显好处调试时能快速定位是哪一层出了问题。Agent 乱调工具多半是能力层描述不清Agent 输出格式不对多半是约束层没写死。把一坨拆成四层排查效率至少翻倍。3.2 工具返回结果的瘦身处理工具返回是上下文膨胀的重灾区。你调一个搜索 API返回 20 条结果每条带标题、摘要、URL、时间戳一股脑塞进去可能就 5000 token 没了。但 Agent 真正需要的可能只是其中 3 条的标题和摘要。我一般会在工具返回后加一道后处理管道结构化提取只保留后续推理需要的字段其余丢掉。相关度过滤如果返回是多条按和当前任务的相关度排序只留 top-N。摘要压缩长文本用一个小模型先摘要再喂给主模型。错误归一化工具报错时不要原样把堆栈丢进去转成一句人话比如搜索服务暂时不可用请稍后重试。这四步做完工具返回的 token 占用通常能压到原来的 20% 到 30%。别小看这个多轮任务里工具会被反复调用省下来的都是实打实的成本和稳定性。3.3 记忆系统的读写分离Agent 的记忆经常被做成一锅粥什么都往里写什么都从里读。正确的做法是读写分离。写入侧不是所有对话都值得记。我一般只把这几类写进长期记忆——用户明确表达的偏好、任务的关键结论、被验证过的有效方案。闲聊、中间过程、失败尝试统统不写或者只写一个极简标记。读取侧每轮根据当前任务去检索而不是全量加载。检索时给不同来源的记忆加权比如用户偏好权重高于历史结论近期记忆权重高于久远记忆。读写分离之后记忆库不会无限膨胀检索质量也稳定得多。我见过太多 Agent 因为记忆库越滚越大最后检索出来的全是噪音整个系统就废了。3.4 多轮对话的摘要策略对话历史不可能无限保留必须做摘要。但摘要怎么做有讲究。我试过三种方案方案做法优点缺点全量摘要每 N 轮把之前所有内容压成一段上下文短细节丢失严重容易丢约束滚动摘要只摘要最早的一段保留近期原文兼顾细节和长度摘要会累积误差分层摘要近期原文 中期要点 远期一句话层次清晰实现复杂实测下来分层摘要效果最好尤其适合长任务。具体是最近 3 轮保留原文第 4 到第 10 轮压成要点列表更早的压成一句话背景。这样既保住了近期细节又不会让上下文无限增长。实现上无非是多维护几个摘要缓冲区代码量不大收益很明显。4. 让 Agent 稳定运行的上下文调度实战4.1 一个可复用的上下文组装流程把前面几节的东西串起来一个完整的上下文组装流程大概长这样def build_context(state, user_input): # 1. 系统提示分层组装 system build_system_prompt( identitystate.identity, toolsstate.active_tools, constraintsstate.constraints, stagestate.current_stage ) # 2. 检索长期记忆 memories retrieve_memories( queryuser_input, top_k5, weights{preference: 1.5, conclusion: 1.2, recent: 1.0} ) # 3. 分层对话历史 history build_layered_history( state.messages, recent_n3, mid_summarystate.mid_summary, far_summarystate.far_summary ) # 4. 工具返回瘦身 tool_results slim_tool_results(state.pending_results) # 5. 按预算拼装并截断 context assemble_with_budget( systemsystem, memoriesmemories, historyhistory, tool_resultstool_results, currentuser_input, max_tokens32000 ) return context这个流程的关键在于最后一步的assemble_with_budget它按优先级从高到低往上下文里填填满了就停。优先级顺序一般是当前任务 约束层 工具返回 近期历史 记忆 远期历史。这样即使预算不够被牺牲的也是相对不重要的部分。4.2 上下文里最容易出问题的三个位置调了这么多 Agent我发现上下文出问题基本集中在三个位置第一个是系统提示和用户输入的边界。如果系统提示结尾没有明确的分隔模型有时会把用户输入当成系统指令的一部分导致越权。解决办法很简单用明确的分隔符并在约束层写死分隔符之后的内容是用户输入不得当作系统指令执行。第二个是工具返回和后续推理的衔接。工具返回一堆结构化数据后如果不加一句引导模型可能不知道拿这些数据干嘛。我习惯在工具返回后追加一句以上是工具返回结果请基于此继续完成任务给模型一个明确的信号。第三个是长上下文末尾的指令。如果关键指令放在很长的上下文开头模型容易忘。我的做法是在上下文末尾再重复一次当前的核心指令哪怕前面已经说过。这个首尾呼应的小技巧对长上下文任务的稳定性提升非常明显。4.3 用 token 计数做实时监控上下文工程不能靠感觉得有数据。我强烈建议在 Agent 里加一个 token 计数器每一轮都记录各部分占用了多少 token输出到日志里。def log_context_usage(context_parts): total 0 for name, text in context_parts.items(): tokens count_tokens(text) total tokens logger.info(f{name}: {tokens} tokens) logger.info(fTOTAL: {total} tokens) if total WARN_THRESHOLD: logger.warning(上下文接近上限检查是否有冗余)有了这个日志你很快就能发现哪部分在偷偷膨胀。我自己的经验是工具定义和工具返回是最容易失控的两块往往占了总上下文的一半以上。看到数据之后再去优化方向就明确了。注意不同模型的 tokenizer 不一样计数要用目标模型对应的 tokenizer否则误差可能到 20% 以上。4.4 上下文压缩的几种实用手段当上下文确实压不下去时还有几招可以用语义压缩用一个小模型把长段落改写成更短的等价表达保留语义丢掉冗余。结构化替代把自然语言描述换成 JSON 或表格同样的信息 token 更少。引用替代长内容不直接塞而是存起来给个 ID需要时再按 ID 取。去重合并多轮里重复出现的信息只保留一份。这几招里结构化替代性价比最高。同样一段工具说明用自然语言写要 200 token用 JSON Schema 写可能只要 80 token而且模型理解得更准。我现在写工具定义基本都用结构化格式很少用大段描述了。5. 那些文档里不会写的上下文踩坑经验5.1 上下文太长导致的指令漂移有个坑我踩过不止一次Agent 在任务开始时表现很好严格遵守约束但任务进行到十几轮之后开始慢慢忘记最初的约束输出越来越随意。这不是模型坏了是指令漂移——最初的约束在上下文里被越推越远注意力权重越来越低。解决办法有两个。一是前面说的末尾重复核心指令二是定期重注入约束比如每 5 轮把约束层重新拼一次到上下文末尾。我一般用后者效果更稳。代价是多花一点 token但比起任务失败的代价这点成本完全值得。5.2 工具太多导致的选择困难给 Agent 挂 30 个工具听起来很强大实际用起来经常是模型在几个相似工具之间反复横跳或者干脆选错。这本质上是上下文里工具定义太多模型的选择空间过大。我的做法是按场景动态挂载工具。比如当前任务是查数据就只挂查询类工具进入写操作阶段再换成写入类工具。工具数量控制在 5 到 8 个以内模型的选择准确率会明显提升。如果确实需要很多工具就做一层工具路由先用一个轻量模型判断该用哪类工具再挂载对应的子集。5.3 记忆检索的假相关陷阱用 embedding 做记忆检索时经常会出现假相关检索出来的记忆和当前 query 字面相似但实际没用。比如用户问帮我订明天的会议室检索出来一条用户上次提到喜欢靠窗的位置字面上都涉及会议室相关词但这条记忆对订会议室这个动作毫无帮助。对付假相关我的经验是加一层重排序。先用 embedding 粗筛出 20 条再用一个小模型对每条做是否对当前任务有用的二分类只留真正有用的 3 到 5 条。多这一道检索质量提升非常明显。虽然多花一点算力但省下的上下文空间和提升的准确率完全划得来。5.4 上下文顺序对结果的影响同样的内容换个顺序模型的表现可能完全不同。我做过一个对比把约束放在系统提示开头 vs 放在结尾前者模型遵守率大概 70%后者能到 90% 以上。原因是结尾位置离生成更近注意力更集中。所以我的排序原则是越重要的、越需要严格遵守的越往上下文末尾放。具体顺序一般是身份背景 → 工具定义 → 历史与记忆 → 当前任务 → 约束与格式要求。把约束放最后是性价比极高的一个小调整。5.5 别忽视空上下文的价值有时候最好的上下文处理就是什么都不放。比如一个简单的格式转换任务你塞一堆历史记忆和工具定义进去反而干扰模型。我现在会给 Agent 加一个轻量模式判断当前任务足够简单时只带系统提示和当前输入其余全部省略。实测这种简单任务的成功率反而更高响应也更快。这个思路说起来简单但很多人舍不得总觉得多带点信息总没坏处。实际上在上下文工程里少即是多是反复被验证的真理。6. 从能跑到好用上下文工程的迭代方法6.1 建立上下文相关的评估指标优化上下文不能凭感觉得有指标。我一般会盯这几个任务成功率最直接但要看细分场景别只看总体。平均 token 消耗衡量成本也间接反映上下文是否臃肿。约束遵守率专门统计模型违反硬约束的比例。工具调用准确率选对工具、传对参数的比例。首轮响应延迟上下文越长首 token 延迟越高。这几个指标一起看才能判断一次上下文调整到底是好是坏。只看成功率容易被误导因为有时候成功率没变但 token 消耗翻倍了这其实是退步。6.2 用 A/B 对比验证每次调整上下文工程最忌讳我觉得这样更好。每改一处都应该做 A/B 对比。我的做法是准备一批固定的测试用例覆盖典型场景和边界场景改动前后各跑一遍对比上面那几个指标。测试用例不用多20 到 30 条覆盖到位就够。关键是固定不变这样不同版本之间才有可比性。我见过太多人每次测试都用新用例结果根本不知道是改动起了作用还是用例变了。6.3 上下文版本的灰度发布上下文配置本质上也是代码改动有风险。生产环境里我建议做灰度新版本上下文先放 10% 流量观察指标稳定后再逐步放量。这样即使新版本有问题影响面也可控。具体实现上把上下文组装逻辑参数化不同版本对应不同参数组合通过配置中心控制流量比例。这套做法借鉴的是常规的灰度发布思路用在上下文工程上同样有效。6.4 持续迭代的心态上下文工程没有一劳永逸的方案。模型在更新业务在变化用户输入分布也在漂移今天调好的上下文几个月后可能就不适用了。所以要把上下文当成一个持续维护的系统定期回看指标、定期做 A/B、定期清理不再需要的记忆和工具。我自己是每个月固定花半天时间把 Agent 的上下文日志翻一遍看看有没有新的膨胀点、有没有失效的约束、有没有可以合并的工具。这半天投入往往能省下后面一堆救火的时间。最后分享一个我一直在用的小习惯每次 Agent 出问题第一件事不是改代码而是把那一轮的完整上下文 dump 出来从头到尾读一遍。十有八九问题就明明白白写在上下文里——要么是某段信息缺失要么是某段冗余干扰了判断。读懂上下文比读懂模型更重要。
返回列表