🐸 青蛙编程网
首页 / AI / 智能体 Agent / 9. 记忆与上下文

9. Agent 的记忆与上下文管理

这是什么:上下文(context) = 每次请求发给模型的那段消息历史,它就是 Agent 全部的工作记忆。

为什么用:Agent 每走一步都要重发全部历史,跑十几步就撑爆上下文——记忆问题本质是上下文预算问题。

如何用:先用消息历史当短期记忆,历史的肥了就摘要压缩,跨会话要记的就落到文件或向量库。

9.1 上下文窗口 = Agent 的工作记忆

模型没有真正的记忆。每次请求,它能看到的只有你发过去的那段 messages:

messages = [
  {user: "北京天气怎么样?需要穿什么?"},
  {assistant: tool_calls[get_weather]},
  {tool: "北京:晴,26℃"},
  {assistant: tool_calls[get_tip]},
  {tool: "穿薄外套就行"},
]

这份清单就是 Agent 的全部记忆。把它删了,模型就失忆了;把它撑太长,前面的内容就会被挤出去(或者干脆报错超长)。

上下文状态 后果
太短 忘了自己查过什么,重复劳动
刚好 最佳
太长 token 成本飙升,还可能超出模型上限报错

9.2 短期记忆:就是消息历史

最简单也最常用的一招:把裁剪后的历史一起发过去。

messages.append(msg)                 # 模型的决定
messages.append({"role": "tool", ...}) # 工具的结果
# 下一轮把整个 messages 发过去 —— 记忆就是这么来的

但要注意:工具返回的大块原始数据是最占地方的部分,也是最该被裁掉的。

9.3 摘要压缩:把老对话折叠成一段话

当历史太长,用模型自己把老部分总结成一段话,替换掉明细:

def compress(messages, keep_recent=6):
    """老消息压成一条摘要,最近几条原样保留"""
    if len(messages) <= keep_recent:
        return messages

    old, recent = messages[:-keep_recent], messages[-keep_recent:]
    summary = client.chat.completions.create(
        model=CHAT_MODEL,
        messages=[{
            "role": "user",
            "content": "把下面的对话过程总结成一段话,保留关键结论、"
                       "已执行的动作和结果,200 字以内:\n\n"
                       + "\n".join(str(m.get("content", "")) for m in old)
        }],
    ).choices[0].message.content

    return [{"role": "system", "content": f"【此前过程摘要】{summary}"}] + recent

调用处:

for step in range(max_steps):
    resp = client.chat.completions.create(
        model=CHAT_MODEL,
        messages=compress(messages),   # ← 每次发之前先压
        tools=TOOLS,
    )

压缩的代价:细节丢了。所以要留几条最近的消息原样不动——模型至少还记得「刚刚做了什么」。

9.4 长期记忆:写出去再读回来

Agent 要跨会话记住东西(比如「用户 prefers 简洁回答」),有两个办法:

① 简单做法:写文件

import json
from pathlib import Path

MEMORY_FILE = Path("memory.json")

def remember(fact: str):
    facts = json.loads(MEMORY_FILE.read_text("utf-8")) if MEMORY_FILE.exists() else []
    facts.append(fact)
    MEMORY_FILE.write_text(json.dumps(facts, ensure_ascii=False, indent=2), "utf-8")

def recall() -> list[str]:
    if not MEMORY_FILE.exists():
        return []
    return json.loads(MEMORY_FILE.read_text("utf-8"))

用法:给 Agent 一个 remember 工具,它觉得该记的就自己存;每次新会话开头,把 recall() 的内容作为 system 消息注入。

② 讲究做法:向量库

记忆条目多起来(几百条以上)时,用 RAG 教程 里的那套:切块 → 向量化 → 存 Chroma → 检索时按当前问题召回最相关的几条。本质就是给 Agent 配了个外接大脑。

9.5 该用哪种

场景 用什么
一次任务内的多步协作 消息历史(第 2、3 章那套)
任务跑长了、历史太肥 摘要压缩
跨会话记住用户偏好/事实 写文件(少量)
跨会话记忆量很大 向量库 + 检索(RAG 那套)

先从最简单的开始:绝大多数 Agent 用「消息历史 + 偶尔压缩 + 一个 memory.json」就够了。别一上来就上向量库。

9.6 常见翻车现场

报错 / 现象 原因 修法
context_length_exceeded 消息太长 压缩,或减少工具返回量
Agent 重复做已经做过的事 老结果被压没了 保留最近几条,或在结果里标注「已查过」
摘要越压越糊 反复压缩导致信息层层丢失 只压一次老部分,别压摘要的摘要
记忆里混进垃圾 什么都被 remember 存了 记忆要筛选,别全存
换个会话就失忆 记忆没落到持久化 用文件/数据库存,别只放内存

9.7 动手改

  1. 给你的 Agent 循环加上 compress(),跑一个 10 步的任务,看看上下文长度变化
  2. 加一个 remember 工具,让它把「用户喜欢简短回答」记下来,新会话时能想起来
  3. 把工具返回值里的原始 JSON 换成裁剪版,对比 token 消耗

9.8 小结

  • 上下文窗口就是 Agent 的全部记忆,每轮都要重发
  • 短期记忆 = 消息历史;太长就摘要压缩,但要保留最近几条
  • 长期记忆 = 写到文件或向量库,跨会话时再读回来
  • 压缩有代价:细节会丢,所以要留最近的原文
  • 从最简单做起:消息历史 + 压缩 + 一个 memory.json 足够大多数场景

下一章:综合练习,把前五章拼成一个能干活的 Agent 🐸

📥 下载本章代码

ZIP 内含本章全部代码文件与说明,解压即用,与教程内容同步生成。