Skip to content

第 12 章 · 记忆 Memory:用户画像与摘要

本章目标:理解记忆与历史/状态的本质区别,掌握 update_memory_on_runenable_agentic_memory 两种记忆模式,学会自定义 MemoryManager、手动管理记忆,并区分"记忆优化"与"会话摘要"两件事。

12.1 记忆是什么:跨会话的用户画像

第 10 章的结论在这里展开:history 记住"聊过什么"(session 维度),memory 记住"这个人是谁"(user 维度)。客服、个人助理类应用里,用户的偏好不该随一次会话结束而消失——"这位用户偏好邮件沟通""他负责支付模块"这类提炼后的事实,就是用户记忆。

官方文档描述的工作机制有四步:

  1. user_id 标识"这些记忆属于谁";
  2. 配置的数据库存储每个用户的记忆记录(默认表名 agno_memories);
  3. 后续 run 中,该用户的记忆被自动加入模型上下文;
  4. 记忆模式决定记录何时被创建、更新或删除。

12.2 两种记忆模式:自动提取 vs 智能体决策

Agno 2.x 提供两种互斥的记忆模式:

模式配置行为
自动模式update_memory_on_run=True每次 run 固定执行一轮记忆提取,把新输入加工成记忆
智能体模式enable_agentic_memory=True给 Agent 增加一组记忆工具,由模型自主决定何时增删改查

二选一

官方明确:两者同时开启时智能体模式优先,自动提取会被跳过。为每个 Agent 只选一种。

python
# memory_basic.py —— 跨会话记忆最小示例
import os
from agno.agent import Agent
from agno.db.sqlite import SqliteDb
from agno.models.openai import OpenAIChat

agent = Agent(
    model=OpenAIChat(
        id="deepseek-chat",
        api_key=os.getenv("DEEPSEEK_API_KEY"),
        base_url="https://api.deepseek.com/v1",
    ),
    db=SqliteDb(db_file="tmp/memory.db"),   # 记忆与会话共用这个库
    update_memory_on_run=True,              # 每次 run 后自动提取记忆
    markdown=True,
)

# 会话 1:陈述事实
agent.print_response(
    "我叫小高,喜欢在周末爬山,工作日只想收到简短的邮件。",
    user_id="gao",          # 关键:记忆挂在 user_id 上
    session_id="onboarding",
)

# 会话 2(不同 session):换了个话题,但画像还在
agent.print_response(
    "以后项目周报应该怎么发给我?",
    user_id="gao",
    session_id="weekly-report",
)   # 模型能答出"简短邮件",因为它读到了 gao 的记忆

注意 user_id 是记忆的钥匙——同一个 user_id 的所有会话共享画像,这正是它区别于 history/state 的地方。

12.3 自定义 MemoryManager:让提取更可控

默认的记忆提取用主模型完成。生产中常见两个诉求:用便宜的小模型做提取自定义"记什么不记什么"的规则。这就要显式构造 MemoryManager

python
# memory_manager.py —— 自定义记忆管理器
import os
from agno.agent import Agent
from agno.db.sqlite import SqliteDb
from agno.memory import MemoryManager            # 已核实的 v2 导入路径
from agno.models.openai import OpenAIChat

manager = MemoryManager(
    # 提取记忆用小模型即可,省钱又快;同样接三方 OpenAI 兼容端点
    model=OpenAIChat(
        id="deepseek-chat",
        api_key=os.getenv("DEEPSEEK_API_KEY"),
        base_url="https://api.deepseek.com/v1",
    ),
    db=SqliteDb(db_file="tmp/memory.db"),
    # 覆盖默认的"记忆捕获指令",收紧提取口径
    memory_capture_instructions=(
        "只记录长期有效的用户事实(身份、职责、稳定偏好),"
        "忽略一次性请求和临时情绪。"
    ),
)

agent = Agent(
    model=OpenAIChat(
        id="deepseek-chat",
        api_key=os.getenv("DEEPSEEK_API_KEY"),
        base_url="https://api.deepseek.com/v1",
    ),
    db=SqliteDb(db_file="tmp/memory.db"),
    memory_manager=manager,      # 挂接自定义管理器
    update_memory_on_run=True,
)

agent.print_response("我最近在纠结要不要换工作", user_id="gao", session_id="s1")
# "纠结换工作"是临时状态,按上面的指令不应被写入长期记忆

12.4 手动管理与检查记忆

调试和合规场景下,你需要直接查看甚至修改记忆内容:

python
# memory_admin.py —— 查看 / 删除用户记忆
from agno.agent import Agent
from agno.db.sqlite import SqliteDb

db = SqliteDb(db_file="tmp/memory.db")

agent = Agent(db=db, update_memory_on_run=True)

# 读:列出某用户的全部记忆
memories = agent.get_user_memories(user_id="gao") or []
for m in memories:
    print(m.memory_id, "->", m.memory)

# 数据库层面也支持清空重建(测试环境常用)
db.clear_memories()

get_user_memories() 返回的是结构化对象(含 memory_idmemory 文本),适合做"用户画像页"、审计导出或删除工作流——官方也提醒这是合规审查的标准入口。

12.5 记忆优化与会话摘要:别混淆的两件事

初学者常把三个"总结"混为一谈,它们在 Agno 中是完全不同的机制:

机制配置作用对象目的
用户记忆update_memory_on_run=True用户事实(跨会话)画像
记忆优化策略agno.memory.strategies.SummarizeStrategy记忆列表本身防止记忆条目无限膨胀,合并/压缩旧记忆
会话摘要enable_session_summaries=True单个会话的历史消息控制 token 成本,长对话降本

记忆优化的思路:记忆条目越攒越多既费上下文又互相矛盾,SummarizeStrategy 会用模型把冗余记忆合并成摘要式的少量条目。而会话摘要针对的是单个会话内部——对话太长时自动生成滚动摘要替代全量历史:

python
# session_summary.py —— 长对话的滚动摘要
import os
from agno.agent import Agent
from agno.db.sqlite import SqliteDb
from agno.models.openai import OpenAIChat

agent = Agent(
    model=OpenAIChat(
        id="deepseek-chat",
        api_key=os.getenv("DEEPSEEK_API_KEY"),
        base_url="https://api.deepseek.com/v1",
    ),
    db=SqliteDb(db_file="tmp/chat.db"),
    enable_session_summaries=True,        # 开启后会话摘要自动生成并更新
    add_history_to_context=False,         # 不再全量携带历史,靠摘要顶上
    markdown=True,
)

sid = "long-chat-001"
for i in range(5):
    agent.print_response(f"我们来讨论第 {i + 1} 个需求点……", session_id=sid)

summary = agent.get_session_summary(session_id=sid)
if summary:
    print(summary.summary)   # 整段对话的浓缩版

12.6 隐私与合规注意事项

记忆系统天然涉及个人信息,上线前必须回答四个问题:

  1. 收集边界memory_capture_instructions 里写清楚只收业务必需的事实,避免"顺手"存下敏感信息;
  2. 隔离:确保 user_id 来自可信的登录体系而不是客户端可控参数,否则 A 用户可伪造 ID 读 B 的记忆;
  3. 可删除:提供"忘记我"入口(按 memory_id 删除或 clear_memories()),对齐 GDPR 类要求;
  4. 留存期限:给记忆表设置定期清理任务,陈年画像往往弊大于利。

本章小结

  • memory 以 user_id 为作用域跨会话共享,与 history(session)/state(session)三者互补;
  • 两种模式二选一:update_memory_on_run=True(固定提取)或 enable_agentic_memory=True(模型自主决策);
  • MemoryManager(model=..., db=...) 支持用独立小模型 + 自定义捕获指令控制提取行为;
  • get_user_memories() 是调试与合规审查的标配入口;
  • 分清三件"总结的事":用户记忆、记忆优化(SummarizeStrategy)、会话摘要(enable_session_summaries)。

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. 用户记忆与聊天历史最核心的区别是?

2. 同时开启 update_memory_on_run=True 和 enable_agentic_memory=True 会发生什么?

3. 想用更便宜的模型做记忆提取并限制"记什么",正确做法是?

4. 关于 enable_session_summaries=True 的作用,正确的是?

🛠️ 动手实践

  1. 分别用"自动模式"与"智能体模式"跑同一段包含 5 句个人信息的自我介绍,对比 get_user_memories() 得到的记忆条数与措辞差异。
  2. memory_capture_instructions 中加入"不得记录任何手机号、身份证号",然后故意发送含假手机号的消息,验证提取器是否遵守规则。
  3. 用脚本向同一 session 连续发送 20 条消息,分别在开启/关闭 enable_session_summaries 时打印 response.metrics,量化摘要机制的省币效果。

完成动手实践后,进入第 13 章:知识库 Knowledge 与 RAG 入门