ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

上下文工程实战:从堆料到精准组织,提升大模型问答效果

上下文工程实战:从堆料到精准组织,提升大模型问答效果

上下文工程这个词这两年被聊得很多,但真正动手做的时候,大部分人还是停留在“把提示词写长一点、把资料多塞一点”的阶段。我最早也是这样,觉得上下文嘛,不就是往输入框里堆东西,堆得越全模型答得越好。后来在一个知识问答类的小项目里连续踩了几次坑才发现,上下文工程远不是“堆料”这么简单,它更像是在给模型布置一个恰到好处的工作现场:给多了它抓不住重点,给少了它答非所问,给错了它一本正经地胡说。这篇就把我对上下文工程的理解、拆解和实操经验完整讲一遍,从它到底解决什么问题,到怎么一步步搭出一套能复用的上下文组织方案,尽量让刚入门的朋友看完能直接上手,也让已经做过一些提示词工作的同行能对照着查漏补缺。

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

1.1 从“提示词写得好”到“上下文组织得好”的认知转变

很多人入门大模型的第一课是学提示词,学怎么加角色设定、怎么加输出格式约束、怎么用少样本示例。这套东西确实有用,但它有个隐含前提:模型需要的信息,你都能在一段话里说清楚。现实项目里这个前提经常不成立。比如做一个内部文档问答,用户问“上季度的报销流程有没有变化”,模型要答对,至少得知道三件事:当前生效的报销流程是什么、上一版流程是什么、两者差异在哪。这三块信息分散在不同文档里,靠一句提示词根本装不下。

提示词工程关注的是“怎么把一句话说漂亮”,上下文工程关注的是“怎么把一堆相关信息组织成一个模型能消化的结构”。前者是修辞问题,后者是信息架构问题。我自己的体会是,提示词技巧在简单任务上收益明显,但任务一旦涉及多源信息、长文档、多轮对话,决定效果上限的往往是上下文怎么组织,而不是那句话写得多巧妙。这也是为什么同样一段提示词,有人用起来很稳,有人用起来时好时坏——差别常常在喂进去的上下文上。

1.2 上下文窗口变大之后,为什么“多塞”反而更糟

现在主流模型的上下文窗口动辄几十万 token,看起来“塞得下”已经不是问题。但我实测下来,窗口大不等于效果好。有个很典型的现象叫“中间遗忘”:当关键信息被放在一长段上下文的中段时,模型对它的利用率明显低于放在开头或结尾。这不是玄学,而是注意力机制在长序列上的固有特性——距离越远、干扰越多,权重越容易被稀释。

我做过一个粗糙的对照实验:同一份 3 万字的产品手册,一次整篇塞进去问一个细节问题,一次只截取相关章节塞进去问同样的问题。整篇塞的那次,模型答对了大方向但细节张冠李戴;截取章节的那次,答案精准得多。这说明上下文工程的核心矛盾不是“容量够不够”,而是“信噪比高不高”。塞得越多,噪声越多,模型越容易在无关信息里迷路。所以真正要练的本事,是在有限窗口里放最该放的东西,而不是把窗口填满。

1.3 一个判断标准:模型答错时,先问“它是不是没看到”

我现在排查模型答错的问题,第一步不是改提示词,而是问自己:答对这道题所需的信息,模型在上下文里真的看到了吗?如果没看到,改再多提示词也是白搭;如果看到了还答错,才轮到提示词和推理能力的问题。这个判断顺序能省掉大量无效调试。很多所谓的“模型不行”,拆开看其实是“上下文没给对”。把这条当成习惯之后,我调问题的效率高了不少,也更清楚哪些问题该靠检索解决、哪些该靠提示词解决、哪些是模型能力边界。

2. 拆解上下文的几个组成部分

2.1 系统指令、任务描述与约束条件的分层

一份组织良好的上下文,我习惯把它拆成三层。最上层是系统指令,定的是模型的角色和总体行为边界,比如“你是一个严谨的技术文档助手,不确定时明确说不知道”。中间层是任务描述,说清楚这一次具体要干什么,比如“根据下面的文档回答用户问题”。最下层是约束条件,管的是输出形态,比如“回答不超过三句话,引用原文时标注章节号”。这三层分开写的好处是,改任务的时候不用动系统指令,调格式的时候不用碰任务描述,维护起来清爽很多。

很多人把这三层揉成一大段,结果每次微调都得通读全文,改一处可能影响另一处。分层之后还有个隐性收益:系统指令可以复用,任务描述可以按场景切换,约束条件可以做成配置项。我在一个多场景助手里就是这么干的,同一套系统指令配不同的任务模板,切换成本很低。这里的关键不是格式多好看,而是让每一层职责单一,出问题时能快速定位是哪一层没写对。

2.2 检索内容、示例与对话历史各自扮演的角色

除了指令层,上下文里通常还有三类“素材”:检索来的外部知识、少样本示例、以及多轮对话的历史。这三类东西的作用完全不同,混在一起塞容易互相干扰。检索内容提供的是事实依据,示例提供的是输出风格和推理范式的参照,对话历史提供的是当前会话的连贯性。我一般会把它们用明确的分隔标记隔开,让模型清楚哪段是“参考资料”、哪段是“示范”、哪段是“之前聊过的”。

举个具体的坑:早期我把示例和检索文档混在一起给模型,结果模型有时候会把示例里的虚构内容当成事实引用出来。后来加了清晰的分隔和一句“示例仅用于说明格式,不作为事实依据”,这个问题就基本消失了。对话历史也一样,轮次多了之后旧信息会和新问题打架,我的做法是对历史做摘要压缩,只保留和当前问题相关的部分,而不是无脑全量带上。这三类素材各司其职,上下文才干净。

2.3 上下文里最容易被忽略的“元信息”

还有一类东西经常被漏掉,我管它叫元信息:时间、来源、版本、适用范围。比如一份流程文档,是哪个版本、什么时候生效、适用于哪个业务线,这些信息如果不给,模型很可能拿旧版本当现行版本答。我在一个政策问答场景里就吃过这个亏,模型引用了已经废止的条款,答案看着有理有据,实际是错的。后来我在每段检索内容前面都带上“来源+版本+生效时间”的元信息头,模型引用时也会带上,可追溯性强了很多。

元信息还有个作用是帮模型做取舍。当检索回来多份内容互相矛盾时,如果每份都标了时间和版本,模型就有依据判断哪份更新、更该采信。没有元信息,模型只能瞎猜或者把矛盾原样抛给用户。所以别小看这几行标注,它往往决定了答案的可信度。我的经验是,凡是会随时间变化的信息,元信息必须给足,这是上下文工程里性价比很高的一步。

3. 把上下文组织起来的实操方法

3.1 先做信息分层:哪些必须给、哪些可以给、哪些坚决不给

动手组织上下文之前,我习惯先列一张信息清单,把候选信息分成三档:必须给、可以给、坚决不给。“必须给”的是答对问题不可或缺的,比如用户问题的直接依据;“可以给”的是有帮助但非必需的背景,比如相关但非核心的章节;“坚决不给”的是容易引发误导的,比如过时版本、相似但不同场景的案例。这个分档动作看着简单,但能逼着你想清楚每份信息到底为什么进上下文。

分档之后,上下文长度往往能砍掉一大半,效果反而更好。我做过一次对比,把一份两万字的资料按“必须给”筛到三千字,问答准确率不降反升。原因很直白:噪声少了,模型注意力集中了。这里有个实操建议,分档时多问一句“如果删掉这段,答案会不会变错”,如果不会,它大概率属于“可以给”甚至“不给”。用这个标准过一遍,上下文会精炼很多。

3.2 结构化排版:用分隔符和标题让模型“看得清”

模型读上下文不像人那样有天然的段落感,所以排版要显式一点。我常用的做法是用清晰的分隔符把不同区块隔开,每个区块加一个小标题,比如“参考资料”“历史对话”“输出要求”。分隔符用简单的等号线或者井号标题都行,关键是前后一致,让模型能稳定识别区块边界。实测下来,结构化排版对长上下文的稳定性提升很明显,尤其是多份资料并列的时候。

除了分隔,顺序也有讲究。我一般把最关键的指令和问题放在靠前或靠后的位置,把大段参考资料放中间,这样能一定程度上缓解中间遗忘。如果参考资料本身很长,我会在开头先给一个目录或者摘要,让模型先建立全局印象,再往下读细节。这个技巧在长文档问答里特别好用,相当于给模型一张地图,它读起来不容易迷路。排版这件事不花什么成本,但对效果的影响比想象中大。

3.3 动态组装:按问题类型切换上下文模板

固定一套上下文模板应付所有问题,早晚会不够用。我的做法是按问题类型准备几套模板,运行时根据问题特征动态选。比如事实型问题走“检索为主、示例为辅”的模板,创作型问题走“示例为主、约束为辅”的模板,多轮追问走“历史摘要+当前问题”的模板。这样每类问题拿到的上下文都是为它定制的,信噪比更高。

动态组装的关键是有一个靠谱的问题分类逻辑。简单点可以用关键词规则,复杂点可以用一个小模型先做意图识别。我在一个客服场景里用的是规则加轻量分类的组合,先按关键词粗分,再让模型确认,准确率够用且成本可控。模板本身要留好插槽,检索内容、历史摘要、用户问题各占一个位置,组装时按需填充。这套结构搭好之后,新增场景基本就是加一套模板的事,扩展性比一坨硬编码的提示词好太多。

4. 实测中踩过的坑和应对

4.1 检索内容互相矛盾时,模型会“和稀泥”

最典型的一个坑:检索回来两份文档,一份说流程 A,一份说流程 B,模型没有判断哪份对,而是把两个都列出来让用户自己选。表面看很“全面”,实际是把矛盾甩给了用户。根因是上下文里没给足判断依据。我的应对是给每份内容加元信息(版本、时间、适用范围),并在指令里明确“当资料冲突时,优先采信版本更新、适用范围更匹配的那份,并说明取舍理由”。加了这条之后,模型的和稀泥行为少了很多。

还有一种情况是资料本身没错,但适用场景不同,模型没区分就混着用。比如面向新员工和面向管理层的两套流程,细节有差异。这时候光靠元信息不够,还得在指令里点明“根据提问者身份选择对应资料”。所以遇到矛盾别急着怪模型,先看上下文里有没有给它判断冲突的依据。给了依据还出错,才考虑是不是模型推理能力的问题。

4.2 长对话里旧信息“污染”新问题

多轮对话做久了,历史越堆越长,旧话题的信息会干扰新问题。我遇到过一个情况:用户前面问过退款,后面问发票,模型答发票时还把退款政策带出来了。原因是历史全量塞进去,模型分不清哪些还相关。解决办法是对历史做滚动摘要,每几轮压缩一次,只保留和当前话题相关的要点,同时给摘要标注时间,让模型知道哪些是“过去时”。

摘要本身也有坑,压得太狠会丢关键约束,压得太松等于没压。我的经验是摘要里至少保留三类东西:用户明确表达过的偏好或约束、已经确认过的事实、尚未解决的问题。其余的寒暄和过程性内容可以大胆砍掉。另外,当前问题如果明显切换了话题,我会在上下文里显式提示“以下进入新话题,此前历史仅供参考”,帮模型做区隔。这个提示很小,但效果立竿见影。

4.3 示例给多了反而带偏输出

少样本示例是好东西,但给多了会出问题。我有一次为了追求格式稳定,塞了五六个示例,结果模型开始机械模仿示例的措辞,遇到示例没覆盖的情况也不会变通。后来减到两三个,并且特意让示例覆盖不同难度和不同边界情况,泛化性反而好了。示例的作用是“示范范式”,不是“穷举答案”,给太多等于把模型的发挥空间压没了。

还有个细节是示例的摆放位置。放在指令之后、正式问题之前,模型更容易把它当成参照;放在问题之后,有时候会被当成问题的一部分。我一般固定在指令和问题之间,并用分隔符隔开,标注“以下为格式示例”。另外示例本身要干净,别有错别字或者和当前任务无关的内容,否则模型会连噪声一起学。这些细节看着琐碎,但都是实测出来的经验,能少走不少弯路。

5. 一套可复用的上下文组装流程

5.1 从需求到上下文的五步落地法

把前面的经验收拢一下,我平时落地一套上下文方案基本走五步。第一步,明确任务和评判标准,先想清楚“答成什么样算对”,这决定了后面所有取舍。第二步,列信息清单并分三档(必须给、可以给、不给),砍掉噪声。第三步,设计上下文结构,分层写指令、分隔素材、安排顺序。第四步,准备动态模板,按问题类型分几套。第五步,拿真实问题跑一轮,看答错的是“没看到”还是“看到了没用好”,分别对应检索问题和提示词问题。

这五步里,第一步最容易被跳过,但恰恰最重要。评判标准不清楚,后面分档和排版都是拍脑袋。我现在的习惯是先攒二三十个真实问题作为测试集,每改一版上下文就跑一遍,看准确率和稳定性变化。有了这个测试集,调优就有了依据,不至于改了半天不知道是变好还是变坏。这套流程不复杂,但坚持走下来,上下文质量会稳定很多。

5.2 用测试集验证上下文改动的实际收益

上下文调优最怕“感觉变好了”。我吃过这个亏,凭感觉改了一版,上线后才发现某些边缘问题反而变差了。后来固定用测试集做回归,每次改动都跑全量,记录准确率、引用准确率、拒答合理性几个指标。指标不用多,但要能反映你关心的维度。比如知识问答场景,我会重点看“答案正确率”和“引用是否真实存在”,后者能抓出模型编造来源的问题。

测试集本身也要维护,随着业务变化补充新问题,淘汰过时的。我一般保持测试集里有一半是常见问题、一半是边界和刁钻问题,这样既能看整体水平,也能看鲁棒性。跑测试的时候别只看总分,要逐条看错例,很多时候总分没变但错的地方换了,说明改动引入了新问题。这套验证习惯建立起来之后,上下文工程就从“玄学调参”变成了有据可依的工程活。

5.3 上下文长度与成本的平衡取舍

上下文不是免费的,长度直接关系到调用成本和响应速度。我的原则是“够用就好,留一点余量”。具体做法是先按必须给的信息组装,跑测试集看效果,如果准确率达标就不再加;如果不达标,再逐档加入“可以给”的信息,每加一档测一次,找到收益拐点就停。这样能避免无脑堆料带来的成本浪费。实测下来,很多场景在信息精简后效果不降,成本却降了不少。

还有个省成本的技巧是缓存。系统指令和固定示例这类不常变的部分,很多平台支持缓存复用,能省掉重复计算。动态部分(检索内容、历史摘要)才每次重新组装。另外,长文档场景可以考虑先做一轮粗筛再精排,把候选范围缩小再进上下文,而不是把所有候选都塞进去让模型自己挑。这些手段组合起来,能在保证效果的前提下把成本控制住。上下文工程不只是效果问题,也是成本问题,两者要一起考虑。

6. 几个容易混淆的概念澄清

6.1 上下文工程和提示词工程不是替代关系

经常看到有人把两者对立起来,说“提示词工程过时了,现在讲上下文工程”。我的理解是,两者是包含和协作的关系,不是替代。提示词工程解决的是“怎么把指令写清楚”,上下文工程解决的是“给模型看什么、怎么组织”。指令本身就是上下文的一部分,写得好不好依然重要。只不过当信息量变大、来源变多时,光靠指令技巧不够,得靠上下文组织来兜底。

实际项目里两者是配合使用的。上下文结构搭好之后,每个区块里的指令怎么写、示例怎么措辞,还是提示词工程的活。我一般先把上下文骨架搭起来,再逐块打磨措辞。反过来只练提示词不练上下文组织,遇到多源信息任务就会卡壳。所以别被“新概念取代旧概念”的说法带偏,把两者当成一套组合拳来练,效果最好。

6.2 上下文工程和检索增强的边界在哪

检索增强解决的是“从哪里把相关信息找出来”,上下文工程解决的是“找出来之后怎么组织给模型”。两者衔接紧密但职责不同。检索没做好,上下文里全是无关内容,再怎么组织也救不回来;检索做好了但组织得乱,模型照样用不好。我排查问题时会把这两段分开看:先看检索回来的内容相不相关,再看组织方式有没有问题,定位清楚再动手改。

实践中这两块经常要一起调。比如检索返回的片段太长,上下文里塞不下,就得在检索侧做片段切分和精排;反过来上下文结构设计得好,也能容忍检索结果稍微粗一点。我的经验是先把检索的召回率和相关性做到及格,再重点打磨上下文组织,因为组织方式的优化空间往往更大、成本更低。两边都到位了,整体效果才稳。

7. 我个人的几条实操心得

第一条,别迷信“万能模板”。上下文模板一定要结合具体场景调,别人的模板拿过来只能当起点,真实问题跑一轮才知道哪里要改。第二条,养成“先看上下文再改提示词”的排查习惯,能省掉大量无效调试。第三条,元信息别省,来源、版本、时间这几行字,往往决定了答案可不可信。第四条,测试集是上下文工程的命根子,没有测试集就是在盲调。第五条,长度和成本要一起算账,够用就好,别为了心理安慰堆料。

还有个小技巧分享下:调上下文的时候,我会把组装好的完整上下文打印出来,自己当模型读一遍,看能不能顺畅找到答对问题所需的信息。如果我自己读着都觉得绕、觉得信息打架,模型大概率也读不明白。这个“人肉自检”花不了几分钟,但经常能提前发现结构问题。上下文工程说到底是在替模型整理信息,站在它的角度想问题,很多坑就能提前避开。

返回列表