ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

大模型上下文管理:context-mode 设计与实践指南

大模型上下文管理:context-mode 设计与实践指南 最近在做一个客服机器人的迭代发现一个很典型的问题模型回答越来越“飘”。用户明明在第一轮说过“我是老客户上次买的空气炸锅有噪音问题”聊到第八轮问“那维修费怎么算”模型居然一本正经地给出了完全不符合售后政策的答案。查日志才发现根本不是模型不行而是我们的上下文处理太粗暴——所有历史消息一股脑往请求里塞塞不下就截断早期的关键信息全被挤掉了。后来我把这个问题的核心收敛到了一个词上“context-mode”。在 AI 应用开发里它指的是我们用什么方式组织、取舍、注入对话上下文——每次请求把哪些历史信息交给模型、以什么结构交、保留多久。它直接影响回答质量、算力成本和响应速度。这篇文章我就把我在实际项目里用到的 context-mode 设计思路、落地细节和避坑经验整理出来适合正在做对话机器人、智能助手、Agent 应用的同学参考。1. context-mode 是什么决定质量与成本的“隐形开关”1.1 从一次“模型失忆”事故说起先讲清楚我为什么会专门研究这个东西。当时我们的售后客服助手已经上线了一段时间用户反馈集中在同一个问题上“怎么聊着聊着模型就不记得我之前说过什么了”。一开始我怀疑是模型上下文窗口不够大。结果把日志翻出来一看发现每次请求的 payload 里历史消息已经塞了六千多字而模型的上下文窗口是 128k根本不存在“装不下”的问题。真正的问题在于这六千多字里70% 是寒暄、重复确认、无意义的过渡句真正有用的信息——订单号、产品型号、用户的投诉记录——被淹没在一大堆噪音里模型反而抓不住重点。那时候我才意识到一个关键结论上下文的质量比上下文的长度更重要。而 context-mode 要解决的正是“怎么把上下文组织好”这件事。1.2 我给 context-mode 下的一个可操作定义这个术语在不同的框架、不同的文章里指的东西不完全一样。有人的意思是“模型处理上下文的方式”有人的意思是“上下文的存储结构”。我做项目时把它归一成了四个维度方便团队对齐范围Scope这次请求带多少历史、带哪些消息。是只带当前一轮还是带最近 N 轮还是带全量会话。结构Structure历史信息是按原始对话一条条给还是先压缩成摘要或者提取成结构化的关键字段。来源Source上下文只依赖当前会话内的信息还是需要从知识库、业务数据库里检索相关片段拼进来。生命周期Lifecycle上下文在多轮之间如何更新什么时候清空什么时候触发压缩。这四个维度组合出来的典型模式有单轮模式、多轮全量模式、滑动窗口模式、摘要模式、检索增强模式、分层记忆模式。后面我会逐个拆开讲。1.3 用“工作记忆”理解上下文的价值打个比方上下文之于大模型就像工作记忆之于人。你不可能记住过去所有同事说过的每一句话但你记得住关键结论、关键承诺、关键数字。context-mode 就是在帮模型做这件事。很多人调 prompt 调得特别认真指令写得滴水不漏但上下文没管理好前面写再多优质指令也会被淹没在混乱的历史消息里。上下文是模型的“工作记忆”它的组织形式直接决定四件事回答准确性该用到的信息有没有被带进来、有没有被冲淡成本每次请求的 token 消耗直接跟上下文长度挂钩延迟输入越长首字响应越慢用户体感越差稳定性上下文越乱越容易出现幻觉、重复回答、前后矛盾。后面我会结合具体的数据和代码讲清楚每一层的关系。2. 核心模式拆解从单轮到分层记忆的设计逻辑2.1 单轮模式与多轮全量模式什么时候用哪个单轮模式最简单每次请求只带当前用户输入不带任何历史。适合纯咨询类、翻译类、一次性问答。比如用户问“这个产品支持 220V 电压吗”模型只需要回答当前问题不需要知道用户上一句说了什么。这个模式的好处是 token 消耗最小、响应最快、不会出现上下文污染。代价也很明显对话一多轮就会“失忆”。我有一条经验如果你判断用户 80% 的场景是一条消息能说清的用单轮模式省钱效果又好一旦发现用户开始说“那刚才那个呢”“我都说过了呀”就是该引入多轮模式的时候了。多轮全量模式把整个会话历史原样塞给模型。这个方案最直观但问题非常实际——token 消耗随时间线性增长而且长历史里大部分是无效内容。举个例子假设每轮对话平均 400 token中英文混合场景很常见10 轮就是 4000 token20 轮就是 8000。按市面上常见模型的价格算一个日请求一万次的对话应用光上下文成本就可能远超预算。更要命的是上下文超过一定长度后模型对早期信息的注意力会显著下降——你付了 token却不一定换来记忆。所以多轮全量模式基本只适合会话短、轮次少、对细节要求高的调试场景不太适合直接上生产。2.2 滑动窗口模式最简单有效的第一步改进滑动窗口就是“保留最近 N 轮更早的丢弃”。比如设置 6 轮那么第 7 轮请求时只带第 2 到第 7 轮的内容。这个方案实现简单、成本可控是在不做摘要的情况下最划算的改进。但它的缺点也很明显丢信息非常粗暴。用户在第 1 轮说“我是铂金会员订单号 20241001”第 8 轮问“我的订单到哪了”模型看到的历史里根本没有这个信息它就不可能知道用户是铂金会员。所以窗口 N 值的设定不能拍脑袋。我常用的做法是把会话按“信息密度”分块把用户主动提供过的实体信息订单号、型号、地址、会员等级提取出来单独存成一个“会话关键字段”和最近 N 轮一起放进上下文。这就是从滑动窗口往摘要模式过渡的第一步。2.3 摘要模式用空间换记忆的经典方案摘要模式的核心是定期把旧对话“浓缩”成一段摘要放到上下文最前面后续请求携带的格式类似这样[会话摘要] 用户购买了 A 型号空气炸锅订单号 SO-20241001-001下单日期 2024-10-01。 反馈加热时噪音偏大已预约维修维修单号 R-5678保修期内免上门费。 [最近对话] 用户请问维修大概什么时候上门 助手已为您查询维修师傅预计明天 14:00-16:00 上门请保持电话畅通。摘要一般也是用模型生成的每隔 N 轮调用一次把“旧摘要 新增对话”压缩成“新摘要”。这个过程相当于让模型帮你做了信息提炼。但注意摘要模式有个常见误区很多人以为它一定省 token。实际上不一定。如果每两轮就做一次摘要摘要本身的 token 开销加上历史开销反而可能比全量多轮还贵。我实践下来的合理策略是会话长度超过阈值比如 3000 token再做摘要摘要保留最近 10 轮作为“原始片段”其余全部压进摘要。2.4 检索增强模式把上下文从“背下来”改成“查得到”对话场景里经常要引用知识库内容比如产品手册、售后政策、操作视频链接。全塞进上下文不现实上下文窗口再大也塞不下整个知识库。于是就有了检索增强模式每次请求时先把用户当前输入拿去检索召回最相关的几段文档再和对话历史一起交给模型。这个模式要重点解决两个问题检索什么不是检索整个历史消息而是检索知识库、FAQ、订单数据结构化信息。检索源要跟“会话内记忆”分开各管各的。怎么融合检索出来的片段放在上下文的哪个位置。我的经验是放在用户消息之前、历史摘要之后并且加一个明确的分隔说明。检索增强模式和摘要模式不冲突它们解决的是不同维度的信息摘要管“会话内记忆”检索管“会话外知识”。在知识密集型的场景里两者通常会一起用。2.5 分层记忆模式近期窗口加长期摘要加业务知识这就是我最终在项目里采用的框架我叫它“三级上下文”L1 近期对话窗口最近 4-6 轮原始消息保证细节保真L2 会话摘要更早的内容压缩成结构化摘要含关键实体和已办事项L3 业务相关知识从知识库检索到的资料、用户画像、订单信息。拼装顺序固定为L3业务资料→ L2会话摘要→ L1近期对话→ 当前用户输入。这个顺序是有讲究的先让模型了解“背景知识”再了解“历史脉络”最后看到“当前问题”信息损失最小。如果顺序反了模型可能还没读到业务资料就把注意力聚焦在当前问题上导致后续资料被忽略。分层记忆的好处在于每一层的大小、更新频率、来源可以分别控制出了问题时也容易定位——到底是检索没召回还是摘要丢了信息还是近期窗口太小。查起来比一锅粥的状态省力太多。3. 实操落地给项目接上 context-mode 的完整步骤3.1 第一步明确业务场景与上下文边界先别写代码先回答几个问题用户会在一个会话里完成多步操作吗如果会多轮是刚需哪些信息必须跨轮保留列成清单比如订单号、用户 id、选中的型号哪些信息可以安全丢弃寒暄、重复追问、已完结环节的细节会话的合理长度是多少一般客服场景 8-12 轮就该能收尾。我建议把这些答案写进文档不要只记在脑子里。后面调参、排查问题这份文档就是你的对照基准。3.2 第二步选定模式组合与切换策略根据第一步的答案选模式纯工具类、单轮咨询单轮模式 关键字段会话存储大多数客服场景滑动窗口 实体摘要强知识依赖场景售前咨询、医疗咨询滑动窗口 摘要 检索增强调试、内测多轮全量模式。切换策略也很重要不是整个会话绑死一种模式。我现在的实现里会话前 4 轮用滑动窗口超过 4 轮且 token 超过阈值自动切换为“摘要 窗口”。这个动态切换比一刀切更符合真实对话节奏。3.3 第三步理解 token 与模型参数的关系这部分很容易被忽略但直接影响成本。先讲 token 估算。以中文为例大约 1 个汉字约等于 1-2 个 token不同模型有差异。最靠谱的方法是把一段真实对话丢进官方 tokenizer 去数不要凭感觉。每轮请求的 token 消耗可以这样估算总输入 token ≈ 系统提示词 上下文内容 当前用户输入 总输出 token ≈ max_tokens你给模型预留的输出上限 单次计费 token ≈ 总输入 token 总输出 token举个例子系统提示词 500 token上下文摘要 历史1500 token用户输入 200 tokenmax_tokens 设 800那么单次请求大约是 3000 token。如果日请求 10 万次就是 3 亿 token/天。上下文每省 500 token一个月下来成本差异都是肉眼可见的。max_tokens 别乱设。客服场景 300-800 通常够用生成类任务才需要 1024 以上。设得越大单次成本越高模型也更容易开始“废话”。3.4 第四步实现核心的上下文管理器我写过一个比较精简的上下文管理器核心逻辑是维护一个会话对象里面有三个区域近期窗口、摘要、相关资料每次发送前根据当前模式拼装消息。下面给出可以直接参考的示例伪代码风格换成你自己的 SDK 即可class ContextManager: def __init__(self, max_window_rounds6, summary_threshold_tokens3000): self.recent_messages [] # L1: 近期对话窗口 self.session_summary # L2: 会话摘要 self.related_docs [] # L3: 检索到的资料 self.max_window_rounds max_window_rounds self.summary_threshold_tokens summary_threshold_tokens def add_user_message(self, text): self.recent_messages.append({role: user, content: text}) def add_assistant_message(self, text): self.recent_messages.append({role: assistant, content: text}) def should_trigger_summary(self, token_counter): return token_counter(self.recent_messages) self.summary_threshold_tokens def build_llm_messages(self, query, retrieve_fnNone): docs retrieve_fn(query) if retrieve_fn else [] summary_block f[会话摘要]\n{self.session_summary} if self.session_summary else recent_block self.recent_messages[-self.max_window_rounds * 2:] messages [] if docs: messages.append({role: system, content: f[相关资料]\n{docs}}) if summary_block: messages.append({role: system, content: summary_block}) messages.extend(recent_block) messages.append({role: user, content: query}) return messages def update_summary(self, llm_fn): prompt ( 请把以下对话信息压缩为结构化摘要 保留关键实体订单号、产品型号、地址、会员等级和已办事项\n\n f旧摘要{self.session_summary}\n\n新对话{self.recent_messages} ) self.session_summary llm_fn(prompt) self.recent_messages []这段代码不是让你直接抄重点看两个思路拼装顺序用列表控制每一层都是独立的调试时能直接打印 messages 检查摘要触发是独立的函数你可以先手动调用调试再改成自动触发。3.5 第五步加日志、埋点与成本观测上线之前一定要有可观测性不然出了问题你根本不知道是上下文策略的锅还是模型本身的问题。我在项目里会记录这几个指标每次请求的输入 token、输出 token、总 token上下文中摘要在输入里的占比、历史占比、检索资料占比平均响应首字延迟用户主动说“你听不懂”“不是这个意思”“换个问题”的次数人工打标后分析。这些数据能帮你判断上下文是不是还在膨胀、摘要有没有及时触发、检索相关度是不是在下降。没有这些数据后续优化基本靠猜。4. 常见问题与排查技巧实录4.1 症状对照速查表症状可能原因优先检查项模型忘记用户早期说的关键信息滑动窗口太小或摘要丢字段查看 L1 窗口轮数、摘要内容是否包含关键实体回答风格突变、前言不搭后语上下文拼装顺序不对摘要被截断检查 build_llm_messages 里消息顺序token 消耗异常增长摘要触发阈值设太高、窗口过大看埋点里历史占比是否越来越大模型胡编乱造、引用不存在的信息检索召回的资料不相关或缺失单独测检索模块看召回 top-5 准确率响应速度明显变慢输入 token 过大或检索耗时过长对比不同上下文长度下的首字延迟4.2 三个高频问题的手把手排查第一个模型忘事。我遇到过的场景是用户第六轮问“那维修费谁出”模型答非所问因为前两轮用户已经说过“机器还在保修期内”但窗口只保留了最近 4 轮。排查方法很简单把 build_llm_messages 的输出直接打印出来人工读一遍马上就能发现关键信息没被带进上下文。解决方案是把“保修期”这类关键字段提取到摘要里并且保证摘要始终在上下文最前面。第二个token 暴涨。一开始我只用了滑动窗口没有摘要。用户会话超过 20 轮时每次请求要带最近 10 轮全部内容token 涨到 6000 多成本涨了 3 倍。后面加了摘要并把摘要触发阈值设为 3000 token。实测下来单次输入 token 从 6000 降到 2500 左右回答质量没有明显下降。第三个检索增强反而带偏模型。接入知识库检索后模型总引用不相关的产品说明。一查发现检索的 top-5 里只有第一条相关后面四条是噪音。解决方法是把召回数量从 5 改成 3并且给检索模块加了相关度阈值低于阈值的直接不返回。模型引用噪音的概率明显下降。4.3 从项目里总结的避坑清单摘要别全丢原始上下文。至少往后保留 2-4 轮因为模型总结也可能漏信息在关键操作节点如下单、退款、预约前保留原始记录。关键字段永远结构化存储。订单号、地址、型号这些别只存在于模型生成的摘要里要用独立字段存否则摘要压缩一次就可能丢。别把检索资料放在 system prompt 最前面。有些模型会把 system prompt 当成强指令检索资料混进去可能导致模型以为“必须引用所有资料”。我习惯用单独的 system 消息承载并写清楚“以下是参考资料可参考但不必须全部引用”。切换模式要做灰度。上下文策略直接影响线上体验别一次性把所有用户切到新模式先用 5%-10% 流量验证再逐步放开。max_tokens 按业务设置。客服场景 400 就够别用默认的 1024成本差很多。5. 一点项目体会做 context-mode 这个方向最有价值的不是我上面写的某一套具体参数而是“把上下文当成一等公民来设计”的意识。模型能力再强喂给它的工作记忆是乱的输出就不可能稳定。我在实际迭代里最大的体会是上下文策略没有银弹只有适合你业务的那一种。你的业务如果一轮对话就完事别迷信多轮你的业务如果天然依赖历史信息就必须认真设计摘要和检索。关键是先想清楚边界再定模式最后才是写代码。另外一个小建议每次调整上下文策略后保留至少一周的线上日志对比调整前后的关键指标token、延迟、用户消极反馈率。这些数据比你感觉“好像变好了”要可靠得多。最后再说一个细节摘要的更新最好放在用户消息进来之后、构建请求之前做。这样既能保证摘要用到最新信息又不会多占一次模型调用——如果这一轮不需要触发摘要就完全不碰摘要逻辑。这个安排是我踩了几次坑之后摸索出来的能省掉不少没必要的开销。
返回列表