AI 短期记忆:会话上下文如何管理与实现
什么是 AI 的短期记忆
当我们说 AI Agent 有“短期记忆”,其实指的是它在一次任务执行过程中临时记住的信息——比如用户刚说了什么、上一步用了哪个工具、中间结果长什么样。这些内容不会永久保存,任务一结束就清掉,但对当前会话的连贯性至关重要。

没有短期记忆,Agent 就像每句话都从零开始聊天:你让它“查天气然后订机票”,它可能查完天气就忘了后面还要订票。短期记忆就是让 Agent 能把多步操作串起来的关键。
为什么上下文管理是个难题
大模型本身有 token 上限(比如 GPT-4 最多处理 128K tokens),而一次长对话或复杂任务很容易超过这个限制。如果把所有历史一股脑塞进去,要么报错,要么模型注意力分散,关键信息反而被淹没。
所以,上下文管理的核心问题其实是:在有限空间里,保留哪些信息最有用?
常见的上下文管理策略
滑动窗口:只留最近几轮
最简单的方法是只保留最近 N 轮对话。比如设定窗口为 5 轮,那么第 6 轮开始时,第 1 轮的内容就被丢掉。
这种方法实现容易,但有个明显缺陷:如果早期信息很重要(比如用户一开始设定了任务目标),后面就找不回来了。
基于重要性的裁剪
更聪明的做法是判断每条消息的重要性。例如:
- 用户最初的指令通常最关键,要保留
- 工具调用的结果如果是中间步骤,可压缩或摘要
- 纯寒暄(“好的”“明白了”)可以直接删
有些框架会用 LLM 自己来判断:“这段对话对完成当前任务还有用吗?”然后根据回答决定去留。
分块与摘要
当上下文太长时,可以把早期多轮对话合并成一段摘要。比如前 10 轮讨论了需求细节,系统生成一句:“用户需要整理桌面 PDF 并按日期归档”,后续就用这句代替原始记录。
不过摘要也有风险:可能丢失细节。所以一般只对非关键路径做摘要,核心指令仍保留原文。
ReAct 循环中的上下文使用
ReAct(Reasoning + Acting)是目前主流的 Agent 执行范式,它的每一轮都包含三部分:
- 思考(Thought):分析当前状态,决定下一步
- 行动(Action):调用某个工具
- 观察(Observation):获取工具返回结果
这些内容会被逐轮追加到上下文中,形成一条执行轨迹。例如:
任务:帮我查北京明天的天气并建议穿什么
思考:我需要先获取天气数据
行动:调用 weather_api("北京", "明天")
观察:晴,最高 28°C,最低 18°C
思考:根据温度推荐衣物
行动:无(直接回答)
观察:建议穿短袖+薄外套这个轨迹就是短期记忆的主体。系统必须确保整个轨迹在 token 限制内,否则后续步骤无法看到前面的结果。
实现一个带上下文管理的简易 Agent
下面是一个简化版的 ReAct Agent,加入了基本的上下文长度控制:
import tiktoken
class ContextManagedAgent:
def __init__(self, llm, tools, max_tokens=4000):
self.llm = llm
self.tools = {tool.name: tool for tool in tools}
self.max_tokens = max_tokens
self.encoder = tiktoken.encoding_for_model("gpt-3.5-turbo")
def run(self, task):
# 初始上下文包含任务描述
context = f"任务:{task}\n"
for _ in range(10): # 最多10轮
# 检查当前上下文长度
current_tokens = len(self.encoder.encode(context))
if current_tokens > self.max_tokens:
# 简单裁剪:去掉最早的一半内容(实际中应更精细)
lines = context.strip().split('\n')
keep_from = len(lines) // 2
context = '\n'.join(lines[keep_from:]) + '\n'
# 思考
thought_prompt = context + "\n请思考下一步该做什么。如果已完成,请回答‘任务完成:[结果]’"
thought = self.llm.generate(thought_prompt)
if "任务完成" in thought:
return thought.split("任务完成:")[-1].strip()
# 决定行动
action, args = self._parse_action(thought)
# 执行并观察
if action in self.tools:
observation = self.tools[action].execute(args)
else:
observation = f"未知工具:{action}"
# 更新上下文
context += f"\n思考:{thought}\n行动:{action}({args})\n观察:{observation}"
return "达到最大轮数,任务未完成"
def _parse_action(self, thought):
# 简化解析,实际中可用正则或结构化输出
if "weather_api" in thought:
return "weather_api", "北京, 明天"
return "none", ""
这个例子虽然粗糙,但体现了几个关键点:
- 每轮都检查 token 数量
- 超限时做裁剪(这里只是简单砍掉前半部分)
- 把思考、行动、观察完整记录下来
真实系统会用更精细的策略,比如优先保留任务目标、工具调用结果,而压缩中间推理过程。
工具调用与上下文的关系
工具返回的结果往往是上下文里最重要的部分。比如你让 Agent “下载某网页并总结”,那么网页内容本身可能很长,但 Agent 通常不会把全文塞进上下文,而是:
- 先用工具下载
- 让 LLM 对内容做摘要
- 只把摘要放进上下文继续后续操作
这样既保留了关键信息,又控制了长度。
有些框架(如 LangChain)还支持“异步记忆”:工具结果先存到外部存储,上下文里只留一个引用 ID,需要时再取。这适合处理超大结果。
实际工程中的权衡
在真实项目里,上下文管理往往要做以下权衡:
- 保真度 vs 长度:保留原文更准确,但占空间;摘要省空间,但可能失真
- 实时性 vs 完整性:每轮都重新计算摘要很耗时,但一次性裁剪可能误删
- 通用性 vs 场景定制:通用裁剪策略适合多数情况,但特定任务(如代码生成)可能需要保留更多上下文
很多团队会针对核心场景定制策略。比如客服 Agent 会优先保留用户问题和工单号,而数据分析 Agent 则保留 SQL 查询和结果表头。
不要迷信“无限上下文”
现在有些模型宣传“支持百万 token 上下文”,但这不等于可以无脑堆历史。原因有二:
- 注意力稀释:上下文越长,模型对每个 token 的关注度越低,关键信息反而容易被忽略
- 成本飙升:输入越长,API 调用费用越高,延迟也越大
所以即使有长上下文能力,合理的裁剪和摘要仍然是必要的。
小结
AI Agent 的短期记忆不是简单地“记住所有对话”,而是在资源限制下做智能的信息筛选。核心思路是:
- 识别关键信息(初始指令、工具结果)
- 压缩或丢弃次要内容(重复确认、中间推理)
- 动态调整上下文长度,确保不超过模型限制
好的上下文管理能让 Agent 在复杂任务中保持连贯,而不好的管理则会导致“失忆”或混乱。这既是技术问题,也是产品设计问题——毕竟最终目标是让用户觉得 Agent “靠谱”,而不是“记性差”。