上下文工程这个词这两年被聊得很多,但真正动手做起来,很多人还是停留在“把提示词写长一点”的阶段。我自己在这个方向上踩了不少坑,也带过几个刚入门的同学,发现大家最困惑的不是概念本身,而是不知道从哪里下手、哪些细节真正影响效果、出了问题怎么排查。这篇就把我实际做上下文工程的经验完整拆开讲一遍,从整体设计思路到具体操作步骤,再到常见问题的排查方法,尽量做到看完就能上手。
先说清楚这篇适合谁看:如果你刚开始接触大模型应用,想搞明白“上下文工程”到底在工程上意味着什么,这篇能给你一条清晰的路径;如果你已经写过一些提示词、搭过简单的对话流程,但效果时好时坏、不知道怎么稳定下来,这篇里的参数选择、结构设计和排查表应该能帮你省不少时间。全文围绕一个核心问题展开——如何把“给模型看的信息”组织好,让它在有限窗口里稳定输出你想要的结果。
1. 上下文工程的整体设计与思路拆解
1.1 为什么单独把“上下文”拎出来做工程
很多人第一次听到“上下文工程”会以为是提示词工程的换皮说法,其实两者关注点不一样。提示词工程更多在打磨“这一句话怎么说”,而上下文工程关心的是“模型这一次调用时,整个输入里到底放了什么、按什么顺序放、放多少”。这个区别在实际项目里非常关键,因为模型的表现往往不是被某一句提示词决定的,而是被整个上下文的结构和内容分布决定的。
我举个自己遇到的例子。早期做一个文档问答的小工具,提示词写得挺讲究,但回答经常跑偏。后来把整个输入打印出来逐段看,发现问题出在检索回来的片段太长、噪声太多,真正相关的那一小段被埋在一大堆无关内容里。提示词一个字没改,只是把上下文做了裁剪和重排,回答质量立刻上了一个台阶。这件事让我彻底意识到,上下文本身就是一个需要被“工程化”对待的对象。
从工程角度看,上下文工程要解决的是三个层面的问题:放什么(内容选择)、怎么放(结构组织)、放多少(预算控制)。这三个层面互相牵制,任何一个处理不好都会拖累整体效果。下面这张表是我自己总结的三层关注点,后面所有内容基本都围绕它展开。
| 层面 | 核心问题 | 典型手段 | 出问题的表现 |
|---|---|---|---|
| 内容选择 | 哪些信息真正有用 | 检索、过滤、去重、摘要 | 答非所问、被噪声带偏 |
| 结构组织 | 信息按什么顺序和格式排列 | 分区、标记、角色划分 | 模型忽略关键指令 |
| 预算控制 | 窗口里放多少、怎么取舍 | 截断、压缩、优先级排序 | 关键内容被挤掉、超长报错 |
理解这张表之后,你会发现上下文工程其实很像做一道“信息配餐”——食材要挑对,摆盘要有讲究,分量还要控制好,任何一环马虎,最后端上来的菜都不对味。
1.2 方案选型的几个关键取舍
真正动手时,第一个要面对的选择是:上下文是“一次性拼好”还是“动态组装”。一次性拼好就是把所有内容按固定模板塞进去,简单直接,适合输入稳定、场景单一的任务,比如固定格式的文本分类。动态组装则是根据当前请求实时决定放哪些内容,灵活但复杂,适合检索问答、多轮对话这类输入变化大的场景。
我的建议是新手先从固定模板起步,把结构跑通、把效果基线建立起来,再逐步引入动态组装。原因很实际:固定模板下你能清楚知道每一段内容的作用,出问题容易定位;一上来就搞动态组装,变量太多,效果不好时根本不知道是检索的问题还是拼装的问题。
第二个取舍是长上下文模型 vs 精简上下文。现在不少模型支持很长的窗口,很多人第一反应是“那我全塞进去不就行了”。实测下来这个思路要谨慎。窗口长不代表模型对每个位置的信息都同等敏感,中间位置的内容被忽略的情况相当常见,业内一般叫“中间遗忘”。而且塞得越满,推理成本和延迟越高,噪声也越多。我的做法是:窗口是上限不是目标,能精简就精简,把最相关的内容放在开头和结尾这两个模型更敏感的位置。
第三个取舍是结构化标记的粒度。用分隔符、标签、小标题把不同来源的内容区分开,能显著降低模型混淆的概率。但标记也不是越多越好,过度标记会让上下文变得冗长,反而稀释了真正的内容。我一般遵循“一个来源一个区块、区块内用自然语言过渡”的原则,够用就行。
1.3 一个可复用的整体架构
把上面的取舍落到具体结构上,我常用的上下文组织方式是这样的:开头放角色和任务说明,中间放参考资料或历史对话,结尾放当前问题和输出格式要求。这个顺序不是随便定的——开头和结尾是模型注意力相对集中的区域,把最关键的指令放在这两头,中间放相对次要的支撑材料,符合前面提到的注意力分布特点。
具体到每一块的内容,角色说明要短而明确,别写成一大段背景介绍;参考资料要带来源标记,方便模型区分不同文档;当前问题要单独成段,避免和参考资料混在一起;输出格式要求尽量用示例而不是纯描述,模型对示例的遵循度通常更高。这套结构我在多个不同类型的任务里复用,改动的主要是中间参考资料部分,骨架基本不动,省了很多重复设计的时间。
2. 核心细节解析与实操要点
2.1 内容选择:怎么判断一段信息该不该放
内容选择是上下文工程里最影响效果、也最容易被忽视的一环。我的判断标准很简单:这段信息如果删掉,模型还能不能答对?如果能,就删。这个标准听起来粗暴,但实操中非常有效,因为它逼着你去想每一段内容的实际贡献,而不是“多放点总没坏处”。
具体操作上,我一般分三步走。第一步是粗筛,把明显无关的内容先去掉,比如检索时得分低于阈值的片段、和当前问题主题不沾边的历史对话。第二步是去重,不同来源的内容经常有重复表述,重复内容不仅浪费预算,还会让模型误以为某个信息特别重要。第三步是压缩,对保留下来的长片段做摘要或抽取关键句,把一段几百字的内容压到几十字的核心信息。
这里有个容易踩的坑:压缩的时候容易把限定条件、否定表述这类“小词”丢掉,导致语义反转。比如“在特定条件下不推荐使用”被压成“推荐使用”,意思完全变了。我的经验是压缩后一定要回读一遍,重点检查否定词、条件词、数量词有没有丢失,这几类词是语义的骨架,丢了就散架。
提示:内容选择阶段宁可保守一点,先把明显无关的去掉,别急着大幅压缩。压缩带来的语义损失往往比多放一点噪声更难排查。
2.2 结构组织:让模型一眼看懂每段是什么
结构组织的目标只有一个:让模型不用猜就能知道每段内容的角色。实现方式主要靠分隔和标记。我常用的做法是用清晰的分隔符把不同区块隔开,每个区块开头用一句话说明这段是什么,比如“以下是参考资料”“以下是历史对话”“以下是当前问题”。
标记的写法有讲究。纯符号分隔(比如一排横线)简单但信息量低,模型只能靠位置推断内容角色;带说明的分隔(比如“【参考资料开始】……【参考资料结束】”)信息量高,模型能明确知道边界和角色。我一般用后者,虽然多几个字,但换来的是更稳定的解析。
另一个细节是区块内部的顺序。参考资料如果有多个文档,按相关度从高到低排,最相关的放最前面;历史对话按时间顺序排,最近的放最后面靠近当前问题。这个顺序安排的道理和前面说的一样,都是把重要信息往注意力敏感的位置靠。
还有一点值得单独说:输出格式要求尽量用示例。纯文字描述“请输出JSON格式”远不如直接给一个JSON示例来得有效。模型对示例的模仿能力很强,一个清晰的示例往往顶得上一大段格式说明。我在需要结构化输出时,基本都会附一个最小示例,效果比反复强调格式要求稳定得多。
2.3 预算控制:窗口快满了怎么办
预算控制是很多人做到后期才会重视的问题。刚开始内容少,随便塞都够;一旦接入真实数据,窗口很快就吃紧。这时候需要有明确的取舍策略,而不是等到报错才临时删内容。
我的策略是给不同区块设优先级:当前问题和输出要求优先级最高,永远保留;角色说明次之,可以精简但不能删;参考资料优先级最低,窗口紧张时优先压缩或截断这部分。截断时从相关度最低的片段开始删,而不是简单地从末尾砍——末尾砍容易把相关度高的内容误删。
参数上,我一般会给参考资料预留窗口的百分之五十到六十,剩下的留给指令、历史和输出。这个比例不是死的,任务越依赖外部知识,参考资料占比越高;任务越依赖指令遵循,指令部分占比越高。实际调的时候可以先按这个基线跑,观察效果再微调。
| 区块 | 建议预算占比 | 紧张时的处理 |
|---|---|---|
| 当前问题与输出要求 | 15%–20% | 不删,最多精简措辞 |
| 角色与任务说明 | 10%–15% | 精简,保留核心约束 |
| 历史对话 | 15%–25% | 按时间截断,保留最近几轮 |
| 参考资料 | 50%–60% | 按相关度截断或压缩 |
注意:预算占比是起点不是终点,一定要结合具体任务实测。我见过直接照搬别人比例结果效果很差的案例,原因是任务性质完全不同。
2.4 实操心得:几个反直觉的发现
做了这么多轮,有几个发现和直觉相反,值得单独拎出来说。第一个是上下文不是越长越好,前面提过中间遗忘,实际测试中把无关内容删掉后效果提升的情况非常普遍。第二个是格式整齐比内容丰富更重要,结构清晰、分区明确的上下文,效果往往好过内容更多但杂乱无章的上下文。第三个是示例的力量被低估,一个恰当的输入输出示例,对输出稳定性的提升经常超过大段文字说明。
这些发现背后其实指向同一个道理:模型处理的是模式,你给它的上下文越有规律、越清晰,它越容易抓住你要的模式。所以上下文工程的核心不是“堆信息”,而是“造模式”。
3. 实操过程与核心环节实现
3.1 从零搭一个上下文组装流程
这一节我把完整的组装流程走一遍,你可以直接照着搭。整个流程分四步:收集候选内容、筛选与压缩、按模板组装、校验与截断。
第一步收集候选内容。以文档问答为例,候选内容包括检索返回的文档片段、最近几轮对话历史、当前用户问题、系统角色说明。这一步不做任何删减,先把所有可能用到的内容集中起来,方便后续统一处理。
第二步筛选与压缩。对文档片段按相关度排序,去掉低于阈值的;对历史对话按轮次截断,只保留最近若干轮;对长片段做抽取式压缩,保留关键句。这一步是效果的关键,压缩质量直接决定最终表现。
第三步按模板组装。用前面说的结构:角色说明、参考资料、历史对话、当前问题、输出要求。每个区块用带说明的分隔符隔开,区块内按相关度或时间排序。
第四步校验与截断。组装完先估算总长度,超过预算就按优先级截断参考资料,再超就精简历史对话。截断后重新检查结构完整性,确保分隔符成对、区块说明没被截掉。
下面是一个组装模板的示意,用普通文本表示,实际使用时把占位内容替换成真实数据即可:
【角色说明】 你是一个文档问答助手,只依据下方参考资料回答,资料中没有的信息如实说明。 【参考资料开始】 文档A(相关度0.92):…… 文档B(相关度0.85):…… 【参考资料结束】 【历史对话开始】 用户:…… 助手:…… 【历史对话结束】 【当前问题】 …… 【输出要求】 用简洁的中文回答,不超过三句话。示例: 问:…… 答:……这个模板看起来朴素,但每一块都有明确作用,改动时也容易定位问题。我建议新手先把这个骨架跑通,再根据任务特点增删区块。
3.2 参数选择与计算过程
上下文组装里涉及几个需要拍脑袋的参数,我把自己的取值逻辑说清楚,方便你按需调整。
相关度阈值:检索片段的相关度分数分布因任务而异,我一般先跑一批样本看分布,取能覆盖大部分正确片段的分位点作为阈值。比如正确片段大多落在前百分之三十,阈值就设在这个位置附近。这个值需要根据实际数据调,没有通用数字。
历史对话保留轮数:取决于任务对上下文的依赖程度。纯问答任务保留最近两到三轮足够;需要跟踪多步操作的任务可能要保留更多。我的经验是保留轮数乘以每轮平均长度,控制在窗口的百分之二十以内。
压缩比例:抽取式压缩一般保留原片段的三成到五成关键句。压缩太狠容易丢信息,太松又起不到省预算的作用。可以先按四成试,观察效果再调。
总预算:按模型窗口的百分之八十设安全线,留出百分之二十给输出和波动。这个安全线很重要,卡着上限跑很容易在长输出时出问题。
这些参数没有一组放之四海皆准的值,关键是理解每个参数影响什么,然后基于自己的数据去调。我一般会准备一个小测试集,每次调参后跑一遍对比,避免凭感觉改。
3.3 一次完整的实操记录
拿一个具体的文档问答任务走一遍。输入是一个用户问题加一批检索片段,我先按相关度排序,去掉低于阈值的两个片段,剩下四个片段。四个片段里有两个内容高度重复,去重后剩三个。三个片段总长度超出参考资料预算,对最长的那个做抽取式压缩,从约四百字压到约一百五十字。
组装时按模板填入:角色说明用固定文案,参考资料按相关度排三个片段并带来源标记,历史对话保留最近两轮,当前问题单独成段,输出要求附一个简短示例。组装完估算长度在预算内,无需额外截断。
跑出来的回答准确引用了参考资料里的信息,没有编造,格式也符合示例。对比之前不做筛选压缩的版本,回答的准确率和稳定性都有明显提升。这次实操让我更确信:上下文工程的价值不在某个神奇技巧,而在把每个环节做扎实。
4. 常见问题与排查技巧实录
4.1 典型问题速查表
实际做上下文工程,问题五花八门,我把高频的整理成表,方便对照排查。
| 问题表现 | 可能原因 | 排查方向 | 处理办法 |
|---|---|---|---|
| 答非所问 | 参考资料噪声多或相关度低 | 打印上下文逐段检查 | 提高阈值、去重、压缩 |
| 忽略关键指令 | 指令被埋在中间或表述模糊 | 检查指令位置和措辞 | 指令前移或后置、用示例 |
| 输出格式不稳定 | 格式要求只有文字描述 | 检查是否附示例 | 补一个最小示例 |
| 长输入时报错 | 超出窗口或预算没留余量 | 估算总长度 | 按优先级截断、留安全线 |
| 回答前后矛盾 | 参考资料内部有冲突信息 | 检查资料一致性 | 去重、标注来源、明确优先级 |
| 关键信息丢失 | 压缩时删掉了限定词 | 回读压缩后内容 | 保留否定词、条件词、数量词 |
这张表是我自己排查时的第一道工具,大部分问题能在这里找到方向。表里没覆盖的,再往下看具体案例。
4.2 两个真实排查案例
第一个案例是回答突然开始编造。之前一直正常,某次更新检索逻辑后回答开始出现资料里没有的内容。排查时先打印上下文,发现检索返回的片段里混进了一些相关度虚高的无关文档,模型被这些内容带偏,开始“脑补”。处理办法是收紧相关度阈值,并对片段做去重,问题消失。这个案例的教训是:检索逻辑的改动会直接影响上下文质量,改检索一定要回归测试上下文效果。
第二个案例是关键指令被忽略。输出格式要求明明写了,模型就是不遵守。检查上下文发现,格式要求放在了很长的参考资料后面,位置靠后且只有一句文字描述。把格式要求移到当前问题之后、并补了一个示例,问题解决。这个案例说明位置和示例对指令遵循的影响很大,光有文字要求不够。
4.3 独家避坑技巧
最后分享几个踩坑换来的技巧。第一,每次改动只动一个变量,上下文工程变量太多,一次改多处,效果变化了根本不知道是哪处起的作用。第二,保留上下文快照,出问题时能回看当时的完整输入,比事后回忆靠谱得多。第三,建立小测试集,固定一批有代表性的输入,每次调参后跑一遍对比,避免凭感觉判断好坏。第四,压缩后必回读,重点看否定词和条件词,这是语义最容易翻车的地方。
我个人在实际操作中的体会是,上下文工程没有一劳永逸的配置,它是一个持续调优的过程。任务变了、数据变了、模型换了,上下文策略都得跟着调。把结构搭稳、把排查工具备好、把测试集建起来,后面无论怎么变,你都有底气快速定位和调整。这套方法我在不同类型的任务里反复用过,骨架基本不用大改,改的主要是具体参数和区块内容,省下来的时间可以花在真正影响效果的地方。