上下文工程实战:如何让大模型输出更稳定、成本更低
1. 从一个真实困境说起为什么模型“变笨”了很多人应该都遇到过这种场景同一个模型同一套接口昨天跑得好好的任务今天换了个问法就答非所问或者在一个多轮对话里聊到第五六轮之后模型开始忘记前面说过的关键约束甚至自相矛盾。第一反应往往是“模型是不是降智了”“是不是服务端出问题了”但排查一圈下来接口正常、网络正常、参数没动问题其实出在一个很容易被忽略的地方——上下文工程。所谓上下文工程简单说就是你喂给模型的全部信息以及这些信息被组织、裁剪、排序、压缩的方式。它不只是“写个好提示词”那么简单提示词只是其中一层。真正决定模型输出质量的是模型在生成每一个 token 时眼前能看到的那一整个上下文窗口里到底装了什么、按什么顺序装、哪些被丢掉了、哪些被强调了。这个窗口是有限的、有成本的、有注意力偏好的所以“装什么”和“怎么装”本身就是一门工程。这篇文章想聊的就是标题里那个问题为什么上下文工程重要。我会从实际项目出发把上下文窗口的物理限制、信息组织策略、多轮对话的上下文管理、检索增强场景下的拼接技巧、以及常见的踩坑经验都拆开讲一遍。适合正在做 AI 应用开发、Agent 编排、RAG 系统或者只是想让日常对话质量更稳的人参考。不管你是刚接触这块的新手还是已经写过不少 prompt 的老手相信都能从中找到一些能直接抄作业的东西。2. 上下文窗口到底是个什么东西2.1 用生活类比理解 token 预算把上下文窗口想象成一张固定大小的会议桌。模型就是坐在桌前的顾问你每次提问他能看到的只有桌上摆着的材料。桌子就这么大你摆得越多每份材料能分到的“注意力”就越少你摆得越乱他越容易抓错重点你把关键材料压在最后一叠纸下面他可能翻都懒得翻。技术上这张桌子的容量用 token 衡量。一个 token 大约对应中文的 1 到 2 个字英文的 0.75 个单词左右。不同模型的窗口大小差异很大从早期的 4K、8K到后来的 32K、128K再到宣称百万级的。但这里有个关键认知窗口大不等于能用好。这就像给你一张能坐 50 人的大桌子但你只有 10 分钟会议时间人越多反而越难达成共识。模型也一样上下文越长中间部分的信息越容易被“稀释”这就是业内常说的“lost in the middle”现象。所以上下文工程的第一性原则是不是塞得越多越好而是让每一份进入窗口的信息都有明确目的。我见过太多项目为了“保险”把整篇文档、整个历史记录全量塞进去结果模型反而抓不住重点还白白烧了 token 成本。2.2 窗口里的信息分几类在实际项目里一个典型的上下文通常由这几部分拼成系统指令定义角色、边界、输出格式通常放最前面权重最高。任务描述当前这一轮到底要干什么。背景知识从知识库检索出来的片段、文档、数据。对话历史之前几轮的问答记录。示例few-shot 的输入输出样例。当前输入用户这一轮的原话。这六类信息在窗口里是竞争关系。系统指令太长背景知识就没地方放对话历史不裁剪当前输入就可能被挤到窗口边缘。上下文工程的核心工作就是在这几类信息之间做动态分配和优先级排序。这跟操作系统管理内存是一个道理你得决定哪些常驻、哪些换出、哪些压缩。2.3 为什么“能塞下”和“用得好”是两回事很多人有个误区既然模型支持 128K那我就把 128K 塞满信息越全越准。实测下来恰恰相反。当上下文里存在大量与当前问题弱相关的信息时模型的注意力会被分散输出质量反而下降。原因在于注意力机制本身它会给每个 token 分配权重无关信息多了相关信息的相对权重就被拉低了。我做过一个对比实验同一个问答任务方案 A 塞入完整 20 页文档方案 B 只塞入检索命中的 3 个相关段落。结果方案 B 的准确率明显更高而且 token 消耗只有方案 A 的十分之一。这个结论在多个任务上都复现过。所以上下文工程不是“搬运工”而是“编辑”——你要像编辑一样删繁就简把最有价值的信息放在模型最容易注意到的地方。3. 上下文工程为什么直接决定输出质量3.1 注意力是有偏好的位置很关键模型对上下文不同位置的敏感度是不一样的。经验规律是开头和结尾的信息权重最高中间部分最容易被忽略。这跟人读长文档的体验很像开头认真读结尾认真读中间容易走神。这个规律直接指导我们的拼接策略。系统指令、核心约束、输出格式要求应该放在最前面当前这一轮的具体任务和用户输入应该放在最后面中间放背景知识和历史对话。如果你把最关键的任务描述埋在中间模型很可能“看不见”。我在一个信息抽取项目里踩过这个坑。当时把抽取字段的定义放在了一大段背景资料后面结果模型经常漏字段。后来把字段定义提到系统指令里漏字段的问题基本消失。同样的信息换个位置效果天差地别这就是上下文工程的威力。3.2 上下文污染会引发连锁错误多轮对话里最怕的是上下文污染。所谓污染就是前面某一轮的错误信息、过时信息、或者用户随口说的无关内容被后续轮次反复引用导致错误不断放大。举个例子用户第一轮说“帮我查一下 A 产品的价格”模型答了。第二轮用户说“那 B 呢”模型正确理解为 B 产品。第三轮用户说“再便宜点的”这时候“便宜点”是相对 A 还是相对 B如果历史记录里 A 和 B 的信息混在一起没有清理模型很可能搞混参照系。这就是典型的上下文污染。解决办法是对历史做结构化压缩而不是原样堆叠。比如把每一轮压缩成“用户意图 关键实体 结论”这样的三元组丢弃寒暄和冗余表述。这样既保留了必要的上下文又大幅降低了污染风险。3.3 成本和延迟是绕不开的现实约束抛开效果不谈上下文长度直接决定成本和延迟。token 是按量计费的输入越长越贵同时长上下文意味着更长的推理时间用户等待更久。在一个日调用量百万级的应用里上下文从 8K 优化到 2K省下的成本是相当可观的。所以上下文工程不只是技术问题也是成本问题。一个成熟的团队会把“上下文预算”当成一个明确的指标来管理每类信息分配多少 token超出预算怎么裁剪哪些信息可以缓存复用。这些决策直接影响到产品的毛利和用户体验。4. 核心实操上下文组织与压缩的具体方法4.1 分层组织给上下文定一个骨架我习惯把上下文分成三层来组织稳定层系统指令、角色定义、输出格式、通用约束。这部分内容基本不变可以缓存放在最前面。半稳定层检索到的背景知识、few-shot 示例。这部分每轮可能变但结构固定。动态层对话历史摘要、当前任务、用户输入。这部分每轮都变放在最后。这个骨架的好处是清晰、可维护。稳定层可以做成模板半稳定层由检索模块填充动态层由对话管理模块生成。每一层都有明确的 token 预算超了就按优先级裁剪。下面是一个简化的预算分配示例层级内容建议预算占比裁剪策略稳定层系统指令、格式约束10%不裁剪精简措辞半稳定层检索知识、示例50%按相关度排序截断尾部动态层历史摘要、当前输入40%历史按轮次压缩当前输入优先保留这个比例不是固定的要根据任务调整。比如纯问答任务半稳定层可以多给纯对话任务动态层要多留。4.2 历史对话的压缩技巧历史对话是上下文膨胀的主要来源。我的做法是滚动摘要 关键实体表。滚动摘要每积累 N 轮对话就用模型把这几轮压缩成一段简短摘要替换掉原始记录。摘要只保留结论、决策、未解决的问题丢弃过程性内容。关键实体表单独维护一个结构化的表记录对话中出现的关键实体及其属性比如产品名、价格、时间、约束条件。这个表每轮更新以紧凑的格式放进上下文。这样即使原始对话被压缩了关键信息也不会丢。实测下来一个 20 轮的对话原始记录可能要 6000 token压缩后摘要加实体表只要 800 token 左右而且模型对关键信息的召回率反而更高因为信息更集中了。4.3 检索结果的拼接与重排RAG 场景下检索回来的片段怎么拼进上下文直接影响效果。几个要点按相关度排序但不要只按相关度。最相关的放最前面和最后面次相关的放中间。去重和去噪。检索结果里经常有大量重复或无关片段先做一轮过滤。加来源标注。每个片段前面标上来源编号方便模型引用也方便你排查问题。控制单片段长度。一个片段太长会挤占其他片段的空间必要时做二次切分。我一般会把检索结果控制在 3 到 5 个片段每个片段 200 到 400 字。片段太多模型反而挑不出重点片段太少可能覆盖不全。这个数量需要根据你的知识库密度和问题复杂度来调。4.4 用结构化格式降低理解成本同样的信息用自然语言描述和用结构化格式呈现模型的理解效率是不一样的。结构化格式如 JSON、表格、键值对能显著降低模型的解析负担减少歧义。比如你要告诉模型几个约束条件与其写“请注意回答不能超过 100 字不能使用专业术语必须包含一个例子”不如写成约束 - 长度 100 字 - 术语禁用专业术语 - 必须包含一个例子后者模型更容易准确执行。这个技巧在复杂任务里尤其明显我见过不少项目只是把指令从散文改成列表输出合规率就上了一个台阶。5. 多轮对话与 Agent 场景下的上下文管理5.1 多轮对话的上下文生命周期多轮对话里上下文是有生命周期的。不是所有历史都值得一直保留。我的经验是把对话历史分成三个状态活跃状态最近 2 到 3 轮原样保留因为和当前问题最相关。压缩状态更早的轮次压缩成摘要和实体表。归档状态超出窗口的直接丢弃或存到外部需要时再检索回来。这个生命周期管理能有效控制上下文长度同时保证关键信息不丢。关键是压缩和归档的触发时机要设计好不能等到窗口快满了才动手那时候已经晚了。5.2 Agent 任务里的上下文隔离Agent 场景比普通对话更复杂因为它可能同时处理多个子任务每个子任务有自己的上下文。如果不做隔离所有子任务的上下文混在一起模型很容易串台。我的做法是给每个子任务独立的上下文空间主流程只保留任务调度所需的最小信息。子任务完成后只把结果摘要回传给主流程过程细节丢弃。这样既保证了每个子任务的专注度又控制了主流程的上下文规模。在一个多步骤数据处理的项目里我用这个思路把主流程的上下文从 10000 token 压到了 1500 token任务成功率反而提升了因为模型不再被无关步骤干扰。5.3 工具调用结果的精简Agent 调用工具后返回结果往往很长比如一次搜索返回十几条结果一次数据库查询返回几十行。这些结果如果原样塞进上下文很快就爆了。我的处理原则是工具返回结果先在外部做一轮筛选和摘要只把最相关的部分放进上下文。比如搜索结果只取前 3 条数据库查询只取关键字段和聚合值。这个筛选逻辑可以写在工具封装层对模型透明。这样模型看到的是精炼后的信息决策效率更高。6. 常见问题与排查技巧实录6.1 模型忽略指令怎么办这是最高频的问题。排查顺序建议是检查指令位置。是不是被埋在了中间移到最前面试试。检查指令长度。是不是太长太啰嗦精简成列表。检查冲突。是不是有两条指令互相矛盾模型会随机选一条执行。检查上下文长度。是不是接近窗口上限了超长上下文里指令权重会被稀释。我遇到过的案例里八成以上是位置和长度问题。把指令提到最前面并精简后基本都能解决。6.2 多轮之后开始胡言乱语这通常是上下文污染或窗口溢出的信号。排查方法打印出每一轮实际送入模型的完整上下文看看里面到底有什么。检查历史压缩逻辑是否正常工作有没有把关键信息压没了。检查是否有过时信息没被清理被模型当成了当前事实。我一般会在开发阶段加一个上下文快照功能每轮把完整上下文存下来出问题时直接回看比猜要快得多。6.3 检索增强反而让答案变差RAG 不是万能的。如果检索结果质量差或者拼接方式不对反而会拖累模型。常见原因检索片段和问题不相关模型被带偏。片段太多太杂模型抓不住重点。片段之间互相矛盾模型无所适从。解决办法是提高检索精度用更好的 embedding、加 rerank控制片段数量以及在指令里明确告诉模型“如果检索内容与问题无关请忽略并基于常识回答”。6.4 常见问题速查表现象可能原因排查方向忽略指令位置靠后、指令冗长、指令冲突前移、精简、去冲突多轮后混乱上下文污染、窗口溢出压缩历史、清理过时信息RAG 效果差检索不准、片段过多、内容矛盾加 rerank、控数量、去矛盾输出格式不稳格式要求不明确、示例缺失结构化格式、加 few-shot成本过高上下文冗余、未缓存分层预算、缓存稳定层7. 我踩过的坑和几条实用心得第一个坑是过度依赖长窗口。早期我总觉得窗口越大越好什么都往里塞结果发现模型在长上下文里的表现并不稳定而且成本飙升。后来转向“精准投喂”效果和成本都改善了。窗口大小是能力上限不是使用目标。第二个坑是忽视上下文的顺序。同样的信息换个顺序效果完全不同。这个认知是我在一个分类任务里建立的把类别定义放在最后准确率比放在中间高了近十个百分点。从那以后我对上下文的每一段位置都会刻意设计。第三个坑是不做上下文监控。上线之后如果不记录每轮的实际上下文出了问题只能靠猜。后来我加了上下文日志排查效率提升非常明显。这个投入是值得的尤其是在多轮和 Agent 场景里。几条实用心得稳定层内容尽量缓存复用能省不少钱历史压缩要趁早别等窗口满了才动手结构化格式永远比自然语言描述更可靠每类信息都要有明确的 token 预算超了就裁不要心软。上下文工程这件事说到底是在有限的资源里做最优的信息编排。模型能力是给定的但你怎么组织喂给它的信息是你完全可控的变量。把这个变量用好往往比换一个更强的模型带来的提升还大。我在实际项目里的体会是一个精心设计的上下文能让中等模型跑出接近顶级模型的效果而一个糟糕的上下文能让顶级模型表现得像个新手。这个杠杆值得每个做 AI 应用的人认真对待。