ARTICLE DETAIL

资讯详情

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

大模型上下文管理模式:从全量到摘要+检索的工程实践

大模型上下文管理模式:从全量到摘要+检索的工程实践 做AI应用这一年多我踩过最深的坑几乎都跟“上下文”这三个字有关。最典型的一次是我们把一个客服问答机器人的历史消息全部塞进prompt里结果模型答是能答但账单涨得比业务量还快用户多问几轮之后回答质量反而明显下滑前言不搭后语的情况越来越多。后来我把问题拆开才发现根源不在模型本身而在我们压根没有想清楚该用哪种context-mode——也就是上下文管理模式。这个话题在近期做LLM应用开发的圈子里讨论得特别多值得好好捋一遍因为它直接决定你的产品是“真聪明”还是“看起来聪明”也直接决定你每个月要为Token付多少钱。这篇文章我打算用自己实际参与的一个多轮问答项目做线索把context-mode的几个基本维度、主流方案的工程实现、各自的坑和选型思路完整讲一遍。如果你是正在做AI应用开发、或者准备把大模型接进自己业务里的同学这篇应该能帮你少走不少弯路。1. 先搞清楚上下文模式到底在解决什么问题1.1 一个“失忆”案例的完整现场当时我们的客服机器人接的是主流商用大模型最开始实现得很天真用户每发一句话我们就把这通对话从第一条开始的所有消息再加上系统提示词一股脑全发给模型。最初的几十轮测试里效果还不错模型能记住用户在前面提到的产品型号、发票要求、收货地址。但跑到第20轮以上问题开始冒头——模型开始重复问已经给过的信息有时候甚至会把A用户在某句话里提过的偏好安到B用户的头上。一开始我以为是模型能力问题换成参数更大的版本情况有所缓解但推理延迟上去了单次调用成本也翻了不止一倍。后来我仔细翻请求日志才发现每次发给模型的内容里包含大量早就失去时效的对话片段比如用户中途改过地址、取消过订单但旧地址依然占据上下文里的重要位置模型自然容易被带偏。这个现象业内有个通俗说法叫上下文污染本质上就是没有用正确的context-mode去组织信息。1.2 大模型本身没有记忆只有上下文要理解context-mode首先得接受一个事实:大模型没有真正的记忆。它只是在每次收到请求时把你给它的全部文本当作“临时工作台”在这个工作台上完成推理并输出。工作台有多大你能放多少内容进去工作台里摆了什么直接决定模型“记得”什么。这个机制有点像程序员临时开的一个终端会话。你把历史命令放在终端里终端就能基于这些命令继续执行你开一个新终端之前的操作就全没了。大模型的上下文窗口就是那个终端的缓冲行数。窗口越大能带上的历史越多但并不意味着效果一定更好——窗口再大也装不下无限信息而且模型在超长上下文里提取关键信息的能力不是线性保持的。1.3 成本与效果的双重约束上下文管理模式的选择本质上是在三个变量之间找平衡效果、成本、延迟。Token是计费的把几千轮历史全塞给模型每轮都在为那些无用的旧内容付费Token多到一定程度模型处理时间变长用户等得烦躁上下文过长时注意力机制还会“稀释”模型反而容易忽略真正关键的最新指令。我实测过一组数据同样一个多轮任务上下文从2K Token增加到20K Token首字延迟大概会从0.8秒涨到2秒以上单次调用的费用则是数量级地增长。所以context-mode真正解决的问题可以概括成一句话在有限的上下文预算里用合理的策略组织信息让模型在需要的时候拿到最该拿到的历史同时别让成本和延迟失控。2. 窗口、策略和状态context-mode的三个核心维度2.1 窗口能装多少历史窗口是最容易理解的一个维度就是模型一次请求能接收的最大Token数量。商用模型的窗口从32K到200K不等更大窗口的模型通常单价也更高。强上更大的窗口能解决一部分问题比如塞下更长的资料但真做生产环境时你会发现窗口大不等于你可以随便塞因为注意力的有效范围其实有限的超长上下文的“大海捞针”测试很多模型成绩并没有那么理想。我个人的经验是窗口大小应当看成一条“预算上限”而不是“默认使用量”。规划上下文管理模式时先算清楚一次典型请求需要多少历史信息再反推需要的窗口规格而不是反过来为了用满窗口而堆料。2.2 策略怎么决定哪些历史保留策略是context-mode里最值得花心思的部分。简单粗暴的全量保留只是最基础的一种更常见的是下面这几种策略滑动窗口只保留最近N条消息更早的丢弃。截断合并按Token上限截断必要时把旧消息压缩成一句话摘要。摘要分层定期为早于某个时间点的对话生成摘要摘要本身占据少量Token细节下沉到存储层。检索注入先把全部历史库化每轮请求根据当前问题做语义检索只把命中的片段放进上下文。这些策略不是互斥的生产环境里经常混用。比如先做滑动窗口限流再对窗口外的内容做摘要同时对摘要也做上限控制超出上限的部分就交给向量检索来处理。设计策略的核心是回答三个问题哪些信息必须保留哪些信息可以压缩哪些信息可以彻底丢掉。2.3 状态如何提供“记忆一致”的体验第三个维度容易被忽略状态。用户感知里的“记忆”其实是开发者通过上下文管理制造出来的“拟态记忆”。要把这种拟态做得连贯你得管理好几个状态层——对话ID对应的消息时序、用户画像或偏好的固化信息、业务数据比如订单状态、售后进度的实时查询结果。我在项目里专门建立了一个状态对象每次构造请求时把固定信息系统提示、用户档案摘要放在最前把实时数据放在中间把最新的用户意图放在最后。这样模型读取时不会因为顺序混乱而误解优先级。很多早期AI应用失效其实不是模型变笨了而是状态层没做好该固化的用户偏好被当成普通聊天内容挤掉了。3. 实测对比三种主流上下文模式的工程实现3.1 全量模式简单直接但钱花得心疼全量模式也就是把所有对话历史连同系统提示全部发给模型。好处是开发效率极高几个小时就能跑通适合原型验证和低并发内部工具。我一开始的客服机器人就是这么做的。坏处也很明显。首先是成本按每1000Token的输入单价计算假设一次请求带20K历史Token一个月十万次请求光输入费用就够买好几台云服务器了。其次是质量历史越多不相关的内容越容易干扰模型。就我在客服场景里的效果看全量模式在消息超过30轮之后回答一致性明显下降模型会开始“选择困难”——同时看到新旧地址、新旧偏好它不知道听哪个。所以这个模式我现在的判断是演示可以用生产要慎重除非你的业务本身就是极短对话为主。3.2 滑动窗口模式省预算但容易“断片”滑动窗口的实现很简单只保留最近N轮消息。N可以按消息条数算也可以按Token数算。相比全量模式成本一下子降下来了请求体积稳定延迟也可控。但它有一个致命短板模型会“断片”。用户在第5轮提过一个重要约束到第16轮窗口已经把第5轮滑出去了模型就彻底忘了这个约束。客服场景里尤其明显用户可能在第3轮就说了“我一定要在周五前收到”之后聊了很久其他问题到第12轮突然问“那我那个能到吗”模型根本不知道“那个”是什么。滑动窗口适合那些“只依赖最近几轮信息”的场景比如单轮问答、轻度闲聊但多轮复杂任务光靠它根本不够。3.3 摘要检索混合模式适合生产环境的折中方案我最后落地的方案是摘要检索混合模式。思路不复杂把所有历史消息入库最近N轮完整保留在上下文中更早的消息经过摘要模型压缩成“早期对话摘要”如果摘要也超过上限就再把摘要按段落切块做向量化需要时检索注入。这个模式的好处是三点第一关键信息不会因为滑动窗口被彻底丢弃第二上下文体积被压在可控范围内成本和延迟都能接受第三检索只取最相关的片段规避了无关信息干扰。坏处是实现复杂度明显上升你得维护消息库、摘要任务、向量索引还得多调一次相似度阈值。但放到生产环境里这套组合的稳定性远远好于前两种模式。我把自己实测的一组对比数据放出来给大家一个直观感受。同一个客服项目跑50轮对话三种模式的差异是这样模式单次请求平均Token数回答一致性回答精确召回率单次调用成本相对实现复杂度全量模式18000中约60%高低滑动窗口4000低约45%低低摘要检索混合5500高约83%中中高一致性说的是“模型是否前后矛盾”精确召回率是我拿测试集里“模型能否正确引用用户早期提到的关键信息”来算的。数字不是绝对标准但趋势很明确混合模式在成本只比滑动窗口高一点的情况下效果远超另外两种。4. 我的选型过程把context-mode用在一个客服问答项目上4.1 项目背景和限制条件这个项目是一个电商售后客服机器人用户会咨询物流、退换货、发票、优惠券等问题。业务上最痛的一点是用户经常分开好几轮补充信息比如第1轮说订单号第3轮才说要退货的原因第8轮问退货地址。模型如果没有长期记忆就得让用户反复重复体验非常糟糕。限制条件有三条单次调用成本不能比原来的人工客服高太多回答延迟控制在3秒内支持至少40轮的长会话。这三条直接排除了全量模式也排除了单纯的大窗口硬扛。4.2 分阶段实测记录三组数据我分了三个阶段来推进第一阶段用全量模式跑通功能记录效果基线第二阶段改滑动窗口观察成本和质量的真实差距第三阶段才上摘要检索。每改一次我都用一个包含300组对话的测试集去跑记录三个数回答准确率、单次请求Token中位数、P95延迟。第一阶段的准确率大概72%Token中位数高达19000P95延迟接近4秒超出限制第二阶段Token中位数降到4500延迟降到1.5秒但准确率掉到了61%因为模型老忘记用户早期提的条件第三阶段准确率回到82%左右Token中位数6200延迟1.8秒成本处于可接受区间。数据出来之后答案就很明确了。4.3 实现“摘要检索”模式的关键代码思路下面贴一段我当时的核心实现思路用Python伪代码风格方便你理解整个流程。实际工程里还会加缓存、队列之类的东西但主干逻辑就这些def build_context(messages, user_profile, top_k3): # 1. 最新若干轮消息完整保留 recent messages[-6:] # 2. 更早的消息全部入库生成/更新摘要 old_messages messages[:-6] summary summarize_if_needed(old_messages) # 3. 如果摘要过长做向量化检索 if token_count(summary) 1500: chunks split_chunks(summary) vectors embed(chunks) hits search_vectors(vectors, queryrecent[-1].text, top_ktop_k) history_part \n.join(hits) else: history_part summary # 4. 组装最终上下文 context_parts [ system_prompt, f[用户档案] {format_profile(user_profile)}, f[历史摘要] {history_part}, f[最近对话] {format_conversation(recent)}, ] return \n\n.join(context_parts)有几个细节我想重点说明一下。recent保留最近6轮这个数字不是拍脑袋定的是根据测试集跑出来的——6轮以内用户最新意图基本完整再多保留就会显著推高Token成本。summarize_if_needed也不是每轮都调用只在旧消息累积超过一定阈值时触发一次因为摘要模型本身也是成本。threshold1500的用意是让历史摘要始终小于最近对话的体积确保模型的注意力中心始终落在最新交互上。4.4 参数调优过程中的教训调参过程中的第一个教训是摘要时机不能太频繁。我最初设计成每5轮就做一次摘要结果摘要任务频繁触发单轮请求的Token数反而比全量模式还高因为摘要模型的输出被反复写进上下文形成了一种“自我重复”。后来改成“每当旧消息超过10条才把最旧的5条合并进摘要”请求体积才真正降下来。第二个教训是检索的相似度阈值要跟着业务调。客户咨询里大量出现“订单”“退款”这类高频词如果阈值设得太低检索出来的是泛泛而谈的通用信息不是具体的那一单。我把阈值从0.7提到0.82再配合按用户ID过滤候选池检索命中率从51%提升到74%。第三个教训关于用户档案的格式。这类固定信息不能一股脑全塞我们维护了一个极简结构用户ID、VIP等级、常用收货地址、历史退换货次数、最近一次投诉内容摘要。这些字段是强业务相关的比让模型从对话里自己“领悟”用户偏好靠谱得多。5. 踩过的坑上下文泄露、截断边界与记忆串扰5.1 上下文泄露用户隐私数据被带进prompt第一次做全量模式的时候我犯过一个现在想起来背脊发凉的错用户对话里的电话号码、身份证号、完整家庭住址全都原样进了模型的上下文。虽然商用模型的API都有数据加密和隐私保护条款但从架构上说没有谁会希望把敏感信息无差别地抛给大模型处理。排查链路是这样的一个用户在测试群里反馈“为什么模型知道我上周的体检报告内容”我们查了日志发现是另一个用户在某次对话里发过体检报告的文字版而那次对话的全文被当成上下文喂给了同一套模型配置。这属于典型的跨会话污染本质上是上下文池没有按用户隔离。从那以后我在所有prompt组装之前加了一层脱敏过滤凡是匹配手机号、身份证、地址等正则规则的内容一律替换成占位符。敏感字段走单独的字段映射查询不回灌上下文。5.2 截断边界按字符硬截断导致语义断裂滑动窗口阶段我又踩了一个坑实现的时候为了省事按固定字符数从前面截断旧消息。结果经常出现一句话被砍掉一半的情况比如“用户要求寄到新地址上海市…”后面没了模型把这个残缺信息当作完整地址去回答问题很严重。正确的做法是按“完整消息条目”截断要么保留某一条消息的全文要么整条丢掉绝不从中间切开。这背后是Token化本身的逻辑——模型是按完整词元理解文本的半句话在模型眼里就是残缺的噪声。随后我又加了一步截断后检测最后一条消息是否以“”“,”“.”这类高概率“未完待续”的特征结尾如果是就回退再多丢一条。这个小细节让响应质量提升不少。5.3 记忆串扰不同会话共用了同一个上下文池还有一次印象深刻的踩坑发生在做向量检索时。我的检索逻辑一开始没有按会话隔离直接在整个向量库里搜历史消息。结果是用户A问“上次那个怎么处理了”系统从库里检索到了用户B的退款记录因为语义相似度很高。这造成的“记忆串扰”比模型幻觉更隐蔽因为回答读起来很流畅只是完全答非所问。排查过程我复盘一下给碰到相似问题的朋友做个参考第一步看召回片段里的消息ID是否属于当前会话第二步发现片段五花八门之后在召回SQL里加WHERE conversation_id ?准确率立刻回升第三步再进一步在向量索引的元数据里写入conversation_id检索时同时过滤。最终把召回命中率稳定在了85%以上。5.4 排查链路复盘一次上下文质量问题的完整定位思路这里分享一套我自己总结的通用排错顺序遇到上下文相关问题别急着怀疑模型先走这个链路第一步复现问题并打印出实际发给模型的上下文全文肉眼检查有没有明显错位。第二步定位是“缺信息”还是“错信息”。缺信息多半是滑动窗口丢历史或截断策略太激进错信息则大概率是数据隔离没做好或检索召回不精准。第三步看Token构成。在日志里给每一段上下文系统提示、摘要、检索片段、最近对话分别打上标记统计各自占了多少Token。如果摘要占了60%以上说明摘要时机太频繁或摘要不够凝练。第四步做对比实验。固定模型不变只改context-mode的一处参数比如保留轮数、阈值连续跑20组测试用准确率判断改动方向是否有效。这套排错流程帮我解决过至少五个线上问题每次排查时间都能控制在半天以内。6. 别追热点按业务选模式一份实用判断清单6.1 六类业务场景与推荐模式每个团队的情况不一样别人说好用的方案照搬到你的业务里可能水土不服。我整理了一份粗粒度的判断清单覆盖常见的几类LLM应用形态业务形态对话特征推荐模式理由单轮知识问答一问一答为主无上下文或轻量滑动窗口不需要历史省成本最重要通用聊天机器人闲聊、轻度多轮滑动窗口保持轻快无需深度记忆客服售后多轮补充信息、早期约束重要摘要检索混合关键信息不能丢成本可控数据分析助手依赖当前文件和历史操作全量摘要混合文件原文必须完整操作历史可摘要角色扮演/写作助手高度依赖人设与风格一致性固定人设段全量或长摘要人设信息应作为固化状态长期保留长文档处理单次输入文档很长大窗口分段检索窗口扛主体检索补细节这个表格的通用性有限但能给你一个起点。核心思路是先看业务对“早期信息”的依赖程度有多高。依赖越高越要往检索/摘要方向走依赖越低越可以用简单的截断策略。拿客服来说用户早期提到“不要发邮政”到第20轮问“寄出了吗”模型必须知道早期这个约束所以摘要检索是刚需。而一个纯粹的旅游攻略问答机器人用户前后问的问题彼此独立滑动窗口完全够用没必要上复杂架构。6.2 三个“不折腾”原则最后说说我踩过无数次坑后总结出来的三个原则。原则一优先简单。能用滑动窗口解决的绝不上摘要检索。每一层额外机制都意味着新的失败模式比如摘要模型质量问题、向量召回漂移、两级缓存不一致。只有在效果测试明确证明简单方案不够时才升级复杂度。原则二用数据说话别凭感觉。每次调整context-mode参数都得有一套可复现的评测集。我习惯固定30~50个典型长对话跑完后人工打分同时记录Token、延迟数据。任何改动上生产前必须在这套评测集上看到正向收益否则宁可保持现状。原则三设计成可切换的。不要把某种context-mode写死在代码里做一个配置开关全量、滑动窗口、混合模式都保留允许线上快速切换。因为业务是变化的用户行为也会变今天适合的模式三个月后未必还适合。我们后来因为业务线调整把主要模式从“摘要检索”切换成了“用户档案滑动窗口”因为新业务的对话轮次很浅靠配置开关几分钟就切换完了省掉了一次重构。我自己在实际项目里的体会是context-mode不是一个“调一次就完事”的参数而是需要持续观察、持续调整的活。你投进去的精力最终会体现在用户的复购和留存上——毕竟没人愿意和一个每次都要重新自我介绍的人工智障聊天。把你手头业务的对话跑起来把日志里的请求体打出来亲眼看一看模型每次到底“看到了什么”你会比读一百篇教程都更明白自己该怎么选。
返回列表