1. 上下文工程到底在解决什么问题1.1 从一个真实翻车现场说起去年我帮一个团队调一个智能客服系统模型用的是当时第一梯队的大语言模型接口参数调得漂漂亮亮提示词也反复打磨了十几版。测试环境里回答得滴水不漏一上生产就出洋相用户问“我上周买的那双鞋能退吗”模型一本正经地回答了退货政策却完全不知道这个用户上周到底买没买鞋、买的是哪双、订单状态是什么。问题不在模型笨而在于我们喂给它的东西里压根没有“这个用户上周的订单信息”。这就是上下文工程要解决的核心矛盾模型本身的能力是固定的但你能让它“看到”什么是可以设计的。很多人把精力全砸在提示词措辞上却忽略了更根本的一件事——模型在生成回答的那一刻它的“视野”里到底有哪些信息。上下文工程说白了就是一门“在正确的时间把正确的信息以正确的格式放进模型视野”的手艺。1.2 上下文不等于提示词这里必须先厘清一个常见误解。很多人把“上下文工程”和“提示词工程”当成一回事其实两者是包含关系而且上下文工程的范畴要大得多。提示词工程关注的是“怎么说”——措辞、语气、指令结构、少样本示例的排列。上下文工程关注的是“给什么”——哪些信息该进、哪些该出、以什么顺序进、占多少空间、什么时候动态替换。打个比方提示词工程像是你写一封求职信的文笔上下文工程则是你决定随信附上哪几份证明材料、按什么顺序装订、哪些无关的证书干脆不放。我见过太多团队在提示词上抠字眼却让整个对话历史无脑全量塞进去结果模型被几十轮无关闲聊淹没关键信息反而被稀释。这就是典型的“提示词很精致上下文很粗糙”。1.3 为什么现在特别值得聊这件事三个现实原因叠加让上下文工程从“锦上添花”变成了“生死攸关”。第一上下文窗口变大了但没大到可以浪费。现在主流模型动辄支持几十万甚至上百万token的上下文听起来很爽但实测下来塞得越满模型对中间部分信息的注意力越弱这就是业内常说的“迷失在中间”现象。窗口大不等于可以乱塞反而更需要精挑细选。第二检索增强生成成了标配但检索质量参差不齐。很多系统接了个向量库就以为万事大吉结果召回一堆语义相似但实际无关的片段模型被带偏。检索只是手段把检索结果工程化地组织进上下文才是目的。第三成本敏感。token是要花钱的上下文越长延迟越高、费用越贵。一个设计良好的上下文可能只用三分之一的信息量就达到更好的效果这在规模化场景下是实打实的成本差异。1.4 这篇文章适合谁看如果你正在做智能问答、知识库助手、代码辅助、文档分析这类应用或者你只是想让日常用模型处理复杂任务时效果更稳那这篇内容都对你有用。我不打算讲空泛的理论而是把我在实际项目里踩过的坑、总结的取舍逻辑、能直接抄的配置方法都摊开讲。基础概念我会用生活化的例子解释有经验的读者可以直接跳到实操章节。2. 上下文的组成结构与设计原则2.1 一个完整上下文里通常有哪些块把模型每次收到的内容拆开看大致可以分成这么几层从外到内、从稳定到动态层级内容类型变化频率典型作用系统层角色设定、行为边界、输出格式要求极低定基调约束模型行为指令层当前任务的具体要求中告诉模型这次要干什么知识层检索到的文档、数据库记录、工具返回结果高提供事实依据记忆层历史对话摘要、用户画像、长期偏好中维持连贯性输入层用户当前这句话极高触发本次生成这个分层不是学术定义而是我在工程实践中总结的“心智模型”。它的价值在于每一层的更新策略、压缩策略、优先级都不一样。系统层可能一个月才改一次知识层每次请求都要重新检索记忆层需要定期做摘要压缩。如果你不把它们分开管理就会陷入“一锅乱炖”的困境。2.2 设计上下文的第一原则相关性优先于完整性新手最容易犯的错是追求“信息越全越好”。我早期也这样觉得多给点总没坏处。实测下来完全相反无关信息是负资产。举个具体例子。做一个法律咨询助手用户问“租房押金不退怎么办”。如果你把整部相关法律条文全塞进去模型确实“什么都知道”但它生成回答时会平均用力把大量无关条款也带出来回答又臭又长重点反而不突出。正确做法是只召回与“押金”“租赁合同解除”“违约责任”强相关的几个条款模型反而能给出精准、可操作的建议。判断相关性的实操标准很简单如果这段信息删掉模型的回答会不会变差如果不会那它就是冗余的应该拿掉。这个“删除测试”是我做上下文裁剪时最常用的判断方法。2.3 第二原则结构清晰优于堆砌模型对信息的组织方式很敏感。同样三条信息平铺直叙地堆在一起和用清晰的结构标记出来效果差别很大。我做过一个对比测试让模型根据几段产品信息回答用户问题。A组把信息直接拼接B组用明确的标签分隔比如“【产品参数】……【用户评价】……【售后政策】……”。结果B组的回答准确率明显更高而且模型更少出现张冠李戴的情况。原因不难理解结构标记相当于给模型画了地图告诉它每块内容是什么性质它就不容易混淆。所以我在组织上下文时习惯给每一块内容加上轻量的“信封”——用简洁的标记说明这块是什么、来源是什么、时间是什么。这些标记本身也占token但带来的准确性提升完全值得。2.4 第三原则动态组装优于静态模板很多团队的做法是写一个巨大的提示词模板把能想到的内容都占好位置每次请求填空。这种方式在简单场景能用一旦信息源变多就崩了。更合理的做法是把上下文当成一个动态组装的流水线先判断这次任务需要哪些类型的信息再去对应的数据源取取回来做相关性过滤和压缩最后按优先级排序拼装。这个过程每次请求都可能不一样。比如用户只是打招呼那知识层和记忆层可能都不需要触发用户问具体业务问题才去检索。这种“按需组装”的思路既省token又提准确率。实现上通常需要一个调度逻辑判断当前意图决定激活哪些信息通道。这部分我在第4章会展开讲具体实现。3. 核心环节拆解从检索到拼装的全流程3.1 信息召回怎么把“该看的”捞出来召回是上下文工程的第一道关口捞错了后面全白搭。常见的召回手段有三类各有适用场景。向量语义检索适合处理“意思相近但用词不同”的情况。比如用户问“怎么退钱”文档里写的是“退款流程”字面不匹配但语义一致向量检索能捞出来。它的短板是对精确匹配不敏感比如订单号、产品型号这类向量检索经常抓瞎。关键词检索比如基于倒排索引的方案擅长精确匹配订单号、专有名词、代码符号这类它最靠谱。但它对同义表达无能为力。结构化查询针对数据库、表格类信息直接按字段条件查准确率最高但前提是信息本身是结构化的。我的实战经验是混合召回然后重排。先用向量和关键词各召回一批合并去重再用一个重排模型按相关性打分排序取前若干条。这套组合拳下来召回质量比单用任何一种都稳。重排这一步很多人省掉觉得麻烦但它恰恰是过滤噪声的关键我强烈建议不要省。3.2 信息压缩窗口有限怎么塞得下召回回来的内容往往很长直接塞会爆窗口。压缩是必须的但压缩方式有讲究。最粗暴的是截断取前N个字符。这种方式简单但危险关键信息可能正好在被截掉的后半段。我一般只在信息结构均匀、前后重要性一致时才用。更聪明的是抽取式压缩从长文档里挑出与问题最相关的句子或段落。实现上可以用句子级的相关性打分保留高分句。这种方式能保住关键信息但可能破坏上下文连贯性读起来跳跃。还有生成式压缩让模型先把长文档总结成短摘要再喂给主模型。这种方式压缩率高但引入了额外的一次模型调用有信息损失风险而且增加延迟。我通常只在文档特别长、且对细节精度要求不高的场景用。实际操作中我倾向于分层压缩先按段落做粗筛把明显无关的段落整段丢掉剩下的段落内部再做句子级筛选。这样既保住了结构又去掉了冗余。3.3 信息排序位置决定注意力前面提过“迷失在中间”现象这意味着信息放在上下文的什么位置直接影响模型对它的重视程度。实测规律大致是开头和结尾的信息模型注意力最强中间部分最容易被忽略。基于这个规律我的排序策略是最关键的指令和约束放开头最重要的知识证据放结尾紧挨着用户问题次要的、辅助性的信息放中间用户当前问题永远放最后这个顺序不是拍脑袋而是大量测试后总结的。你可以自己做个对照实验把同一条关键信息分别放在开头、中间、结尾看模型回答的准确率差异大概率能复现这个规律。3.4 格式选择让模型好读而不是让人好读一个反直觉的点给模型看的上下文格式标准和人阅读的舒适度不完全一致。人喜欢排版精美、有图表有颜色的文档。模型对纯文本的结构标记反而更敏感。我测试下来简洁的标记符比如用方括号、井号、短横线做分隔比复杂的Markdown表格更稳因为表格在token化时容易产生歧义模型偶尔会读错行列对应关系。另外重复关键信息有时是有效的。比如一个重要的约束条件在系统层说一遍在用户问题附近再强调一遍模型遵守的概率会提升。这看起来冗余但在关键约束上值得。当然别滥用否则上下文会变得啰嗦。提示格式设计没有万能模板最好的办法是拿你的真实数据做A/B测试。同一个问题换两种上下文格式各跑几十次对比准确率和稳定性用数据说话比凭感觉靠谱得多。4. 实操搭一套可复用的上下文组装流水线4.1 整体架构长什么样我把自己项目里用的上下文组装逻辑抽象成一个通用流程大致分五步意图识别判断用户这句话属于哪类任务决定需要激活哪些信息源并行召回从向量库、关键词索引、数据库、会话记忆里同时取信息重排过滤对召回结果打分排序砍掉低分项压缩组装按预算压缩每块内容按优先级拼装成最终上下文预算校验检查总token是否超限超了就按优先级继续砍这套流程的核心思想是把上下文当成一个有预算的资源来管理。每次请求都有一个token预算各个信息块按重要性竞争这个预算重要的多占次要的少占或不占。4.2 意图识别怎么做才不鸡肋意图识别不需要搞得很复杂。早期我用过专门的分类模型后来发现对于大多数场景直接用主模型做一次轻量判断就够了甚至可以在同一次调用里让模型自己决定要不要检索。一个实用的做法是在系统提示里明确告诉模型“你有以下工具可用”然后让模型在需要时主动发起检索请求。这种方式叫“工具调用”或“函数调用”现在主流模型都支持。它的好处是省掉了单独的意图识别环节模型自己判断需不需要外部信息。但要注意不能让模型完全自主决定。我遇到过模型该检索时不检索、凭记忆瞎编的情况。所以我会加一层兜底规则涉及具体事实、数字、时效性信息的问题强制触发检索不给模型偷懒的机会。4.3 召回参数怎么定召回环节有几个关键参数定不好直接影响效果。召回数量向量检索一般取top 10到top 20关键词检索取top 10合并后可能有三四十条。这个数量不是越多越好因为后面还要重排。我通常让召回阶段宽松一点重排阶段严格一点这样既不漏掉潜在相关项又能保证最终质量。相似度阈值向量检索会返回相似度分数设一个下限能过滤掉明显不相关的。但这个阈值因模型而异不能照搬别人的数值。我的做法是拿一批标注好的问题-文档对跑一遍看分数分布取一个能平衡召回率和准确率的点。通常0.7到0.8是个常见区间但一定要自己验证。重排保留数量重排后一般保留3到8条进入最终上下文。具体几条取决于单条信息的长度和总预算。如果每条都很长就少留几条如果每条很短可以多留几条。4.4 组装顺序的代码化表达下面这段伪代码展示了我常用的组装顺序逻辑用Python风格写出来方便理解def build_context(user_query, intent, budget8000): context_blocks [] # 1. 系统层角色和约束永远放最前 context_blocks.append(system_prompt) # 2. 记忆层会话摘要帮助维持连贯 if intent.needs_memory: context_blocks.append(compress_memory(session_summary)) # 3. 知识层检索结果按相关性排序 if intent.needs_retrieval: docs retrieve(user_query) # 混合召回 docs rerank(docs, user_query) # 重排 docs docs[:intent.top_k] # 取前k条 for doc in docs: context_blocks.append(format_doc(doc)) # 4. 输入层用户问题放最后 context_blocks.append(user_query) # 5. 预算校验与裁剪 return enforce_budget(context_blocks, budget)这段逻辑里enforce_budget是关键。它的策略是先算总token如果超了从优先级最低的块开始砍。优先级排序通常是系统层 用户问题 知识层 记忆层。也就是说宁可牺牲历史记忆也要保住当前问题的相关证据。4.5 一个具体的参数计算示例假设你的模型上下文窗口是128K token但你不应该用满。我的经验是实际使用不超过窗口的60%到70%留出余量给模型生成回答也避免注意力稀释。按70%算可用预算约90K token。再往下分配系统层约500 token固定记忆层约2000 token会话摘要知识层约80000 token大头用户问题约200 token生成预留约7000 token知识层这80K token如果单条文档平均2000 token理论上能放40条。但前面说了放太多会稀释注意力所以我实际只会放5到10条高质量文档剩下的预算宁可空着。空着比塞满好这是很多人想不通但实测有效的一点。5. 常见问题与排查技巧实录5.1 模型答非所问怎么定位是上下文的问题排查这类问题我有一套固定的检查顺序。第一步看上下文里到底有没有答案所需的信息。把实际发给模型的完整上下文打印出来人工读一遍。如果信息压根不在里面那就是召回环节的问题跟模型无关。这一步能排除掉一大半“模型背锅”的情况。第二步如果信息在看它放在什么位置。如果关键信息被埋在一大堆无关内容中间很可能被模型忽略了。试着把它挪到开头或结尾再测一次。第三步看信息格式是否清晰。如果多块信息混在一起没有标记模型可能张冠李戴。加上明确的分隔标记再测。第四步看是否有冲突信息。有时候召回回来的多条文档互相矛盾模型不知道该信哪个就会给出模棱两可的回答。这时候需要在重排阶段做去重和冲突检测。这套排查流程走下来绝大多数“模型不行”的抱怨都能找到真正的原因。5.2 召回总是不准有哪些提升手段召回不准是最常见也最头疼的问题。我整理了几个见效比较快的手段按投入产出比排序手段投入效果适用场景加混合召回向量关键词低明显几乎所有场景加重排模型中明显召回量大时优化文档切分粒度中明显长文档场景补充同义词/别名表低中等有领域术语时微调嵌入模型高明显领域差异大时加查询改写中中等用户表达口语化时其中文档切分粒度最容易被忽视。很多人把文档按固定字数切结果一句话被切成两半语义断裂。更好的做法是按语义边界切比如按段落、按小节保证每块内容自洽。切分粒度太大召回不精准太小上下文碎片化。我一般控制在200到500字一块具体看文档类型。查询改写也值得一试。用户问“那个东西怎么弄”直接拿去检索肯定不行。可以先让模型把口语化问题改写成规范的检索查询再拿去召回效果提升明显。5.3 上下文太长导致变慢变贵怎么优化优化方向有三个减量、缓存、并行。减量就是前面说的压缩和裁剪把不必要的token砍掉。这是最直接的省钱手段。缓存针对的是那些每次请求都重复的稳定内容比如系统提示、固定的知识库片段。很多模型服务商支持上下文缓存重复部分可以打折甚至免费。把稳定内容放前面、动态内容放后面能最大化缓存命中率。并行指的是召回环节。如果要从多个数据源取信息串行执行会很慢改成并行能显著降低延迟。这个在工程上不难但很多人一开始没意识到。5.4 几个我踩过的坑坑一把历史对话全量塞进去。早期我图省事把整个对话历史原样拼进上下文。结果聊到二十轮以后模型开始答非所问因为早期无关内容占了大量空间。后来改成滚动摘要只保留最近几轮原文加更早内容的摘要效果立刻好转。坑二检索结果不去重。混合召回时向量和关键词经常召回同一条文档如果不去重同一条信息在上下文里出现两三次模型会误以为它特别重要过度强调。加一步去重很简单但效果明显。坑三忽视时间信息。知识库里的文档有新旧之分如果不标注时间模型可能拿过时的信息回答时效性问题。我现在强制给每条召回结果打上时间戳并在提示里说明“优先参考较新的信息”。坑四格式标记用得太花哨。有段时间我用复杂的嵌套标记觉得结构清晰结果模型经常解析错误。后来简化成最朴素的方括号加短横线稳定性反而上去了。给模型看的格式简单就是美。注意上下文工程没有一劳永逸的配置。业务在变、数据在变、模型也在迭代今天调好的参数过几个月可能就不适用了。建议把关键参数做成可配置项定期用真实数据回归测试别把参数写死在代码里。6. 进阶思路让上下文自己进化6.1 从静态配置到动态学习前面讲的都是人工设计的规则。更进一步的做法是让系统根据反馈自动优化上下文策略。比如记录每次请求的上下文组成和最终回答质量用这些数据训练一个策略模型预测“对于这类问题什么样的上下文组合效果最好”。这个方向门槛较高需要足够的反馈数据积累。但即使不做完整的策略学习简单的A/B测试机制也值得搭。把不同的上下文配置做成实验组用真实用户反馈或人工评分做对比逐步淘汰效果差的配置。这种“小步快跑”的优化方式比一次性设计完美方案更现实。6.2 记忆的长期管理会话记忆不只是“记住之前说了什么”更包括用户偏好的长期积累。一个设计良好的记忆系统应该能区分短期上下文本次对话和长期画像这个用户一贯的偏好、习惯、历史问题。长期记忆的管理难点在于写入和读取的策略。不是什么信息都值得记记太多会污染后续上下文。我的做法是只记录那些“跨会话仍然有效”的信息比如用户明确表达的偏好、反复出现的问题类型。读取时按当前任务相关性筛选不是无脑全取。6.3 多模态上下文的组织当上下文里开始出现图片、表格、代码等非纯文本内容时组织方式又不一样了。图片需要配文字说明表格需要转成模型好理解的格式代码需要标注语言和用途。我目前的做法是给每种模态的内容加统一的描述性信封比如图片用“【图片内容描述】”包裹表格转成简洁的键值对或列表形式。核心原则不变让模型一眼能看出这块是什么、跟当前问题什么关系。6.4 评估体系怎么建没有评估优化就是盲人摸象。上下文工程的评估我建议至少覆盖三个维度准确性回答是否基于上下文中的正确信息完整性该用到的信息是否都用上了简洁性是否引入了无关信息导致回答冗长评估方法上小规模可以用人工评分大规模需要自动化。自动化评估可以构造一批“问题-标准答案-应召回文档”的测试集检查系统是否召回了正确文档、回答是否与标准答案一致。这套测试集要持续维护随着业务变化更新。我在实际项目里的体会是上下文工程的投入产出比往往比调模型本身更高。同一个模型上下文组织得好效果能上一个台阶组织得差再强的模型也救不回来。这件事没有太多玄学更多是细致的工程活——把信息管好、排好、压缩好模型自然就能“看到该看的东西”。最后分享一个小技巧每次上线新策略前先拿历史失败案例跑一遍看新策略能不能修复那些老问题这比随机测试更能暴露真实短板。