
先聊个真实经历。今年我负责一个客服知识库问答系统最初为了省事直接开了某家大模型的 200K 上下文窗口把整本产品手册、500 条 FAQ、三个月工单记录一股脑全塞进去。结果呢模型一本正经地告诉用户“本产品支持 4G 网络”而手册里白纸黑字写着仅支持 WiFi。那段日子里我听到最多的一句话就是“context-mode 不是这么用的”。这篇文章就把我对 context-mode 的理解、踩坑和最终落地方案一次说清楚适合正在做 LLM 应用、提示词工程或者被长上下文折磨过的开发者参考。1. context-mode 被高估了从 200K 窗口的翻车现场说起很多人一听到 context-mode第一反应是“把上下文窗口调大让模型记住更多东西”。这个理解不能说错但真按这个思路做项目很快会被现实教育。1.1 一个让我怀疑人生的实测案例我把产品手册拆成 300 多个 PDF 页面转成纯文本后塞进系统提示词。当时还觉得自己很聪明——这样就不用接 RAG 了模型什么问题都能回答。第一次调用我问“S 系列路由器支持 5GHz 频段吗”模型回答支持还补充了一句“默认开启双频合一”。实际上手册里写的是 S 系列 2.4GHz only。我第一反应是手册数据有问题于是把手册全文拉出来搜关键词确实存在。问题出在哪我重新用只有 3 页的规格摘要做提示词同一模型回答正确。也就是说资料越多模型反而越“糊涂”这和很多人的直觉相反。1.2 上下文失效的三种典型表现这段经历之后我整理了项目里反复出现的三类问题指令服从性下降50 条系统规则后面跟着 2000 条知识模型变成知识搬运工忘了自己“只依据知识回答、不编造”的指令开始自由发挥。中间遗忘把用户问题放在上下文中部模型处理时优先级明显低于放在开头或结尾的信息。你问它上下文里提过的订单号它可能说“没有找到相关记录”但这条信息就在中间某一段。幻觉变多长上下文中自相矛盾或语义模糊的内容一多模型倾向于挑高频词或最近出现的表述而不是认真比对原文。尤其当答案距离问题所在位置较远时幻觉率明显上升。1.3 底层原因注意力不是无限带宽为什么窗口变大反而变笨先看注意力机制。Transformer 的自注意力计算复杂度是 O(n²)上下文 token 数翻倍计算量和显存占用近似四倍增长。更要命的是 KV Cache——模型推理时要缓存历史的 Key 和 Value上下文越长每一轮生成都要读取或更新更长的缓存延迟和成本同步上升。注意力还有一个“被摊薄”的问题窗口有 200K信息分布在上万个位置每个 token 的注意力权重是有限的中间位置的信息很难被有效聚焦。这就像听一个人连续讲三小时开头自我介绍和最后一分钟总结记得清楚中间讲什么细节基本忘光。模型也一样。所以 context-mode 真正考验的不是“塞多少”而是“怎么排列、怎么取舍”。2. 别把 context-mode 当垃圾桶上下文分层的设计原则搞清楚长上下文的局限之后我开始把 context-mode 当成一个有限资源来做信息架构设计。一套靠谱的上下文结构至少应该分成四层顶层指令区、知识资料区、对话历史区、当前任务区。每一层有自己的目标和纪律。2.1 顶层指令区要短、要硬、要能复核系统提示词是上下文里最有价值的区域相当于“上岗培训”。很多人把产品手册、历史对话也塞进系统提示词这个误区我踩过。顶层指令应该控制在几百字以内用强约束的句式写。我当时把系统提示词精简成这样一个模板[角色] 你是客服助手小智只依据知识区内容回答禁止编造。 [硬性规则] 1. 先给结论再给依据。 2. 依据必须引用知识编号格式【知识3】。 3. 知识区没有答案时直接回复“无法确认”并建议人工客服。 4. 不回答与客服无关的问题。规则不超过五条每条都是 if-then 形式。这样模型在长上下文中依然能抓住核心指令。如果指令写成长篇大论它自己和知识区就混在一起了。2.2 知识资料区按需加载而不是全量塞入知识区要遵循一个原则一次只喂和当前问题相关的知识。我后来把产品手册拆成 200 个语义块每个块 200-400 字检索时用混合检索取 top 5 到 10 个块。这块单独一个分区的标签比如[知识] 【知识12】S 系列路由器支持 2.4GHz 频段不支持 5GHz。 【知识57】S 系列路由器包装内含电源适配器、网线、快速安装指南。每个知识块都带编号方便模型引用也方便我们排查它到底用了哪部分资料。检索这一步用向量召回加 BM25 关键词召回做融合能避免纯向量检索漏掉品牌型号这类专有名词。2.3 对话历史区滚动摘要加关键片段保留对话历史不能无脑全堆。我现在的做法是保留最近 6 轮原始对话更早的内容压缩成滚动摘要但摘要里必须保留用户的核心诉求、已确认事实、待办事项。一个可用的摘要模板[历史摘要] 用户核心诉求退款 20 元运费 已确认事实订单号 20240612商品已于 6 月 10 日签收 待办事项客服需核实运费金额后答复如果对话里出现过订单号、金额、地址这类关键信息即使原始轮次已被摘要替代也要原样保留在“关键事实”字段里。后面我要讲这一步最容易出问题。2.4 格式与分隔符让模型自己知道“现在该看哪里”各分区之间要有清晰分隔符。我习惯用尖括号标签模型对齐效果好。结构大概是system 角色与规则 /system knowledge 相关知识块 /knowledge history 滚动摘要 最近对话 /history task 当前用户问题 /task这么做的好处是当模型回答时它会按照标签的优先级去检索信息而不是把整段上下文当散文读。很多调试问题其实加几个分隔符就解决了。3. 三个可落地的 context-mode 工程方案方案没有绝对好坏只有适不适合当前场景。我服务过的项目里最终沉淀下来三种可复用的 context-mode 方案你可以按需选择。3.1 方案 A静态长上下文 Prompt Caching适合指令和知识相对固定、每天大量重复调用的场景。比如我们的客服系统系统提示词加产品核心知识大概 8K token每次调用都是同一份内容。这就可以把前缀部分做成静态然后开启平台的 Prompt Caching 能力。原理很简单相同前缀的请求会被平台缓存后续调用只按新增 token 计费。实测下来8K 前缀的重复请求成本能降 80% 以上延迟也有改善。但这个方案有个坑缓存的 key 是前缀一致如果你在 system 位置动态插入用户昵称、时间戳前缀一旦变化缓存就全部失效。所以动态内容必须放到上下文最尾部别放在前缀区。3.2 方案 B动态压缩 滚动摘要适合多轮对话、Agent 这类上下文会持续增长的场景。实现流程是新消息进来先计算当前总 token。超过阈值比如 12K时把最老的 N 条消息丢进摘要生成器。生成的摘要插入 history 分区头部被压缩的原始消息从上下文移除。如果摘要也过长再按时间分块压缩保留结构化摘要字段。注意摘要生成器建议用便宜的小模型或者同模型低 temperature 版本。成本上压缩十轮对话的成本远低于每次请求都携带十轮原始对话。更重要的是摘要后的上下文更简洁模型注意力更容易锁定关键信息。3.3 方案 CRAG 短上下文适合知识库规模大、更新频繁的场景。RAG 的本质是“把上下文窗口变成按需加载”先在外部把检索做了再将命中的片段拼进 context-mode。我最终在客服项目里就是用的这个方案混合检索拿 top 5 知识块加上滚动历史摘要再加上最近对话整个上下文控制在 4K 左右。RAG 不是要取代 context-mode而是把 context-mode 的输入控制在一个高质量范围内。很多人纠结“RAG 不如直接把全文塞进大模型”实际对比下来在知识量大、且答案需要严格依赖原文的业务里RAG 的准确率和成本表现都好得多。3.4 三种方案怎么选维度方案 A静态长上下文方案 B动态压缩方案 CRAG适用场景固定知识 高频重复调用多轮对话 / Agent 任务大规模知识库问答实现难度低中中高成本低依赖缓存命中中摘要生成有开销低上下文短延迟中低中中上下文长度8K-50K5K-15K2K-6K知识更新更新即失效无影响更新检索库即可我的建议是如果业务目标就是“基于固定文档回答”优先考虑方案 A如果用户会连续追问、来回扯皮方案 B 是底线如果知识库超过几万字并且经常更新方案 C 是唯一稳妥路线。4. 实测踩坑记录客服知识库项目的三个教训再好的理论落到真实项目里都会出幺蛾子。分享一下我那个客服项目里最刻骨铭心的三个坑。4.1 教训一系统提示词被 500 条 FAQ 淹没最初我以为把 500 条 FAQ 塞进 knowledge 区是安全的结果模型对高频问题的回答开始“串味”。比如用户问“路由器重置后怎么配置”模型引用了“恢复出厂设置会导致所有配置丢失”这条 FAQ又在后面补了一句“建议重新设置 WiFi 密码”。看起来没问题但这条建议并不在 FAQ 里是模型“自由发挥”出来的。排查链路是这样的先把 500 条 FAQ 单独喂给模型回答正常再把 FAQ 数量减少到 20 条也正常一旦接近 100 条开始出现额外补充。最终我把 FAQ 按意图拆成 12 个主题每次检索后只放 3 个主题块问题消失。根因不是模型能力而是知识区里相似内容太多模型不知道该跟哪条走。4.2 教训二全部对话历史上传后费用和延迟同时失控有一次为了“不遗漏用户关键信息”我把最近 20 轮对话全部放进上下文。tokens 从 2K 涨到 18K单次调用费用翻了将近十倍最长一次等了 10 秒才吐第一个字。用户问一句话系统要“想”这么久产品经理当场就不干了。我做了组实测对比历史处理方式上下文总 tokens平均延迟单次费用相对值20 轮全量18.2K8.2s10.0x10 轮全量9.6K4.1s5.2x6 轮全量 摘要5.1K1.8s1.7x6 轮全量 摘要 RAG3.8K1.2s1.0x最后我采用“6 轮全量 11 轮摘要”的组合费用和延迟都在可接受范围。多轮对话的场景真不是传得越全越好。4.3 教训三摘要压缩后模型开始“失忆”元凶竟是摘要模板有一次用户 A 申请退还 20 元运费客服 ok 之后用户又在隔天追问进度。这时模型竟然说“没有相关退款申请记录”。我在本地复现时原始对话里明明有金额和同意退款的句子。排查到最后发现我的摘要生成器只提取了“用户核心诉求退运费”没有提取金额这种实体数字导致后续轮次的模型根本无法知道具体要退多少钱。修复方式在摘要模板里加一个“关键数字与实体”字段强制保留金额、订单号、日期。从那以后我所有摘要模板的设计原则都变成宁可多保留几个字段也不要丢实体和数字。4.4 一个可复用的 context-mode 监控脚本调试 context-mode 时我习惯先看 token 分布。下面这个脚本用 tiktoken 统计各分区 token 数你可以直接抄走import tiktoken def inspect_context(context_dict: dict): enc tiktoken.get_encoding(cl100k_base) total 0 print(分区 token 分布) for key, value in context_dict.items(): count len(enc.encode(value)) total count print(f {key}: {count} tokens) print(f总 tokens: {total}) return total context { system: 角色与硬性规则约 500 字, knowledge: 检索到的 5 条知识块, history_summary: 滚动摘要 最近 6 轮对话, task: 当前用户问题, } inspect_context(context)如果发现 knowledge 区域占比异常高比如超过了总上下文的 60%就该回头收紧检索数量或者换更精准的检索。监控指标里我还加了“知识命中率”模型最终引用的知识块编号必须包含在检索返回的块里否则说明检索失败。5. 如果重新设计这个系统我会先做这四件事项目收尾复盘时我把经验浓缩成四条原则。如果你要从零开始做 context-mode 相关的功能我建议你先照着做。5.1 固定上下文的排布顺序不要今天把指令放前面明天放中间。我最终固定的顺序是系统角色 → 核心规则 → 少量示例 → 知识块 → 历史摘要 → 最近对话 → 当前任务。每一次请求都是这个骨架。模型在反复迭代中会慢慢适应这个结构相当于给它一个稳定的“工作台”。5.2 为每一类上下文设定 Token 配额我给自己的项目设了一个预算表系统指令 800、示例 600、知识区 2500、历史摘要 1200、最近对话 1500、当前任务 300总计约 6900 tokens。哪个分区超了就走对应的处理知识超了缩小检索范围历史超了就压缩。没有配额context-mode 一定会偷偷膨胀直到某一天费用和延迟把你吓一跳。5.3 开启缓存之前先把前缀稳定性设计好如果你打算用方案 A 的 Prompt Caching设计上下文时就要让前缀保持稳定。具体来说用户 ID、会话 ID、时间戳这类动态值全部放在任务区尾部system 区和知识区不要用模板变量做拼接尽量用固定文本。我自己曾把用户会话 ID 插在 system 开头结果缓存命中率几乎为零等于白开。5.4 用“依据引用”做事实校验而不是靠感觉我后来给客服系统加了一条硬性规则回答必须引用知识编号。实现上我会在模型输出后做一个简单校验检查引用编号是否存在于本次上下文的 knowledge 区。如果不存在说明模型在编造或检索遗漏直接拦截或改写。这比单纯调温度、调 prompt 要可靠得多——相当于给模型回答加了一双“审计的眼睛”。如果你也正在被 context-mode 困扰我的体会是别把它当一个大号记忆体而是当成一个需要精心设计的输入界面。上下文里放什么、不放在什么、放在哪个位置、用什么格式分隔这些工程细节对结果的影响往往比换一个更贵的模型更直接。每次我忍不住想“干脆加大窗口一了百了”的时候都会先问自己一句这个内容的排列模型真的看得懂吗