Working Memory(工作记忆)
当前 run 内的 context window
Session Memory(会话记忆)
SQLiteSession / 自定义 Backend
Long-term Memory(长期记忆)
向量数据库 / 外部存储
从「Agent 为什么总是失忆」的开发者痛点切入,系统讲解 SDK Memory 模块的核心机制:两种上下文(本地 Context vs LLM Context)的本质区别、四种对话状态管理策略对比、SQLiteSession 的两种存储模式与完整代码示例、session_id 颗粒度设计、WAL 并发安全、SessionSettings 的 Token 成本控制,以及自定义 Session Backend 的扩展路径。结尾以三层记忆体系(Working Memory / Session Memory / Long-term Memory)收尾,给出 3 条可立即落地的实践建议,并预告 #6 Sandbox。
リサーチノート
run() 调用都是一张白纸,你不显式喂给它历史,它就不知道历史存在。Runner.run(..., context=your_object) 传入。这个对象不会发送给 LLM,纯粹是 Python 运行时层面的依赖注入。run() 调用之间,把 LLM 应该知道的历史消息持久化,下次调用时自动喂回去。
| 策略 | 方式 | 适用场景 |
|---|---|---|
result.to_input_list() | 手动把上次结果拼回输入 | 完全自控,灵活但繁琐 |
session(客户端管理) | SDK 自动维护历史,存本地 | 本地持久化,本文重点 |
conversation_id(OpenAI 服务端) | OpenAI 服务器存历史 | 云端对话,无本地存储 |
previous_response_id | 响应链接,指向上条回复 | 轻量级链接,无需完整历史 |
to_input_list() 最直接,代码大概长这样:result = await Runner.run(agent, "第一个问题")
new_input = result.to_input_list() + [{"role": "user", "content": "第二个问题"}]
result2 = await Runner.run(agent, new_input)Session 是 SDK 推荐的持久化方案,核心思路是把历史管理从业务代码里剥离出去3。Session 是一个 Protocol(协议接口),定义了四个方法:# Session Protocol 定义
async def get_items(limit: int | None) -> list[TResponseInputItem]: ...
async def add_items(items: list[TResponseInputItem]): ...
async def pop_item() -> TResponseInputItem | None: ...
async def clear_session(): ...Runner.run(),SDK 自动在每次 run 前读取历史、run 后写入新消息。业务代码完全感知不到这个过程。SQLiteSession,基于 SQLite 实现3,支持两种模式:from agents.memory import SQLiteSession
session = SQLiteSession(session_id="user-123")
# 默认 db_path=':memory:',数据只在进程内存在session = SQLiteSession(
session_id="user-123",
db_path="./conversations.db" # 指定文件路径
)result = await Runner.run(
agent,
"你好,我叫 Alice",
session=session
)
# 第二次 run,SDK 自动注入前面的对话历史
result2 = await Runner.run(
agent,
"你还记得我叫什么吗?",
session=session # 同一个 session 实例
)
# Agent 会正确回答「Alice」to_input_list(),不需要管理消息列表。Session 在后台静默完成了历史的读写。session_id 是关键设计# 用户 Alice 的对话
alice_session = SQLiteSession("alice-2026", db_path="./app.db")
# 用户 Bob 的对话
bob_session = SQLiteSession("bob-2026", db_path="./app.db")
# 两人的历史分开存,互不干扰.db 文件服务多个用户,完全没问题。生产代码里,session_id 通常是用户 ID 或对话 ID,按自己的业务模型定就好。threading.local()),每个线程拥有独立连接,通过 WAL 模式实现并发读写from agents.memory import SQLiteSession, SessionSettings
session = SQLiteSession(
session_id="user-123",
db_path="./app.db",
session_settings=SessionSettings(limit=50) # 只保留最近 50 条消息
)limit=50 是个不错的起点,之后盯着 token 用量按需调。Session 只是一个 Protocol,不和任何具体存储绑定3。只要实现四个方法,任何存储都能接入:from agents.memory import Session
class RedisSession:
def __init__(self, session_id: str, redis_client):
self.session_id = session_id
self.redis = redis_client
async def get_items(self, limit=None):
# 从 Redis 读取历史消息
...
async def add_items(self, items):
# 写入 Redis
...
async def pop_item(self):
# 弹出最新一条
...
async def clear_session(self):
# 清空该 session
...Runner.run() 那边一行代码都不用改。SDK 不在乎底层是 Redis、PostgreSQL 还是向量数据库,它只认 Protocol。context 参数(本地 Context)是运行时的依赖注入容器,用于传递数据库连接、配置对象、用户身份等运行时依赖。它不会被发送给 LLM,也不参与对话历史管理1。session 参数(Memory)则是专门管理 LLM 能「看到」的对话历史。result = await Runner.run(
agent,
"用户输入",
context=AppContext(db=db_conn, user_id=user_id), # 本地依赖,LLM 看不到
session=SQLiteSession(session_id=user_id) # 对话历史,LLM 看得到
)FileSearchTool 或自定义工具对接外部向量库,那是 Tools 模块的事情,不在这里展开。limit 参数可以适当放宽。session_id 通常是 f"{user_id}:{thread_id}",而不是纯用户 ID。这样同一个用户可以开多条独立对话线,互不干扰。limit,控制 Token 成本limit=30 开始试,观察 Token 消耗和对话质量的平衡点。对于长任务 Agent,考虑在业务层实现历史压缩(summarization),先把早期历史浓缩成摘要再存入 session。session.clear_session() 管理对话生命周期clear_session() 清除旧历史,避免无关上下文干扰新任务的推理。这比「不断累积历史直到 token 溢出」要稳定得多。SandboxAgent、Manifest(工作区合约),以及 Docker/Modal/E2B 等托管环境的选择逻辑。
このコンテンツについて、さらに観点や背景を補足しましょう。