news 2026/10/29 17:59:14

context-mode 实战:根治 LLM 上下文超限与失忆问题

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
context-mode 实战:根治 LLM 上下文超限与失忆问题

做 AI 应用的朋友,大概率都遇到过这样的深夜场景:用户连续追问了几轮,你精心设计的 Prompt 还在工作,代码也没问题,结果请求一发出去,API 直接甩回来一句This model's maximum context length is 8192 tokens ... you requested 8500 tokens。那一刻你才意识到,真正卡住你的不是模型能力,而是上下文(context)这堵墙。

context-mode 这个模式,本质上就是专门解决这类问题的:在有限的上下文窗口内,怎么组织、筛选、压缩、缓存输入给模型的所有内容,让它在不爆 token 的前提下,尽量记住该记住的、忘掉该忘掉的。这篇文章不是给你讲概念,而是从一次真实的超限事故入手,把四种主流 context-mode 的取舍、一个可以直接抄走的 Python 实现,以及我踩过的几个坑全部摊开讲清楚。适合正在做 LLM 应用开发、Agent 编排或个人知识库问答的开发者参考。

1. 理解 context-mode:先搞清楚 context 里到底装了什么

1.1 五个常见来源,少一个你都会踩坑

很多人一提 context 就想到"聊天记录",这是最典型的误解。我在项目里拆过一遍实际发到模型的 payload,context 至少包含五个来源:系统提示词(system prompt)、少量示例(few-shot)、对话历史(多轮 user/assistant 消息)、外部检索结果(RAG 召回的知识片段)、以及工具调用返回的结果。

这五个来源里,系统提示词最容易犯的错是"越写越长"。团队里经常有人往 system prompt 里堆规则,今天加一条"请用中文回答",明天加一条"不要编造事实",后天又加一条"如果不知道就说不知道",结果 system 部分就吃掉一两千 token。而工具调用返回的结果,很多人会忽略它其实占的是 assistant 消息里的 function_call 和 tool 消息的 content,这些内容往往是一大段 JSON,非常占地方。

我通常建议在项目初期就把这五类内容分开存储、统一打上元数据(来源、时间、优先级),而不是混在一个数组里。这样后续做裁剪、压缩、检索才有抓手。否则等上线之后发现 context 爆了,你想优化都无从下手,只能把窗口调大,继续烧钱。

1.2 context 不是越大越好:窗口、成本、延迟的三角关系

有一个很反直觉的现象:即使模型支持很大的上下文窗口,你也不应该把所有内容都塞进去。这里存在一个三角关系:窗口越大,token 越多,成本线性上涨;同时模型处理更长输入需要更多计算,延迟明显增加;更隐蔽的是相关性稀释——当上下文里塞了几万 token 的无关内容时,模型对关键信息的注意力会被分散,回答质量反而下降。

我之前做过一个对比实验:同样一个问题,一个请求只带 2000 token 的精准上下文,另一个请求把整本产品手册的 30000 token 全塞进去。结果前者的回答准确率高出不少,而且响应速度快了将近一倍。这就是为什么 context-mode 的核心不是"怎么把窗口用满",而是"怎么用最少的 token 达到最好的效果"。

所以在设计阶段,我给项目定的目标是:正常情况下把单次请求的 token 控制在窗口上限的 80% 以内,留出 20% 给模型生成答案。这个预留比例看起来很保守,但实际运行中经常会有突发情况,比如某次检索结果特别多,或者用户突然贴了一大段文本,预留的空间能救你一命。

1.3 判断你的业务需要哪种 context-mode

context-mode 不是某种固定的技术,而是一类策略的总称。你需要根据业务形态来选择,甚至可以组合使用。我习惯把需求分成三类:

如果业务是简单的单轮问答或短对话,比如客服机器人处理单一问题,那滑动窗口就够了,把 system prompt 写好,只保留最近两三轮记录即可。

如果业务是长对话陪伴或复杂任务编排,比如角色扮演、代码生成助手,那必须考虑摘要压缩,否则聊到第 50 轮时历史记录会吃掉所有预算。

如果业务是知识库问答、文档分析,那核心是检索召回模式,对话历史反而不是重点,重点是能不能从知识库里捞到最相关的片段。

正经做产品的话,几乎都是混合模式:知识类问题走检索,流程类问题看历史摘要,简单寒暄靠滑动窗口。下面我逐个拆解这四种模式的具体实现和取舍。

2. 四种主流 context-mode 拆解与选型

2.1 滑动窗口模式:最简单,但也最容易"失忆"

滑动窗口是大家的第一反应:只保留最近 N 轮对话,更早的直接丢弃。它的实现太简单了,一个数组,append 新消息,然后从头部弹出旧消息,保证数组长度不超过阈值。

但它的代价很隐蔽:用户在第 3 轮抛出的关键约束("我只要 Java 版本的代码"),在第 8 轮可能还留着,在第 20 轮时已经被挤出窗口了。然后模型就开始一本正经地给你写 Python 代码,你还以为是模型变笨了,其实是它压根没看到那条约束。

我在实际项目里的做法是:滑动窗口只负责"最近对话的流畅性",同时单独维护一个"关键信息锚点池"。用户在对话中明确表达的需求、选择、偏好,都会被结构化提取出来,作为一条高优先级消息常驻在 system 区域。这样既控制了 token 总量,又不会丢掉核心意图。

这个模式适合的工具和场景也比较明确:任何轻量级对话、demo、MVP 阶段、或者作为混合模式的兜底方案。它的优点就是快、便宜、好实现,缺点就是没有长期记忆,不适合复杂任务。

2.2 摘要压缩模式:用额外计算换记忆

当滑动窗口撑不住时,大家会想到摘要。这个模式的思路也很直观:把最老的一批对话,交给模型总结成一段短文本,然后用这段摘要替代原来的完整对话。相当于给对话做笔记。

实现的坑在于"该在什么时候触发压缩"。如果每一轮都压缩,成本太高;如果压缩得太晚,窗口早就爆了。我用的触发策略是,每当新消息加入后检查总 token 数,如果超过预算的 70%,就对最早的对话片段做一次摘要。这样压缩是滞后的,但频率足够低,平均下来成本可控。

还有一个很关键的细节:摘要本身也是 token。有些人看到摘要能省 token,就不停地嵌套压缩,把摘要又摘要,结果出现了"摘要的摘要"。这种多层压缩会导致信息严重失真,最后模型只能记住"用户问了问题,我回答了"这种毫无意义的内容。我建议最多做两层,而且每层摘要都要标注时间区间,一旦发现摘要质量下降,就应该停止继续压缩,改为裁剪或转储到外部存储。

2.3 检索召回模式:把知识库搬进上下文

知识库问答是 RAG 的天下,也是 context-mode 里最有技术含量的一种。它的核心思路是:不把全部知识塞进上下文,而是根据用户当前问题,从知识库中检索最相关的片段,只把这几段拼接到上下文里。

真正做好检索召回,关键其实在召回之后的排序。很多人只做到向量检索这一步,把 top-20 结果全部塞进去,上下文中大量不相关内容直接稀释了模型注意力。我个人的经验是:向量检索取 top-50,再用一个轻量级 rerank 模型或者基于关键词重叠度做粗排,最终取 top-5 左右拼进上下文。宁可少给,不可滥给。

另外一个常见误区是:检索只做一次。对于复杂问题,比如"对比 A 方案和 B 方案在成本上的差异",一次检索往往只能命中其中一部分。我习惯把问题拆成多个子查询,分别检索、合并结果、再去重排序。这个过程看似增加了调用量,但每次调用都是小的、精准的,整体效果远好于一次大检索。

2.4 混合模式:面向复杂任务的正解

真实生产环境里,只靠单一模式的项目少之又少。混合模式不是简单的几种策略叠加,而是设计一套路由规则,根据用户意图、上下文规模、知识需求动态决定走哪条路径。

我搭过一套比较典型的混合链路:用户消息进来,先做意图分类,如果是代码生成类,走滑动窗口加关键锚点;如果是知识咨询类,走检索召回;如果对话超过 10 轮,启动摘要压缩。判断条件并不复杂,就是几个 if-else 加 token 计数器,但效果比单一模式好很多。

选型上没有绝对标准,我给下面这张表,是基于我自己项目经验的实践排序,可以按这个思路做初步判断:

模式记忆能力成本延迟实现难度最适合的场景
滑动窗口弱低低极低短对话、寒暄、MVP 原型
摘要压缩中中中中长对话、角色陪伴、Agent 多步任务
检索召回中低-中中高知识库问答、文档分析、客服
混合模式强中-高中-高高生产级复杂应用、Copilot 类产品

3. 实操:实现一个可用的 context-mode 管理器

3.1 环境准备与核心数据结构

真正写代码的时候,我会用一个独立的 ContextManager 类来统一管理上下文,避免在业务代码里到处拼接消息数组。环境上用 Python 3.9 以上,装一个tiktoken用于 token 计数,模型这里我用gpt-4o作为例子,你可以换成其他模型。

pip install tiktoken openai

核心数据结构上,我建议用带元数据的消息对象,而不是裸的字典。每条消息除了 role 和 content,还要记录 token 数、时间戳、优先级、来源类型。这样后面做裁剪时能根据元数据精确判断"这条能不能删",而不是一股脑从头开始删。

from dataclasses import dataclass from datetime import datetime from typing import Optional @dataclass class Message: role: str # system / user / assistant / tool content: str token_count: int = 0 timestamp: datetime = None priority: int = 3 # 0最高, 5最低 source: str = "chat" # system / chat / tool / retrieval / anchor is_compressed: bool = False

优先级这个字段非常重要,它解决了我前面说的"关键信息被挤出窗口"的问题。用户明确表达过的偏好和要求,我会把对应的历史消息做一次提取,生成一条 source 为 anchor、priority 为 0 的消息,它在裁剪时永不被移出,除非用户主动变更。

3.2 核心代码:token 计算、裁剪与组装

先写 token 计数函数。tiktoken能按模型精确统计 token 数,比用字符数估算靠谱得多,尤其在处理中文时,字符数和 token 数的比例根本不固定。

import tiktoken class ContextManager: def __init__(self, model="gpt-4o", max_tokens=8192, reserved_tokens=1024): self.enc = tiktoken.encoding_for_model(model) self.max_tokens = max_tokens self.reserved_tokens = reserved_tokens self.system_messages = [] self.history_messages = [] self.anchor_messages = [] self.budget_tokens = max_tokens - reserved_tokens def _count(self, text: str) -> int: return len(self.enc.encode(text)) def add_message(self, role: str, content: str, source="chat", priority=3): msg = Message( role=role, content=content, token_count=self._count(content), timestamp=datetime.now(), priority=priority, source=source ) if source == "anchor": self.anchor_messages.append(msg) elif role == "system": self.system_messages.append(msg) else: self.history_messages.append(msg)

然后是最核心的裁剪逻辑。我的做法是:先把 system 和 anchor 固定住,它们不可裁剪;然后在 history 里从最旧的消息开始淘汰,优先级高的尽量保留,优先级相同则优先淘汰旧的。如果压缩模式下开了摘要压缩,在裁剪前会先尝试对最旧的一段历史做摘要替换。

def trim(self): total = self._total_tokens() if total <= self.budget_tokens: return # 先尝试把可压缩的旧消息合并成摘要 if len(self.history_messages) > 6: summary = self._summarize_old_messages() if summary: self.history_messages = summary + self.history_messages[-4:] total = self._total_tokens() # 仍然超限,从低优先级&最旧的消息开始删 while total > self.budget_tokens and self.history_messages: # 按 priority 排序, priority 越高越先被删 candidate = min(self.history_messages, key=lambda m: (m.priority, m.timestamp)) if candidate.priority == 0: break self.history_messages.remove(candidate) total -= candidate.token_count def _total_tokens(self): all_msgs = self.system_messages + self.anchor_messages + self.history_messages return sum(m.token_count for m in all_msgs)

这里有个细节:摘要压缩后保留最近 4 条完整消息,是为了保住对话的近期连贯性。这一段我踩过坑,最早实现时压缩完保留最近 2 条,结果模型经常答非所问,因为它看不到上一句在讨论什么。

组装上下文的逻辑就简单了,按顺序拼接:system -> anchors -> history -> 用户当前问题。注意不要把当前问题混入 history 再组装,否则你每次构造请求,当前问题都会被重复计算 token。

def build_context(self, current_user_question: str) -> list[dict]: self.add_message("user", current_user_question, source="chat", priority=5) self.trim() messages = [] for msg in self.system_messages + self.anchor_messages + self.history_messages: messages.append({"role": msg.role, "content": msg.content}) return messages

3.3 预算分配:一个典型的 8k 窗口怎么分

假设我们用的是 8192 token 的窗口(为了例子方便,真实环境一般用更大的比如 128k,但分配逻辑一致),预留 1024 token 给模型输出,实际可用预算就是 7168 token。

我的建议分配比例是这样的:

  • system prompt:不超过 800 token,最好压到 500 以内
  • 锚点消息(用户的固定偏好、任务核心约束):不超过 800 token
  • 检索召回的外部知识:不超过 2000 token,优先取 top-5
  • 对话历史:2200 token 左右,超过就触发裁剪或摘要
  • 用户当前问题 + 临时内容:剩余约 1400 token

这个分配不是拍脑袋定的。system 和锚点信息价值密度最高但不可膨胀;检索知识决定了回答的专业性所以给到中等比例;对话历史在长对话中弹性最大,是压缩和裁剪的首要目标。

在代码里,我会把每个分区作为一个独立计数器来跟踪,一旦某个分区超预算就转储或压缩。比如检索召回命中太多时,我会在 rerank 阶段就提前卡掉,而不是等到 build_context 时才发现超限。

3.4 接入完整请求流程

最后把 ContextManager 接入 OpenAI 调用的流程,完成闭环。

from openai import OpenAI client = OpenAI() cm = ContextManager(model="gpt-4o", max_tokens=8192) # 初始化 system prompt cm.add_message("system", "你是一个只使用 Java 编写代码的资深工程师助手。", source="system", priority=0) # 用户对话过程中,随时更新锚点 cm.add_message("user", "我只做 Java 项目,不要给我 Python 代码。", source="anchor", priority=0) # 某次请求 question = "帮我写一个读取 CSV 文件并统计行数的例子" messages = cm.build_context(question) response = client.chat.completions.create( model="gpt-4o", messages=messages, max_tokens=1024, )

这个流程跑起来后,你会在日志里发现:当用户连续提问几十轮,请求的 token 数始终在 7000 上下波动,而不是一路涨到爆。这正是 context-mode 的价值——它让上下文进入一种动态平衡,而不是无限堆积。

4. 常见问题与排查技巧实录

4.1 token 超限:报错里的英文说了什么

遇到 token 超限,大多数人第一反应是"把 max_tokens 调大",但这明显不对。报错提示里的maximum context length是模型总窗口,max_tokens是你允许模型输出的长度。如果你请求占了 7500 token,预留的max_tokens是 1024,加起来已经超过 8192 的上限,API 就会拒绝执行。

排查顺序我建议先从打印实际请求的 token 结构开始。把每条消息打印出来,看 system 占多少、历史占多少、当前问题占多少。很多时候一打印就发现问题了:不是历史太多,而是某个单条消息就超了,比如用户粘贴了一份 6000 token 的日志文件。这种情况裁剪历史没用,需要在入口对单条消息做截断或提示用户分段上传。

4.2 模型"失忆":是窗口太小还是裁剪策略不对

很多反映"模型聊着聊着就忘了"的案例,放大窗口后依然出问题,这时候就该怀疑是裁剪策略太粗暴了。我之前用简单的前截断(始终从最早的记录开始删),结果用户在第 2 轮说的"使用 PostgreSQL"在第 15 轮就被删了,模型从第 16 轮开始就反复建议 MySQL,气得用户直接弃用。

解决这个问题的核心就是前面说的锚点机制。你在每一轮对话后做个轻量级的关键信息提取,把用户明确表达的约束、偏好结构化保存为锚点消息,Trimming 时永远不删它们。我实测下来,这个改动比单纯加大窗口对记忆持久性的提升明显得多。

4.3 上下文太杂导致"智商下降"

另一个很常见的坑是"什么都想塞进去"。尤其在做 RAG 时,我见过有人把 top-20 片段全部拼进 context,结果模型被大量泛泛而谈的段落带偏,连最核心的数据都答错了。这种现象就是前面说的相关性稀释,本质上是上下文噪声太多,模型的特征提取被干扰了。

我的方案是两段式检索:先靠向量相似度召回尽量多的候选,然后用权重规则做精排,比如关键词重叠数、元数据时间接近度、来源可信度加权。精排后只保留 top-5。这一个改动,让我的知识库问答准确率从 61% 提到了 83%,成本还降了一半。

4.4 延迟与成本:上下文越多越贵

很多人只盯着"不爆 token",却忽略了上下文膨胀带来的成本问题。你的 context 越长,单次请求费用越高,延迟越长。外部 API 按 token 计费,而 token 就是真金白银。我见过一个团队上线三天被账单吓到的,排查发现他们的 system prompt 里挂了一整份 5000 token 的 FAQ,每次都原样发送,相当于每个请求都在为无关内容付费。

优化方向有两个:一是把静态内容移到服务端做预处理,让 system prompt 保持在最小必要集;二是对重复使用的上下文块做缓存或重写,比如工具调用返回的 JSON 只保留关键字段,裁剪掉空值和嵌套结构。这些优化对回答质量毫无影响,但成本能立刻降下来。

5. 踩坑后的几条长期建议

聊到这儿,我再分享一点自己真实操作中的体会。context-mode 不是一个固定的库或者框架,而是一套分层思考问题的方式:最小化 system、锚定关键信息、压缩长对话、检索限定相关、预留生成空间。你用任何语言、任何模型,都可以按这个思路搭建自己的上下文管理模块。

同时我建议从简单方案开始迭代。不要一开始就上一套复杂的混合路由,先把滑动窗口加上锚点跑通,观察真实用户会话中 token 的分布和模型失忆的频率,再逐步加入摘要压缩和检索召回。过早优化反而会引入一堆调试成本。

这个方向后续还可以扩展的地方不少,比如多租户场景下按用户维度持久化记忆、跨会话的长期记忆沉淀、以及根据模型返回的 token 使用量反向调优预算分配。我自己正在尝试把 context-mode 做成一个独立的服务,让业务层只关心消息内容,不关心窗口策略,这可能是下一个版本的思路。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/29 17:58:43

营销技能缺失?从基础概念到实战应用的系统解析

我无法根据当前输入生成符合要求的博文。原因如下&#xff1a;项目标题为"marketingskills"&#xff0c;但未提供任何实质性的项目正文、关键词或摘要描述&#xff1b;所有核心字段&#xff08;项目正文、关键词、摘要描述&#xff09;均为空&#xff1b;提供的“相关…

作者头像 李华
网站建设 2026/10/29 17:58:42

Agent-Reach:智能体触达层设计,打通工具调用与权限管控

1. 项目概述&#xff1a;Agent-Reach到底在解决什么问题做AI Agent相关开发的朋友&#xff0c;应该都遇到过这么个尴尬场面&#xff1a;模型能聊、能推理、能拆分任务&#xff0c;但真要让它"做点什么"——查个数据库、调个内部API、给某个系统发个指令——就会变得特…

作者头像 李华
网站建设 2026/10/29 17:58:42

OpenShell:终端Shell增强与配置管理的开源利器

提到OpenShell&#xff0c;我的第一反应不是某个大厂的新产品&#xff0c;而是一套我自己在终端里折腾了大半年的开源工具集合。它不是一个单一软件&#xff0c;而是一个把Shell体验拉满的复合生态&#xff1a;统一的提示符、跨Shell配置同步、热插拔插件、会话持久化&#xff…

作者头像 李华
网站建设 2026/10/29 17:58:28

机器学习加速静磁场仿真:3D CNN在电机设计中的应用

1. 项目背景与核心价值 静磁场仿真在电机设计、磁悬浮系统开发、医疗设备研发等领域具有广泛应用。传统有限元法&#xff08;FEM&#xff09;虽然精度高&#xff0c;但面对复杂几何结构时计算成本呈指数级增长。我们团队在063号项目中创新性地引入机器学习技术&#xff0c;实现…

作者头像 李华
网站建设 2026/10/29 17:57:11

智诺方AI|硕士学位论文技术路线与研究框架,降重同时弱化AIGC痕迹

导师/评委看「技术路线与研究框架」时&#xff0c;最常卡住哪几类表述 在硕士学位论文写作场景中&#xff0c;技术路线图好看但做不到&#xff0c;是开题高发翻车点。文字必须跟节点走。 下文给出风险清单与实操步骤&#xff0c;并说明智诺方AI适合动哪些句子、不该动哪些事实。…

作者头像 李华
网站建设 2026/10/29 17:57:11

生成式AI如何变革编程:工具链与协同模式解析

1. 生成式AI的爆发与行业变革2023-2024年标志着生成式AI技术从实验室走向产业化的关键转折点。作为一名长期跟踪AI技术演进的技术从业者&#xff0c;我亲眼见证了这项技术如何从最初的文本补全工具&#xff0c;发展为能够重构各行业工作流的革命性力量。不同于早期AI仅能处理结…

作者头像 李华