ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

上下文工程实战:从提示词到信息流,提升AI应用效果

上下文工程实战:从提示词到信息流,提升AI应用效果

1. 上下文工程到底在解决什么问题

第一次听到“上下文工程”这个词,很多人会下意识觉得它只是“提示词工程”换了个马甲。但真上手做过几个AI应用之后就会发现,这两件事的侧重点完全不同。提示词工程关心的是“怎么把话说清楚”,而上下文工程关心的是“怎么把对的信息,在对的时机,以对的格式,塞进模型的视野里”。说白了,前者是话术,后者是信息调度。

我最初接触这个概念是在做一个智能客服原型的阶段。当时团队里所有人都把精力花在打磨提示词上,反复调整语气、格式、示例,效果确实有提升,但很快就撞到了天花板。用户问一个涉及订单状态的问题,模型答得头头是道,可订单号是编的;用户追问上一轮提到的商品,模型完全不记得。问题不在提示词写得好不好,而在于模型压根没看到该看的东西。这就是上下文工程要解决的核心矛盾:模型的能力上限,很大程度上取决于你喂给它的上下文质量。

打个比方,提示词工程像是给一个临时工写工作说明书,告诉他该怎么干活;上下文工程则是给这个临时工配一套完整的工作台,上面摆好他需要的所有工具、资料、历史记录,并且随着任务推进动态更新。你说明书写得再漂亮,工作台上空空如也,他也干不出活来。

这个领域适合谁来关注?我觉得三类人最需要:一是正在做AI应用落地的开发者,尤其是RAG、Agent、多轮对话方向的;二是产品经理,需要理解为什么有些AI功能“看起来能做但实际做不好”;三是对AI应用效果有要求的业务方,知道瓶颈在哪,才能提出合理需求。不管你是哪一类,理解上下文工程的基本逻辑,都能让你在跟AI打交道时少走很多弯路。

2. 上下文工程的核心设计思路拆解

2.1 从“写提示词”到“设计信息流”的思维转变

传统做AI功能,很多人的工作流是这样的:想一个场景,写一段提示词,丢给模型,看结果,不满意就改提示词,循环往复。这个流程在简单任务上没问题,但一旦涉及多轮交互、外部知识、动态数据,就会变得极其脆弱。因为提示词是静态的,而真实场景的信息是动态的。

上下文工程要求你把视角从“这一句话怎么写”提升到“这一轮对话需要哪些信息”。这些信息可能来自用户当前输入、历史对话记录、外部知识库检索结果、工具调用返回、系统状态变量等等。你需要设计一套机制,在每次调用模型之前,把这些信息收集、筛选、排序、格式化,组装成一个完整的上下文包。

我自己的经验是,当你开始用“信息流”的视角看问题,很多之前觉得是“模型不行”的情况,其实都是“上下文没给对”。比如模型总是忽略某个约束条件,你检查一下上下文,很可能那个约束被淹没在一大段无关文字里了;模型回答前后矛盾,很可能是历史对话没有被正确截断或摘要,导致旧信息和新信息打架。

2.2 上下文窗口的预算分配策略

所有模型都有上下文窗口限制,这个限制就是你的“预算”。上下文工程的核心工作之一,就是决定这笔预算怎么花。我习惯把上下文分成几个区块来管理:

  • 系统指令区:定义角色、行为边界、输出格式。这部分通常固定,但也要精简,别把什么都往里塞。
  • 知识检索区:从外部知识库召回的文档片段。这是最需要动态调整的部分,召回多少条、每条多长、怎么排序,都直接影响效果。
  • 对话历史区:之前的交互记录。不能全放,也不能不放,需要设计截断或摘要策略。
  • 当前输入区:用户这一轮的问题。通常放最后,因为很多模型对末尾内容更敏感。
  • 工具返回区:如果涉及函数调用,工具执行结果需要插入到合适位置。

预算分配没有万能公式,但有一个原则:与当前任务最相关的信息,应该占据最靠近输入末尾的位置。这是基于模型注意力机制的经验观察,越靠近生成位置的内容,被参考的概率越高。我试过把关键约束从系统指令区挪到用户输入之前,遵循率明显提升。

2.3 为什么不能把所有信息都塞进去

有人可能会想,既然上下文越丰富越好,那我全塞进去不就行了?理论上可以,实际上有三个问题。第一,窗口有硬限制,塞不下;第二,即使塞得下,信息过载会导致模型注意力分散,关键信息被稀释;第三,无关信息可能引入噪声甚至矛盾,让模型产生幻觉。

我踩过的一个坑是:在一个问答场景里,为了提高召回率,把检索条数从3条提到10条。结果准确率反而下降了。排查后发现,很多召回片段虽然关键词匹配,但实际不相关,模型被这些噪声干扰,反而忽略了真正有用的那一条。后来改成先做相关性重排序,只保留前3条高质量片段,效果立刻回升。这个教训让我明白,上下文工程不是做加法,而是做筛选和排序。

3. 核心细节解析与实操要点

3.1 信息筛选:怎么判断什么该让AI看到

筛选是上下文工程的第一步,也是最关键的一步。我的做法是建立一个多级过滤漏斗:

第一级是硬性过滤,比如时间范围、权限范围、数据类型。这一步用规则就能搞定,不需要模型参与。比如只检索最近30天的文档,只返回当前用户有权限访问的内容。

第二级是相关性排序,通常用向量检索或关键词检索的分数来排。但要注意,检索分数高不等于对当前任务有用。我习惯在检索之后加一个轻量级的重排序步骤,用一个小的交叉编码器或者甚至直接用大模型做一次快速判断,把真正相关的片段挑出来。

第三级是去重与压缩。多个片段可能包含重复信息,需要合并;长片段可能包含无关段落,需要截取核心部分。这一步可以人工设计规则,也可以让模型做摘要。但摘要会损失细节,所以我的原则是:能截取就不摘要,能保留原文就不改写,除非长度实在超限。

注意:筛选过程中最容易犯的错误是“过度召回”。宁可少而精,不要多而杂。模型不是人,它不会自动忽略无关信息,你给什么它就参考什么。

3.2 格式设计:让模型一眼看到重点

信息选对了,还得摆对位置、用对格式。我总结了几条实操经验:

  • 用明确的分隔符:不同区块之间用清晰的分隔标记,比如---或者### 参考资料。模型对结构化的输入理解更好。
  • 关键信息前置或后置:如果某条信息特别重要,要么放在最前面,要么放在最靠近用户输入的位置。中间位置最容易被忽略。
  • 控制单条长度:一条检索结果最好不要超过500字,太长了模型抓不住重点。如果原文很长,先切分成语义完整的短段。
  • 标注来源:给每条信息加上来源标识,比如[来源:产品手册],这样模型在回答时可以引用,也方便你排查问题。

我做过一个对比实验:同样的检索结果,一组直接拼接,一组加上编号和来源标注。后者在回答准确性上高出不少,而且模型更倾向于引用具体来源,幻觉明显减少。

3.3 动态更新:多轮对话中的上下文管理

多轮对话是上下文工程最复杂的场景。每一轮交互都会产生新信息,窗口就那么大,必须决定哪些旧信息保留、哪些丢弃、哪些压缩。

我的策略是分层管理:

  • 最近N轮:完整保留,因为和当前话题最相关。
  • 较早轮次:做摘要,保留关键实体和结论,丢弃具体措辞。
  • 更早轮次:只保留与当前话题相关的片段,其余丢弃。

具体N取多少,取决于任务类型。客服场景可能3到5轮就够,复杂咨询可能需要10轮以上。我通常会做一个滑动窗口,同时维护一个“长期记忆”摘要,每轮结束后更新。

还有一个技巧是话题检测。当用户切换到新话题时,旧话题的历史可以大幅压缩甚至清空,只保留可能有关联的实体信息。这样可以腾出大量预算给新话题。实现方式可以用简单的关键词匹配,也可以让模型判断话题是否延续。

4. 实操过程与核心环节实现

4.1 搭建一个最小可用的上下文组装流程

下面我以一个知识库问答场景为例,拆解完整的上下文组装流程。这个流程不依赖特定框架,用任何语言都能实现。

第一步:接收用户输入,提取关键信息。

用户问:“上个月发布的那个新功能,怎么配置超时时间?”

这里的关键信息是:时间范围“上个月”、对象“新功能”、动作“配置超时时间”。可以用规则或模型提取这些槽位。

第二步:检索相关知识。

用提取的关键信息去知识库检索。检索时不要只用原始问题,要用提取后的关键词组合,比如“新功能 配置 超时时间”。检索返回若干候选片段。

第三步:重排序与筛选。

对候选片段做相关性打分,保留前3到5条。打分可以用向量相似度,也可以用一个小的模型做交叉判断。我通常会把检索分数和重排序分数加权结合,避免单一指标偏差。

第四步:组装上下文。

按以下顺序拼接:

[系统指令] 你是一个技术支持助手,根据提供的资料回答用户问题。如果资料中没有答案,如实告知。 [对话历史摘要] 用户之前询问过功能发布时间,已告知为上月15日。 [参考资料] 1. [来源:配置手册] 超时时间配置方法:进入设置页面,找到“高级选项”,修改“请求超时”字段,单位为秒。 2. [来源:发布说明] 新功能于上月15日发布,包含超时配置、重试策略等改进。 3. [来源:常见问题] 超时时间默认30秒,最大可设置为300秒。 [用户当前问题] 上个月发布的那个新功能,怎么配置超时时间?

第五步:调用模型并解析输出。

把组装好的上下文发给模型,获取回答。如果回答中引用了资料,检查引用是否准确;如果回答“不知道”,检查是资料没检索到还是模型没理解。

4.2 参数选择与计算过程

上下文组装中有几个关键参数需要根据实际情况调整:

检索条数K:不是越大越好。我的经验公式是K = min(5, 窗口剩余预算 / 平均片段长度)。假设窗口剩余8000 token,平均片段300 token,那么K最多26,但实际取5左右就够了。因为超过5条之后,边际收益急剧下降。

历史保留轮数N:取决于对话的平均轮次长度。如果每轮平均200 token,窗口预算2000 token给历史,那么N=10。但实际我会取更小,比如5,因为早期轮次的相关性衰减很快。

摘要触发阈值:当历史轮数超过N时,把最旧的轮次做摘要。摘要长度控制在原文的20%到30%。比如原文500 token,摘要100到150 token。

这些参数没有标准答案,需要根据你的场景做A/B测试。我通常会准备一组测试问题,对比不同参数下的回答准确率和响应延迟,找到平衡点。

4.3 一个完整的代码示例

下面用Python伪代码展示上下文组装的核心逻辑:

def build_context(user_input, history, knowledge_base, max_tokens=8000): # 1. 提取关键信息 keywords = extract_keywords(user_input) # 2. 检索知识 candidates = knowledge_base.search(keywords, top_k=10) # 3. 重排序 ranked = rerank(candidates, user_input) selected = ranked[:5] # 4. 压缩历史 if len(history) > 5: recent = history[-5:] older_summary = summarize(history[:-5]) else: recent = history older_summary = "" # 5. 组装 context_parts = [] context_parts.append(SYSTEM_PROMPT) if older_summary: context_parts.append(f"[历史摘要]\n{older_summary}") context_parts.append("[参考资料]") for i, doc in enumerate(selected): context_parts.append(f"{i+1}. [来源:{doc.source}] {doc.content}") context_parts.append(f"[用户问题]\n{user_input}") context = "\n\n".join(context_parts) # 6. 检查长度 if count_tokens(context) > max_tokens: context = truncate_context(context, max_tokens) return context

这个流程看起来简单,但每一步都有优化空间。比如关键词提取可以用模型做,也可以用TF-IDF;重排序可以用交叉编码器,也可以用大模型打分;摘要可以用抽取式,也可以用生成式。选择哪种方案,取决于你的延迟要求、成本预算和效果目标。

5. 常见问题与排查技巧实录

5.1 模型忽略关键信息怎么办

这是最常见的问题。你明明把约束条件放进上下文了,模型就是不理。排查思路如下:

首先检查位置。关键信息是不是被埋在一大段文字中间了?试着把它挪到最前面或最后面。我遇到过好几次,把约束从系统指令区挪到用户输入之前,遵循率立刻从60%提到90%。

其次检查格式。关键信息有没有被其他内容淹没?加粗、编号、单独成段,都能提升注意力。模型对结构化内容的敏感度远高于纯文本。

最后检查冲突。上下文里有没有和关键信息矛盾的内容?比如系统指令说“只回答产品相关问题”,但历史对话里用户问了一个无关问题且模型回答了,这个历史记录就会干扰当前指令。解决办法是清理冲突历史,或者在系统指令中强调优先级。

5.2 检索结果不相关怎么调

检索不相关通常有三个原因:查询词没提取好、检索算法不行、知识库本身有问题。

查询词方面,不要直接用用户原话去检索,先做关键词提取和同义词扩展。比如用户说“怎么弄超时”,提取出“配置 超时时间”,再扩展“设置 超时 参数”。

检索算法方面,纯向量检索对语义匹配好,但对精确关键词匹配差;纯关键词检索反过来。我通常用混合检索,两路召回再合并排序。

知识库方面,检查文档切分是否合理。切得太碎会丢失上下文,切得太大会引入噪声。我的经验是每段300到500字,按语义边界切分,不要按固定字数硬切。

5.3 多轮对话中信息丢失怎么防

多轮对话的信息丢失,往往是因为历史压缩太狠。我的做法是维护一个“关键信息池”,把每轮对话中提取的实体、结论、用户偏好存进去,不随历史压缩而丢失。比如用户说过“我用的是专业版”,这个信息就存入池中,后续每轮都带上。

另一个技巧是显式确认。当用户提供重要信息时,让模型在回答中复述一遍,这样信息就进入了最近的对话历史,不容易被压缩掉。比如用户说“我要配置300秒超时”,模型回答“好的,为您配置300秒超时”,这个300秒就留在了历史里。

5.4 常见问题速查表

问题现象可能原因排查方向解决技巧
模型忽略约束信息位置太靠前或太靠中间检查关键信息在上下文中的位置挪到用户输入之前或之后
回答前后矛盾历史信息与新信息冲突检查历史对话是否包含过时信息清理冲突历史或加优先级标注
检索结果不相关查询词提取不准检查关键词提取逻辑加入同义词扩展和混合检索
回答太笼统参考资料不够具体检查检索片段是否包含细节提高检索精度,减少泛泛文档
响应太慢上下文太长统计token数量和检索条数压缩历史,减少检索条数
幻觉严重上下文缺少依据检查参考资料是否覆盖问题增加相关文档,要求模型引用来源

提示:排查上下文问题时,最有效的办法是把组装好的完整上下文打印出来,自己读一遍。很多时候你一眼就能看出问题,比如关键信息被截断了、格式乱了、或者塞了一堆无关内容。

6. 我在实际项目中的几点体会

做上下文工程这段时间,最大的感受是:模型的能力是固定的,但你能让它看到什么,决定了它表现的上限。同样的模型,上下文设计得好,效果可以差出好几倍。这不是玄学,而是信息论的基本逻辑——输入决定输出。

另一个体会是,上下文工程没有一劳永逸的方案。业务在变,数据在变,用户行为在变,你的上下文策略也得跟着调。我习惯每隔一段时间就回顾一下线上bad case,看看是不是上下文组装出了问题。很多时候,改一行检索逻辑或者调一个参数,就能解决一批问题。

最后分享一个小技巧:如果你不确定某条信息该不该放进上下文,问自己一个问题——“如果我是回答者,看到这条信息会改变我的答案吗?”如果不会,那就别放。上下文预算很宝贵,留给真正有用的信息。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进