目录

05 - 会话与持久化:让 Agent 有记性

一句话总结:用 session_id 管理对话上下文,用 SqliteDb 把对话存下来,Agent 重启也不会失忆。


问题:Agent 天生失忆

默认情况下,Agent 的每次 run()print_response() 都是独立的。它不记得你上一句说了什么,更不记得昨天聊过什么。就像跟一个失忆症患者对话 – 每次都是初次见面。

from agno.agent import Agent
from agno.models.openai import OpenAIChat

agent = Agent(model=OpenAIChat(id="gpt-4o-mini"))

agent.print_response("我叫小明")
agent.print_response("我叫什么名字?")  # 它不知道,因为两次调用完全独立

这在某些场景下没问题 – 比如一次性的翻译、摘要任务。但只要涉及多轮对话,你就需要两样东西:会话管理持久化存储


会话和 session_id

一个 session(会话)就是一串连续的对话,你可以把它理解成微信里的一个聊天窗口。Agno 用 session_id 来标识不同的会话:

  • 相同的 session_id = 同一个对话上下文
  • 不同的 session_id = 全新的对话,互不干扰

光有 session_id 还不够,你还得告诉 Agent “请把历史对话带上”。这就是 add_history_to_context 的作用:

from agno.agent import Agent
from agno.models.openai import OpenAIChat

agent = Agent(
    model=OpenAIChat(id="gpt-4o-mini"),
    instructions="你是一个友好的助手,请记住我们的对话内容",
    add_history_to_context=True,  # 把历史对话塞进上下文
    num_history_runs=5,           # 保留最近 5 轮对话
)

# 同一个 session -- Agent 记得住
agent.print_response("我叫小明", session_id="chat-001")
agent.print_response("我叫什么名字?", session_id="chat-001")  # 它知道你叫小明

# 换一个 session -- Agent 又失忆了
agent.print_response("我叫什么名字?", session_id="chat-002")  # 它不知道

原理很简单:当你指定 session_id="chat-001" 时,Agent 会把这个 session 里之前的对话记录拿出来,拼到发给模型的 messages 里。模型看到了上下文,自然就”记得”了。

如果你不指定 session_id,Agno 会自动生成一个。这意味着同一个 Agent 实例的多次调用默认在同一个 session 里。


用 SQLite 做持久化存储

上面的方案有个致命问题:对话历史只存在于内存中。脚本一结束,什么都没了。下次启动,Agent 照样失忆。

解决方案是给 Agent 接上一个数据库。Agno 内置了 SQLite 支持,一行搞定:

from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.db.sqlite import SqliteDb

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

agent = Agent(
    model=OpenAIChat(id="gpt-4o-mini"),
    instructions="你是一个友好的助手",
    db=db,                          # 接上数据库
    add_history_to_context=True,    # 带上历史
    num_history_runs=10,            # 保留最近 10 轮
)

# 第一次运行脚本:告诉 Agent 一些事情
agent.print_response("我最喜欢的编程语言是Python", session_id="my-session")

现在关闭脚本,重新运行:

# 第二次运行脚本:Agent 还记得
agent.print_response("我最喜欢什么编程语言?", session_id="my-session")
# 它会告诉你:Python。因为对话历史已经存在 SQLite 里了

SqliteDb 会自动在 tmp/agents.db 创建数据库文件和表结构,你不用操心 schema。对话记录、session 状态全都帮你存好了。


Session State:结构化的会话数据

有时候你不光要记住对话历史,还想存一些结构化的数据 – 比如用户的偏好、已讨论的话题、购物车内容等。这就是 session_state 的用武之地。

from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.db.sqlite import SqliteDb
from agno.run import RunContext

def update_user_name(run_context: RunContext, name: str) -> str:
    """记录用户的名字。"""
    if run_context.session_state is not None:
        run_context.session_state["user_name"] = name
    return f"好的,我记住了,你叫{name}"

agent = Agent(
    model=OpenAIChat(id="gpt-4o-mini"),
    db=SqliteDb(db_file="tmp/agents.db"),
    # 初始化结构化状态
    session_state={"user_name": "", "topics_discussed": []},
    # 在指令中引用 session_state 的变量
    instructions="""\
你是一个助手。当前用户名: {user_name}
当用户告诉你名字时,调用 update_user_name 工具记录。
""",
    tools=[update_user_name],
    markdown=True,
)

agent.print_response("我叫小红", session_id="stateful-001")
print(f"当前状态: {agent.get_session_state()}")

几个关键点:

  • session_state 是一个字典,你定义初始结构,Agent 运行时可以通过工具修改它
  • 指令里用 {user_name} 这样的占位符可以直接引用 state 中的值
  • session_state 跟着 session 走,存在数据库里,重启不丢失
  • agent.get_session_state()response.session_state 随时查看当前状态

session_state 和对话历史的区别在于:历史是原始的聊天记录,state 是你提炼出来的结构化信息。两者配合使用效果最好。


控制历史大小

对话历史越长,发给模型的 token 就越多,既费钱又可能撑爆上下文窗口。num_history_runs 就是用来控制这个的:

agent = Agent(
    model=OpenAIChat(id="gpt-4o-mini"),
    db=SqliteDb(db_file="tmp/agents.db"),
    add_history_to_context=True,
    num_history_runs=5,  # 只带最近 5 轮对话,省 token
)

如果不设置 num_history_runs,默认值是 3。对于大多数场景,3 到 10 轮就够了。设太大一是费钱,二是早期对话往往已经不重要了。

一个实用技巧:如果你的对话轮次很多但关键信息集中在最近几轮,保持 num_history_runs 较小就行。如果关键信息分散在整个对话中,考虑用 session_state 把重要信息提取出来,而不是无限扩大历史窗口。


Storage vs Memory:一个重要的区分

到这里你可能会想:这不就是”记忆”吗?是,但只是记忆的一半。

Agno 把”记性”分成了两个层次:

概念 作用 范围
Storage(存储) 保存对话历史和 session 状态 单个 session 内
Memory(记忆) 保存关于用户的长期信息 跨所有 session

打个比方:

  • Storage 就像你的聊天记录 – 你可以翻看和某个人的某次对话说了什么,但每个聊天窗口是独立的
  • Memory 就像你大脑中对这个人的印象 – 不管在哪个聊天窗口,你都知道他叫什么、喜欢什么

今天讲的 dbadd_history_to_contextsession_state 都属于 Storage 的范畴。Agent 能在同一个 session 内记住上下文,但换一个 session 就什么都不知道了。

Memory 解决的是跨 session 的问题:即使开了一个全新的对话,Agent 依然”认识”这个用户。这个下一篇专门讲。


今天学了什么

回顾一下关键概念:

  • session_id 标识一个对话会话,相同 id 的调用共享上下文
  • add_history_to_context=True 让 Agent 把历史对话带进上下文,这是”记住”的前提
  • num_history_runs 控制带多少轮历史,默认 3 轮,按需调整
  • db=SqliteDb(…) 接上数据库,对话历史持久化存储,脚本重启也不丢
  • session_state 用于存储结构化的会话数据,比如用户偏好、计数器等
  • Storage 是 session 级别的,跨 session 的”记忆”需要 Memory 系统

一个快速对照表,帮你选择用什么:

你想做的事 用什么
多轮对话中记住上下文 add_history_to_context=True
对话重启后还能继续 db=SqliteDb(...)
存储结构化数据 session_state={...}
跨对话记住用户信息 Memory(下一篇讲)

生产环境建议用 PostgreSQL 替代 SQLite – Agno 同样内置了 PostgresDb,用法几乎一样,换个连接字符串就行。


下一篇预告

06 - 记忆系统:让 Agent 真正”认识”你

Storage 解决了单个会话内的记忆问题,但用户一开新对话就回到陌生人状态,这体验不行。下一篇我们聊 Memory – 让 Agent 跨越所有会话,真正”认识”每一个用户,记住他们的偏好、习惯和背景信息。从”有记性”进化到”有记忆”。