上下文工程这个词这两年被聊得越来越多,但真正动手做过的团队其实没那么多。我最早接触这个概念是在一个对话类产品的迭代里,当时模型换了一版,回答质量反而掉了,排查了整整两天才发现问题不在模型本身,而在我们喂进去的上下文结构变了——历史消息截断策略改了,系统提示词的位置挪了,几个工具描述的顺序也调了。那次之后我才真正意识到,上下文工程不是玄学,它是一套可以拆解、可以复现、可以量化的工程方法。这篇文章我想把这两年踩过的坑、试过的方案、验证过的参数,尽量完整地摊开讲一遍,从概念到落地,从设计到排查,适合刚入门想搞明白这套东西的读者,也适合已经在做但总觉得效果不稳的同行对照着看。
1. 上下文工程到底在解决什么问题
1.1 从提示词工程到上下文工程的认知升级
很多人把上下文工程当成提示词工程的新马甲,这个理解偏差挺大的。提示词工程关注的是"这一句话怎么写",上下文工程关注的是"这一整块信息怎么组织"。打个比方,提示词工程像是你在跟人说话时斟酌措辞,上下文工程则是你在安排一场会议——谁先发言、谁坐哪、材料什么时候发、哪些信息该提前铺垫、哪些该留到最后揭晓,这些都属于上下文工程的范畴。
具体到技术层面,一次模型调用的输入其实由好几块拼起来:系统指令、历史对话、检索到的外部知识、工具定义、当前用户输入、格式约束。这些内容不是简单堆在一起就完事,它们的顺序、篇幅、粒度、冗余度都会直接影响输出质量。我见过太多案例,模型本身没问题,就是上下文塞得太满或者太乱,导致关键信息被淹没。
从提示词工程到上下文工程的转变,本质是从"单点优化"走向"系统设计"。前者是局部技巧,后者是全局架构。你在做上下文工程时,要考虑的不只是这句话怎么说,还要考虑整个信息流的生命周期——什么进、什么出、什么留、什么丢。
1.2 上下文窗口变大为什么反而更难用
现在主流模型的上下文窗口动辄几十万 token,理论上能塞进去的东西越来越多,但实际用下来你会发现,窗口大不等于效果好。这里有个很反直觉的现象:把一堆相关资料全塞进去,模型的表现有时候还不如只给两三段精准内容。
原因在于注意力机制的特性。模型在处理长上下文时,对中间位置的信息关注度会下降,业内常说的"中间遗忘"就是这个现象。你辛辛苦苦检索了二十篇文档全塞进去,结果模型只记住了开头和结尾的几段,中间的关键论据反而被忽略了。所以上下文工程的一个核心任务,不是"能塞多少塞多少",而是"该塞什么、塞多少、怎么排"。
我实测下来的经验是,对于大多数问答和生成任务,有效上下文控制在窗口容量的三到五成是比较稳的区间。留出余量不仅能让模型注意力更集中,也给输出留了空间。当然这不是绝对数字,具体要看任务复杂度和信息密度,但"宁精勿滥"这个原则基本通用。
1.3 哪些场景最需要上下文工程
不是所有场景都值得投入精力做上下文工程。简单的一问一答、单轮翻译、格式转换这类任务,提示词写清楚就够了。真正需要上下文工程发力的是这几类场景:
- 多轮对话系统:历史消息怎么压缩、怎么摘要、哪些轮次要保留原文,直接决定对话连贯性。
- 检索增强生成:检索回来的片段怎么排序、怎么去重、怎么标注来源,影响答案的准确性和可追溯性。
- 工具调用型应用:多个工具的定义怎么描述、按什么顺序排列、参数说明写多细,关系到模型选工具的准确率。
- 长文档处理:合同审阅、报告分析这类任务,文档怎么分块、块之间怎么建立关联,是成败关键。
- 角色扮演与人格化应用:人设信息怎么稳定注入、怎么防止多轮之后人设漂移,是这类产品的核心难题。
你可以对照一下自己手上的项目,如果命中上面任意两条以上,那上下文工程就值得认真做一轮。
2. 上下文的组成结构与设计原则
2.1 一次调用的上下文分层模型
我把一次模型调用的上下文拆成五层,从外到内依次是:系统层、知识层、历史层、任务层、约束层。这个分层不是官方标准,是我自己在项目里总结出来的组织方式,好处是每一层职责清晰,改动时不容易互相干扰。
系统层放的是角色设定、能力边界、总体行为准则,这部分内容相对稳定,一次会话内基本不变。知识层放检索到的外部信息或者预置的领域知识,这部分是动态的,每次请求可能不同。历史层是对话记录,需要做压缩和摘要处理。任务层是当前这一轮的具体指令,要写得明确具体。约束层是输出格式、长度、语气这些硬性要求。
分层的价值在于可维护性。当输出出问题时,你可以逐层排查:是系统层的人设被稀释了,还是知识层塞了无关内容,还是历史层压缩过度丢了关键信息。不分层的话,所有内容糊成一团,排查起来就是大海捞针。
2.2 信息密度与冗余度的平衡
上下文里最忌讳两种极端:一种是信息太稀,说了半天没说到点子上;另一种是信息太密,每句话都是干货但堆在一起反而让人抓不住重点。模型处理信息的方式和人有点像,需要一定的结构信号来引导注意力。
我的做法是给关键信息加"锚点"。比如在知识层里,每段检索内容前面加一句简短的主题标注,告诉模型这段讲的是什么;在历史层里,对重要的用户偏好做显式标记,而不是指望模型自己从对话里总结。这些锚点会增加一些 token 开销,但换来的是模型定位信息的准确率提升,这笔账算下来是划算的。
冗余度控制上,我一般遵循"同一信息不重复超过两次"的原则。系统层说过的人设,任务层就不用再强调;知识层已经给出的数据,约束层就不用再复述。重复不仅浪费窗口,还会让模型误以为这是重点而过度关注,反而干扰判断。
2.3 顺序编排对输出质量的隐性影响
上下文里内容的排列顺序,影响比大多数人想象的大。有几个我验证过的规律:
重要指令放两端。开头和结尾是模型注意力最集中的位置,核心要求尽量放在系统层开头或者任务层结尾,别埋在中间。
知识在前,问题在后。检索内容应该放在用户问题之前,让模型先"读完材料"再"回答问题",顺序反了容易导致模型先形成判断再找证据,产生确认偏误。
示例紧跟指令。如果你给了输出示例,示例要紧挨着对应的指令,中间别插其他内容,否则模型可能对不上号。
工具定义按使用频率排。工具多的时候,常用的放前面,冷门的放后面,实测能提升工具选择的准确率。
这些规律不是拍脑袋想出来的,是我在不同项目里做 A/B 对比测出来的。当然不同模型可能有细微差异,建议你在自己的场景里也做一轮小规模验证。
3. 核心实操:上下文组装的完整流程
3.1 系统提示词的编写与稳定注入
系统提示词是上下文的"地基",写得好不好直接决定后续所有环节的上限。我总结了一个四段式结构,实测下来比较稳:
第一段定角色和能力边界,说清楚"你是谁、你能做什么、你不能做什么"。第二段定行为准则,比如遇到不确定的信息要怎么处理、遇到超出范围的问题要怎么回应。第三段定输出风格,语气、详略、格式偏好。第四段放少量高质量示例,给模型一个具体的参照。
这里有个细节很多人忽略:系统提示词要防稀释。多轮对话之后,系统提示词在上下文里的占比会越来越小,模型对它的"记忆"会减弱。解决办法是在历史层压缩时,始终保留系统提示词的核心要点摘要,或者在每若干轮之后重新注入一次精简版人设。我一般设置每八到十轮做一次轻量重注入,效果比较明显。
还有一个坑是系统提示词写太长。我见过有人写了三千字的系统提示,结果模型反而抓不住重点。系统层控制在五百到八百字是比较舒服的区间,超过一千字就要考虑拆分,把部分内容挪到知识层按需注入。
3.2 历史对话的压缩与摘要策略
多轮对话的上下文管理是重灾区。全量保留会撑爆窗口,粗暴截断会丢关键信息,怎么取舍是门手艺。我目前用的是分层压缩方案:
最近三轮对话保留原文,一个字不改,保证即时连贯性。第四到第十轮做轻度摘要,保留用户的核心诉求和模型的关键结论,删掉寒暄和试错过程。十轮以前的对话做深度摘要,只保留事实性信息,比如用户透露的偏好、已经确认的结论、明确排除的方案。
摘要不是让模型随便总结,而是要用固定的模板。我用的模板大概是:"用户在此轮提出了X需求,确认了Y信息,排除了Z方案。"这种结构化摘要比自由发挥的摘要稳定得多,信息丢失率也低。
注意:摘要环节本身也要消耗一次模型调用,如果对话轮次不多,直接截断可能更划算。我一般设置在对话超过十五轮之后才启用摘要机制,短对话用滑动窗口截断就够了。
另外有个实操技巧:在历史层的最前面放一句"以下是历史对话摘要,按时间顺序排列",给模型一个明确的结构信号。别小看这句话,实测能减少模型把摘要内容当成当前指令的误判。
3.3 检索内容的筛选、排序与标注
检索增强场景下,检索回来的内容怎么处理是上下文工程的重头戏。我的处理流程分四步:
第一步去重。不同来源的文档经常有重复段落,用简单的文本相似度做一轮过滤,能砍掉两三成的冗余。
第二步相关性重排。检索系统给的排序不一定适合直接喂给模型,我会用一个轻量模型或者规则做二次排序,把和当前问题最相关的片段提到前面。
第三步分块粒度调整。块太大信息杂,块太小上下文断裂。我的经验是每块控制在三百到五百字,块与块之间保留一句重叠内容做衔接。
第四步加标注。每块前面加一行来源和主题标注,格式大概是"[来源:某文档 | 主题:XX]"这种。标注的作用是让模型知道这段内容的出处和主旨,回答时能引用来源,也方便它判断内容可信度。
排序上我遵循"倒金字塔"原则:最相关的放最前,次相关的放最后,中间放补充材料。这样即使模型只关注了两端,也能拿到最关键的信息。
3.4 工具定义的描述技巧与排列顺序
工具调用型应用里,工具定义的写法直接影响选择准确率。我踩过的坑是工具描述写得太技术化,模型理解不了工具的适用场景。后来改成"功能描述+适用场景+不适用场景"三段式,准确率明显提升。
举个例子,一个查询订单状态的工具,光写"根据订单号查询订单状态"是不够的,要补上"适用于用户询问物流进度、订单是否发货等场景;不适用于修改订单、退款等操作"。把边界说清楚,模型就不会乱用工具。
工具数量多的时候,排列顺序也有讲究。我一般按"高频通用工具在前,低频专用工具在后"排,同时把功能相近的工具放在相邻位置,方便模型对比选择。工具超过十个之后,建议在系统层加一句工具使用总则,告诉模型优先考虑哪几类工具。
参数描述同样重要。每个参数要写清楚类型、是否必填、取值范围、示例值。我见过参数描述只写"订单号"三个字的,模型经常传错格式。补上"字符串类型,必填,格式为纯数字,例如12345678"之后,传参错误率降了一大截。
4. 参数调优与效果验证方法
4.1 关键参数的取值逻辑与计算
上下文工程涉及几个关键参数,取值不能拍脑袋,要有计算依据。我列一下自己常用的几个:
上下文预算分配。假设模型窗口是 128K token,我不会全用满。我的分配比例大概是:系统层占 5%,知识层占 40%,历史层占 25%,任务层和约束层占 10%,剩下 20% 留给输出。这个比例是根据任务类型调的,检索密集型任务知识层可以提到 50%,对话密集型任务历史层可以提到 35%。
分块大小计算。分块大小 = 目标知识层预算 ÷ 预期块数。比如知识层预算 50K token,预期放 100 块,那每块就是 500 token 左右,换算成中文大概三百多字。这个计算能保证块数和预算匹配,不会出现块太大放不下或者块太小信息碎的问题。
摘要压缩比。轻度摘要保留原文 40% 到 50% 的信息量,深度摘要保留 15% 到 20%。这个比例是通过对比摘要前后关键信息召回率测出来的,低于这个比例信息丢失明显,高于这个比例压缩效果不够。
这些数字不是金科玉律,但提供了一个起点。你在自己场景里可以先按这个比例跑一轮,再根据实际效果微调。
4.2 用对照实验验证上下文改动
上下文工程的改动最怕"感觉变好了"。感觉这东西不靠谱,必须用对照实验说话。我的做法是建一个小型评测集,覆盖典型场景,每次改动上下文结构就跑一轮对比。
评测集不用很大,三五十条就够,但要覆盖不同难度和不同类型。每条样本记录改动前后的输出,人工或者用规则打分。打分维度我一般看四个:准确性(信息对不对)、完整性(该说的说了没)、相关性(有没有跑题)、格式合规(输出格式对不对)。
跑对照实验时有个原则:一次只改一个变量。同时改顺序又改分块大小,出了问题根本不知道是哪个引起的。我吃过这个亏,后来老老实实单变量测试,虽然慢一点但结论可靠。
4.3 上下文长度与响应质量的实测曲线
我在一个问答项目里做过一组实测,把上下文长度从 2K token 逐步加到 64K token,观察响应质量的变化。结果挺有意思:
| 上下文长度 | 准确性 | 完整性 | 响应延迟 |
|---|---|---|---|
| 2K | 中等 | 偏低 | 快 |
| 8K | 较高 | 中等 | 较快 |
| 16K | 高 | 较高 | 中等 |
| 32K | 高 | 高 | 偏慢 |
| 64K | 略降 | 高 | 慢 |
可以看到,质量在 16K 到 32K 之间达到峰值,再往上加反而准确性略降,延迟还明显上升。这个曲线的拐点因任务而异,但"存在最优区间"这个结论是通用的。盲目堆上下文长度是性价比很低的做法。
5. 常见问题排查与避坑实录
5.1 输出跑偏的逐层排查法
输出跑偏是最常见的问题,排查时我按分层模型从外往内查:
先看系统层,人设有没有被稀释,行为准则有没有被后续内容覆盖。再看知识层,检索内容里有没有和问题冲突的信息,有没有无关内容干扰。然后看历史层,摘要有没有丢关键信息,有没有把旧指令当成新指令。最后看任务层,当前指令写得够不够明确,有没有歧义。
这个排查顺序的逻辑是"从稳定层往动态层查",因为稳定层出问题的概率低但影响大,动态层出问题的概率高但影响局部。大部分跑偏问题最后都定位在知识层和历史层。
5.2 上下文超限的应急处理
上下文超限报错是家常便饭,应急处理有几个手段,按优先级排:
第一优先是压缩历史层,这是最不影响效果的。第二是精简知识层,去掉相关性低的块。第三是缩短系统层,把非核心内容挪走。最后才是截断任务层,这个要慎用,容易丢关键指令。
我一般会在组装上下文之前先做一次预算检查,预估 token 数,超了就按上面的优先级自动降级。这个预检查机制能避免大部分超限报错。
5.3 多轮对话中的人设漂移修复
人设漂移是多轮对话的顽疾,聊到十几轮之后模型就开始"出戏"。修复手段我试过几种,效果最好的是周期性重注入:每八到十轮,在历史层末尾插入一段精简人设摘要,格式是"提醒:你的角色是X,核心行为准则是Y"。这段内容不占太多 token,但能有效把人设拉回来。
另一个辅助手段是在摘要模板里固定保留人设相关字段,每次摘要都带上,防止人设信息在压缩过程中被丢掉。两个手段配合使用,人设稳定性提升很明显。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方向 | 解决手段 |
|---|---|---|---|
| 输出跑偏 | 知识层有冲突信息 | 检查检索内容相关性 | 去重+重排+过滤 |
| 人设漂移 | 系统层被稀释 | 检查轮次和人设占比 | 周期性重注入 |
| 上下文超限 | 预算分配失衡 | 检查各层 token 占比 | 按优先级降级压缩 |
| 工具选错 | 工具描述不清 | 检查描述三段式完整性 | 补适用/不适用场景 |
| 信息遗漏 | 摘要压缩过度 | 检查摘要保留比例 | 提高压缩比或改模板 |
| 响应变慢 | 上下文过长 | 检查总 token 数 | 压缩到最优区间 |
这张表是我从实际项目里攒出来的,遇到问题先对照着查一遍,大部分情况能快速定位。
6. 工程化落地与持续优化
6.1 上下文模板的版本管理
上下文结构一旦确定,就要当成代码一样管理。我的做法是把上下文模板抽成配置文件,每个版本打标签,改动走评审流程。这样做的原因是上下文改动的影响很隐蔽,今天改个顺序明天可能才暴露问题,有版本记录才能回溯。
模板配置里我一般包含这几块:各层的模板文本、参数默认值、压缩策略配置、工具定义列表。改任何一项都要记录改动原因和预期效果,跑完对照实验再合并。这套流程听起来重,但比出了问题抓瞎强得多。
6.2 监控指标与迭代节奏
上线之后要有监控。我关注的指标分两类:质量指标和成本指标。质量指标看输出准确率、用户反馈、重试率;成本指标看平均 token 消耗、响应延迟、调用次数。
迭代节奏上,我不建议频繁大改。上下文结构稳定下来之后,小步快跑地调参数就行,大结构调整一个季度做一次顶天了。频繁大改会让效果波动,用户感知很差,排查起来也困难。
6.3 不同模型间的上下文适配差异
换模型是上下文工程最容易翻车的时候。不同模型对上下文顺序的敏感度、对指令的遵循程度、对长上下文的处理能力都不一样。我的经验是换模型之后必须重跑一遍对照实验,重点看三个地方:系统提示词的遵循度、知识层的利用效率、工具选择的准确率。
有些模型对结构化标注更敏感,有些模型对自然语言描述更买账。适配的时候不要照搬,要根据模型特性调整。比如某个模型对列表式指令遵循得好,那就把关键要求改成列表;另一个模型对示例学习效果好,那就多放几个示例。这些调整都要靠实测确定,没有通用答案。
7. 我个人的几条实操心得
做上下文工程这两年,有几个体会比较深,分享出来供参考。
第一,别迷信窗口大小。窗口大是好事,但用不好反而是负担。我见过太多项目把窗口当垃圾桶,什么都往里塞,结果效果还不如精简版。上下文工程的核心是取舍,不是堆砌。
第二,结构化比自然语言更稳。同样的信息,用固定模板和标注组织起来,比一段流畅的自然语言描述效果稳定得多。模型对结构的敏感度比我们想象的高,给它清晰的骨架,它填内容的质量就高。
第三,对照实验是唯一真理。所有"我觉得这样更好"的判断都要用数据验证。上下文改动的影响很微妙,直觉经常骗人。建个小评测集,养成改完就跑的习惯,长期下来能省很多返工。
第四,留余量是门艺术。上下文预算、压缩比例、摘要保留度,这些参数都要留缓冲。卡着极限用,遇到边界情况就崩。我一般所有预算都留两成余量,稳字当头。
第五,人设和指令要防衰减。多轮对话里,越靠前的内容越容易被"遗忘"。重要的东西要么周期性重注入,要么放在靠后的位置。这个规律在几乎所有模型上都成立,值得当成常识记住。
上下文工程这个方向还在快速演进,新的模型、新的能力会不断改变最佳实践。但底层的那套思路——分层组织、预算分配、对照验证、持续迭代——短期内不会过时。把这几条吃透,换什么模型都能快速适配。后续如果要做更细的展开,工具调用的上下文优化和长文档的分块策略是两个值得深挖的方向,我后面有机会再单独聊。