前几天有个朋友问我:“上下文的思路我基本懂了,但真正上手项目时,结果总是不稳定,到底卡在哪?”
我反问他:“你是在做提示词拼接,还是在做上下文工程?”
他愣了一下,然后说:“这两者有区别吗?”
这就是问题所在。很多人把上下文工程等同于写Prompt,实际上Prompt只是上下文工程的一个环节,甚至不是最核心的环节。上下文工程是一套系统性的方法论,覆盖了从窗口管理、信息压缩、知识注入到输出约束的完整链路。这篇文章就顺着我的实际经验,逐层把这个话题拆开讲透。
1. 先搞清楚:上下文工程到底在解决什么问题
1.1 局限性的边界,决定了你的系统上限
前两年刚做模型应用时,我最常犯的一个错误,就是以为“问题是模型能力不够”。排名靠后的模型效果差,换个强的模型的确会有明显改善,但做到后期就会发现效果趋平,成本却在飙升。
真正让应用从“能跑”变成“可用”的核心差异,往往不是模型本身的智力,而是你对上下文空间的管理水平。
每一代模型都有一个上下文窗口上限,目前主流模型从几十K到两百K不等。你输入给模型的,无论是系统指令、历史消息、检索结果还是业务数据,所有内容都要占用这段窗口。这个窗口就是你的上下文空间。
限制就在这:窗口再大,相对于真实世界的信息量都是杯水车薪。如果不对这段空间做精细化管理,那些真正重要的信息会被次要信息淹没。这就是上下文工程存在的意义,在有限的上下文空间里,让关键信息以最恰当的粒度、顺序和格式,出现在最恰当的位置。
1.2 上下文工程与提示词工程的真实边界
有不少人问我,上下文工程是不是提示词工程换了个说法?
从协作方式看,提示词工程是一种单点优化,重点在“怎么把需求描述清楚”,它是上下文工程的地基。而上下文工程是一种系统性思想,它关心的是“哪些信息能否进来、以什么顺序进来、进来后如何组织、用完如何淘汰”。
一个清晰的分界线:如果系统的输入只有一条写死的指令,你确实只需要提示词工程。但只要你的系统有动态数据、多轮状态或外部知识接入,你就已经身处上下文工程的范畴了。
我自己做过一个模拟项目X,早期版本里把用户所有问题先塞进一个“历史记录”字段,再把搜索结果整段拼进去,效果时好时坏。后来把体系全面改为上下文管理方案后,同样的模型,效果稳定性和答案质量都有了非常明显的提升,这正说明上下文工程的核心价值在整套组合拳,而不在某一两个提示词技巧。
2. 第一层:上下文窗口的理解与预算管理
2.1 上下文窗口到底是什么
看起来像是技术基础,却是最容易被忽视的一层,理解了它,后面所有的策略才有了依据。
上下文窗口是模型一次推理所能“看到”的内容范围。比如一个上下文窗口为128K的模型,它一次大约能处理16万到20万个英文token(不同模型分词策略有差异),再长就放不下了。放不下的有两种处理方式:一是直接报错,二是自动截断掉最前面的内容——这会造成什么后果后面我会详细说。
实操中经常有人问:那我的对话历史明明没多长,为什么输入量这么大?这是因为token的计费方式很容易让人低估消耗。中文字符在主流分词器下通常1个汉字约等于1到2个token,一段400字的消息大约只占500到800token,看起来不多。但一次完整的业务请求,光系统指令、示例、检索结果和几轮历史加起来,轻松就能超过三五千token。到了高并发场景,这个基数的放大效应非常显著。
提示:在项目初期就做token用量埋点统计,按会话维度记录输入输出token数。不然后期排查成本超支问题时,你会连原因都定位不到。
2.2 预算拆分策略与硬性上限
我给团队定过一个很朴素的预算建议:总上下文窗口留出20%到30%作为安全余量,实际可用空间按70%到80%规划。原因很简单,如果在满打满算的状态下运行,一个突发的长文档注入或异常长回复都会导致截断,而这恰恰是线上事故的高发场景。
一个典型的预算分配结构大概是这样,以128K窗口为例:
| 用途 | 预估占用 | 说明 |
|---|---|---|
| 系统指令 | 1000-2000 token | 包含角色设定与输出规则 |
| 少样本示例 | 1500-4000 token | 根据任务复杂度调整 |
| 检索上下文 | 8000-12000 token | 控制在有效信息范围内 |
| 对话历史 | 3000-8000 token | 建议启用滑动窗口或摘要压缩 |
| 本轮用户输入 | 2000-4000 token | 取决于业务场景 |
| 安全余量 | 约25%窗口上限 | 防御突发长度超限 |
上面的数字只是一个参考框架,真实项目中通过监控token的实际消耗并不断调整分配比例,你会逐步建立起一套适合自己业务的预算模型。
2.3 上下文超限的致命问题:中间截断
“中间截断”在业内是公认的一大坑,然而很多文章很少真正讲透。模型对开头和结尾的信息关注度最高,对中间内容的注意力相对最低。所以如果你的长文本被“掐头去尾留中间”式地截断,留下的恰恰是最不容易被模型关注的内容,关键信息被淹没的风险最高。
规避方法很简单,提前截断优先保留头尾。如果只能保留部分历史,优先保留最近的几轮和最早的系统说明。如果检索插入的内容过长,优先保留相关性最高的片段,而不是维护文档完整度。如果发现线上答案质量明显下降,第一排查点不应该是提示词,而应该先看截断日志。
3. 第二层:上下文构造工程——输入端的信息编排
3.1 系统指令的分层设计
上下文构造工程的重点,在于输入端的信息编排方式。
我见过很多人把系统指令写成一大段塞进去,这样并不是不行,但对后续的维护和调优非常不友好。我自己写系统指令,统一遵循分层原则。
核心层描述角色与任务目标,尽量精炼,一到两句话讲清“你是谁、你要做什么”。规则层列出必须遵守的硬性约束,比如“只能基于给定材料回答”“答案超过200字时总结要点”,每一条都要可检查、可判定。格式层说明输出结构,必要时附上一个简短示例,告诉大家什么叫“符合要求的格式”。边界层明确不能做的事,比如“不推测用户所在地以外的信息”“不重复用户原话”。
这么设计的好处是,每一层可以独立调整。业务规则变了,改规则层;输出格式变了,改格式层。调整风险被控制在局部,不会因为改一句话影响系统整体表现。
3.2 少样本示例:不是越多越好
少样本示例是提升稳定性的有效手段,但很多人控制不住示例数量。原则是“够用即可”:同样的输出风格,两条高质量示例就能说明问题,放五条反而产生干扰。
选示例最有效的方法论是:边界覆盖式选取。你选取的示例尽可能覆盖不同边界场景,比如一个正常输入、一个模糊输入、一个需要拒绝回答的输入。边界覆盖能让模型在三个方向上都有参照物,效果远比一次性给五个正常示例要好。
一个额外技巧:示例的顺序也有影响。把最典型的示例放最前面,边界示例放中间,格式最标准的示例放最后。参考逆向位置偏差的规律,这样的排列可以让开头和结尾同时覆盖参考性最强的样例。
3.3 动态上下文插入顺序
很多人对动态内容的插入位置不太讲究,示例和数据都在系统指令之后拼接,其实这会影响输出效果。
优先级原则是这样的:全局规则放最前,这是系统指令;历史关键信息次之,紧跟在系统指令之后;检索到的参考资料放中间偏前,不要在用户问题之后才注入;用户当前输入最靠后。这样做的目的,是想让模型在读到用户当前问题前,就已经掌握背景,而不是看到问题后再去找答案。
如果你用的API支持不同的消息角色序列,建议把检索内容放在独立的“知识”消息块中,并明确标注来源。这比直接把资料粘进用户消息里要干净得多。
4. 第三层:上下文回收与压缩——动态状态管理
4.1 不回收的上下文,是最贵的浪费
上下文预算里最容易被忽略的动态因素:会话时间越长,历史信息占用的空间越大,这个增长如果不加控制,会逐渐蚕食检索注入的空间。
这就引出了上下文工程里很核心的“回收”概念。回收不是简单地把老消息删掉,而是要建立具备淘汰和压缩机制的管理策略。
最优方案是三层结构:短期记忆保留最近几轮完整对话,用于维持连贯性;中期记忆为前面部分对话生成摘要,用于保留关键信息;长期记忆只保留与当前任务相关的实体和结论,例如用户偏好、身份信息、业务上下文等。
4.2 滑动窗口与摘要的配合策略
实践下来,最简单的组合是:滑动窗口+摘要压缩。
当总历史token数超过预设阈值(比如6000),触发一次整理动作。最近两轮完整保留;更早的对话通过一次摘要压缩请求,让模型输出若干句要点;加上系统指令等固定内容,整体历史区从可能失控的上万token,被压缩到一个稳定可控的水平。
写摘要时有一个细节:不要只压缩成一段平面的概括,要维持“信息结构”。比如明确保留结论、决策依据、未决事项三个维度,这样后续检索摘要时,模型能快速定位到状态信息,而不是从头读一遍。
注意:摘要压缩会不可避免地损失细节,因此对细节有高要求的任务,要在摘要策略里做“关键信息提取提醒”,在压缩指令中逐项列出必须保留的字段,而不是笼统地写“请总结上文”。
4.3 失效信息标记机制
动态管理里有一类信息常被忽视:时效性信息。几天前某次对话里用户提到“项目预计本周五上线”,到了下周三这条信息的时效性早就过了,如果还留在上下文里,模型可能会基于它做出过时判断。
建议在检索或上下文拼装阶段,对动态信息做时效元数据标注:对每条信息标注“获取时间”。在拼接资料时,过滤掉已明确过期的信息,或者标注“该信息采集于X月X日,时效性未知”。这层信息管理虽然看起来不起眼,但在长期会话场景下,它对答案稳定性的提升非常明显。
5. 第四层:外部知识注入与检索增强
5.1 检索增强不是把资料一股脑塞进去
检索增强生成(RAG)是上下文工程最实用的落地场景之一。但我发现许多人做RAG的做法就是:用户一问,系统检索几十段文本,全塞进上下文。结果信息过载,模型看不过来又想照顾全面,最后给出一个平庸甚至含混的回答。
检索增强的核心原则是少而精。真正高质量的RAG,在上下文层面做的事情是“筛选—重组—精简”而不是“搬运”。
一条资料从数据库中取回到进入上下文之前,至少要经过三步处理:去除页眉页脚和空白噪声,避免干扰模型识别;压缩长段落,去掉重复表述和例子,只保留核心结论与支撑细节;拆分为适宜模型引用的信息片段,一个片段只表达一个核心要点。
5.2 检索块重排与融合策略
早期做RAG时,我有一个错觉:检索排序越靠前的片段越有效。实测后发现,完整文档中连续取回的片段,如果按原始顺序拼接进上下文,模型阅读负担最高。后来改成重排策略后,效果提升非常明显。
实现思路也不复杂,有两路取回的候选片段,先按相关性过滤,去掉低分的;再做所谓的“边界重组”:同一来源的片段保留结构顺序,不同来源的片段同类合并,将结论性内容前置,细节性内容后置。最后在上下文里用明确的来源标记(如“【资料1】”)分隔开。
模型识别到清晰的信息边界后,引用质量和答案结构化程度都会提高。这一点在需要多源信息综合的任务上尤其明显。
5.3 上下文注入中的知识冲突处理
最棘手的场景,是外部资料与系统指令或者几个资料之间的结论直接冲突。不处理冲突就注入,模型就可能自作主张地调和矛盾,给出一个看似合理、实际两边都不靠的答案。
处理方案取决于冲突类型。如果是资料之间的冲突,不要试图让模型自己判断,而是在上下文里显式呈现两个观点及其支撑条件,并要求在答案中分别说明。如果资料内部的结论因为时效不一致出现矛盾,则在每条资料边上标注相关时效信息,模型会基于此自行决策优先采用哪个来源的结论。如果冲突发生在系统规则和资料之间,一律以系统规则为最高优先级,并将该规则写入边界层。
6. 第五层:输出端的上下文约束
6.1 输出格式与最大长度控制
输入侧做再多,输出管控不好一样影响质量。这里的“输出端”指的是你在上下文里如何约束模型的最终回复。
最朴素的一条:显式声明输出长度限制时,要说“全文控制在300字以内”,而不是“请简洁回答”。前者是可量化的硬约束,后者是不可量化的软意图。实测经验:声明具体数字限制的回复,长度波动远小于单纯声明“简洁”的。
输出格式约束用“结构模板+示例”比描述性语言强得多。比如你不想模型自由发挥输出格式,就给出具体三段式模板,并附带一段已填好的样例。闭源语言模型最擅长模仿格式,你给它一份标准例子,它给回来的格式稳定度会高很多。
6.2 结构化的输出思维链引导
还有一种约束方式,是控制模型的思考路径,在上下文里给模型布置“思考轨迹限制”。比如要求“先判断输入是否完整,再判断是否存在外部资料支撑,最后才给出结论”,这相当于给输出过程装了一个思考护栏。
这种方式有几层好处:一是避免模型跳过推理直接“凭感觉”回答;二是当最终答案质量出问题时,你能通过中间轨迹快速定位问题环节;三是模型在结构化路径下生成的内容,事实一致性明显好于无路径引导的自由生成。
有一点需要提醒:这种思考路径不要设计得过于细碎,建议控制在2-4步。步骤太长反而会限制模型的推理灵活性,尤其是在复杂任务上,过度的路径限制会导致模型“囿于流程、疏于推理”。
7. 常见故障与排查技巧实录
7.1 答案变差时,按序排查
上下文工程体系里的问题很少是单一原因,一旦线上效果劣化,不能东一榔头西一棒子地试,要按链路排查。
我自己固定的排查路径是:先看上下文中动态内容的完整日志,确认当前会话实际注入的数据与预期是否一致。再检查token消耗曲线,判断是历史膨胀、检索占用还是截断导致的内容丢失。之后看最近一次上下文的最终拼装顺序,核验各区块顺序是否符合预期。最后再回到指令层,检查是否有新规则与旧规则冲突。
在这个排查链路上,最后才看提示词。因为大多数问题都出在前三层的数据状态上,如果盲目调提示词,很可能在错误方向上绕好几个小时。
7.2 你大概率会踩到的三个坑
这里分享几个比较常见的坑。预防式机制已经有很多成熟实践,踩过一遍后你会理解为什么这些机制如此重要。
第一个坑是忽略尾词漂移:模型在生成长段回答时,后半段容易开始复述或发散。解决思路是强制引用来源、强制分段,并通过明确字段要求来拉回模型注意力。
第二个坑是让模型为上下文噪声买单:不需要的信息塞进去后,模型可能被无关信息吸引注意力。从源头过滤“不太相关的资料”,比让模型自己忽略噪声更可靠,你的系统上下文里应始终保持“每条信息都有存在理由”的状态。
第三个坑是最容易致命的:把秘密配方放在上下文里并被检索暴露。在构建上下文时,越权访问的业务信息会被模型错误引用。建议在输入侧就过滤权限标注,而不是依赖模型自主识别。上下文里不应出现无权限可见的数据,这是一条不可逾越的安全边界。
7.3 线上性能监控的三个关键指标
最后是线上监控建议。把我的经验浓缩成三个必须跟踪的核心数字:上下文填充率(当前session二次请求时的上下文token数与窗口上限的比例),超过70%你就该预警了。截断次数,一旦出现截断,说明预算管理失效,这是事故级别的信号。有效引用率(答案中引用内容占最终输出的比例),参考信号反映了注入的知识有多大比例真正进入答案生成过程。
这三个指标可以长期指导你做上下文工程的动态优化,也能让你迅速定位线上问题来自资源不足还是策略失效。
8. 反向思考:什么时候你不必做复杂的上下文工程
8.1 简单任务不值得复杂化
上下文工程虽有用,但凡事讲成本与收益。但我也要提醒:不要为了用上下文工程而用它。
如果你的任务是单轮、无历史、无检索的固定逻辑问答,那一条设计良好的指令就够了,没必要引入一套会话管理系统。如果你的任务数据量极小,比如只判断一段文字的情绪正负向,复杂的检索注入反而徒增延迟与成本。如果一次请求内所有信息都能在两三千token内装下,优先考虑构造清晰的静态上下文,别急着上动态方案。
8.2 复杂的上下文工程什么时候值得做
反过来,价值和收益都体现在哪些场景。多轮会话且信息持续增长的场景,比如客服对话、AI助手长期陪聊,不做历史回收很快就爆窗口;需要大量外部知识支撑才能回答的专业问答,比如法律咨询、医疗导诊、技术问答,不搞检索注入则效果天花板极低。在此之上,凡是同一个模型能力无法满足、但通过优秀的信息编排就能大幅提升可用性的业务,上下游的上下文工程都是你不可或缺的手段。
提示:上下文工程的复杂度梯队应该是“静态指令—固定示例—滑动窗口—摘要+检索—多层记忆”,按这个顺序逐步升级,永远不要一步到位。
最后分享一个经验
最开始做上下文工程,我花在调提示词上的时间,和花在梳理信息链路上的时间比大约是8比2。现在倒过来了,是2比8。不是提示词不重要了,而是它已经沉淀成一套稳定的基础结构,而真正让项目产生质变的,是对上下文的整体理解与精细运营。先管理好窗口,再构造好内容,最后才谈润色表达,这个顺序走顺了,你手头那些模型应用,效果和稳定性总能再往上走一截。