
写这个主题前先交代一句背景我在做AI Agent项目时被一个看似不起眼的问题反复折腾了很久——模型回答质量时好时坏同一个问题换个说法结果天差地别甚至有时候它完全忘记了前面聊过的东西。排查到最后问题出在上下文这一层。后来我把整套上下文处理逻辑归纳成了一套可复用的模式内部叫它context-mode效果稳定了不少。这篇文章就是把这套思路完整梳理一遍从概念到落地细节再到坑位排查一次性说透。如果你是做对话系统、Agent开发、状态管理或者任何涉及多轮信息传递场景的技术人这篇文章值得花十分钟慢慢看。即使是刚入门的新手我也会把基础概念讲清楚保证你能跟着落地。1. 上下文模式到底是什么为什么大家都在谈先给一个不算严格但足够实用的定义上下文模式context-mode是指程序在运行过程中对当前场景相关信息进行采集、组织、传递和遗忘的一套处理机制。它不是一个具体的库或者框架更像是一组设计的约定和策略的组合。我在刚接触这个概念时也犯过迷糊——上下文不就是把历史消息拼起来丢给模型吗后来发现远不止这么简单。1.1 一个例子看出问题假设你做一个售前客服机器人。用户进来问你们的服务器多少钱你回复了配置和报价。用户接着问那支持多少并发此时模型需要知道那指的是刚才讨论的那台服务器。如果你没有把上一轮的信息传进去模型大概率会答非所问。再往后用户聊了十分钟咨询了服务器、存储、带宽三个话题。如果不做任何处理把这些全部塞进上下文模型会被大量无关信息干扰如果只保留最后一轮又可能丢失关键约束条件。这就是上下文管理的核心矛盾上下文太长噪声多、成本高太短则信息不足、逻辑断裂。1.2 上下文模式的三个核心目标结合我的实践一套合格的context-mode至少要满足三个目标连续性保证跨轮次、跨模块的信息可追溯不会出现失忆。相关性在当前任务中只暴露必要信息抑制无关干扰。经济性控制上下文的体积避免无谓的token消耗和响应延迟。这三个目标在很多场景下是互相拉扯的。你保留的信息越多连续性越有保障但相关性和经济性就会下降。所以上下文模式的设计本质上是做一套有损压缩与精准检索的平衡方案而不同的业务场景对平衡点的要求并不一样这也解释了为什么市面上没有一套放之四海而皆准的实现。1.3 适合用context-mode解决的典型场景从我的经验看以下四类场景收益最明显多轮对话系统尤其是客服、助理、教育类产品需要长时间维持话题。Agent类的任务编排模型需要在一个流程中多次调用工具、读取结果、修正计划。流式数据处理或事件驱动架构不同模块之间需要共享一份当前状态。各类需要记忆的客户端应用比如IDE里的编程助手编辑器里的上下文补全。如果你正在做的项目属于上述类型并且已经出现了模型忘记上文回答不稳定上下文越用越贵等迹象那你大概率需要一个明确的context-mode方案。2. 四种主流上下文模式的选型思路我不喜欢直接丢结论先讲清楚为什么市面上会有这么多种模式每种模式背后解决的是哪一类问题。这样才能在项目里做对选择。2.1 全量保留模式简单但代价高这是最本能的做法把完整的对话历史或状态快照全部塞进上下文。实现起来几乎零成本也是很多原型项目的默认方案。优点是信息无损任何一轮的引用都能找到出处缺点也最直观——成本随轮次线性增长而且大量早期信息会稀释模型对近期意图的注意力。我见过一个项目调了二十轮以后单次请求的token消耗比最初涨了十几倍延迟从300ms飙到2秒以上用户体感明显变差。2.2 窗口滑动模式控制体积的首选为控制体积窗口滑动是最常见的改进只保留最近N轮对话或最近N条消息更早的直接截断。这个方案实现也不难却能解决大部分对话场景下的上下文膨胀问题。但它有个天生缺陷——长任务中的关键信息一旦被滑出窗口就永久丢失。比如用户在第一轮说了预算不超过五万中间唠唠叨叨聊了十轮别的等聊回采购方案时模型可能已经不记得预算约束了。所以窗口滑动更适合话题短、轮次少、信息时效强的场景。真要用于长任务必须叠加摘要等机制。2.3 摘要压缩模式保重点、省空间摘要压缩模式是在窗口滑动的基础上把将要被淘汰的历史信息做一次提炼生成一段结构化摘要继续保留。举个例子系统每5轮生成一次阶段性总结用户已确认预算五万重点关注服务器与存储暂不考虑网络设备以此替代原始对话内容。这样做的好处很明显既控制了token体积又保住了核心约束。代价是摘要本身就是一种有损压缩如果摘要做得不好细节会静默丢失而且摘要的生成也有额外的延迟与成本。实现上需要注意摘要的层次与更新策略。我建议采用分层结构全局摘要负责用户画像、硬性约束、长期目标局部摘要负责最近若干轮的要点未进入摘要的原始消息仍然短期保留。这样可以兼顾记得住和控得住。2.4 检索增强模式需要的时候才去翻检索增强RAG模式是另一种思路上下文不是一股脑全放进去而是按当前请求去检索最相关的历史片段拼装成当前轮次的上下文。它像一个记忆抽屉平时把大量信息存好使用时只抽出有用的那几页。这种模式适合两类场景一是知识库问答需要从大量文档中寻找答案片段二是长周期、多话题的复杂对话需要跨很多轮次找回早期的某个细节。检索增强模式的核心不在存而在于索引切分、向量化、相似度召回这一整套链路的质量。它的缺点是工程复杂度最高且引入检索本身就会带来误差——召回不准再好的上下文方案也白搭。所以不要把RAG当成万能药只在小规模上下文能覆盖的场景优先考虑前三种模式。2.5 场景选型对照下面这张表是我在实际项目中总结的选型参考可以直接对照你的场景来做决定模式适用场景主要优点主要代价全量保留原型验证、短对话实现简单信息无损成本高噪声大窗口滑动客服短会话、即时问答实现简单控制体积长任务关键信息易丢摘要压缩长对话、任务追踪体积可控保留重点有损压缩需额外开销检索增强知识库问答、超长历史精确定位相关信息工程复杂有召回误差提醒一句这四种模式并不是互斥的。实际项目中我经常叠加使用比如窗口滑动摘要压缩双保险或者摘要缓存检索召回组合关键是要理解每种模式的损耗点在哪里。3. 核心细节拆解上下文里到底该放什么很多实现之所以效果不好根本原因不是技术选型错了而是没有想清楚上下文里到底应该放什么内容。我倾向于把上下文内容分成四个维度每次构建context-mode时我都会先列一张内容清单。3.1 硬性约束信息包括用户明确的限制条件、不可触碰的红线、必须完成的强制性目标。比如预算不超过五万不要推荐Windows系统的服务器必须在今天内出方案。这类信息优先级最高在任何精简策略下都不能丢。我的做法是在上下文中为约束信息设置一个独立的域不随历史消息滑动。就算对话绕了一大圈只要约束域存在模型就不会忘掉这些刚性的前提。3.2 当前任务状态在中长期任务中任务状态是比对话历史更重要的信息。比如已确认CPU配置存储方案待定价格已报价待用户确认。任务状态最好用结构化的数据结构来表示而不是纯文本。从我的经验看结构化状态在摘要、检索和界面展示上都有巨大优势模型理解得也更准。如果状态量超大可以对状态做版本管理——记录每次变更的关键差异而不是每次覆盖全量。这样既保证当前状态的准确性又保留了追溯能力。3.3 最近交互明细最近几轮的完整交互内容仍然需要保留用于理解即时的语气、情绪、措辞等细节信息。这个区间不需要太长一般3到5轮就够。窗口外但又重要的信息交给摘要或检索去处理。这里有个很多人忽略的细节最近交互最好区分用户输入与系统输出两类分别控制保留内容。系统输出往往包含大段的推理过程或工具返回结果这类信息在消息轮次中占比很大但价值密度不高可以只保留系统输出的结论部分而不是保留完整输出。3.4 用户画像与长期记忆如果是长期使用的产品用户画像和长期记忆是决定体验上限的部分。画像包括用户身份、偏好、活跃时间、沟通习惯长期记忆则是从多次会话中沉淀下来的稳定事实。长期记忆的沉淀不能完全依赖模型自动抽取否则会有不少幻觉写进记忆。我建议在每次重要会话结束后维护一个记忆审核队列用规则加人工抽检的方式来保证沉淀质量。3.5 上下文内容的优先级排序综合来看我一般在构建上下文时按以下优先级排序硬性约束不可丢失当前任务状态必须保留最近交互结论尽力保留长期记忆摘要按需加载原始历史消息按窗口控制这个优先级顺序贯穿了context-mode的构建全过程。之后你做任何上下文压缩策略都可以拿这个顺序作为裁切依据先牺牲优先级低的再考虑优先级高的。4. 实操过程从零搭建一套context-mode讲完概念和选型接下来进入最关键的部分——实际怎么落地。我会以一套Python实现的多轮对话系统为例演示从设计到代码的具体流程。这套方案不依赖任何特定框架你可以直接迁移到自己的项目。4.1 定义上下文的数据结构首先给上下文定义一个清晰的数据结构。我的建议是不要用JObject或者自由字典满天飞而是用明确的类来承载语义。from dataclasses import dataclass, field from typing import List, Dict, Any, Optional from enum import Enum class SegmentType(Enum): CONSTRAINT constraint # 硬性约束 TASK_STATE task_state # 任务状态 INTERACTION interaction # 最近交互明细 PROFILE profile # 用户画像与长期记忆 dataclass class ContextSegment: seg_type: SegmentType content: Dict[str, Any] created_at: float updated_at: float version: int 1 dataclass class ContextFrame: session_id: str segments: Dict[str, ContextSegment] field(default_factorydict) def add_segment(self, seg: ContextSegment) - None: key f{seg.seg_type.value}_{seg.created_at} self.segments[key] seg def get_segments(self, seg_type: Optional[SegmentType] None) - List[ContextSegment]: if seg_type is None: return list(self.segments.values()) return [s for s in self.segments.values() if s.seg_type seg_type]这个结构做了几件事区分了上下文的不同语义域保留了创建与更新时间便于做过期淘汰预留了版本字段供将来做状态回滚。结构是所有上层策略的地基所以这里别偷懒。4.2 窗口滑动与摘要压缩的联合实现接下来是核心策略窗口滑动加摘要压缩。我定义一个ContextManager负责处理消息接收、窗口滑动和摘要触发。import time from collections import deque class SlidingWindowWithSummary: def __init__(self, max_window_messages: int 10, summarize_every: int 5): self.max_window max_window_messages self.summarize_every summarize_every self.recent_messages: deque deque(maxlenmax_window_messages) self.summaries: deque deque(maxlen20) self.message_count 0 def add_message(self, role: str, content: str) - None: self.recent_messages.append({ role: role, content: content, ts: time.time(), }) self.message_count 1 if self.message_count % self.summarize_every 0: self._generate_summary() def _generate_summary(self) - None: # 取最近 summarize_every 条消息调用LLM生成摘要 recent_window list(self.recent_messages)[-self.summarize_every:] raw_text \n.join(f{m[role]}: {m[content]} for m in recent_window) summary self._call_llm_for_summary(raw_text) self.summaries.append({ summary: summary, ts: time.time(), range: (self.message_count - self.summarize_every, self.message_count), }) # 可选将已摘要的消息从窗口移除 # 这里用deque自动滑动摘要保留的策略实际项目中可自定义 def _call_llm_for_summary(self, raw_text: str) - str: # 这里接入你的LLM调用示例略 # 建议以独立函数实现便于替换不同模型 return f[摘要占位] {raw_text[:50]}... def build_prompt(self) - str: sections [【历史摘要】] for s in self.summaries: sections.append(s[summary]) sections.append(【最近对话】) for m in self.recent_messages: sections.append(f{m[role]}: {m[content]}) return \n.join(sections)这个类的核心逻辑recent_messages用deque限制最大条数超出自动丢弃最老的summarize_every控制多长时间生成一次摘要避免每次对话都触发LLM调用build_prompt把压缩后的摘要和最近窗口拼接成最终发送给模型的提示词。摘要生成频率是个需要实际调参的点。我项目里一开始用2轮一次结果token省了但摘要太碎调到5轮一次后信息密度高了很多。你在落地时建议从max_window10、summarize_every5起步再根据实际效果调整。4.3 硬性约束的独立保活机制这部分是我特别想强调的。很多项目把约束信息放在对话历史里窗口滑动一裁就丢结果模型说出好的那我推荐一款超预算的方案。要避免这个需要把约束域从滑动窗口中独立出来。class ConstraintGuard: def __init__(self): self.constraints: List[Dict[str, Any]] [] def add_constraint(self, content: str, source: str user) - None: self.constraints.append({ content: content, source: source, added_at: time.time(), }) def update_constraint(self, idx: int, content: str) - None: if 0 idx len(self.constraints): self.constraints[idx][content] content self.constraints[idx][updated_at] time.time() def to_prompt_section(self) - str: if not self.constraints: return lines [【不可违背的用户约束】] for c in self.constraints: lines.append(f- {c[content]}) return \n.join(lines)在拼装最终提示词时ConstraintGuard.to_prompt_section()永远放在最前面不随窗口滑动。这样模型每轮都能先看到不可违背的约束再从摘要和最近对话中获取其他信息。有个细节约束不是一成不变的用户可能在对话中途说预算提到八万吧所以约束域需要支持更新的能力。我的经验是每轮对话结束后跑一个约束抽取器用规则或LLM判断新增内容里有没有约束信息有则add或update避免约束域越积越多。4.4 组装完整上下文最终把以上部件组装起来class ContextMode: def __init__(self, llm_caller): self.llm_caller llm_caller self.window SlidingWindowWithSummary( max_window_messages10, summarize_every5 ) self.constraint_guard ConstraintGuard() def handle_user_message(self, user_text: str) - str: # 1. 先检测用户消息里是否包含新约束 detected self._detect_constraints(user_text) for d in detected: self.constraint_guard.add_constraint(d) # 2. 记录用户消息到窗口 self.window.add_message(user, user_text) # 3. 组装完整prompt prompt self._build_full_prompt() # 4. 调用LLM response self.llm_caller.call(prompt) # 5. 记录响应到窗口 self.window.add_message(assistant, response) return response def _detect_constraints(self, text: str) - List[str]: # 简单规则示例实际项目中可替换为训练好的分类器 keywords [不要, 必须, 不能, 预算, 禁止, 一定] detected [] for kw in keywords: if kw in text: detected.append(text.strip()) break # 实际项目建议加置信度阈值避免误判 return detected def _build_full_prompt(self) - str: parts [] constraint_section self.constraint_guard.to_prompt_section() if constraint_section: parts.append(constraint_section) parts.append(self.window.build_prompt()) return \n\n.join(parts)这就是一个最小可用的context-mode实现也是我项目早期的雏形。先跑通这个流程再逐步加入检索增强、画像管理等更复杂的能力。4.5 不同规模场景下的落地建议代码只是骨架真正落地还要看你的项目规模和团队资源。如果是在做一个轻量级的客服机器人窗口滑动加约束保活基本就够用不需要上摘要和RAG系统的稳定性和可维护性反而更好。如果是做Agent或复杂工作流摘要压缩是标配因为Agent经常需要跨很多工具调用才能完成一个任务中间任何一步状态丢失都会导致整体失败。如果是知识库问答那检索增强模式绕不开。这里我建议检索结果在进入上下文前先做一轮重排不要只拿top-k直接拼接。重排可以用规则比如关键词匹配也可以用小模型过滤实测对最终回答准确率提升明显。5. 常见问题与排查技巧实录上下文相关的Bug往往很隐蔽表现五花八门。这段我记录一些真实的排查经验给你当速查表用。5.1 模型总是忘记早期信息现象对话超过十轮以后用户提到早期设定的偏好或要求模型毫无反应。排查思路先检查窗口是否太小max_window_messages是否已经把关键早期信息滑掉了。再确认关键信息是否被纳入约束域。我的经验是凡是用户明确表达过的硬性条件应该在对话发生时立刻写入约束域而不是等它被滑出窗口后再补救。如果走了摘要压缩还要检查摘要是否真的抓住了要点。有时摘要模型会偷懒只提取了最后几句话的内容前面的关键信息被它丢了。处理建议引入摘要质量抽检机制定期人工查看生成的摘要是否覆盖了核心约束和任务状态摘要生成时在提示词里强制要求按约束/任务状态/其他三个维度输出结构化摘要比自由文本可靠得多。5.2 上下文越长回答质量反而越差现象历史消息很多时模型回答开始答非所问甚至出现自相矛盾的言论。排查思路这通常不是不够长的问题而是噪声太多的问题。模型面对一堆无关信息时注意力被稀释了。你可以做一个实验只把最近3轮对话发给模型看回答质量是否反而回升以此验证是不是噪声干扰。处理建议对上下文做瘦身。把系统输出里的推理过程截断只保留结论把工具返回的原始数据做字段级裁剪对检索召回的文档设置更高的相关度阈值。记住一个原则——上下文不是越多越好而是越相关越好。5.3 摘要内容跟原始对话对不上现象摘要生成后模型依据摘要回答结果用户认为模型理解错了。排查思路这是摘要压缩模式最典型的风险。问题可能出在摘要生成时的信息取舍也可能出在摘要结构不合理导致模型解读偏差。处理建议第一摘要提示词中加一句If any specific numbers or constraints appear in the original messages, keep them verbatim.这类保留关键信息的指令第二摘要生成后做一次关键信息比对把摘要里的数字、名称、时间跟原文做个简单匹配缺失则打回重生成。这个小技巧帮我挽回了不少因为摘要丢信息导致的返工。5.4 上下文状态在不同模块间不一致现象一个Agent流程里有多个步骤每个步骤各自读写了上下文导致最后一步用的上下文和最初几步对不上。排查思路典型的状态同步问题。通常是多个实例各持有一份ContextFrame互相之间没有同步或者共享存储没有加锁导致脏读。处理建议建议将上下文状态收敛到单一数据源各模块只通过统一的读写接口访问不要各自缓存副本。如果性能是瓶颈再引入本地缓存并保证写后失效。这个问题的本质不是上下文模式本身而是并发架构的共享状态管理。5.5 排查工具与日志设计上下文模块的日志很重要但容易被忽略。我建议至少打三类日志上下文快照日志每轮组装完成的prompt完整结构含各段长度和内容摘要方便事后复盘。裁切决策日志窗口滑动时记下被淘汰的消息ID、摘要生成的时间范围和触发原因。约束变更日志约束域每一次增删改都要记下来因为约束丢了是大事查日志能定位到是哪个环节丢的。这套日志在刚开始看起来浪费但一旦线上出问题它能帮你大幅缩短定位时间。我在项目里就因为约束变更日志快速查出过一次上游接口把约束字段置空的Bug否则靠猜不知道要猜多久。6. 从context-mode延伸出去几条进阶玩法如果你已经掌握了上面的基础方案可以在这个框架上继续扩展。这里分享几条我已经验证过或正在尝试的方向。分层上下文机制。把上下文按层级组织L0是全局用户画像L1是当前会话摘要L2是最近窗口明细。每次请求按需加载L0L1部分L2。这个思路尤其适合长期使用的助手类产品能让记忆跨会话生效。上下文版本控制。像代码一样给每轮上下文打版本号如果某轮回答质量很差可以快速回退到上一个版本重试。这在Agent自动化流程中价值很高因为自动流程没法人工干预只能靠版本回退来兜底。多模态上下文的探索。现在的上下文已经不局限于文本了图片、表格、语音都可能进入上下文中。多模态信息如何做摘要、如何检索、如何压缩都是很新且值得投入的方向。用小模型管上下文用大模型管生成。上下文管理摘要、抽取、分类是相对机械的任务不一定非要大模型来做。我实测过某些小模型在摘要任务上表现足够好成本却能降低一个量级。把理解、压缩和生成、创作分开是降本增效的有效手段。讲到这里我只是把context-mode从概念到实践完整过了一遍。做这套东西给我的最大感受是模式本身不值钱值钱的是你做取舍时的判断力——你在什么场景下保留什么、舍弃什么、用什么机制兜底这些决策的质量直接决定了系统的上限。希望这篇文章能让你少走一些我走过的弯路。