资讯动态

大模型上下文模式实战指南:从注意力分配到RAG与压缩策略

发布时间:2026/10/8 13:58:57 来源:尧图企业网站定制
1. 为什么我开始认真研究 Context Mode做了一段时间大模型应用开发的人大概率都经历过这种困惑模型明明是最新的提示词写得也不短知识库也接了可回答就是时好时坏同一个问题换个说法效果完全不一样。有一阵子我被这种不确定性搞得非常头大后来把所有请求的完整输入日志都抓出来逐字比对才意识到问题根本不在模型参数调得对不对而在我们喂给模型的那一坨上下文的组织方式上。所谓 Context Mode上下文模式说白了就是在调用大模型之前你决定哪些内容进入上下文、以什么结构组织、按什么顺序排列、塞多少量、要不要压缩、要不要临时检索补充。这一整套取舍策略就是上下文模式。它直接决定模型的注意力分布进而决定输出质量、稳定性和单次调用的成本。为什么同一个模型有人调出花有人调出一坨差别往往就藏在这里。这篇文章算是我过去几个项目里的阶段性沉淀涉及企业知识库问答、客服助手、长文档分析这几类真实场景。内容偏工程实践会讲原理但绝不掉书袋会给你可以直接拿去改的代码骨架也会把我踩过的坑原原本本摆出来。适合正在做 RAG、Agent、多轮对话记忆这类功能的开发者阅读。产品经理要是能耐心看完也会对为什么研发那边说效果上不去多一层理解。2. 先把瓶颈讲透为什么上下文本身会成为问题2.1 模型的注意力是有限预算不是无限硬盘Transformer 架构里的注意力机制理论上能处理任意长度的序列但工程上模型都设了上下文窗口上限常见的有 8k、32k、128k。窗口之内也不是雨露均沾。我自己做过一个很简单的测试把一份百页文档拆成若干段落随机挖掉几段再问模型某一段内容讲的是什么结果有非常明显的规律——放在最开头和最结尾的内容模型记得更牢靠藏在中间位置的段落即使总长度远没到窗口上限也经常被忽略或者答错。这个现象在论文里被称为 Lost in the Middle学术圈反复验证过。它说明一个道理模型对长上下文的有效利用不是线性的存在明显的位置偏差。所以别把上下文窗口当成一块可以随便填充的硬盘它更像一笔注意力预算预算总量有限而且花在开头和结尾最划算。你在有限预算里怎么分配直接决定模型的输出质量。2.2 Context Mode 解决的是注意力分配策略理解了上面这一点你就能明白上下文模式这个概念为什么会在圈子里迅速热起来。它真正要回答的是几个非常具体的问题哪些信息值得进入上下文它们以什么顺序和格式放置如果内容太多先压缩谁、保留谁用户当前这句话到底需要哪些背景打个比方你准备一个演示文稿不会把公司所有资料堆成一百页幻灯片而是挑三页最相关的数据、一页客户痛点、一页方案描述。Context Mode 干的就是挑三页这件事只不过对象换成了喂给大模型的信息。现在有个趋势叫上下文工程Context Engineering某种意义上比提示工程更值得投入精力。提示工程优化的是怎么说上下文工程优化的是给它什么两者叠加才是一个完整的大模型调用策略。注意不要只盯着提示词模板抄来抄去。提示词写得再花哨上下文里塞满了无关噪音效果照样拉胯。先解决喂什么再谈怎么喂。2.3 一个让我印象深刻的对照组为了说服团队我做过一组很朴素的对照实验。同样一个问题我们上个季度的老客户复购率是多少用同一个知识库、同一个模型唯一区别就是上下文组织方式。方式A把整份经营分析报告约3万字直接拼进提示词让模型自己找答案方式B先从知识库里检索出10段相关内容按相关度排序后注入上下文并明确标注以下是检索到的资料片段。结果差异非常明显。方式B的回答准确率从62%跃升到91%生成时间缩短了近30%单次调用成本也从六千多token降到一千八左右。没有加任何神秘提示词纯粹是给什么、怎么给的差别。这就是上下文模式最直白的价值证明。3. 我在项目里沉淀出来的几种 Context Mode下面按原理-适用场景-关键参数-优缺点的顺序把我在生产环境里真正用过的几种模式讲清楚。它们之间不是互斥关系很多场景需要组合着用。3.1 全量直连模式Full-context Direct Mode这是最直接的一种把系统指令、对话历史、用户问题原样拼接尽量把所有相关信息一次给足。原理上模型看到的是完整未被裁剪的上下文信息损失最少。它适合单轮问答、文档总量不大、原型验证阶段以及需要快速响应的任务——因为少了检索和拼装的额外延迟。但缺点同样突出。第一Token 成本随对话轮数线性增长多轮对话很快就撞到窗口上限。第二无关信息太多会稀释注意力模型反倒抓不住重点。第三延迟随输入长度增加而明显变慢。我在早期做客服机器人时用过这种模式结果发现聊到第八、九轮之后系统提示词里强调的语气规范开始逐渐失效。历史消息占据了绝大多数篇幅模型早期收到的指令被活生生挤出了注意力范围这让我第一次直观感受到上下文管理不是可有可无的优化而是必需品。关键参数max_tokens输出长度上限注意别和上下文窗口混为一谈truncation_strategy超出窗口时的截断策略。常见有从头截断和从中间截断我实测后者效果通常更好因为开头往往是系统指令不能丢。3.2 检索增强模式RAG-Based Context Mode这是目前企业知识库场景里最主流的一种。核心思路不让模型记住所有知识而是在每次调用前从外部知识库检索出与当前问题最相关的若干片段再组合成上下文。完整链路一般包括查询改写、向量检索、重排序、上下文拼装。每一步都有讲究。以我常用的实现为例流程是这样的用户输入问题对问题做轻度改写或扩展比如把上季度规范到具体日期范围或者补全缩写用同一套 Embedding 模型把问题向量化在向量数据库里执行相似度检索取回 top_k6 的候选片段在内存里对候选片段和原始问题再做一次相关性重排Rerank由轻量级模型打分排序取 top_n3 进入最终上下文。为什么要重排因为向量检索的相似度不等于语义相关度。很多时候排第一的片段看着像、实际答非所问真正需要的答案落在第五、第六位。重排这一步能把命中质量明显拉上来。上下文拼装也有讲究。我会把检索结果放进一个明确的区块用资料片段标签包裹并在每个片段前面标注来源文件名和页码。别小看这个动作。对模型来说来源可追溯是一种强提示能帮它区分这是资料里的信息和这是你现有知识。关键参数基于我的实测经验chunk_size文档切块大小经验值500-800字符具体看文本语言和结构chunk_overlap相邻块重叠建议50-100字符避免把一句话从中间切断embedding_top_k召回候选数6-10比较平衡太少漏召回太多增加重排负担rerank_top_n最终注入数3-5为宜再多边际收益很低成本却实打实上涨。这种模式的优点是对知识库实时更新友好、Token可控、答案可溯源。缺点是多了检索链路存在检索不到就答错的额外风险。这个我在第5节会专门讲排查方法。3.3 折叠压缩模式Compaction / Summary Mode对话一旦拉长历史消息会迅速撑爆窗口。折叠压缩模式的思路是保留一份精简记忆把早期原始对话提炼成摘要而不是一字不差地全带着。典型的结构是三层记忆短时记忆保留最近 N 轮原始消息我常用6轮保证当前话题连贯长期记忆更早的对话被压缩成若干条事件化摘要比如用户在某日询问退货政策我们答复支持7天无理由全局记忆用户画像、偏好、长期目标单独维护不参与对话历史压缩。我在实现时给折叠压缩加了几个关键参数compact_threshold触发压缩的阈值比如对话超过16轮或者历史token超过2000就执行一次压缩summary_keep_topics摘要中必须保留的主题列表比如用户过敏信息订单编号预算上限防止压缩把关键事实丢掉compact_debounce防抖时间比如10分钟内只压缩一次避免每轮触发压缩导致额外成本。压缩摘要看似简单实际很考验质量。我的一个教训是不要让摘要生成过程吃掉过多预算。如果历史累计5000 token压缩成600 token省了很多但如果每一轮都重新生成摘要成本反而更高。所以触发阈值和摘要保留最近原文的三明治结构要一起设计不要只盯着单轮省了多少。3.4 结构化分区模式Structured Context Mode前面几种模式主要解决选什么、省多少这一种解决怎么摆的问题。结构化分区模式要求把不同类型的信息放进上下文的不同区块每个区块用明确标签标记。我在实践中常用的区块划分系统指令区身份、语气、任务边界、输出格式要求事实数据区知识库检索结果、业务规则、最新数据只陈述事实不做过度解释工具能力区当前可用的工具、每个工具的用途、调用约束对话历史区按时间顺序排列的最近交互记录当前任务区用户最新问题、待解决问题、明确目标。为什么要分区因为模型在冗长上下文里区分身份设定和实时信息是有额外认知成本的。如果你把所有内容揉成一团它可能把知识库里某段话当成系统指令来遵守或者把用户闲聊当成事实背景来采信。分区之后相当于给模型画了一张地图哪块是路标、哪块是路面、哪块是终点一目了然。我的实测结果是同一套 RAG 数据从全量拼接检索结果堆在末尾改成结构化分区检索结果放事实数据区回答准确率提升约7个百分点输出格式违规率几乎降到了0。这个提升没有引入任何新模型纯粹是布局变化带来的。关键点有两个区块标签建议用 XML 标签或者 Markdown 标题模型对这两种格式的理解都比较好区块顺序不要乱动系统指令在最前当前任务在最后。模型对最前和最后的内容记忆最强把最重要的两件事放在这两个位置是成本最低的注意力分配手段。3.5 动态混合模式Adaptive Hybrid Mode这是我最推荐在生产环境落地的模式。说白了就是前面几种模式的组合拳由一个轻量路由模块根据当前请求特点动态决定走哪条上下文策略。我的实现思路分三步第一步意图识别。用一个小模型或者规则引擎判断请求类型比如闲聊/追问事实查询文档分析需要调用工具第二步策略选择。事实查询走检索增强模式多轮追问走短时记忆加折叠压缩文档分析走全量直连或分区模式工具调用则在上下文里补充工具能力区第三步降级链。如果检索结果相关性普遍低于阈值自动丢弃检索内容退化为纯对话模式并在回答里告诉用户资料库未找到直接答案。动态混合模式看起来复杂收益却很实在它把每次调用的上下文控制在最经济范围既保证效果又控制成本和延迟。我遇到过一个很有意思的情况用户问你还在吗如果系统仍然触发向量检索、等待数据库返回结果就会白白多花三五百毫秒。加了路由之后这类闲聊直接走纯对话模式快且省钱。别小看这种优化生产环境里日调用量上了几十万次省下的费用相当可观。4. 实操搭一套可切换的 Context Mode 框架理论讲了不少下面落地。我给出一个基于 Python 的简化示例把几种模式抽象成统一接口方便自由切换和组合。这不是唯一写法但结构清晰适合起步。4.1 统一上下文构建接口首先定义一个基础抽象类让所有模式实现同一个接口from abc import ABC, abstractmethod from dataclasses import dataclass from typing import Optional, List dataclass class ContextInput: query: str history: list system_prompt: str user_profile: Optional[dict] None dataclass class BuildResult: messages: list meta: dict # 记录模式名、token数、检索命中数等方便观测 class BaseContextBuilder(ABC): mode_name: str base abstractmethod def build(self, inp: ContextInput) - BuildResult: 把各类输入组装成大模型调用所需的messages结构 pass这里把返回结果设计成 messages meta 两个部分。messages 直接给模型调用meta 专门记录本次构建的过程信息。我强烈建议从一开始就保留 meta后面排查问题全靠它。4.2 实现检索增强模式检索增强模式的实现核心是调用检索器、重排、拼装三个环节class RAGContextBuilder(BaseContextBuilder): mode_name rag def __init__(self, retriever, rerankerNone, top_k6, top_n3): self.retriever retriever self.reranker reranker self.top_k top_k self.top_n top_n def build(self, inp: ContextInput) - BuildResult: query inp.query # 1. 召回候选 candidates self.retriever.retrieve(query, top_kself.top_k) # 2. 重排如果有 if self.reranker: candidates self.reranker.rerank(query, candidates) candidates candidates[:self.top_n] # 3. 拼装上下文明确分区 fact_block \n.join( f[来源:{c.meta.get(source)}页码:{c.meta.get(page)}]\n{c.content} for c in candidates ) messages [ {role: system, content: inp.system_prompt}, {role: system, content: f以下是相关资料片段\n{fact_block}}, ] messages.extend(inp.history[-8:]) messages.append({role: user, content: inp.query}) return BuildResult(messagesmessages, meta{ mode: self.mode_name, retrieved: len(candidates), token_estimate: estimate_tokens(messages), })代码里有两个容易出错的点我专门提醒检索片段不要无止境地塞。top_n 超过5之后边际收益下降很明显成本却在涨历史轮数控制在8轮以内超出部分交给折叠压缩模式管。否则对话一长历史反客为主把检索结果挤出了注意力范围。4.3 实现折叠压缩模式折叠压缩的逻辑先判断是否需要压缩需要就调用一次摘要模型生成历史摘要再把摘要和最近 N 轮原始消息一起保留。class CompactContextBuilder(BaseContextBuilder): mode_name compact def __init__(self, llm, max_history_rounds8, compact_threshold2000, debounce_seconds600): self.llm llm self.max_history_rounds max_history_rounds self.compact_threshold compact_threshold self.debounce_seconds debounce_seconds def build(self, inp: ContextInput) - BuildResult: history inp.history recent history[-self.max_history_rounds * 2:] older history[:-self.max_history_rounds * 2] if len(history) self.max_history_rounds * 2 else [] # 检查是否需要压缩摘要 if older and estimate_tokens(older) self.compact_threshold: summary self._summarize(older) # 用LLM生成精简摘要 else: summary older # 未达到阈值就原样保留 messages [{role: system, content: inp.system_prompt}] if summary: messages.append({role: system, content: f早期对话摘要\n{summary}}) for item in recent: messages.append(item) messages.append({role: user, content: inp.query}) return BuildResult(messagesmessages, meta{mode: self.mode_name})注意我用older和recent区分老家底和近期对话避免每轮都对全部历史重复做摘要。摘要生成建议用便宜且快的小模型甚至可以异步执行不让用户等摘要生成完才收到回复。工程上要优先保证响应速度用异步更新摘要来换取体验这是非常实际的一个妥协。4.4 路由模块实现动态混合模式动态混合模式的代码并不复杂关键是策略表class AdaptiveContextBuilder(BaseContextBuilder): mode_name adaptive def __init__(self, routers: dict): # routers: {chat: builder_a, rag: builder_b, tool: builder_c} self.routers routers def build(self, inp: ContextInput) - BuildResult: intent detect_intent(inp.query, inp.history) builder self.routers.get(intent, self.routers[chat]) return builder.build(inp)detect_intent可以用规则也可以是一个更小、更快的模型。我建议先用规则跑通再慢慢往模型迁移。规则的好处是行为可预期、出问题容易定位模型的好处是能覆盖更复杂的表达。不要一上来就上模型先把链路跑稳。4.5 关键参数速查表我把项目里用过的参数经验整理成一张表不是标准答案但可以作为初值参考参数含义经验值/策略chunk_size文档切块长度500-800字符chunk_overlap块重叠长度50-100字符embedding_top_k向量召回候选数6-10rerank_top_n重排后注入数3-5max_history_rounds保留原始消息轮数6-8compact_threshold触发压缩的历史token阈值1500-2500compact_debounce压缩防抖时间5-10分钟system-first系统指令放第一个区块固定建议task-last用户当前问题放最后固定建议这张表能看出一个共性几乎所有关于数量的参数都有合理区间不是越大越好。top 拉到10、历史全留看着信息全实际上模型消化不了还会拖慢响应。做上下文工程很多功夫花在做减法上。5. 常见问题与排查实录框架搭好、参数调完剩下来就是各种真实运行中的问题。下面这些问题都是我和团队实际踩过的按现象-原因-排查方法-解决方案整理方便直接当速查表用。5.1 效果越改越差可能上下文被污染了现象某次改动后模型开始把知识库里的内容当成操作指令来执行或者语气突变、输出格式错乱。原因不同信息源的边界没有区分。最常见的是把检索结果和系统指令直接拼在一个文本块里模型分不清这是规矩还是这是数据。排查把当次请求的完整 messages 导出来逐段看结构特别注意检索文本前后有没有和指令性内容混在一起。解决用结构化分区模式给系统指令、事实数据、历史记录各自独立的区块标签同时检索结果本身不要出现你必须你应当这类带指令色彩的表达尽量中性陈述事实。这个细节我调了很久才发现检索段落里一个请注意都会让模型误判。5.2 Token 成本飙升一个请求吃掉几千 token现象账单暴涨或者单次请求频繁提示超出窗口上限。原因全量直连模式把历史消息一股脑全带上RAG 模式下 chunk_size 过大、top_n 过大或者每次都做全量摘要。排查输出日志里记录每次请求的 token 数统计平均值和P95定位是哪个环节贡献最多。建议从第一天接日志就顺手把这个字段加上别等账单爆炸了才想起来。解决历史消息设置保留轮数超出部分走折叠压缩检索片段控制在3-5个摘要触发加防抖超长文档做分层摘要而不是全文进上下文。5.3 检索结果命中但模型不用现象知识库里明明有答案检索也把片段取回了但模型视而不见反而自己编了一个答案。原因大部分情况是注入位置和格式的问题。检索结果放在太靠后的位置模型注意力已经疲软或者检索片段格式混乱模型没有意识到这是可信资料。排查生成时开启流式输出观察模型是从哪一段开始走神的同时检查检索结果有没有来源标注和明确区块边界。解决把事实数据区放到系统指令之后、对话历史之前给每个片段标注来源在系统指令里明确写请优先依据资料片段回答资料中没有相关信息时明确说明而不是根据知识库回答这种含糊表述。5.4 多轮对话越聊越失忆现象对话进行到20轮之后模型忘了用户最开始提到的关键约束比如价格不能超过500元。原因折叠压缩模式把早期原文替换成了摘要但摘要过于泛化没有保留关键约束条件。生成摘要的模型会下意识提炼大意而大意通常会丢掉数字和限制词。排查核对生成的摘要内容看关键约束是否落进去了。如果没有就是摘要提示词和保留策略的问题。解决在摘要生成时维护一个关键信息清单。把用户的明确约束、订单号、偏好这类高价值信息单独记忆不参与压缩丢弃。摘要提示词里点明请保留所有数字、日期、否定条件、用户明确的限制性表述。哪怕摘要稍微长一点也比丢失关键约束强。5.5 延迟过高用户等得不耐烦现象整个链路平均耗时超过3秒其中上下文构建环节占了大头。原因向量检索、重排、摘要生成全部串行执行累加时延或者检索链路本身没有做缓存。排查给每一段链路打点计时从用户进入到你拿到模型首 token拆出检索、重排、拼装、模型调用各阶段耗时。解决判断 query 是否命中缓存比如完全相同的近义词查询命中就直接返回上次的上下文构建结果省掉检索路由模块把闲聊类请求直接拨到纯对话模式重排和检索可以用并发摘要生成异步化不让用户等。这几个优化叠加能把 P95 延迟从三秒压到一点二秒左右。5.6 我的个人排查工具箱最后分享几个平时调试上下文特别有用的习惯打印上下文快照每一条系统级消息都记录下来存成只读日志后面可以完整回放当时的上下文出任何问题都能复盘给每个片段打标签来源、检索分数、重排分数、注入顺序。一旦效果出问题能快速定位是召回烂、排序烂还是拼装烂用消融实验定位变量固定模型和窗口分别测试去掉重排、去掉来源标注、去掉历史轮数限制观察效果变化。几次消融下来你就知道自己项目里哪个环节最值得投入优化而不是每次都面对一整套黑盒瞎猜。6. 写在最后的个人心得这篇文章写到这里我其实最想说的不是某一个具体模式有多好而是上下文模式这个词背后反映出的趋势大模型应用开发已经从堆提示词的阶段进入到了精细化管理输入的阶段。模型的能力是底座而上下文是你和模型之间的临时工作记忆。你怎么管理这份记忆决定了这个模型在你的具体问题上能发挥出多少水平。我个人在实际项目中的体会是不要一上来就追求部署一套什么完美架构先把请求日志和上下文观测配齐再多做几组消融实验。你可能会发现很多效果问题不是模型不够强而是上下文没喂对。与其不停换模型、买更贵的 API不如先把 RAG 的检索质量、分区的规范性、压缩的保留策略打磨扎实往往花小钱办大事。最后分享一个小技巧在设计 Context Mode 的时候一定给自己的系统留一条降级链。我见过很多团队因为执着于复杂链路外部服务一抖动整个系统就挂掉。一个健壮的做法是检索不可用时自动转为纯对话模式向量库超时后直接走关键词搜索重排模型太慢就退回到向量原始顺序。链路可以复杂但故障时要有能兜底的最简路径。这个思路我在多个项目里反复验证过带来的稳定性收益远超想象。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑