☰
LLM上下文模式实战:滑动窗口、摘要压缩与语义检索的选型与实现
2026/10/7 21:25:22 网站建设 项目流程

从去年开始把十几个业务模块从单轮问答改造成带上下文记忆的对话形态,又在一家创业公司帮忙搭过完整的上下文管理中间层,我想我可以说说这个话题了。这里不聊那些浮在纸面上的概念,直接拆解我在真实项目里怎么理解、怎么选型、怎么写代码的。

“context-mode”说白了就是一套怎么处理和传递上下文信息的模式约定。在LLM应用里,它决定了一件事:模型能看到的“记忆”边界在哪里。没有这个边界,对话就是失忆的傻子;边界定得太死,对话就变成了死板的表单填写。真正落地的时候,你会发现这玩意儿的复杂程度远超想象——会话窗口、摘要压缩、向量召回、工具调用状态,全都在跟上下文模式打交道。

我最早接触context-mode这个说法,是在做AI客服系统的时候。当时我们的机器人被用户反复投诉“翻来覆去一句话”,明明前面已经告诉过客服“我的订单号是99812”,用户换了个说法再问一次,机器人就当没听过。后来我意识到,问题根本不是模型不够聪明,而是我们压根没给模型一个稳定的上下文视图。从那以后,我开始系统研究上下文模式的设计,前后经历了四五个项目的迭代,踩过不少坑,也总结出一套还算得心应手的做法。


1. 上下文模式到底是什么,以及为什么它正在成为刚需

1.1 一次事故让我真正理解了上下文的意义

先讲一个具体的场景。我们做过一个给外贸业务员用的AI报价助手,一开始非常简单:用户输入一个产品关键词,系统从商品库检索出匹配项,再让模型生成一个报价单。单个看效果不错,但业务员一旦连着问“上次给那个美国客户报的价格还能再低3个点吗”,系统就完全卡壳了。

问题出在哪?模型是天生没有记忆的。每次你调用API,它都是在“第一次”见到你。所谓带上下文,其实就是你自己把该记住的东西塞回输入里。但塞什么、塞多少、以什么格式塞,这就是context-mode要管的事情。

那会儿我们团队内部提了一个很粗暴的方案:把所有历史对话全部拼进Prompt,一股脑发给模型。一开始确实有效,客户问什么都能接上。但运行了不到一周,问题开始密集爆发,最典型的就是Token消耗飙升——单轮请求从几百Token涨到几千甚至上万,月底看到账单整个人都不好了。更糟的是,当历史消息超过一定量之后,模型反而“眼花缭乱”,经常忽略最新的用户意图,回答质量肉眼可见地下降。

这次事故给我的教训非常直接:上下文模式不是什么锦上添花的设计,而是决定系统能不能活下去的基础设施。你必须有意识地设计上下文的结构、容量和更新策略,不能指望无脑堆历史。

1.2 从三层来理解context-mode

后来我把上下文模式拆成三个层面来看,思路一下就清晰了。

第一层是上下文入口,也就是数据从哪来。可能是用户当前输入、历史对话、检索到的外部资料、数据库里查询到的业务记录,甚至某个传感器上报的实时数据。在这一层,你要解决的是“哪些信息需要进入这次推理”。

第二层是上下文结构,也就是信息以什么形态组织。是平铺的对话历史,还是带角色的结构化记录,比如“用户说/助手说/系统状态”?是原封不动的原文,还是经过摘要压缩后的精简版?结构决定了模型理解信息的效率,也决定了你后续能不能做灵活的裁剪和检索。

第三层是上下文生命周期,也就是信息什么时候写入、什么时候更新、什么时候丢弃。这是一整个会话从开始到结束的状态管理过程,涉及会话窗口的分段、记忆的固化、过期信息的淘汰机制。很多团队把注意力放在前两层,忽略第三层,结果系统在长对话场景下越跑越笨,原因就在这里。

1.3 你什么时候应该认真考虑穿上“上下文模式”

如果你的项目只是做一个一次性问答工具,那确实不需要研究context-mode。用户问一句,你答一句,没有任何状态需要维护,最简单的那种模式就够了。

但只要你遇到下面任何一个信号,就说明你该认真考虑上下文模式了:

  • 用户会围绕同一主题连续追问,期望系统记住他前面说过的话;
  • 对话中存在必须跨轮次保持一致的约束,比如“我不喜欢红色”“预算控制在5000以内”;
  • 你需要让模型访问外部数据源或者调用工具,而工具的执行结果需要被后续对话引用;
  • 单会话轮次多,历史消息长度不断膨胀,Token成本已经明显影响业务毛利;
  • 多个角色或部门需要共享同一个会话上下文,比如客服和质检员查看同一段对话记录。

我记得有个做在线教育辅导产品的朋友找我咨询,他们的AI助教一节课下来要跟学生对话几十上百轮,而且需要根据学生不同阶段的答题情况动态调整讲解策略。没用上下文模式之前,助教经常上一句还在教加减法,下一句就开始讲微积分,学生和家长都一脸问号。后来我们帮他们做了学习状态的动态追踪,每一轮都更新“学生当前水平”和“教学目标”这两个上下文字段,效果立刻不一样了。


2. 三种主流上下文模式的原理与选型建议

2.1 滑动窗口模式:最简单,也最容易翻车

滑动窗口模式大概是所有团队第一反应能想到的方案。做法很简单:维护一个消息列表,新消息不断追加,总长度超过阈值就把最旧的挤掉。就像排队一样,队伍上限100人,来了新人,队尾的老成员自动出列。

我自己早期做客服机器人时就用这种模式,代码好写,测试也容易。只要设定一个最大消息数或者最大Token数,用队列数据结构就能实现。但它有两个明显的坑。

第一个坑是信息被无差别淘汰。窗口只认时间顺序,不认信息价值。假如用户在第3轮说过“我叫陈明,是SVIP会员”,到第30轮的时候这条信息早就被挤出去了。一旦后面用户问“我能享受什么权益”,模型根本不知道他是SVIP,回答自然就错了。信息被挤掉的顺序完全取决于新旧,而不是重要性,这在真实业务里非常致命。

第二个坑是碎片化记忆导致前后矛盾。窗口里的旧消息被部分保留、部分丢弃,模型看到的历史是不完整的,经常出现“前半段以为用户是普通用户,后半段以为用户是SVIP”这种逻辑混乱。我记得做过一个测试,在连续40轮的对话里,滑动窗口模式在第15轮之后就开始出现角色认知漂移,模型有一半概率认错用户的会员等级。

所以滑动窗口模式只适合下面这些场景:对话轮次很短(一般不超过10轮)、信息之间没有强依赖关系、业务对上下文准确性要求不高。如果你只是做一个简单的答疑机器人,可以先用它打好地基。但它绝对扛不住复杂的业务逻辑和长对话。

2.2 摘要压缩模式:让模型帮你提炼“长期记忆”

滑动窗口最大的问题是把历史当作流水,而摘要压缩模式的核心思路是用模型的总结能力,在对话过程中持续提炼关键信息。

具体做法是:当消息列表快要超过阈值时,把已有的消息交给模型做一次总结,生成一段精简的“会话摘要”,然后代替原始历史消息参与下一轮对话。原始消息可以归档,也可以丢弃,新的消息继续追加到会话中。

我最开始用这个模式是在一个售前咨询机器人项目里。客户会一轮一轮地提需求,范围经常变化,最早说要A方案,聊到后面又说要B方案。摘要压缩模式能把整个需求演变过程浓缩成一段话:“客户最初倾向于A方案,但了解到B方案的性价比后,目前主要关注B方案,预算区间为50万至60万,决策周期预计两周。”

这样模型在后面继续对话时,就不会被那些反复拉扯的细节干扰,而且关键结论始终在上下文里。

这个模式的优点是信息密度高,Token占用稳定。摘要本身可以控制长度,不管聊了多久,上下文里的“长期记忆”都只有一小段摘要。缺点是摘要过程有信息损耗,模型认为不重要的细节可能被丢掉;而且每次生成摘要都需要额外调用一次模型,增加了一点成本和时间。

实操中我建议做两级摘要:短期窗口保留最近N轮完整对话,长期记忆用上文摘要。这样既保留了近期细节,又保证了长期关键信息不丢失。具体N取多少需要根据业务实测,我常用的起点是20,也就是最近20轮对话保持完整,再往前就纳入摘要。

2.3 语义检索模式:把记忆外包给向量数据库

摘要压缩模式虽然好,但有一个致命缺陷——摘要一旦生成,内容就固化了。如果用户在第50轮突然反问“我们第3轮讨论的方案参数是多少”,摘要里可能根本没有这个细节。这时候就需要语义检索模式出场。

语义检索模式的思路是:把所有历史消息(或者消息的摘要、关键实体)全部存起来,每一轮对话之前,用“当前问题+最近几条消息”作为查询向量,从历史中召回最相关的几条消息,拼接到上下文里。这就像你虽然读不完一整本书,但每次写作前都能通过检索找到最需要引用的那几页。

这个模式的引入,把上下文管理从“内存管理思维”转变成了“索引+检索思维”。内存再大也有限,但索引可以在外置存储里无限膨胀。代价就是系统复杂度明显增加,你需要引入向量数据库(比如Milvus、Qdrant或pgvector),还要解决数据切片、向量化、召回排序一整套问题。

我在一个企业知识库问答项目上完整用过这个模式。那套系统要处理几十万字的产品手册,用户可能问到任何一章的内容。如果只靠滑动窗口,模型根本看不到相关段落。后来我们把手册按章节和自然段切块,全部向量化存入数据库,用户提问时先召回最相关的3到5个片段,再把这些片段作为上下文注入Prompt,效果那叫一个脱胎换骨。

语义检索模式尤其适合知识密集型场景:用户问题可能涉及海量知识,且这些问题之间没有明显的时序关联。它的关键词是“让上下文成为可查询的存储池”,而不是“让上下文跟着对话一起滚动”。

2.4 三种模式的对比与选型框架

为了让你能更直观地做决策,我把三种模式的对比写在下面,这是我反复实践后整理出来的选型框架:

维度滑动窗口摘要压缩语义检索
实现复杂度低中高
长对话表现差较好好
信息完整性低,按新旧淘汰中,按重要性保留高,按相关性召回
Token占用随轮次线性增长相对稳定取决于召回数量
延迟影响低中,需额外调用模型中,需查询向量库
适用场景短对话、快速验证长对话、业务状态跟踪知识库问答、开放域问题

选型这里我有个体验很深的判断:不是越复杂的模式越好,而是越匹配业务形态越好。你问天气、问时间这种一次性信息,滑动窗口就是最优解;你要做一个多轮谈判助手,摘要压缩大概率是性价比之王;你做一个能够回答全公司政策问题的智能客服,语义检索才是正路。

而且这三种模式完全可以组合使用,我在不少项目里就是把摘要压缩作为主力,再给摘要配上滑动窗口,外围接语义检索。层层配合,才能对付真实世界的混沌。


3. 亲手实现一个可用的context-mode模块

3.1 先确定上下文的数据结构与接口

纸上谈兵没意思,我带你实际写一个混合模式的小模块。我会用Python写,依赖不多,核心逻辑你看懂之后完全可以迁到任何语言。

首先定义会话类。我们要存储原始消息、会话摘要、以及一个由摘要和最近窗口组成的最终上下文包:

from typing import List, Dict, Optional, Any from dataclasses import dataclass, field from collections import deque @dataclass class Message: role: str # "user" 或 "assistant" 或 "system" content: str metadata: Dict[str, Any] = field(default_factory=dict) @dataclass class ContextState: session_id: str # 所有历史消息的完整存储,用于语义检索或回溯 full_history: List[Message] = field(default_factory=list) # 实时会话摘要 summary: str = "" # 最近N轮保留的原始消息 recent_window: deque = field(default_factory=lambda: deque(maxlen=20)) def add_message(self, message: Message): self.full_history.append(message) self.recent_window.append(message)

recent_window用一个长度20的deque,自动挤掉最老的消息,保持最近的原始内容始终在。summary是持续更新的摘要,full_history是完整的审计底账,召回和回溯都用它。

接着定义一个上下文管理器,核心职责是把ContextState转换成最终送给模型的messages列表:

class ContextManager: def __init__(self, token_limit: int = 3000): self.token_limit = token_limit def build_context(self, state: ContextState, query: str) -> List[Message]: # 真实的token计算应该按模型分词器来,这里用粗略估算代替 system_meta = Message( role="system", content=( "你是业务助手。请优先遵循会话摘要中确定的结论," "再结合最近对话回应用户。如果摘要和最近消息冲突,以最近的用户意图为准。" ), ) merged: List[Message] = [system_meta] if state.summary: merged.append(Message(role="system", content=f"[会话摘要] {state.summary}")) # 把最近窗口接到后面,注意deque是顺序结构,需要先转list merged.extend(list(state.recent_window)) # 加一个包含当前问题的系统提示,防止“上下文还在上一步”的呆滞 merged.append(Message(role="system", content=f"[当前用户问题] {query}")) return merged

细心的你可能会问,为什么当前用户问题单独放在system里而不直接作为user消息?原因是我想让模型在生成回答时,对“用户正在问什么”有更清晰的锚点;特别是当最近窗口里有多条历史user消息时,直接把这些拼接容易让模型混淆哪一条才是需要本轮响应的。后面还会提到一个更好用的变体,就是给每条消息打上序号。

3.2 摘要更新机制:怎么避免“连摘要本身都失控”

摘要压缩模式下,摘要怎么更新是很关键的。最傻的方法是每轮都重新总结全部历史,那样越到后期Token消耗越离谱。我推荐的做法是增量摘要:每一轮最多把“旧的摘要 + 最近新消息 + 当前问题”交给模型,生成一篇更新后的摘要。

完整实现的代码骨架大概是这样:

def refresh_summary( state: ContextState, llm_compress_func, max_summary_tokens: int = 600, ) -> None: # 先把最近窗口的新消息拿出来 recent_messages = list(state.recent_window) if not recent_messages: return old_summary = state.summary or "(无)" prompt = ( "请把以下旧摘要和最新对话合并,形成新的会话摘要。\n" "要求:\n" "1. 保留用户的关键身份信息、约束条件、业务决策和未完成事项;\n" "2. 删除已经失效或重复的细节;\n" f"3. 新摘要控制在{max_summary_tokens}字以内。\n\n" f"旧摘要:\n{old_summary}\n\n" f"最新对话:\n" ) for msg in recent_messages: prompt += f"{msg.role}: {msg.content}\n" new_summary = llm_compress_func(prompt) state.summary = new_summary

这里有一个不起眼但极其重要的细节:摘要更新前,要把recent_window清空。为什么?因为旧摘要和最近窗口合并以后,已经包含了窗口里的信息;如果窗口不清空,下一轮build_context时,这些信息又会原封不动地返回一遍。不仅Token翻倍,模型还可能被重复信息干扰,导致答案出现“复读机”现象。

我踩过这个坑。有一版代码忘了在refresh_summary之后清空最近窗口,结果线上的Token消耗直接从预估的每轮3000涨到每轮5500,而且模型回答里频繁出现重复的搬运内容。排查了半天才发现是这个原因,后来在每次摘要刷新之后执行state.recent_window.clear(),一切恢复正常。

3.3 混合检索召回:让外挂记忆真正发挥作用

如果想加入语义检索,我们需要把全量历史向量化,并在每次请求时召回相关内容。为了演示完整链路,我加一个简单的向量检索函数。真实项目里可以用OpenAI的Embedding接口配Qdrant,这里我用一个伪代码说明核心流程,你替换成自己的向量化服务就行。

def retrieve_relevant(state: ContextState, query: str, top_k: int = 3) -> List[Message]: # 用向量模型把query编码 query_vec = embed(query) # 对full_history中的所有消息编码后做相似度计算 # 为了效率,真实项目应该用向量数据库预先索引 scored = [] for idx, msg in enumerate(state.full_history): # 生产环境不会在这里现算,而是先存好在向量库里 msg_vec = cached_embed(msg.content) score = cosine_similarity(query_vec, msg_vec) scored.append((score, idx, msg)) scored.sort(key=lambda x: x[0], reverse=True) # 注意:召回的只是参考信息,要按原文返回给模型 return [m for _, _, m in scored[:top_k]]

召回结果怎么接进build_context?我建议把召回消息显式标记为system角色的“参考资料”,并说明它们来自历史对话但并非按时间顺序,方便模型区分:

def build_context_with_retrieval(self, state: ContextState, query: str) -> List[Message]: messages = self.build_context(state, query) recalled = retrieve_relevant(state, query, top_k=3) if recalled: ref_block = "\n\n".join(f"历史对话片段{i+1}:\n{m.content}" for i, m in enumerate(recalled)) messages.insert( -1, Message( role="system", content="以下是从历史对话中检索到的相关资料," "可能有与当前不一致的地方,请以最近对话为主,但有助回答问题的信息请采用。\n\n" + ref_block, ), ) return messages

这一版就是完整的混合上下文模块了:滑动窗口保最新的细节,摘要保最关键的长期信息,语义检索补足深度知识。三条腿走路,系统才立得稳。

3.4 工具调用状态:context-mode里最容易被忽略的“隐藏状态”

对话上下文不只是用户聊了什么,还包括系统执行过什么动作。比如AI助手去查了天气接口、调用了订单系统、生成了报价单,这些动作本身以及它们的结果,必须在后续对话中被引用。

我在做过一个订单管理助手,用户问“帮我查一下最新一单物流”,助手调了物流接口,返回“预计明天送达”。用户接着问“那这个订单的发票开了吗”。如果没有把“当前正在查的订单号”写入上下文,模型根本不知道“这个订单”指的是哪个。

解决这个问题的思路:定义一个function_context字段,每执行一次工具调用就写入当时的关键参数和结果摘要,下一轮对话时把它作为系统状态注入。

@dataclass class ContextState: ... function_context: List[Dict[str, Any]] = field(default_factory=list) def record_call(self, tool_name: str, args: Dict, result_summary: str): self.function_context.append({ "tool": tool_name, "args": args, "result": result_summary, "time": time.time(), }) # 只保留最近5条工具调用记录,防止混乱 if len(self.function_context) > 5: self.function_context.pop(0)

build_context里把最近几次工具调用拼成一段“系统已执行动作”的提示。你在真实落地时一定要做这件事,否则模型就是“没有手的助理”——能说不能做,做了也不记得。


4. 实战中踩过的坑与排查实录

4.1 上下文污染:模型被自己几轮前的话带偏

我印象最深的一次事故,发生在做行业报告生成器的时候。用户连续生成了三份报告,第三次生成时问“把第二份的结论放到开头,再补充第三份的数据”。我们当时把所有历史消息一字不差地拼进上下文,模型生成出来的报告,居然把前三轮讨论中提到的错误假设也写进去了,而且分不清哪一条是用户最终确认过的结论。

后来排查发现,问题出在把“过程性讨论”和“结论性信息”混在一起。用户在第2轮说“我觉得不一定要聚焦国内市场”,但后边又推翻了这句话,说“还是先看国内”。模型把两条矛盾信息都当作上下文。从那以后我给自己定了一条铁律:进入上下文的信息必须分级。结论、约束、用户身份是“核心层”,每次都要进;过程性讨论是“临时层”,只保留当轮需要的内容。千万不要让模型同时看到相互矛盾的过程和结论,除非你明确提示它“以最近结论为准”。

4.2 Token爆炸:为什么有时候上下文越长,效果越差

Token成本是每个做LLM应用的人都绕不开的坎。滑动窗口模式下Token随轮次线性增长,很容易失控。我见过一个团队线上单次请求的Prompt达到好几万Token,一问才知道他们把整个客服对话记录都塞进去了,结果月账单比团队工资还高。更搞笑的是,模型在这种超长上下文下经常“注意力涣散”,用户问第50轮的问题,模型引用的却是第3轮的旧信息。

我的建议很明确:给上下文设一个预算,并且好钢用在刀刃上。把大部分Token留给用户最近的几句话和检索出的关键资料,摘要和工具状态压缩到最小限度。宁可让模型说“我不记得了”,也不要让它带着满脑子历史瞎猜。实际项目里我习惯把Token预算按比例分配——当前问题和最近窗口占一半,摘要和检索资料占四成,工具状态和系统指令占一成。这套比例不是拍脑袋定的,是从多轮评测里试出来的,你可以当做一个起点去调自己的。

4.3 记忆固化后无法更新:用户改主意了,你还死死抱着旧结论

摘要压缩模式有一个隐藏风险:用户中途改变了业务约束,但摘要里仍然写着旧约束。比如用户在第十轮确定了“预算50万”,到第二十轮说“算了,超一点也能接受,提到60万”,摘要更新不及时的话,模型会继续按50万来推理。

我解决这个问题的方法是,在摘要刷新提示词里明确加了一条规则:“如果最新对话与旧摘要存在明显的状态变更,请把变更后的状态放在摘要最前面,并标注‘已变更’。”同时,我们也在业务层面做了一层“约束覆盖”记录,每次检测到类似“算了”“改为”“实际上”这些转向信号时,强制触发一次摘要更新。上线之后,记忆固化问题基本没有再出现过。

4.4 实测排查思路记录:一个典型的debug全流程

某天线上反馈,用户在多轮对话里问“我刚才提到的价格你记住没有”,模型答非所问。我的排查流程是这样的:

第一步,打开日志,查看这一轮实际发给模型的完整上下文,确认价格信息到底在不在里面。第二步,如果在,看它是在摘要里还是在最近窗口里;如果在摘要里,检查摘要刷新逻辑有没有把价格字段覆盖掉。第三步,如果不在,检查消息写入ContextState时有没有漏掉,或者是工具调用返回结果没有被记录。第四步,检查检索召回有没有把那段历史消息召回出来。第五步,如果全都没问题,那就得考虑是不是模型本身没理解Prompt里“记住用户的重要信息”这句话,需要调Prompt或者在系统层面对这类关键信息做强约束。

那个案例最后定位到的原因很有意思:价格信息确实在摘要里,但摘要里有两条价格记录——开始谈的时候是“早期报价48万”,后来改成“初步优惠后46万”,模型不知道应该以哪一条为准。解决方案是给摘要里的关键状态增加时间戳,并且在system指令里写清楚“报价以最近一次更新为准”。从那以后,我又给上下文模型加了一个铁律:关键业务信息必须显式带版本号或者时间,绝不能让模型自己猜。


5. 上线之后的取舍、评测与降本提效

5.1 四种上下文策略的实测对照

为了让你少走弯路,我把我们团队在一次长对话场景下(连续40轮业务咨询)做的实测结果整理成了一个表。这些数据是从真实日志抽样统计的,不一定代表所有场景,但至少能给你提供一个直观的参照。

策略平均每轮Token回答完整率上下文冲突率用户满意度
全部历史无脑拼接820076%22%3.1
滑动窗口(最近20条)210088%9%3.8
摘要压缩 + 最近10条140091%4%4.2
摘要 + 最近10条 + 语义召回170094%2%4.5

无脑拼接的Token消耗吓死人,冲突率也非常高,模型一会儿记那一会儿记这,回答经常自相矛盾。滑动窗口虽然Token控制住了,但完整率一般,用户问到被窗口挤出去的内容就歇菜。摘要压缩模式综合表现最好,Token最省,冲突率也低。再加上语义召回后完整率最高,但Token有小幅上升,因为召回内容额外占了一些空间。

要说明的是,语义召回带来的延迟一般在几十毫秒到一二百毫秒,这个对于大多数业务是可以接受的。我在线上把召回阈值调到了0.75以上,确保只召回相关性真正高的内容,避免无关信息浪费Token。

5.2 上下文配置参数化:把模式选择变成开关

做了这么多项目之后,我最大的体会是:不要把context-mode的逻辑写死在业务代码里,一定要做成可配置的。线上业务经常要调整参数,比如最近窗口条数、摘要刷新间隔、召回数量、Token预算,这些如果靠改代码来调,那运维成本就太大了。

我习惯的做法是提供一个配置文件或环境变量,核心内容大概长这样:

# context_config.py CONTEXT_CONFIG = { "mode": "hybrid", # sliding | summary | retrieval | hybrid "recent_window_size": 20, "summary_max_tokens": 600, "summary_refresh_interval": 5, # 每5轮刷新一次摘要 "enable_retrieval": True, "retrieval_top_k": 3, "retrieval_threshold": 0.75, "token_budget": 3000, }

这样一来,同一个后端服务可以服务不同的机器人,只需切换配置就可以调整上下文策略。有段时间我们做了一批行业模板,教育助教模板用summary模式、知识库问答模板用hybrid模式,几十套模板共用一套上下文管理内核,效果和迭代速度都明显提升。代码层面只维护一个通用引擎,业务差异全在配置里,出问题也好回滚。

5.3 评测:你怎么知道上下文模式调得好不好

很多人做完context-mode,不知道怎么评估效果。我建议不要只看单轮问答质量,要专门测“需要跨轮记忆”的用例。比如你可以在测试集里设计三类问题:直接依赖历史信息才能回答的问题(比如“我之前说的收货地址是什么”)、需要综合多个历史论点的问题(比如“根据前面几天你给我的建议,列出方案B在不同场景下的优劣势”)、需要跟随用户变更状态的问题(比如“我之前说要买红色,现在改成蓝色,还有货吗”)。

我习惯搭一个自动化评测脚本,把这些问题跑一遍,对比不同上下文策略下的准确率。每次改配置,都要先跑这组测试再上线。单独看某一个case感觉不明显,但几组数据放在一起,好坏立现。尤其是“上下文冲突率”这个指标,我后来养成了一个习惯——在线上随机抽对话对,让两个人工标注员判断模型有没有在前后矛盾的地方,慢慢积累出这个指标,它非常有参考价值。

5.4 降本提效的一个小众技巧:按消息价值分层存储

最后一个技巧,成本相关。Token成本每天都在烧,但你没必要对所有消息一视同仁。我把消息按价值分了三层:

高价值消息——包含用户身份、预算、决策、关键偏好、合同条款等,必须进摘要、必须可检索,而且摘要刷新时重点保留; 中价值消息——普通业务问答记录,进历史存储,可以参与语义召回,但不保证每次都注入上下文; 低价值消息——闲聊、寒暄、重复确认、无关噪声,只保留最近窗口,不参与检索,甚至可以定期清除。

这个分层的本质是把记忆当成资产管理,而资产是要讲回报率的。把Token花在最值得记住的信息上,系统聪明了,成本也降下来了。我在几个百万级日请求的项目里,靠这一套分层策略,把Token成本压缩了三成以上,同时回答质量没有下降。


写在最后的一个小建议

如果你想快速验证这套设计,别贪大,先挑一个场景坐上三轮以上真对话。拿最简单的滑动窗口启动,什么时候觉得“模型忘了”,再加摘要,什么时候觉得“模型知识不够”,再加检索。每一步都看一眼Token和回答质量的变化,你会对context-mode有非常直观的体感。

我自己做下来最大的心得是:上下文模式的价值不在于技术有多复杂,而在于你给模型看的信息是不是刚刚好。刚好多一点,它会迷茫;刚好少一点,它会失忆。找到那个“刚刚好”的度,是所有对话式AI系统成功的分水岭。希望这篇整理能帮你在做这个判断时,少走一些我走过的弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询