最近Agent圈子有个特别有意思的转向——大家终于开始意识到,把模型变聪明这件事,光靠调Prompt已经玩不出花了。真正让Agent从“demo能用”到“生产环境扛得住”的分水岭,其实是上下文工程。我用这个词有点拗口,换个说法你就懂了:Prompt决定模型怎么回答,而上下文决定模型看见什么。后者才是Agent性能的天花板。
这篇东西写给两类人:一类是被复杂Agent搞得焦头烂额的开发者,另一类是刚入门、以为“写个好的system prompt就能搞定一切”的AI应用爱好者。我会从概念拆到实操,再把我踩过的坑、排查过的病例全倒出来,尽量让你读完就能用到自己的项目里。
1. 为什么说 Prompt 不是 Agent 的全部
1.1 一个让我彻底改观的实验
先说个我自己的经历。之前做一个文档问答类Agent,模型效果一直不稳,同一个问题换个说法就答偏。我当时第一反应肯定是调Prompt——把系统提示词写得越来越长,加了各种约束、few-shot示例,甚至给模型“立人设”。结果呢?效果有提升,但天花板很明显,而且Prompt越长,模型越容易在无关约束上“用力过猛”,有时候会为了遵循某条规则而忽略用户真正的问题。
后来我换了个思路:不碰Prompt,只改上下文的组装方式。原来我一股脑把检索到的文档片段拼进上下文,为了让“相关”内容多,甚至塞进去五六段。改动之后,我只保留两段最相关的片段,再把检索得分、文档来源、时间戳这些元信息用结构化方式附加上去。结果非常直接——同样的问题,回答准确率肉眼可见地上升了。
这个实验让我意识到:模型的推理能力是固定的,但上下文是你唯一可以完全控制的东西。你给它看什么、用什么样的顺序看、哪些信息被强调、哪些信息被省略,这决定了它能力的上限。Prompt重要吗?重要,但它只是上下文里的一小层。
1.2 分清 Prompt 和上下文工程的边界
很多人把两者混为一谈,其实分工完全不同。拿人来做类比:Prompt相当于你给一个新同事发了一封邮件,写清楚“请你做某件事,注意12345条要求”;上下文工程则相当于你把工作台收拾好了——相关资料放在左手边、历史记录打开、常用工具摆上手边、杂乱的无关文件全部收走。邮件写得再好,工位乱成一团,效率照样上不去。
在Agent场景里,上下文通常由这么几块组成:
| 组成部分 | 典型内容 | 传统Prompt工程会管吗 |
|---|---|---|
| 系统提示词 | 角色定位、输出格式、行为约束 | 管 |
| 用户请求 | 当前轮次的输入 | 一般不管 |
| 检索结果 | RAG召回的相关文档片段 | 不管 |
| 历史对话摘要 | 之前聊了什么的压缩记忆 | 不管 |
| 工具调用结果 | 函数返回的JSON、状态码、API响应 | 不管 |
| 运行时元信息 | 时间、用户身份、环境状态 | 不管 |
看到了吗?传统Prompt工程只覆盖其中一小块,而其余几块,恰恰决定了Agent能不能在真实场景里稳定工作。这就像你给下属安排工作——要求写得再细,如果他手里拿到的资料是错的、工具是缺的,照样干不成。
2. 上下文工程的四块核心拼图
2.1 检索与召回:决定哪些信息能进上下文
上下文工程的第一步,是决定“哪些信息值得被放进上下文”。这一步在RAG场景里尤其关键。很多人以为检索就是“把相关片段找出来”,但实际做的时候会发现,难点在于“相关”的定义。
我常用的方案是混合检索:先用向量相似度召回Top 20,再用BM25做关键词召回Top 10,两路结果做一个重排序(rerank),最后只取Top 2-4个片段放进上下文。为什么不用纯向量?因为纯向量在某些专有名词、代码片段、编号规则上经常翻车——语义相似不等于字面匹配,而很多业务场景恰恰吃字面匹配。
切片策略也要单独提。固定的256 token或512 token切片,看起来省事,但会把一句话从中间截断。我做项目的时候习惯用“章节感知切片”——先按文档结构(标题、段落、列表)切,再把太短的段落合并到临近片段里。这样检索到的片段语义更完整,模型理解起来也更省力。
另外,召回片段一定要带元信息。比如来源文件名、章节标题、页码、时间戳。这不仅是给模型提供引用依据,更关键的是让模型判断信息的优先级。我见过很多项目把“最近更新的一版”和“三个月前的老文档”同时塞进上下文,模型完全分不清哪个优先,最后答出来一个混合版本,看起来都对,细究全是坑。
2.2 记忆分层:全局记忆、会话记忆与工作记忆
Agent和单次问答最大的区别,就在于它要处理“多轮连续性”。这里最容易犯的错,是拿一个大列表把所有历史消息全存下,每次请求全部拼进上下文。方案简单,但token消耗会爆炸,而且越往后的对话,模型越容易被早期的细节带偏。
我做项目时一般把记忆拆成三层:全局记忆存用户的长期偏好和基础信息,比如“用户是产品经理,关心指标是转化率”;会话记忆存当前这轮对话的要点,用自然语言摘要的方式维护,而不是存原始消息列表;工作记忆则是当前正在处理的任务状态,比如“正在生成第三季度报告,数据源已选定”。
这三层会以不同的方式进入上下文。全局记忆固定放在系统提示词后面,会话摘要每次动态生成并放在用户消息前,工作记忆通常以结构化的JSON片段嵌入。做这个分层之后,多轮对话的稳定性会有质的提升——因为模型每次看到的上下文都是“当前任务最重要的信息”,而不是一坨糊涂账。
2.3 工具结果回填:让Agent真正“动手”
Agent区别于ChatBot的核心在于工具调用。很多做Agent的人会发现:工具返回的结果,模型经常“视而不见”,或者胡乱解读。问题大概率出在回填方式上。
最典型的错误是把工具返回的原始JSON直接拼接给模型。模型能读JSON,但读起来累,关键信息全淹没在一堆字段里。我的做法是加一个“格式化层”——把我预期的字段抽取出来,转成自然语言再放回上下文。比如一个查询天气的工具返回了JSON,你直接塞进去模型也能解读,但如果你转成“北京当前温度32度,体感36度,建议午后减少户外活动”,模型就不需要再从json里自己找重点——这不但减少理解偏差,还省token。
还有个细节容易被忽视:工具调用的失败信息也要回填。不止是成功返回的,比如“你调用的接口超时了,错误码是408”,模型才能决定下一步——是重试还是换个方案。如果失败信息不暴露给模型,它就会卡在“假装成功”的幻觉里继续往下走。
2.4 压缩与裁剪:上下文不是越大越好
上下文窗口这几年越做越大,动辄128K甚至200K token,但“窗口大”不等于“应该全塞满”。我实测下来的结论是:上下文越长,模型的注意力和指令遵循能力都会下降——这实际上是注意力机制的一个已知弱点,也叫“lost in the middle”。哪怕上下文窗口容得下,也不要无脑堆。
有效的操作有两个方向。一是裁剪:按时间衰减和按相关性衰减,把不重要的历史对话、检索片段、工具记录逐层淘汰。二是压缩:用摘要模型把长对话压成一段简洁的summarized memory,并把原文的关键数据点(数字、日期、ID)单独抽出来存成结构化字段,防止摘要丢信息。
这一块我最想强调的是:上下文工程里的“工程”二字,意味着你要把上下文当成一个有生命周期的资源——它不只是一次请求里的输入,而是一个持续维护、更新、淘汰的动态结构。谁把这件事做好了,谁就能让Agent在长周期任务里一直保持稳定状态。
3. 实战:一个多工具 Agent 的上下文搭建全过程
3.1 场景设定与选型
纸上谈兵说得再多,不如直接拆一个实际项目。我这边最近做了一个“项目数据分析助理”——用户可以用自然语言问项目状态、查历史报告、生成周报摘要。这个Agent涉及三个工具:文档检索(查项目报告)、数据库查询(查指标数据)、周报生成(调内部报表接口)。
这个场景非常适合讲上下文工程,因为它的核心难点在于:用户一句话进来,Agent需要自己判断调用哪些工具、以什么顺序调用,然后把多路结果整合进一个回答里。模型的推理能力在这里不是瓶颈,真正决定成败的是——每轮请求里,Agent能看到哪些信息。
3.2 上下文组装五步法
我整理了一个可复用的组装流程,五个步骤,每一步都对应一个上下文区块:
第一步,系统提示词保持精简。只写角色定位、工具清单、输出约束,控制在200-400 token以内。不需要把所有的业务背景和示例问题都塞进去,那些应该放检索,而不是固定占住上下文。
第二步,插入全局记忆。比如“你正在服务产品团队,关注的指标是项目进度、风险、人天消耗”。这段一般是动态取的,来自用户画像,不随对话变化。
第三步,注入当前请求。这里有个小技巧:不光写用户的原话,还要加上“请求意图解析”的备注。比如用户问“三季度的上线时间能保住吗”,我会先让一个小模型识别出意图标签,然后组装成“用户想确认三季度上线计划是否延期,敏感指标:上线时间,期望输出:判断+依据”。
第四步,塞检索结果和工具结果。按信息源类型分组排列,同一个数据源的多个片段要紧挨着,并且按相关性降序排。关键片段前面可以加一行摘要提示,比如“以下内容来自项目周报V3版,是最新的计划数据”。
第五步,追加会话摘要。前面的对话压缩成分条摘要,放在最后作为“记忆区”。这里要控制长度——我一般限制在3-5条,每条一句话以内。
一个组装好的上下文大概是这个样子的,片段顺序从上到下是System、全局记忆、用户请求、检索结果、工具结果、会话记忆。为什么这么排?因为模型对上下文开头和结尾的信息注意力更强(位置偏差),所以最重要的请求放中间偏前,记忆类信息放最靠后让模型做参考,而不是抢关注。
3.3 实操中两个让我印象极深的调整
第一个调整是关于工具结果回填的。最早版本,我直接把SQL数据库查询的返回字段原样拼进上下文,比如status: 1, delay_days: 3, owner: 张三。模型经常能读到,但回答里的表达很生硬,有时候还分不清字段含义。我加了一个轻量级的中转:工具返回后,先用一个格式化函数把结果转成自然语言句子,比如“项目A当前状态为进行中,已延期3天,负责人为张三”。就这么一个改动,回答的自然度和可靠性同时提升,属于性价比最高的一次调整。
第二个调整是每次对话结束后的记忆更新。最初的方案是固定保留最近10轮对话原文,但跑到第五轮的时候,模型就开始“遗忘”第一轮说过的重要前提。后来改成“每轮对话结束后,单独调一次摘要模型,生成新的会话记忆”。这样虽然多了一次模型调用,但长对话的稳定性提升非常明显。实际项目里如果对延迟敏感,可以用更轻量的抽取式摘要,甚至直接把关键字段维护在一个结构化状态对象里,不一定要每次都跑生成式模型。
4. 常见翻车现场与排查思路
4.1 上下文太长,模型响应变慢还容易答偏
这个现象几乎是必然的——上下文越长,首token延迟越高,模型注意力被稀释的概率也越高。我见过有人盲目信任128K窗口,把整个项目文档全塞进去,结果单次请求要等十几秒,回答质量还忽上忽下。
排查思路很简单:先看单轮请求的token分布,用计费信息就能看到System占了多少、历史占了多少、检索占了多少。一般会把历史对话换成摘要,检索限制到Top 3以内,工具结果做字段裁剪。更重要的是打电话“越大越好”的念头——在性能测试里,同样的问题用2K上下文和8K上下文分别测一次,体验差距会大到让你吃惊。
4.2 检索结果明明相关,但模型不参考,反而自己编
这个问题我也被坑过好多次。检索Top片段其实是对的,但模型答出来的内容和片段完全对不上——它在“自由发挥”。最可能的原因是:检索内容在上下文里被系统提示词盖住了,或者被别的信息冲淡了;再一种可能就是,你检索到的片段缺少“对话里类似的表述”——模型没意识到这片内容就是对当前问题的直接回答。
对策有两个方向。一个是给检索结果加一个明确的“权威标签”,比如“以下内容来自项目知识库,回答时优先依据此内容推理”。另一个是:把用户问题直接嵌到每个检索片段前面,让每个片段看起来像是“针对用户问题的潜在答案”,这种方法经常被用在一些RAG项目里,实测有效。
4.3 多轮对话越聊越笨,早期信息全被冲掉
这是最典型的上下文管理问题。早期信息不是“消失”,而是被淹没在后来的一大堆工具结果和检索记录里。模型从概率上就会更关注新信息、更关注周围有详细展开的内容,于是重要的早期约束就丢了。
排查方法:把完整上下文拉出来,看早期的关键约束在第多少token的位置。如果超过了总长度的70%,那么模型几乎肯定已经“失忆”。此时要么做硬性的截断和摘要,优先级高的关键约束单独抽出来放System或最顶层;要么对早期信息做“强化”,每次组装上下文的时候把关键点重复一遍。
4.4 问题排查速查表
| 表现症状 | 可能原因 | 排查手法 |
|---|---|---|
| 答案和检索内容对不上 | 检索片段权重太低,或未标记为权威依据 | 检查检索片段在上下文中的位置,加优先级标签 |
| 多轮后遗忘关键约束 | 早期信息位置太靠后,被新信息淹没 | 测量关键信息token位置,提前或强制重复 |
| 工具结果被模型无视 | 返回JSON格式难理解,或失败信息未暴露 | 改用自然语言回填,暴露错误状态 |
| 上下文爆炸、响应变慢 | 历史消息无压缩,检索塞入过多片段 | 改成摘要式历史,检索裁剪到Top 3 |
| 模型“两头看、中间忘” | 关键信息被放在上下文中间位置 | 把核心数据提到上下文前30%或后20%位置 |
| 输出不一致,同一问题答案不同 | 上下文顺序不稳定,或检索结果波动 | 固定组装顺序,检索结果按相关性重排去噪 |
这张表基本覆盖了我日常排查Agent问题时的80%场景。遇到奇怪的效果波动,先别急着怀疑模型能力——拿一张纸把上下文里的内容写出来,从上到下看一遍,很多时候问题直接就暴露了。
5. 上下文工程的后续演进方向
做了一段时间上下文工程后,我个人对Agent的发展有了一个明确判断:模型的通用能力会继续提升,但Agent应用的上限,比拼的是谁更会管理信息环境。有几个方向我认为接下来一定会更热。
一是上下文路由。不是每个请求都要走完整的检索-召回-重排链路,而是先判断这个请求需要哪些上下文源——需要查库的直接查库,需要检索的直接检索,能靠缓存回答的直接用缓存。这背后是一个独立的“上下文调度器”,它会根据当前任务动态决定组装策略。这其实很像操作系统里的内存管理,有缓存、有交换、有优先级。
二是结构化的上下文协议。目前上下文基本都是自然语言堆叠,未来很可能会出现一种“结构化上下文”的形式——标准的元信息层、数据层、指令层,让模型和工具都能更高效地解析。这个方向的价值在于减少模型的理解成本,提升信息的确定性。
三是上下文压缩的智能化。目前的摘要式压缩还会丢信息,尤其丢数字和关系。更好的方案是“语义压缩”——不只压缩为几句话,而是提取关键实体和关系,转成结构化的知识表示,再搭配自然语言摘要一起进入上下文。这个思路还比较新,但我在一些实验里已经看到明显收益。
我个人实际摸下来的体会是,上下文工程不是一个“要不要做”的问题,而是“早做还是晚做”的问题。只要你的Agent要从demo走向真实场景,迟早会遇到和上下文管理相关的瓶颈。早一点把上下文组装流程结构化、可观测、可调试,后面遇到问题你会少掉很多头发。而且很多工具和方法论都是通用的,不管底下的模型换成什么,这套思路都能复用。