loading

Loading

探索与发现

首页 AI 开发实战

上下文工程:从 Prompt 到 Context 的工程化实践

字数: (2873)
阅读: (7)
0
摘要:客服 Agent 答错时,团队第一反应是换模型。把那次请求的上下文打印出来才发现,模型看到的是一份过期政策、两份互相矛盾的条款,还有一长串无关日志。上下文工程要解决的,就是这类问题。

上个月帮一个团队看客服 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

手机扫码阅读

微信或手机浏览器扫一扫,随时随地随心阅读与分享

1 / 1
预览图