上个月帮一个团队看客服 Agent 的问题。用户问“去年买的套餐还能不能退”,Agent 答得很自信,但引用的是一份已经下线的退款政策。团队第一反应是模型不行,准备换一个更大的。我让他们把那次请求的上下文完整打印出来,问题当场就清楚了:检索到的三份文档里,两份是旧版,一份是新版,拼在一起互相矛盾;前面还塞了十几条无关的工单日志。模型不是不会答,它是在一堆互相打架的信息里挑了一个看起来最像答案的。
这类问题,换模型解决不了。
上下文工程到底在管什么
很多人把上下文工程理解成“把 Prompt 写长一点”。不是。Prompt 是你对模型说的话,上下文是模型在这个时刻能看到的全部信息:系统规则、任务状态、历史对话、检索到的文档、工具返回的结果、用户画像,甚至包括这些东西的排列顺序。
Prompt Engineering 关心的是“怎么问”,上下文工程关心的是“模型凭什么做出这个判断”。前者是表达问题,后者是信息架构问题。
一旦系统里有了 RAG、工具调用和多轮状态,后者就会变成主要矛盾。因为模型不会告诉你它缺了什么,它只会用一个看起来很合理的答案把窟窿补上。
先看模型看到了什么
我现在排查这类问题,第一步不是改 Prompt,而是把那次请求的上下文快照打出来。至少要能看到:
- 这次请求带了哪些来源的内容;
- 每段内容占了多少 token;
- 哪些内容被检索出来但最后被丢掉了;
- 工具返回了什么,错误有没有被吞掉;
- 上下文里有没有互相冲突的版本。
很多“模型幻觉”的案子,到这一步就破了。不是模型编的,是上下文里本来就有一份错的。模型只是很忠实地把它复述了出来。
上下文不是越长越好
窗口变大之后,很多人有一种错觉:既然能塞进去,那就都塞进去。实际效果往往相反。
长上下文里,关键信息会被淹没。尤其是当一堆相似文档、重复日志和无关历史堆在一起时,模型更容易抓住那些“看起来相关”的内容,而不是真正重要的那一段。我们内部管这个叫上下文噪声,学术一点的说法是 context rot,但本质就是一句话:模型注意力也是有限的。
所以我现在做上下文装配,第一件事是定预算。系统规则占多少、当前任务占多少、证据占多少、工具输出占多少,先切出大概的比例,再往里填。超了就做选择,而不是硬塞。
我会怎么分层
我习惯把上下文分成五层,每层单独管理:
系统规则层放角色、边界、输出格式。这层变化最少,也最应该保持字节级稳定,因为很多推理服务的前缀缓存靠它命中。
任务状态层放目标、计划、已完成步骤和待办。Agent 跑到第五步时,最怕的是忘了第一步的目标,这层就是给它一个锚点。
工作记忆层放最近几轮对话、关键实体和临时结论。它更新最频繁,也最容易膨胀,需要滚动摘要。
证据层放检索到的文档、数据库记录和网页摘要。它决定答案的上限,也最容易引入冲突和过期信息。
工具观测层放调用参数、返回值和错误。它应该被明确地包起来,和指令分开,避免模型把工具输出里的内容当成命令。
这五层不是标准答案,但分开管理之后,至少每个问题都能定位到具体一层,而不是笼统地说“模型效果不好”。
装配的关键是做减法
一个可维护的装配流程,大概是:收集、归一化、筛选、压缩、排序、校验、组装。
真正难的是中间三步。
筛选不是把向量检索的 Top-K 直接拼进去。先做规则过滤,比如权限、时效、版本;再做相关性重排;最后去重。很多重复内容不是来自不同文档,而是同一份文档被切成了好几个相似的块。
压缩要按信息类型区别对待。叙述性内容可以摘要,实体信息要保留结构化字段,精确数值和引用最好原样保留。我见过最典型的事故,是把一份价格表摘要成“价格在几百到几千不等”,模型后面只能瞎猜。
排序也有讲究。系统规则放最前,当前任务和输出格式放最后,证据按相关性和时间排,工具输出用明确的分隔符包住。位置本身就是一种信号,别浪费。
有些东西不能压缩
我给自己定了一条规矩:可以压缩表达,不要压缩证据。
数字、日期、订单号、API 参数、引用位置,这些东西一旦被摘要,后面就很难还原。尤其是 Agent 要拿这些值去调工具的时候,错一位就是一次失败的调用。
还有一类不能压的是冲突信息。如果新旧政策同时存在,正确做法不是让模型自己挑,而是在装配阶段就按版本和时效过滤掉旧的;如果过滤不掉,就在上下文里明确标注“以下两份内容存在冲突”,让模型知道这里不确定。
把冲突藏起来,比把冲突摆出来更危险。
怎么知道是上下文出了问题
我一般看几个信号:
模型回答得很流畅,但引用对不上;同一个问题换个问法,答案完全不同;工具调用参数里出现了上下文里没有的值;回答开始重复用户问题里的措辞;长对话到后半段突然变笨。
这些不一定是上下文问题,但概率很高。再配合每次请求的上下文快照,基本能定位到是检索、排序、压缩还是版本管理出了错。
可观测性这块,很多团队只记录最终 Prompt,不记录上下文是怎么来的。这就像只保存了做好的菜,没保存食材和菜谱,出了问题没法复盘。
一次具体的排障
回到开头那个客服案例。我们把上下文拆开之后,发现问题有三层。
第一层是版本。政策库里有三份退款政策,两份是旧版,但没有标记失效时间。检索按相似度返回,旧版因为措辞更详细,反而排得更靠前。
第二层是去重。同一份政策被切成了六个块,其中三个块内容几乎一样,只是标题不同。它们一起挤占了将近一半的上下文预算。
第三层是噪声。工单日志里有大量“用户已登录”“页面已打开”这类事件,和退款问题无关,但因为出现在同一会话里,被整段带了进去。
修复没有换模型。我们先给政策加版本和生效时间,检索时按时间过滤;再在切片阶段做去重,相似度超过阈值的块只保留一个;最后把工单日志改成结构化摘要,只保留状态变更和金额相关的字段。
改完之后,同一个问题的回答准确率上去了,上下文 token 还降了三分之一。
这件事让我记住一点:上下文工程的大部分收益,来自删掉什么,而不是加上什么。后来我们又加了三个指标:上下文召回率、证据冲突率、有效 token 占比。它们比“模型感觉更聪明了”这种判断靠谱得多。
上下文也要做回归测试
上下文装配逻辑改动很频繁:换切片大小、调检索权重、改压缩 Prompt、加一个新数据源。每一次都可能让某些问题的证据悄悄消失。
我现在会维护一组上下文回归用例。每条用例包含一个问题、期望出现的关键证据,以及一组不该出现的内容。每次改动后跑一遍,看召回率、冲突率和有效 token 占比有没有变化。
这套测试不完美,但它能挡住大部分“改一个地方,坏三个场景”的问题。尤其是政策、价格、配置这类经常更新的知识,版本一换,上下文就可能过期。
说回开头
上下文工程的本质,是把“模型需要知道什么”从一句玄学 Prompt,变成一套可设计、可观测、可评测的流程。模型会继续变强,但信息选择、证据管理和状态治理不会消失。
我现在判断一个 AI 应用是否成熟,会先看它能不能说清楚:这次回答,模型到底看到了什么。答不上来的,大概率还在靠运气。
转载请注明出处: 皮哈皮阿
本文的链接地址: https://blog.phpat.com/ai-dev/context-engineering.html
微信或手机浏览器扫一扫,随时随地随心阅读与分享
京公网安备11011402053654 
@muffin:链接已更新,分类图像功能...
AI网址导航模版图标库更换指南
作者大大,图标库邀请链接失效了,麻烦更新...
AI网址导航模版图标库更换指南
太好看了,快点更新!
友情链接
@戴墨镜的刘备:哈喽,路过点赞。
vue.js组件之手写签名板vue-signature-simple
太好看了,快点更新!太棒了!👍真的很赞!
vue.js组件之手写签名板vue-signature-simple
你写得非常清晰明了,让我很容易理解你的观...
Emlog插件RSS订阅器发布