ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

context-mode实战:解决大模型对话失忆的上下文管理策略

context-mode实战:解决大模型对话失忆的上下文管理策略 我最早被“失忆”坑到是在做一个客服机器人项目。线上跑了两个月前20轮对话一切正常到第35轮左右机器人突然开始一本正经地编造订单状态甚至把A用户的数据安到B用户头上。我第一反应是模型能力不行差点去换底座大模型。后来把请求日志翻出来对比才发现问题根本不在推理而在上下文——发给模型的消息列表里最早的约束已经被挤出了窗口模型压根没“看见”它。从那天起我开始认真研究context-mode也就是上下文管理模式。简单说就是你决定“把哪些内容放进模型这一次请求里”的策略。它解决的核心问题不是模型会不会推理而是模型能不能拿到足够且正确的信息来推理。这篇文章我会把我在实际项目里落地的几种context-mode方案、代码、量化对比和踩过的坑全部写出来希望能帮正在做Chatbot、AI Agent、RAG应用的人少走一点弯路。1. context-mode要解决的是哪个“失忆”问题1.1 一次让我尴尬的项目事故先说前面提到的那个客服机器人。业务方要的是一个能处理“订单查询、退换货、催开发票”的助手我们早期实现非常简单把聊天历史全部塞进prompt丢给模型让它回答。demo阶段一切美好因为大家测试最多聊十轮八轮。但上线后真实用户不会按你的剧本走有人连着聊了五六天同一个会话里反复追问不同订单的状态。然后事故就来了。第30轮之后模型开始“忘记”用户最开始强调的“我是白金会员所有优惠都要按最高折扣算”。更离谱的是有一轮我手动翻日志发现模型把用户A的收货地址当成用户B的来比对。Root cause非常清晰模型一次请求的上下文窗口有限消息列表只能容纳最近的十几轮内容早期关键信息被后面的对话给“挤没”了。这个事故让我意识到做AI应用和做传统后端完全不同。你不只是在写一套接口你是在替模型决定“它应该看什么材料”。这个决策本身就是应用的核心逻辑。1.2 上下文窗口的本质模型并没有“记住”很多人对上下文窗口有个错误理解觉得模型聊得多了就“记得”前面聊了什么。实际上大语言模型的上下文窗口更像是一个临时的“阅读稿”。它只在这一次推理时把窗口里的内容全部读一遍然后生成回复生成完了这次读过的内容就丢了。下次推理你又重新把材料递进去。所以所谓“长记忆”根本不是模型的能力项而是应用层不断把历史内容搬回窗口里。窗口总是有限的GPT-4o一类的模型常见上下文是128K token看着很大但真实业务里塞几个长文档、几十轮历史、一段工具返回结果很快就能吃满。而且窗口越大单次请求的成本和延迟也跟着涨。context-mode的本质就是在这个“有限的阅读稿”里做取舍决策。它需要回答三个问题哪些内容必须进窗口哪些内容可以降级处理哪些内容可以直接丢弃1.3 context-mode的三个核心指标我在项目里给自己定了三个可量化的指标所有模式优化都围绕它们来评估信息保留率模型回答需要的关键实体、约束、指令最终是否还留在上下文里。我一般用一组构造好的“必须知道”测试题来验证比如把用户设置的折扣约束放进第40轮历史看第50轮模型还能不能复述出来。单轮token成本每发一次请求实际消耗的输入token数量。这直接决定账单大小特别是高频客服场景成本差一倍月结单就完全不同。响应延迟上下文越长prefill阶段耗时越长。正常情况下几百token和几千token差距不大但一旦塞入上万token首字延迟会明显上升。所有context-mode方案本质都是在三者之间找一个适合业务场景的平衡点。没有最优只有最合适。2. 三种主流context-mode的取舍滑动窗口、摘要压缩、检索增强2.1 滑动窗口把最近N轮留下滑动窗口是最容易想到的方案也是我当时第一个实现的方式。思路很简单每条消息按时间排序构造请求时只取最新的若干条直到接近token预算就截止。它的最大优势是行为可预期。代码量小逻辑直白不会出现“模型因为摘要过拟合而胡说”的情况。你放进去的就是原文模型读到的东西没有经过二次加工信息失真风险低。但它有个非常致命的短板早期关键信息会被无差别丢弃。客服例子里的白金会员约束如果是第2轮说的第35轮已经不在窗口里了模型根本不知道这个约束存在。所以滑动窗口只适合那种“最新内容最重要”的场景比如闲聊机器人、短期任务助手。2.2 摘要压缩把对话变成“工作笔记”摘要压缩是我第二个尝试的模式。它的动机很简单模型真正需要的往往不是对话的逐字原文而是其中蕴含的“事实和意图”。与其保留每一句话不如定期让模型把历史对话整理成一份结构化摘要下次请求时把摘要加原文一起放进窗口。举个例子用户在第3轮说“我周五要去上海出差帮我订虹桥附近的酒店”后面又聊了一堆茶叶价格之类无关话题。第20轮用户说“帮我看看之前说的酒店”这时候模型需要的是“周五、上海、虹桥附近”这几个信息而不是中间17轮闲聊。我的实现方式是滚动摘要每隔一段时间或一定token量把当前摘要与新消息一起丢给模型生成更新的摘要。这类似工作里记笔记不断往里补增量。摘要压缩的优点是信息保留率高能把大量历史浓缩成几百字并且保留全局语义。缺点也很明显摘要本身有损压缩过程中可能丢掉关键细节而且摘要调用本身也要花token和时间。2.3 检索增强让上下文不再连续而是“按需取用”检索增强是我后来最常用的模式也就是把上下文从“连续的切面”变成“可查询的知识库”。核心思路是每条历史消息都向量化存入向量数据库当用户提出新问题时先对问题做语义检索只把相关的历史消息片段取出来放进上下文。这个模式和RAG在外观上很像但目的不同。RAG检索的是外部知识库检索增强检索的是对话自身的记忆。它解决的是“历史很长但高度离散”的场景比如一个用户零零散散地在不同时间问过多个不同订单的问题每个问题之间没有强关联。检索增强的优势是信息保留率可调你可以只取top-k相关片段也可以额外加一个关键词过滤条件。它不会像滑动窗口那样牺牲早期的关键信息也不会像摘要那样把细节抽象掉。缺点是系统复杂度明显上升需要维护向量库、 embedding、检索链路并且检索质量直接决定回复质量检索不到模型就真的不知道。2.4 三种模式的量化对比我把自己项目里的实测数据整理成了一张表方便你在方案选型时直接参考基于128K窗口、中文对话场景模式信息保留能力单轮token成本延迟影响实现复杂度典型适用场景滑动窗口低早期信息易丢低低最简单闲聊、短期任务、客服的“最近意向”判断摘要压缩中高但细节有损中额外摘要调用中中等长会话总结、需要全局语义、关键约束密集检索增强高但依赖检索质量低-中中检索耗时较高知识密集、问题高度离散、长期多主题对话需要注意这三种模式不是互斥的。我在最终版本里做的是混合模式全局摘要保底 滑动窗口保近期 向量检索补细节后面我会写具体实现。3. 我的落地实现一套可切换的context-mode框架3.1 数据结构给每条消息打上“标签”和“权重”在写任何模式之前我先把消息数据结构重新设计了。这一步非常重要后期所有策略都建立在消息的meta信息之上。我的每条消息包括五个字段dataclass class ChatMessage: role: str # system / user / assistant / tool content: str msg_id: str # 全局唯一 ts: float # 时间戳用于排序和时效判断 category: str chat # chat / constraint / fact / tool_result / greeting retention: int 1 # 保留权重0可选丢弃1默认2重要3绝不可丢category和retention是我自己加的。“constraint”是用户明确的约束性指令比如“不要推荐含糖饮料”“fact”是事实型实体比如订单号、日期、金额“tool_result”是工具返回的原始结果“greeting”是寒暄。retention用于告诉上下文构造器这条消息的丢弃优先级。有了这两个字段构造上下文时就灵活多了。系统约束永远保留retention3用户明确指令保留retention2工具结果和事实按需保留retention1寒暄直接丢弃retention0。3.2 token预算的精确计算构建上下文的第一步永远是算预算。我封装了一个token计数器用tiktoken来估算import tiktoken enc tiktoken.encoding_for_model(gpt-4o) def count_tokens(text: str) - int: if not text: return 0 return len(enc.encode(text))然后定义预算结构。核心原则是先留出模型回复的空间再装system prompt再装本次新消息最后剩下的空间才分给历史内容。def build_sliding_context( system_prompt: str, history: list, pending: list, max_tokens: int 16000, reserve_output_tokens: int 2000, ): budget max_tokens - reserve_output_tokens system_msg {role: system, content: system_prompt} budget - count_tokens(system_prompt) # 本次必须带上的新消息 for msg in pending: budget - count_tokens(msg[content]) # 从历史最末端最新的消息往回收集塞满剩余预算 selected [] for msg in reversed(history): cost count_tokens(msg[content]) if budget - cost 0: break selected.append(msg) budget - cost selected.reverse() return [system_msg] selected pending有两个细节容易被忽略。第一reserve_output_tokens不能设得太小否则模型生成到一半就被截断。我一般根据业务回答长度估算客服场景设2000长文生成场景至少4000。第二budget - cost 0才break而不是0这样能尽量利用最后一点剩余空间避免白白浪费。3.3 摘要压缩的实现细节摘要模式我并没有简单地把整段历史一次性丢给模型总结那样很容易超窗口。我采用的是“分批滚动摘要”的方式def incremental_summary(prev_summary: str, new_messages: list, client) - str: content \n.join(f{m[role]}: {m[content]} for m in new_messages) prompt f你正在管理一段长时间对话的记忆。请合并以下两部分内容输出一份新的结构化摘要。 要求: 1. 保留所有用户明确的指令、偏好和约束 2. 保留关键实体订单号、日期、金额、人名、地址等 3. 去掉寒暄、重复表达和低信息量内容 4. 摘要保持在300字以内。 【已有摘要】 {prev_summary} 【新对话】 {content} resp client.chat.completions.create( modelgpt-4o-mini, messages[{role: user, content: prompt}], temperature0.2, ) return resp.choices[0].message.content每次调用前我先统计新消息的总token量如果超过4000就自动切成多个小批然后循环调用incremental_summary把上一轮的摘要作为下一轮的“已有摘要”传进去。这样做的好处是无论对话多长摘要调用的token开销都是可控的不会出现“为了省token反而烧掉更多token”的尴尬。另外一个我踩出来的经验摘要生成用mini模型就行不必用旗舰模型。因为摘要任务本身对推理要求不高关键是提取准确性。我个人用gpt-4o-mini实测信息保留率和旗舰模型相差不大但成本低了差不多一个数量级。如果你用的是开源模型做底座摘要生成也可以复用同一套模型只是速度会慢一些。3.4 自动切换策略什么情况用什么模式做了三种模式之后新的问题来了每次请求到底用哪个我最后写了一个简单的策略决策器规则不复杂但足够有效def decide_mode(app_state): # 历史占总窗口预算的比例 ratio app_state.history_tokens / app_state.max_tokens if ratio 0.4: return full # 历史远没到预算直接全量 if app_state.fact_density 0.2: return sliding # 事实密度低大多是无关联的闲聊 if app_state.query_discrete: return hybrid # 问题之间离散单点查询居多用检索 return summary # 默认用摘要压缩这个决策器依赖两个额外信号fact_density我给每条消息打了category标签统计最近N条消息里“fact”和“constraint”占比占比高说明这段对话信息密集不能随便丢。query_discrete用户在连续多轮里是否在问完全不同的问题。这个可以通过向量相似度来算如果相邻问题间的平均相似度低于阈值就认为是离散多头查询。hybrid模式是我最终推荐的方式系统约束全量保留近期消息用滑动窗口再叠加一层向量检索补充细节。它稍微复杂一点但在长会话场景里效果最稳。4. 实测中踩过的坑与优化建议4.1 上下文污染摘要把工具输出当成了“事实”第一个坑出现在摘要模式上线当天。客服机器人在查询天气工具时返回了“明日有暴风雨”摘要把它记成了一条事实。第二天用户问“明天适合户外活动吗”模型根据摘要里的“暴风雨”给出否定建议。问题在于那条工具结果是2小时前的天气早就变了。这种问题我称为“上下文污染”模型把某个时刻的工具输出当成了永久事实。工具结果天然有时效性不能进入长期摘要至少必须带上时间戳。我的解决方案是给工具结果单独设置categorytool_result并且默认retention1。在做摘要压缩时把tool_result排除在“摘要对象”之外只保留“这条工具结果回答了什么问题”这样的元信息。比如不记“明日有暴风雨”而是记“用户询问了明日天气已返回预报结果”。等到需要精确数据时走检索增强去拿原始记录。4.2 摘要的“歧义塌缩”关键实体被抽象掉了第二个坑比较隐蔽也最难发现。在某次长会话里用户先提到“张总”后来又提到“张经理”其实指的是同一个人。摘要模型在压缩时统一成了“张总”这没问题。但另一段对话里“张经理”是另一个部门的人摘要也统一成了“张总”。结果模型在回答“张经理负责什么业务”时把两个部门的信息混在了一起。这就是摘要压缩的“歧义塌缩”。模型为了把内容压缩进有限字数会倾向于做实体合并而合并产生的歧义水平无法被下游轻易察觉。我的对策是双重的第一摘要prompt里强制要求“实体出现时保留完整称谓不缩写不合并若不确定则并列保留”第二在摘要之外单独维护一个“关键实体表”用正则和命名实体识别抽取出订单号、人名、日期、金额这类数据不做压缩始终原始保留。这个实体表非常值得单独做。需要精确查询时它比摘要可靠得多而且是结构化数据可以直接参与规则判断不一定要经过模型。4.3 token成本失控摘要调用反而把账单推高了第三个坑和钱有关。我最初设计是“每当历史超过阈值就触发一次摘要”结果遇到一个话痨用户几乎每三四轮就触发一次摘要调用而且每轮主请求还是全量发送。月底一看账单摘要花费占了总成本的35%。这个教训是摘要不能是“高频操作”它应该是“低频兜底操作”。我把触发阈值从4000token调高到12000token并且加了冷却时间——两次摘要之间至少间隔30分钟或20条新消息。另外一个优化是不把摘要结果立即放进下一轮请求而是先让它存储在内存只有当历史即将撑爆窗口时才加载。4.4 精心设计评估怎么判断context-mode改好了还是改坏了context-mode做得对不对不能靠感觉。我后来搭了一套专门的评估集这里分享一个最值得做的“关键信息保持”测试准备一组20个“关键约束”分别散布在对话历史的第1、第10、第30、第50轮。让测试脚本自动运行对话到第60轮然后向模型提问每个约束对应一个问题。统计答对率对比不同模式下的得分。我实测下来单纯用滑动窗口时第30轮之前的约束答对率只有约40%摘要压缩能到75%左右混合检索模式可以稳定在85%以上。这组数据很有说服力也很容易让业务方理解“为什么需要做context-mode”。同时还要监控两个反向指标回复的“串线率”把不同主题的信息混在一起的比例和每条消息的平均成本。有时候一个方案信息保留率很高但成本高得离谱那就得调整策略。5. 场景化落地建议不是所有应用都需要最强模式5.1 先判断你的业务属于哪一型我见过很多团队一上来就上向量检索结果项目跑了一个月发现瓶颈根本不在上下文而在意图识别。这里我把常见应用分成四类你可以对照着选模式应用类型典型特征推荐context-mode客服辅助会话长、问题离散、事实密集混合模式摘要检索个人助理短期任务多、最新意图重要滑动窗口轻量摘要文档问答外部知识为主、对话历史短滑动窗口即可重点是RAG角色扮演/闲聊全局人设重要、细节容忍度高摘要压缩这个表不是绝对标准但能帮你快速定位。最重要的是先量化你的历史特征再选模式。我每次接到新项目都会先跑一遍真实对话日志统计平均会话长度、事实密度、问题相似度然后才动手设计。5.2 给初次落地的人一个靠谱的推进路径如果你是第一次做context-mode我建议不要直接堆复杂方案。按下面的顺序演进第一版只做滑动窗口但把消息加好category和retention标签。这步很快一两天就能完成。上线后收集真实日志统计“最早出现的关键约束在第几轮被挤出窗口”找到一个可复现的失忆案例。加入摘要压缩配上实体表。先让摘要只保留“关键指令和事实”不要贪全。如果还有离散查询场景回答不好再上向量检索。每一步都留出观察时间至少跑一周真实流量用上一节说的评估集量化效果。不要跳步否则出了问题你根本没法定位是摘要丢信息还是检索没召回。5.3 后续可以继续扩展的方向context-mode这套东西写完并不代表一劳永逸。我接下来计划做几件事第一把摘要和检索的触发条件从规则改成可学习策略。现在已经积累了不少“哪些消息最终帮助正确回答”的日志数据后续可以用这些数据训练一个轻量级模型来决定上下文构成而不是靠人工阈值。第二做跨会话记忆。现在的context-mode只解决单会话内部的消息管理但很多用户会多次回访下一次会话其实也应该带上之前会话的关键摘要。这个方向我已经在规划本质上是把“会话级摘要”升级成“用户级长期记忆”。第三把上下文预算做成可观测的可视化仪表盘让运营人员能实时看到每一轮请求里“system占多少、历史占多少、检索片段占多少”这样调参就有数据支撑。如果让我重新来一次我会在项目第一天就加一个“输入输出token计数”的中间件把每条消息的类别和保留权重在入库时打好。context-mode不是一个开关而是一套工程习惯在写代码之前先想清楚哪些信息不能丢哪些可以压缩哪些压根不用看。你把这个想明白了后面所有模式实现都会顺很多。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表