AI 短期记忆:会话上下文如何管理与实现

3 阅读

什么是 AI 的短期记忆

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

文章配图

没有短期记忆,Agent 就像每句话都从零开始聊天:你让它“查天气然后订机票”,它可能查完天气就忘了后面还要订票。短期记忆就是让 Agent 能把多步操作串起来的关键。

为什么上下文管理是个难题

大模型本身有 token 上限(比如 GPT-4 最多处理 128K tokens),而一次长对话或复杂任务很容易超过这个限制。如果把所有历史一股脑塞进去,要么报错,要么模型注意力分散,关键信息反而被淹没。

所以,上下文管理的核心问题其实是:在有限空间里,保留哪些信息最有用?

常见的上下文管理策略

滑动窗口:只留最近几轮

最简单的方法是只保留最近 N 轮对话。比如设定窗口为 5 轮,那么第 6 轮开始时,第 1 轮的内容就被丢掉。

这种方法实现容易,但有个明显缺陷:如果早期信息很重要(比如用户一开始设定了任务目标),后面就找不回来了。

基于重要性的裁剪

更聪明的做法是判断每条消息的重要性。例如:

  • 用户最初的指令通常最关键,要保留
  • 工具调用的结果如果是中间步骤,可压缩或摘要
  • 纯寒暄(“好的”“明白了”)可以直接删

有些框架会用 LLM 自己来判断:“这段对话对完成当前任务还有用吗?”然后根据回答决定去留。

分块与摘要

当上下文太长时,可以把早期多轮对话合并成一段摘要。比如前 10 轮讨论了需求细节,系统生成一句:“用户需要整理桌面 PDF 并按日期归档”,后续就用这句代替原始记录。

不过摘要也有风险:可能丢失细节。所以一般只对非关键路径做摘要,核心指令仍保留原文。

ReAct 循环中的上下文使用

ReAct(Reasoning + Acting)是目前主流的 Agent 执行范式,它的每一轮都包含三部分:

  1. 思考(Thought):分析当前状态,决定下一步
  2. 行动(Action):调用某个工具
  3. 观察(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 通常不会把全文塞进上下文,而是:

  1. 先用工具下载
  2. 让 LLM 对内容做摘要
  3. 只把摘要放进上下文继续后续操作

这样既保留了关键信息,又控制了长度。

有些框架(如 LangChain)还支持“异步记忆”:工具结果先存到外部存储,上下文里只留一个引用 ID,需要时再取。这适合处理超大结果。

实际工程中的权衡

在真实项目里,上下文管理往往要做以下权衡:

  • 保真度 vs 长度:保留原文更准确,但占空间;摘要省空间,但可能失真
  • 实时性 vs 完整性:每轮都重新计算摘要很耗时,但一次性裁剪可能误删
  • 通用性 vs 场景定制:通用裁剪策略适合多数情况,但特定任务(如代码生成)可能需要保留更多上下文

很多团队会针对核心场景定制策略。比如客服 Agent 会优先保留用户问题和工单号,而数据分析 Agent 则保留 SQL 查询和结果表头。

不要迷信“无限上下文”

现在有些模型宣传“支持百万 token 上下文”,但这不等于可以无脑堆历史。原因有二:

  1. 注意力稀释:上下文越长,模型对每个 token 的关注度越低,关键信息反而容易被忽略
  2. 成本飙升:输入越长,API 调用费用越高,延迟也越大

所以即使有长上下文能力,合理的裁剪和摘要仍然是必要的。

小结

AI Agent 的短期记忆不是简单地“记住所有对话”,而是在资源限制下做智能的信息筛选。核心思路是:

  • 识别关键信息(初始指令、工具结果)
  • 压缩或丢弃次要内容(重复确认、中间推理)
  • 动态调整上下文长度,确保不超过模型限制

好的上下文管理能让 Agent 在复杂任务中保持连贯,而不好的管理则会导致“失忆”或混乱。这既是技术问题,也是产品设计问题——毕竟最终目标是让用户觉得 Agent “靠谱”,而不是“记性差”。