2026/10/13 16:16:42

上下文工程实战:解决多轮对话中模型失忆与格式错乱

上下文工程实战:解决多轮对话中模型失忆与格式错乱 1. 从一个真实困境说起为什么模型“变笨”了很多人应该都遇到过这种场景明明用的是一个能力很强的模型单轮对话里问它一个独立的问题回答得又快又好。可一旦把它放进一个多轮交互的系统里让它连续处理十几个回合它就开始“失忆”——前面说过的约束忘了中间确认过的格式不遵守了甚至开始胡编乱造一些根本没出现过的信息。我最早踩到这个坑是在做一个文档问答类的小工具时。当时我的思路很朴素把用户的问题和检索到的资料拼在一起丢给模型就完事了。单次测试效果不错可一旦用户追问、补充条件、修改要求整个对话链条就开始崩。我一度以为是模型不行换了好几个版本结果都差不多。后来才意识到问题根本不在模型本身而在于我喂给它的“上下文”是一团乱麻。这就是上下文工程要解决的核心问题。所谓上下文工程说白了就是在模型每一次生成之前精心设计它能看到的所有信息——包括系统指令、历史对话、检索到的知识、工具返回结果、格式约束等等并且管理好这些信息在有限窗口内的取舍、排序和压缩。它不是一个新概念但在实际项目里它往往比选哪个模型、调什么参数更能决定最终效果。这篇文章适合所有正在做对话系统、智能助手、Agent 应用、RAG 问答的开发者也适合那些“模型明明很强但我的产品就是不好用”的团队。我会从设计思路、核心细节、实操落地到问题排查把上下文工程这件事掰开揉碎讲清楚尽量让你看完就能对照自己的项目改起来。2. 上下文工程到底在工程什么2.1 上下文不是“聊天记录”而是一份精心编排的工作简报很多人对上下文的直觉理解就是“把之前的对话都带上”。这个理解在早期确实够用但一旦系统复杂起来就会出问题。我更愿意把上下文理解成一份给模型看的工作简报它要在有限的篇幅里告诉模型“你是谁、现在要干什么、有哪些约束、可以参考哪些材料、之前已经确认了什么”。这个视角的转变很关键。因为一旦你把上下文当成简报你就会开始思考哪些信息是必须的哪些是冗余的哪些应该放在最前面哪些应该放在最后哪些可以压缩成一句话这些问题恰恰就是上下文工程的全部内容。举个生活化的类比。你让一个刚接手项目的同事帮你处理一件事你有两种做法一种是把过去三个月的所有聊天记录、邮件、文档一股脑转发给他让他自己找重点另一种是写一份清晰的简报说明背景、目标、约束和参考材料。前者信息量更大但后者成功率更高。模型面对上下文时和这个同事的处境一模一样。2.2 为什么窗口越大反而越容易出问题现在很多模型的上下文窗口动辄几十万甚至上百万 token于是有一种流行的说法“窗口够大就不用做上下文工程了全塞进去就行。”我实测下来的结论是窗口大不等于效果好甚至可能更差。原因有几个。第一模型对上下文的注意力分布是不均匀的通常对开头和结尾更敏感中间部分容易被忽略这就是常说的“中间迷失”现象。第二无关信息越多噪声越大模型越容易被带偏。第三长上下文意味着更高的成本和更长的响应时间工程上并不划算。所以窗口变大只是给了你更大的操作空间但怎么用这个空间仍然需要上下文工程来决策。把一百万 token 的窗口塞满垃圾效果不会比精心组织的两千 token 更好。2.3 上下文工程的四个核心动作我把日常做的上下文工程工作归纳成四个动作基本覆盖了绝大多数场景选择从所有可用信息里挑出这一次生成真正需要的部分。排序决定这些信息以什么顺序排列谁在前谁在后。压缩把冗长的历史或材料提炼成更短但信息密度更高的形式。约束用清晰的指令和格式要求框定模型的输出边界。这四个动作听起来简单但每一个都有大量细节。下面我会逐个展开结合实操讲清楚怎么做、为什么这么做。3. 核心细节拆解选择、排序、压缩、约束3.1 选择什么该进上下文什么该被挡在门外选择是上下文工程的第一步也是最容易被忽视的一步。很多系统的做法是“能塞就塞”结果上下文里一半是无关内容。我的原则是每一条进入上下文的信息都要能回答“它为什么在这里”。如果答不上来就删掉。具体怎么选我通常按优先级分三层第一层是硬约束包括系统角色设定、必须遵守的规则、输出格式要求。这些是每次生成都必须带的不能省。第二层是任务相关包括当前用户的问题、检索到的相关材料、工具调用的返回结果。这一层需要根据当前任务动态筛选比如检索结果只保留最相关的几条而不是把整个知识库都塞进去。第三层是历史上下文包括之前的对话轮次。这一层最需要克制因为历史会不断累积。我的做法是只保留最近若干轮更早的内容要么丢弃要么压缩成摘要。这里有个实操心得检索类内容一定要做相关性过滤和数量上限。我见过不少 RAG 系统把检索到的十几条文档全部塞进上下文结果模型被无关内容干扰回答质量反而下降。通常保留 top 3 到 top 5 就够了具体数量要看单条内容的长度和任务复杂度。3.2 排序位置决定注意力选好了内容接下来是排序。前面提到模型对上下文不同位置的注意力不一样所以排序不是随便排的。我的经验是遵循“重要信息放两端次要信息放中间”的原则。具体来说系统指令和核心约束放在最前面因为这是模型必须始终记住的。当前任务和最关键的材料放在最后因为这是模型生成时“眼前”的信息注意力最集中。历史对话和次要参考材料放在中间。还有一个细节格式约束最好在开头和结尾各强调一次。比如你要求模型输出 JSON那就在系统指令里说一遍在用户消息末尾再提醒一遍。实测下来这样能显著降低格式错误的概率。原因也不难理解模型在生成时对最近的内容更敏感结尾的提醒相当于临门一脚。3.3 压缩让历史对话不占地方又不丢信息多轮对话最大的敌人就是历史累积。十轮对话下来光历史就可能占掉几千 token而且越往后越臃肿。压缩是必须的但压缩不能简单截断否则会丢关键信息。我常用的压缩策略有三种。第一种是滑动窗口只保留最近 N 轮简单粗暴但有效适合历史信息价值衰减快的场景。第二种是摘要压缩把较早的对话用模型总结成一段简短摘要保留关键结论和已确认事项。第三种是结构化提取把对话中出现的实体、约束、决策点提取成结构化字段比如“用户已确认预算上限为 X”“已排除方案 Y”。这三种策略可以组合使用。我的常见做法是最近三轮保留原文三轮之前的对话压缩成摘要同时把关键约束提取成结构化字段放在系统指令区。这样既控制了长度又保证了关键信息不丢。注意摘要压缩本身也要消耗一次模型调用所以要权衡成本和收益。如果对话轮次不多直接滑动窗口可能更划算。3.4 约束把“希望它这样做”变成“必须这样做”约束是上下文工程里最直接见效的部分。很多人写系统指令时喜欢用“尽量”“最好”“如果可以”这类模糊词结果模型的行为就很不稳定。我的建议是能用硬性规则表达的就不要用软性期望。比如与其写“回答尽量简洁”不如写“回答不超过三句话”。与其写“尽量用中文”不如写“必须用中文回答不得出现其他语言”。与其写“格式尽量规范”不如直接给出一个 JSON schema 示例。约束还包括负面约束也就是明确告诉模型不要做什么。比如“不要编造材料中没有的信息”“不要重复用户的问题”“不要在回答里出现内部字段名”。负面约束在防止模型跑偏时特别有用因为模型天然倾向于“多说话”明确的禁止能有效收敛它的行为。4. 实操落地一套可复用的上下文组装流程4.1 整体流程设计讲了这么多原则接下来给一套可以直接抄作业的流程。我把它分成五步收集所有候选信息系统指令、用户当前输入、历史对话、检索结果、工具返回、格式要求。按优先级筛选硬约束全保留任务相关按相关性排序取 top N历史按轮次和摘要处理。组装成结构化上下文按“系统区—历史区—材料区—任务区”的顺序拼接。做长度检查估算 token 数超限则触发压缩或裁剪。生成前做一次自检确认关键约束在开头和结尾都出现了。这套流程看起来步骤不少但实际实现起来并不复杂核心是把每个环节的决策显式化而不是随手拼接。4.2 一个具体的上下文模板下面是我常用的一个上下文模板结构用伪代码表示[系统区] 你是XX助手。必须遵守以下规则 1. 只基于提供的材料回答材料中没有的信息不要编造。 2. 输出格式必须是JSON字段包括answer和confidence。 3. 回答使用中文。 [历史区] 最近三轮对话原文 更早对话的摘要用户已确认...已排除... [材料区] 材料1... 材料2... 材料3... [任务区] 用户当前问题... 再次提醒只基于材料回答输出JSON格式。这个模板的关键点在于系统区放硬约束历史区做压缩材料区控制数量任务区放当前问题并重复关键约束。实测下来这个结构在大多数问答和助手场景里都很稳。4.3 参数选择与长度预算长度预算是很多人忽略的一环。我的做法是给每个区分配一个 token 预算比如系统区不超过 500 token历史区不超过 1500 token材料区不超过 3000 token任务区不超过 500 token。总预算根据模型窗口留出足够余量一般不超过窗口的 60% 到 70%。为什么要留余量因为模型的输出也要占 token而且留出余量能让注意力更集中。把窗口塞到 95% 满效果往往不如塞到 60% 好。这个比例不是绝对的需要根据具体任务调但“不要塞满”这个原则是通用的。4.4 实操现场记录一次格式错乱的修复过程说一个我实际遇到的案例。当时做一个信息抽取工具要求模型输出固定字段的 JSON。单轮测试没问题但多轮之后模型开始输出多余的解释文字导致解析失败。排查过程是这样的先看上下文发现历史对话里模型之前的回答包含了自然语言解释这些解释被当作历史带进了新一轮模型就“有样学样”继续输出解释。修复方法是在历史区做过滤只保留结构化的 JSON 结果把自然语言解释剔除掉。同时在任务区末尾加了一句“只输出JSON不要任何额外文字”。改完之后格式错误率从大概三成降到了几乎为零。这个案例说明一个道理历史里的坏例子会被模型模仿。所以历史区不仅要压缩还要清洗把不符合期望的输出剔除掉。5. 常见问题与排查技巧实录5.1 模型“失忆”忘了前面的约束这是最常见的问题。排查思路是先确认约束是否真的在上下文里再看它出现的位置。如果约束只在开头出现而对话已经很长模型很可能就淡忘了。解决办法是在任务区末尾重复关键约束或者把约束提取成结构化字段放在显眼位置。5.2 模型被无关材料带偏如果检索结果里混入了不相关内容模型很容易被带跑。排查时先看材料区的相关性做一次人工抽查。解决办法是加相关性阈值过滤并限制材料数量。另外可以在系统指令里明确“只使用与问题直接相关的材料”。5.3 输出格式不稳定格式问题几乎都和约束的强度有关。排查时看约束是否用了模糊词是否在结尾重复。解决办法是给出明确的格式示例并在开头结尾各强调一次。如果还是不稳定可以考虑用结构化输出功能或后处理校验。5.4 响应越来越慢、成本越来越高这通常是历史累积导致的。排查时统计每轮的 token 消耗看历史区占比。解决办法是启用摘要压缩和滑动窗口给历史区设硬上限。下面这张表是我整理的常见问题速查表方便对照排查问题现象可能原因排查动作解决方向忘记约束约束位置太靠前或被淹没检查约束出现位置结尾重复约束被带偏材料噪声大抽查材料相关性加过滤和数量上限格式错乱约束模糊或历史有坏例子检查约束措辞和历史输出明确格式并清洗历史越来越慢历史累积统计 token 占比摘要压缩加滑动窗口编造信息缺少负面约束检查是否有禁止编造的指令加明确的负面约束5.5 几个独家避坑技巧第一个技巧给上下文加“锚点”。在长上下文里用明显的分隔标记比如### 材料开始、### 材料结束把不同区域隔开能帮助模型区分信息边界减少混淆。第二个技巧约束要具体到可验证。“回答要准确”这种约束没法验证“材料中没有的信息不要出现”就可以验证。约束越具体模型越容易遵守。第三个技巧定期做上下文回归测试。准备一组固定的多轮测试用例每次改动上下文结构后跑一遍看效果有没有退化。上下文工程很容易“改一处坏一处”回归测试能帮你及时发现。6. 上下文工程与提示词工程的区别很多人把上下文工程和提示词工程混为一谈其实两者关注点不同。提示词工程主要关注“怎么把一句话说好”比如措辞、语气、示例。上下文工程关注的是“模型能看到什么”包括信息的选取、组织和管理。打个比方提示词工程像是你写邮件时的措辞技巧上下文工程像是你决定这封邮件里附哪些附件、按什么顺序排列、要不要加摘要。两者都重要但在复杂系统里上下文工程的影响往往更大因为它决定了模型的信息基础。我个人的体会是单轮简单任务提示词工程占主导多轮复杂任务上下文工程占主导。随着系统越来越复杂上下文工程的权重会越来越高。这也是为什么这个主题值得单独拿出来讲。7. 不同场景下的上下文策略差异上下文工程没有万能方案不同场景策略差别很大。问答类场景重点是材料筛选和相关性排序。助手类场景重点是历史压缩和约束管理。Agent 类场景重点是工具返回结果的整理和多步任务的中间状态管理。以 Agent 场景为例工具返回的结果往往很长很杂直接塞进上下文会迅速占满窗口。我的做法是对工具返回做一次结构化提取只保留关键字段和结论把原始返回存档备查。这样既保留了信息又控制了长度。再比如多步任务中间状态的管理很关键。我会把每一步的结论提取成简短的状态记录而不是把每一步的完整过程都带上。这样模型在后续步骤里能看到“已经完成了什么”但不会被过程细节淹没。8. 我踩过的几个典型坑第一个坑是过度压缩。有一次为了省 token把历史压得太狠结果关键约束被压没了模型开始乱答。后来我调整策略硬约束永远不压缩只压缩过程性内容。第二个坑是材料排序随意。早期我没在意材料顺序后来发现把最相关的材料放在最后效果明显更好。原因是模型对结尾的注意力更集中。第三个坑是忽视输出长度。有段时间响应特别慢排查后发现是模型输出太长而输出长度也受上下文影响。当上下文里有很多长文本时模型倾向于也输出长文本。后来我在约束里加了输出长度限制问题就缓解了。这些坑的共同点是它们都不是模型能力问题而是上下文设计问题。这也是我想强调的核心观点——很多时候效果不好不是模型不行而是上下文没组织好。9. 一套可以立刻上手的检查清单最后分享一份我日常用的上下文自检清单每次上线前过一遍硬约束是否在开头和结尾都出现历史是否做了压缩和清洗材料是否做了相关性过滤和数量限制关键信息是否放在了注意力高的位置输出格式是否有明确示例是否有防止编造的负面约束总 token 是否留足了余量是否跑过回归测试这份清单不复杂但能挡住大部分低级问题。上下文工程说到底是个细致活没有什么一招制敌的秘诀靠的就是把这些细节一个个抠到位。我在实际项目里的体会是把上下文工程做扎实之后同样的模型能发挥出明显更好的效果而且系统稳定性会大幅提升。这个投入绝对值得。