ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

上下文工程实战:让大模型在Token预算内稳定输出

上下文工程实战:让大模型在Token预算内稳定输出

你有没有遇到过这种情况:明明Prompt写得清清楚楚,资料也塞了一大堆,模型还是答非所问,甚至用你提供文档里一句无关的话生成了整个结论?我最近接手一个跨平台系统项目,文档库有几十万字,一开始我贪心地把检索到的Top 10片段全塞进上下文,结果模型被中后部某段讨论“异常边界情况”的内容带跑了,主流程反而没有回答。后来我花了大量时间调整注入顺序、裁剪检索片段、压缩历史记录,才意识到问题的根源不在Prompt写得不好,而在上下文没管好。

这就是我想聊的“上下文工程”要做的事:让AI看到该看的东西,而且以该看的顺序、该看的粒度去看到。你可以把它理解成给模型写“工作简报”——不是把所有原材料堆上去,而是按照任务目标把关键信息放在它最容易注意到的位置。这篇文章不是科普概念,是我在实际项目里做上下文设计、检索接入、对话记忆管理时的经验记录。适合正在做AI应用开发的工程师、Prompt设计者,以及被“模型输出不稳定”折磨的产品经理参考。

1. 上下文工程:为什么提示工程不够用了

1.1 提示工程解决“怎么说”,上下文工程解决“看什么”

提示工程的核心是优化指令文本,让模型理解你希望它以什么角色、什么风格、什么格式来回答。但有一个问题:提示工程默认“信息都在指令里”,可现实中的AI应用大多是动态的,用户的每句话、系统里的数据库记录、知识库检索结果、多轮对话历史,这些都不是写Prompt时能预知的。

我在实际调试中发现,一组把系统提示、用户查询、检索片段全部加起来的内容里,真正影响答案质量的往往不是那句“请你扮演一个资深分析师”,而是模型“视野”里到底有哪些候选事实、冲突信息、噪音描述。提示工程解决的是“怎么把话说明白”,上下文工程解决的是“给模型看什么材料”,后者更像信息架构师干的活。

举个生活化类比:你让一个新同事帮你整理一份报告。只写清楚“请整理得专业一点”是不够的,你还要决定给他哪些资料、哪些资料放前面、哪些资料只给摘要、哪些资料干脆不给。上下文工程就是做这件事的:把模型的输入窗口当成一份有预算的简报,逐字逐句地分配空间。

1.2 定义与核心目标

上下文工程是我目前最认可的工作定义:它是指对待输入给大语言模型的全部内容——系统提示、用户输入、工具声明、检索结果、对话历史、输出约束——进行结构化设计、动态组织、持续优化的系统性工作。

它的核心目标不是“让上下文更多”,而是“让上下文更有效”。这里有个关键的认知转变:上下文的单位是Token,Token就是钱,也是注意力。模型处理上下文时,不会像人一样均匀地阅读每一个字,它对前置内容有较强记忆,对结尾内容有较强的近期效应,但中间段内容最容易被“忽略”,这在领域里被称为“迷失在中间”现象。上下文工程要做的就是对抗这种注意力分布不均,把高价值信息放到注意力高点。

我在制定上下文规格书时,会先用一句话写清楚:本应用期望模型“优先依据什么、参考什么、忽略什么”。这句话听着简单,但绝大多数项目没有认真想过。

1.3 上下文工程与普通软件开发的三点差异

第一,它是一种“运行时资源管理”。普通后端开发管理的是CPU、内存、带宽,而AI应用还要管理“上下文窗口”这个逻辑资源,它既有大小限制,又影响模型行为和费用。第二,它的复杂度是动态的。同一个系统提示在不同轮次对话、不同用户、不同检索结果下,效果可能完全不同,必须动态组装。第三,它的调试闭环很特别。代码报错有堆栈,上下文出问题往往表现为“输出质量差”“答案偏了”,没有直接异常,只能靠对比实验逐步收敛。

从工程角度看,这三点意味着我们不能把上下文构建写成一段硬编码字符串,而应该把它设计成一条可组装、可观测、可回放的流水线。

2. 上下文的黄金结构:拆解每个注入片段

2.1 系统提示:定义模型的“世界观”

系统提示是上下文里最稳定的部分,它的作用是告诉模型“你是谁、任务是什么、边界在哪里、输出风格是什么”。很多人以为系统提示写一次就完了,实际恰恰相反:系统提示会与后续注入的检索内容、用户输入竞争注意力,一旦后文出现了与系统提示矛盾的信息,模型常常会跟随后文。

我见过一份系统提示写了三百行,从公司愿景写到语气偏好,结果模型频繁“忘记”核心要求。后来我把系统提示重构为固定模块:角色一句话、任务一句话、三条硬性规则、输出格式要求。删掉了所有形容词,实测输出稳定性提升明显。

这里有一条经验:系统提示不是越详细越好,它更像是给模型看的“宪法”,只保留不可违背的原则性内容。关于业务细节、临时变量、检索结果,应该放到下面的“工作记忆”部分。

2.2 工作记忆:任务相关的动态信息

工作记忆是每一轮请求时才组装的信息,包括用户当前问题、候选知识片段、数据库查询结果、表单状态等。这部分是上下文工程中最需要精细化运营的。

我习惯把工作记忆按照“相关性-权威性-时效性”三个维度排序后注入。举例:用户问“某跨平台系统的登录模块怎么设计”,我检索到的前三段如果是“权限模型综述”“登录流程图”“历史遗留Bug说明”,实习生的答案会明显偏向前者,而不是流程图。

另外一个重点:不要把全部检索结果都塞进去。我做了个常见错误实验:同一个问题,检索Top 5时回答准确率62%,塞Top 10时反而降到47%——因为多出来的片段引入了候选矛盾。这也印证了“上下文是预算不是仓库”的原则,相关性不确定的宁可不要。

2.3 工具与能力声明:让AI知道自己手里有什么牌

如果应用涉及工具调用,工具的命名、描述、参数说明都应该被当作上下文的一部分来管理。模型的工具选择不是随机的,它依赖工具描述与用户意图的语义匹配度。工具名如果起得不够直白,描述写得模糊,模型会选错工具或者干脆不调用。

我踩过很实在的坑:某次把“查询订单接口”描述成“获取交易快照信息”,模型在用户问“我上周的订单”时反复调用另一个计算统计接口。把描述改回“根据订单号或时间范围查询订单明细列表”后,选择率明显正常了。工具侧上下文优化的优先级不低于Prompt本身。

3. 上下文减脂:裁剪、压缩与顺序控制

3.1 相关性裁剪:只留下最有用的片段

我用过一个很朴素但有效的分层裁剪策略:第一步做语义去重,连续几段讲同一个点的内容只保留一段;第二步做冲突识别,如果多段内容互相矛盾,优先保留权威度高的来源,并把冲突点在上下文中显式标注出来,比如加一句“注意:历史文档A与最新文档B对超时时间的描述不一致,请以B为准”;第三步做篇幅限制,重要片段完整保留,边缘片段只留摘要。

有读者可能会问:让模型看到矛盾信息有什么坏处?坏处是模型会自己做“和事佬”,把矛盾双方揉成一个含糊结论。我在项目里把“冲突提示”加进系统提示后,模型开始主动说明“资料中存在两种说法”,并给出取舍建议,这个行为是很多团队想要的。

3.2 信息压缩:用摘要代替原文

并不是所有信息都值得原样进入上下文,比如很长的日志、冗长的合同条款、多页调研报告。这种场景建议做摘要压缩。但摘要压缩有个原则:不能只总结主旨,要保留关键实体、数字、结论性的陈述。

我做了一个处理链路:先把原始文本用模型抽取出“实体清单+关键结论+重要数字”,再压缩成结构化格式。实测在同样的Token预算下,压缩后的内容比原文提供了更多有效决策线索。注意,压缩步骤也有成本,可以选择用更小的模型来执行,它在信息抽取任务上表现足够,性价比高。

3.3 顺序与位置效应:把信息放在注意力高点

模型注意力有“首位效应”和“最近效应”,中间区域容易被弱化。所以重要的信息要么早出现,要么出现在最后,不要埋在上下文中部。这是我在实际项目中体会最深的一条。

比如在构建长报告问答时,我会把“结论先行”放在检索片段顶部,把“过程推导”放在中间,把“最后的约束条件”放在底部。效果是模型的输出结构明显更清晰。这里推荐写一个“上下文排序审计”小工具:每次调试时打印注入上下文的顺序,标注每条内容的来源与目的,方便检查有没有把关键信息埋没。

3.4 防干扰项:减少无关指令与闲聊

多轮对话里有大量“好的”“嗯嗯”“谢谢”之类的填充词,以及历史中被模型纠正过的错误回答。这些如果不处理,会持续干扰后续生成。

我处理对话历史的策略:保留每一轮“用户原始意图”而不是用户原话,保留模型“最终有效回答”而不是模型的长篇推理。这样传给模型的历史就像会议纪要,而不是录音带。另外还有一些隐藏干扰项:日期时间、环境描述、上一轮输出里包含的“抱歉我无法回答”之类的话,这些都要在注入前清洗。

4. 从外部世界取上下文:检索增强与知识注入

4.1 检索增强的完整构建链路

让AI“看到该看的东西”最依赖的就是检索环节。我实际使用的链路是:文档切分 -> 向量化 -> 召回 -> 重排序 -> 信息注入。这条链路每个环节都有坑。

先说文档切分。切分太小,比如固定128个Token,经常把一句话拆得七零八落;切分太大,比如2000个Token,又会混杂多个主题。常用的办法是语义切分,先按标题、段落、句子边界切,再控制每段长度在300到800个Token之间。这里的核心是让每一段内容尽量主题单一。

再说向量化。向量模型对文本的表达能力直接决定召回的精度。我通常维护两个索引:一个基于语义的向量索引,一个基于关键词的全文索引,查完之后用RAG融合策略合并结果。单纯靠向量召回,碰到专有名词、缩写、精确型号时会漏召;单纯靠全文,碰到同义表达、口语化问题时又召不全。

4.2 重排序:把最相关的内容捞回来

初召回阶段要“宽进”,一般拿回50到100个候选。但直接全塞进上下文既不经济,也容易引入噪音。这时候就需要重排序模型介入:把候选片段按与用户问题的真实相关性重新打分,然后取Top 5到Top 8。

这里要警惕一种情况:重排序后的Top结果里有大量内容相互重复,或者是同一来源的不同段落。要在重排序之后再做一次冗余去除。我在一次会话历史相关的调试中,因为没做冗余去除,上下文里塞了三段讲同一个权限校验过程的文档,白白吃掉了好几百Token,还让模型回答得啰嗦。

重排序模型虽然精确度高,但计算成本明显高于向量召回。常规做法是只在候选集上做,比如50个里面重排,而不是对全库做。线上如果预算紧,也可以考虑用更轻量级的交叉编码器方案做两阶段重排。

4.3 引用溯源:让模型可以“自证”

没用引用溯源之前,模型给出的答案经常像“编的”。不是它想编,而是上下文里没有明确告诉它“哪些句子来自哪个来源”。于是我改了注入格式:每条信息前加一个ID标签,如[来源1],并要求模型在回答时标注使用到的来源ID。就这么一个改动,输出的“可信感”提升了一个档次。

实际操作中,引用溯源还能帮我排查问题。某次模型引用了一个错误来源的结论,我查看输出后发现是重排序时把一条“历史废弃方案”排到了前面,而系统提示里的时效性规则没有足够权重。定位到问题后,我在重排序打分里加了对文档时间的惩罚因子,问题就解决了。

4.4 知识注入的格式设计

知识注入最忌讳大段堆原文。我会把检索片段包装成统一的“信息卡片”格式:标题、来源、时间、关键内容、与问题的相关性说明。这样模型不需要自己推断哪段是重点,它直接按卡片来处理就行。

这套格式看似简单,带来的收益却很大。它让模型有条件在上下文中进行“比较”而不是“混合”。比如两张卡片分别描述方案A和方案B,模型会针对问题给出选择倾向;如果只是把两段话粘在一起,模型很可能把A和B缝合出一个不存在的方案C。

5. 动态上下文:对话状态与记忆管理

5.1 滑动窗口与“短期工作记忆”

在做多轮对话时,最简单的方式是把最近N轮对话原样拼接到上下文里。这个方案在小场景下能跑,但很快会遇到两个问题:Token消耗随对话轮数线性增长,以及“远距离信息遗忘”。

我现在的做法是给对话历史分区:最近的2-3轮保留原始信息,用于模型理解当前语境;更早的历史做键值抽取,用“用户关注点+已确认事实+未解决问题”的格式记录,而不是留原文。这个方案在跨平台系统项目里实测效果稳定,多轮后模型不会丢失用户在最初提出的核心需求。

5.2 滚动摘要:让历史“沉淀”下来

对话超过一定轮数后,上下文被历史占满,新知识就没地方放了。我采用的滚动摘要策略是:每当历史Token超过阈值,就用模型对早先对话生成一份结构化摘要,替换掉原始对话。摘要内容不是简单概括,而是保留:用户目标、关键实体、已给出的承诺、待办事项。这样等于给模型建了一份持续更新的“项目纪要”,而不是把每句话都背下来。

这里有个操作细节:摘要生成本身会产生幻觉,我建议在每次摘要时附带“对话原文片段”作为校验,只抽取,不生成新内容。生成式摘要适合提炼语气和隐含意图,但涉及事实性内容时,原话引用更保险。

5.3 结构化记忆:把知识存到“库”里

除了文本摘要,还可以把长期记忆结构化。比如用户偏好、项目约束、历史结论,都可以写成实体-属性-值的形式存入一个小型状态层,需要时再实时注入上下文。这种设计的优势是稳定:无论过了多少轮,都能精确还原关键事实,不依赖模型对历史的二次推断。

我在一个“某跨平台系统配置问答”的Demo中,把数据库表结构、接口返回字段、权限规则抽成了结构化记忆,问答准确率显著高于纯文本历史方案。模型需要什么,我就把这部分结构以表格或者JSON格式注入,输出精准很多。

5.4 分层记忆:场景化取舍

完整的记忆架构可以做成分层:场景设置层存放系统性常识;会话记忆层存放本轮对话的目标与状态;工作存储层存放当前正在处理的临时信息。这三层信息在每次请求时组装,优先级从高到低。分层设计避免了“所有信息一锅烩”的混乱,也让调试时更容易定位问题。

我还养成了一个习惯:任何上下文组件都必须标注它的生命周期。是“永久有效”“会话有效”还是“仅本次请求有效”?没有生命周期的信息是最危险的,它会在一个不该出现的时机被模型读到,然后产生误导性输出。

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

6.1 问题速查表

现象大概率原因排查方向
模型输出啰嗦但没重点上下文里缺少结构化信息,或者关键事实被大量修饰性内容淹没检查注入格式是否用信息卡片,裁剪冗余段落
加了很多资料后答案反而变差检索噪声过多,模型被低相关片段带偏引入重排序,限制注入片段数量,加入冲突标注
回答产生幻觉引用没有给模型来源标识,或者模型被允许“猜测”使用来源ID标签并强制引用,关闭开放式生成
多轮对话后面越聊越蠢历史记录膨胀,早期关键信息丢失启用滚动摘要与结构化记忆,保留用户目标摘要
模型不按系统提示执行提示顺序靠前,注意力被后文削弱,或提示内容重复冲突系统提示精简加固,把“不可违背规则”复制到开头和结尾
调用工具总是选错工具描述不直观,名称有歧义,或工具参数说明模糊重写工具描述,用用户话术做工具名,补充典型触发场景

6.2 定位问题的“上下文回放”法

遇到输出质量问题,我第一步不是改Prompt,而是把这次请求的完整上下文打印出来,一步一步回放。看四件事:一是模型“看到”了哪些片段;二是这些片段的顺序是否符合注意力分布规律;三是是否有多余的旧信息占据了有效窗口;四是关键答案线索是否藏在上下文的中段。90%的问题在看完回放后就能定位。

这个方法尤其适合和“错误输出对照实验”配合使用。我会把用户问题固定不变,只调整上下文中一个变量,比如同时做“只保留Top 3 vs Top 8”“带重排序 vs 不带”“原文注入 vs 摘要注入”,观察输出变化。几个来回后,哪个环节在拖后腿就非常清晰。

6.3 成本与延迟的平衡口诀

上下文工程做得好不好,还会直接体现在账单和响应时间上。Token消耗就是钱,重排序和摘要会引入额外延迟,这些都是工程现实。我的平衡口诀是:能在存储层解决的,不放在上下文里解决;能用小模型做的压缩,不让大模型做;能从结果受益的排序,不依赖模型自己去推理信息优先级。

比如高频问题可以预先缓存答案,不需要每回都构造一堆上下文。再比如检索候选特别大时,先用轻量级方案粗筛,再对少量候选做昂贵重排序。这套思路放到任何规模的AI应用里都适用。

7. 最后一件事:写一份上下文规格书

项目上线前,我会要求团队把整个应用的所有上下文来源列成一张表:包括每个内容块来自哪里、为什么存在、优先级多高、生命周期多长、最多占多少Token、位置放在前/中/后。这张表,我称为上下文规格书。

它带来的直接好处是:出问题时不再靠“灵光一现”猜原因,而是对着规格书逐行核对;人员变动时,新人看一遍规格书就能快速上手;换模型或调参数时,能批量评估影响面。很多团队花大量精力调Prompt,却没有一份完整的上下文清单,这是本末倒置的。

最后分享一个小技巧:无论用什么模型、什么框架,每个上下文组件都要起一个“可追溯名称”。我在实际项目里看到太多人把系统提示、业务规则、临时数据全部拼在一段字符串里,最后根本分不清每句话是谁、从哪里来的。给上下文加了名字以后,调试效率提升非常明显,你也可以在下一个项目里直接套用。上下文工程不是一个玄学技巧,它就是把“让AI看到该看的东西”这件事,变成一步步可检查、可验证、可优化的工程实践。

RELATED READING

延伸阅读

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