1. 为什么 context-mode 会成为硬需求先说结论做 LLM 应用和 Agent 开发如果你还没认真对待上下文模式那你的应用大概率撑不过三轮对话或者每天的单量一大成本先把你压垮。context-mode直译就是“上下文模式”。它不是一个具体产品而是一类设计思路的统称——解决的核心问题只有一个模型能看到的输入是有限的但真实业务场景里你让它处理的信息往往远超这个上限。早期大家管这个叫“上下文窗口不够”后来发现窗口可以扩大但扩充窗口解决不了所有问题于是 context-mode 这个词就慢慢成了“上下文管理策略”的代名词。我实际踩过最典型的坑是一次给客户做客服机器人。第一版很天真把用户历史消息一股脑塞给模型对话超过五轮就开始截断截断之后模型完全失忆用户重复问同样的问题体验直接崩盘。当时我以为是模型能力不行后来才意识到问题出在我压根没设计“上下文模式”这个概念。从那以后我开始系统梳理这个问题发现它其实是一套组合拳存储什么、保留多久、如何压缩、怎么注入每一步都需要精心设计。这篇文章会比较长我会结合自己做过的一个真实项目来拆解 context-mode 的完整实现路径。适合正在做 LLM 应用、Agent、智能客服、Copilot 类产品的开发者以及正准备进入这个方向但还没踩过坑的工程师。你看完之后至少能搞清楚三件事context-mode 到底管什么事、国内外主流实现思路分别是什么、以及一个可以抄作业的最小实现长什么样。2. context-mode 的核心实现思路与选型2.1 先想清楚你到底需要哪种上下文模式我见过太多人一上来就搜索“context-mode 开源方案”然后盲目集成某个库最后发现和自己的业务完全对不上。这里必须先做一个判断你的业务属于哪一类上下文模式。按我的经验可以粗暴分成四类窗口模式Sliding Window只保留最近 N 轮对话。适合简单客服、闲聊场景但模型会忘记较早期的关键信息比如用户在第五轮提到的“我要红色的”这种约束。摘要模式Summary Mode把前面的对话压缩成一段摘要再和最近的原始对话一起交给模型。适合信息密度较高、需要长期记忆的场景但摘要本身有信息损失而且摘要质量的波动会影响最终效果。结构化模式Structured Mode把上下文拆分成“预设信息 对话历史 当前输入”三个独立字段甚至进一步把对话历史按主题、时间、实体进行分层存储。这是目前做 Agent 和复杂应用的主流方案。检索模式Retrieval Mode上下文不是线性存的而是向量化之后从库里按需召回。适合知识库问答、文档处理类产品但需要额外的检索服务和排序策略。我最终在项目里采用的是“结构化和检索混用”的模式短期对话走结构化存储长期知识库走向量检索。这个组合适合绝大多数中大型业务场景后面会展开讲。2.2 为什么不能简单依赖大模型的原生长文本能力现在很多模型都宣称支持百万级 token 上下文很多人会问既然上下文窗口这么大直接全塞进去不就行了吗这个想法我能理解但实际工程里行不通原因有三个。第一是成本。百万 token 的输入每次请求的计费完全不可忽视尤其是日活用户上万的场景一个普通客服机器人每轮对话光输入消耗就是几块钱的量级一个月下来是笔大数目。你的商业模型能不能扛住是需要实际核算的。第二是性能。窗口越长首 token 延迟越高。做在线交互产品的人最敏感这个数字用户没耐心等一个超过 5 秒才开始输出的机器人。实测下来上下文从 2K 涨到 8K首字响应时间可能翻倍这是物理规律不是调参能救回来的。第三是注意力稀释。长上下文你塞得越多模型越容易遗漏关键信息。我在项目中做过一个测试在上下文中故意植入一个非常明确的指令“用户所在城市是杭州”然后把一段 3 万字的无关资料也塞进去结果模型在回答时经常忽略城市信息答成“无法获取您的位置”。长窗口不代表长记忆这个认知误区需要尽早破除。所以context-mode 的本质不是“让窗口变大”而是“让有限窗口内的信息更有效”。工程上我们要做的是对进入信息做筛选、压缩和排序让模型始终看到当下最有用的部分。2.3 主流的四层上下文组织架构真正成熟的 context-mode 实现我一般会拆成四层来看。你可以把它想象成一个人的记忆系统而不是一个简单的“输入框”。第一层预设层System / Preset。放最稳定的信息比如角色设定、业务规则、输出格式要求。这部分所有对话共享通常放在最前面因为模型对开头内容的注意力相对最强。第二层场景层Scene。根据当前用户请求动态注入的信息比如用户画像、当前页面、设备信息、历史订单。这层的特点是“和本次请求强相关”不相关就尽量不放。第三层短期记忆层Session。存放当前会话最近的几轮消息通常按原始文本保留最多维持 10~20 轮。第四层长期记忆层Long-term。存放跨会话的重要信息比如用户偏好、历史结论、长期目标。这层通常需要摘要化、结构化甚至向量化后存储在外部系统按需召回。每一层的管理策略都不一样。预设层要控制体积场景层要强调实时抽取短期记忆层是滑动窗口策略最活跃的地方长期记忆层则是检索和摘要技术的用武之地。你看到的很多成熟框架比如 LangChain 的 memory 模块、Dify 的上下文变量、Semantic Kernel 的记忆插件其实底层都是这个逻辑只是封装程度不同。3. 实操过程与核心环节实现3.1 先定数据结构一个最小可用的 context-mode 设计下面我用 Python 写一个最小实现目标是让你看清 context-mode 的骨架。这段代码不依赖任何外部库可以直接跑非常容易迁移进你的项目里。from dataclasses import dataclass, field from typing import List, Dict, Optional from datetime import datetime dataclass class Message: role: str # system / user / assistant content: str timestamp: str field(default_factorylambda: datetime.now().isoformat()) dataclass class ContextBand: 一个典型的 context-mode 上下文结构 preset: List[Message] field(default_factorylist) # 预设层 scene: List[Message] field(default_factorylist) # 场景层 session: List[Message] field(default_factorylist) # 短期记忆层 long_term_summary: Optional[str] None # 长期记忆摘要这个数据结构对应了前面说的分层思想。很多同学会在这里偷懒直接用一个大 List 存消息也能跑。但真正到要优化的时候你会发现没有分层根本无从下手——你想压缩旧消息不知道该压哪一部分你想注入用户画像不知道往哪里插。分层的意义在于管理边界清晰各层可以独立演进策略。3.2 上下文各环节的实现与参数选择接下来是核心怎么把原始对话写进这个结构以及怎么在调用模型前把它拼装成最终的 prompt。我把关键方法写出来class ContextModeEngine: def __init__(self, session_max_len: int 20, summary_threshold: int 10): self.session_max_len session_max_len # session 层最多保留消息条数 self.summary_threshold summary_threshold # 超过多少条后开始触发历史压缩 def add_message(self, ctx: ContextBand, role: str, content: str): ctx.session.append(Message(rolerole, contentcontent)) # 超出长度时把最早的 session 消息交给摘要模块 if len(ctx.session) self.session_max_len: self._rollout_old_messages(ctx) def _rollout_old_messages(self, ctx: ContextBand): overflow 10 # 每次溢出一批避免频繁触发摘要 old_msgs ctx.session[:overflow] ctx.session ctx.session[overflow:] # 这里实际应该调用 LLM 做摘要最简实现里先用一条硬编码文本代替 ctx.long_term_summary self._summarize_chunk(old_msgs, ctx.long_term_summary) def _summarize_chunk(self, msgs: List[Message], prev_summary: Optional[str]): # 真实场景请替换为 LLM 调用f请把{prev_summary}和{msgs}合并成一段不超过300字的摘要 joined ; .join([f{m.role}: {m.content} for m in msgs]) return f[摘要] {prev_summary or } | {joined[:500]} def build_prompt(self, ctx: ContextBand) - str: parts [] if ctx.long_term_summary: parts.append(f[长期记忆] {ctx.long_term_summary}) if ctx.preset: parts.append(\n.join([m.content for m in ctx.preset])) if ctx.scene: parts.append(\n.join([m.content for m in ctx.scene])) if ctx.session: parts.append(\n\n \n.join([f{m.role}: {m.content} for m in ctx.session]) \n) return \n\n.join(parts)这里需要详细解释参数选择的依据这是我在实际项目中容易被问到的点。先说session_max_len 20。这个数字是怎么来的我做过一组对比测试对同一客服场景session 保留 6/10/20/50 轮消息分别看模型在“用户意图理解”和“关键信息召回”两个指标上的表现。结果 20 轮是个性价比拐点再往上效果增益非常有限但 prompt 体积和延迟肉眼可见地上涨。如果你是做简单闲聊建议 6~10 轮就够了如果你是做复杂任务类 Agent可以考虑 30 轮左右但需配合更激进的摘要策略。再说overflow 10。这里的关键是避免每次新增消息都触发摘要让摘要调用过于频繁。很多人的实现是“每超过阈值就整体重做一次摘要”这样又贵又慢。我采用的是批量滚动每次溢出 10 条让摘要模块一次处理一批这样每日 LLM 摘要调用量能控制在一个稳定的频率上。还要注意不要把“溢出消息”直接丢掉它们要进入摘要环节变成长期记忆的一部分。_summarize_chunk方法里我留了一个关键注释真实场景要用 LLM 生成摘要。很多工程同学会觉得“那我自己写个截断不就行了吗”效果确实差很多因为截断丢掉的是逻辑而不是长度。我这里先用占位实现是为了让你看到整体流程。后面我会给你一个可以直接用的摘要 prompt 模板。最后看build_prompt的拼装顺序。我把长期记忆放在最前面然后是预设、场景、短期记忆。这里有个经验最稳定的信息放最前面最易变的信息放最后。长期记忆和预设相当于“定基调”session 里最新的消息是模型最需要优先回应的所以放在离当前输入最近的位置。很多新手的写法是把 system prompt 放最前然后接一堆历史对话最后才是用户当前输入会让模型对当前请求的注意力权重下降需要调整过来。3.3 实操现场的构建步骤与拼接示例下面用一个具体场景走一遍完整流程一个电商导购机器人用户第三次进入会话。第一步初始化上下文并填充预设层ctx ContextBand() ctx.preset [ Message(rolesystem, content你是电商店铺的智能导购请用简洁友好的语气帮助用户选品。), Message(rolesystem, content你掌握的店铺优惠活动全场满299减30新品上市前三天9折。), ]第二步填充场景层。场景层来自业务系统的实时数据也就是用户在进入对话时服务端已经查好的信息ctx.scene [ Message(rolesystem, content[用户画像] 老客近30天消费3笔偏好户外运动类商品。), Message(rolesystem, content[当前上下文] 用户正在浏览商品页户外双肩包价格区间300-600元。), ]第三步模拟两轮用户对话ctx.add_message(ctx, user, 这个包容量多大适合登山用吗) ctx.add_message(ctx, assistant, 这款是40L容量的徒步包设有水袋仓和防雨罩适合单日或两日登山。) ctx.add_message(ctx, user, 那有没有更轻一点的)第四步把上下文构建成最终发给模型的 prompt并打印看看实际效果prompt engine.build_prompt(ctx) print(prompt)输出大概是这样的结构[长期记忆] None 你是电商店铺的智能导购请用简洁友好的语气帮助用户选品。 你掌握的店铺优惠活动全场满299减30新品上市前三天9折。 [用户画像] 老客近30天消费3笔偏好户外运动类商品。 [当前上下文] 用户正在浏览商品页户外双肩包价格区间300-600元。 user: 这个包容量多大适合登山用吗 assistant: 这款是40L容量的徒步包设有水袋仓和防雨罩适合单日或两日登山。 user: 那有没有更轻一点的你看这里并没有把“该用户三年前的购买记录”搬进 prompt只有当前会话的信息。这就是 context-mode 的核心价值有选择地让模型看到最该看的东西。如果这个对话持续到 20 轮以上_rollout_old_messages就会触发最早的 10 条消息会被压缩进长期记忆session 只保留最近 10 条。此时long_term_summary里会有一条类似“用户询问了登山包容量和重量最终关注轻量化产品”的摘要信息。之后模型再回答时既能感知用户早期的需求倾向又不会因为看到完整旧对话而冲淡聚焦点。3.4 给摘要模块的可用提示词模板摘要模块是整个链路里最容易被低估的环节。如果你直接对模型说“请总结这段对话”得到的摘要往往是流水账缺少结构。我实际调过很多版目前比较稳定的是一个带输出约束的 prompt 模板你是一个对话摘要引擎。下面是一段客服/助手的对话片段请按以下要求输出摘要 1. 提取用户的核心诉求与意图变化用一句话概括。 2. 提取用户提供的关键事实如商品偏好、价格预算、时间约束、身份信息逐条列出。 3. 提取尚未解决的承诺/待办事项如“助手答应稍后查找库存”。 4. 输出格式为 核心诉求... 关键事实... 待办事项... 5. 如果和已有摘要合并请保留已有摘要中的有效信息只做增量更新不要重复输出已有内容。 已有摘要 {prev_summary} 当前对话片段 {messages}这个模板有三个关键设计结构化的输出格式保证了摘要能被程序解析和再组织增量更新的约束避免了摘要越滚越长待办事项的提取则保证了 Agent 类应用不会中途丢任务。你可以直接复制到自己的项目里试用按业务字段微调。4. 常见问题与排查技巧实录4.1 模型“失忆”了但排查发现 prompt 里明明有历史内容这是我被问得最多的问题之一。现象是prompt 里明明已经把历史对话拼进去了模型却还是答非所问仿佛完全不记得之前聊过什么。我的排查步骤通常是这样的先确认历史内容在 prompt 中的位置。如果被塞在很长的场景层中间模型的注意力可能被稀释试试把它移到 session 层尾部紧贴着当前用户输入。再确认格式是否清晰。很多人用“换行分隔”但缺少标记我建议用明确的角色标签和格式包裹比如user:/assistant:必要时用代码块包住减少干扰。检查是否“超长截断”。有些实现是偷偷从中间截断的截断之后模型看到的是断裂的信息反而会产生误导。这种情况要把截断策略从“硬截断”改成“摘要压缩”。有一类隐蔽问题需要单独提一下历史消息里的早期指令被后续对话覆盖了。比如用户在第一轮说“我喜欢浅色系”后面几轮都在聊款式模型会把早期的偏好“忘掉”本质不是模型失效而是注意力被近期内容主导了。解决方法是把用户的关键偏好同步写入场景层也就是在做实时抽取时把跨轮次稳定信息提升优先级。这就是为什么要分层——session 里的信息是易变的场景层才放稳定偏好。4.2 摘要越滚越乱长期记忆开始“一本正经地胡说八道”摘要模式用久了以后会出现一种很尴尬的情况摘要里开始出现模型编造的内容。比如原文没提到的价格区间摘要里出现了“用户预算约500元”。这个问题的根源在于摘要模型在压缩时做了过度推断。我的应对策略有三条摘要 prompt 里明确加一条“如果对话中没有明确信息不要推断或补全”。关键业务事实订单号、预算、联系方式不要放在摘要里而是从业务数据库直接查询后放入场景层。摘要只负责语义性内容不负责事实性内容。对摘要增加“事实保鲜期”的概念比如超过 7 天未活跃的摘要不再直接注入而是先做一轮检索判断相关性。这个逻辑可以很简单每次构建 prompt 前计算最后一次活跃时间如果超期就把长期记忆层临时降级。这里有个通用的工程原则不要让 AI 承担它本不该承担的存储职责。摘要只是索引业务数据才应该进数据库。4.3 上下文管理带来额外延迟用户开始流失加了摘要、检索、分层之后每个请求的处理链路变长了很多同学会跑回来问我“这样做到底值不值”我先算一笔账。假设原来每个请求发送 8000 token 的上下文LLM 首 token 响应 4 秒优化后发送 2000 token首 token 响应 1.5 秒。哪怕上下文管理本身耗时 30 毫秒体验也是翻天覆地的提升。用户对“转圈时间”的敏感度远高于你对代码复杂度的敏感度。但前提是上下文管理模块本身不能太慢。我见过有人把摘要、向量检索、数据库查询全部串行执行中间还有几次网络往返最后总耗时比直接发 8000 token 还高。经验是能并行的就并行检索和场景查询同时发起摘要不要每次都做设置触发条件能缓存的结果坚决缓存同一天内同一会话的处理结果可以直接复用。还有一个容易忽略的优化点prompt 模板的稳定部分要提出来预编译。预设层和场景层如果是模板生成的每次请求都重新拼字符串其实无所谓但如果你用了额外的 tokenizer 或长度计算这就是纯浪费存一份预计算结果就好。4.4 常见问题速查表下面把典型问题和对应策略整理成表格方便你回查问题现象可能原因排查思路推荐方案模型忽略早期约束早期信息被后排对话挤占注意力检查消息排列位置将稳定约束提升到场景层或预设层历史内容太长无限制累积对话查看 prompt 发送量设置 session 长度上限触发摘要压缩摘要记录错误事实摘要模型补全了未提及信息抽查摘要内容强制业务事实走数据库查询不让摘要承载上下文注入了无关信息场景层没有做相关性过滤检查场景层构建逻辑按“与本次请求相关”标准动态筛选对话总是超过预期长度summary_threshold 设得过大统计每次请求的 token 数调小阈值更早触发压缩长对话成本暴涨每次请求都塞入完整摘要查看 build_prompt 输出对长期记忆做按需检索不总是全量注入切换用户后出现历史串线会话 ID 未正确隔离检查缓存 key所有上下文缓存必须以 user_id session_id 为维度5. 一些想留给你的经验写到这里关于 context-mode 的工程实现已经讲得比较完整了最后再聊一点个人体会可能会帮你在思路上更快落地。第一个体会是context-mode 首先要解决的是业务问题而不是技术问题。我见过太多团队一开始就引入向量数据库、搞复杂的知识图谱最后发现业务里根本用不上。建议你动手前先画一张“信息流图”用户进来后哪些信息从哪张表取、哪些信息从历史对话里来、哪些信息需要动态生成再决定用哪种模式组合。很多中小项目其实只需要前面的分层结构和摘要逻辑就够了检索模式完全可以等出现“信息太多装不下”的真实场景再引入。第二个体会是上下文模式和具体的模型能力是深度绑定的需要定期重新评估。半年前的模型对长上下文很吃力你可能做了一套激进的压缩策略但这半年模型的长文本能力突飞猛进有些压缩反而变成损失。我现在的习惯是每个季度做一次“上下文消融测试”在相同业务场景下对比不同压缩策略的效果差异然后决定是否调优。最后一个小技巧也是我一直沿用的在生产环境里给每次模型调用记录上下文构成日志。也就是把 build_prompt 的输出连同模型回复一起存下来方便出问题时回放。这个日志比任何监控面板都实在它能帮你快速定位是上下文丢了、排序错了还是模型本身回答偏了。没有这套日志前面讲的所有排查技巧都是纸上谈兵。希望这些内容对你有实际帮助。如果你在实现 context-mode 的过程中碰到一些更具体的问题欢迎带着场景描述来交流我踩过的坑大概率能帮你省点时间。
阅读完成 · 觉得有帮助?