agno 如何用 continue_from 回退到历史消息点,或 fork 出新的 run 与 session?
【免费下载链接】agnoBuild, run, and manage agent platforms.项目地址: https://gitcode.com/GitHub_Trending/ag/agno
如果你已经跑完(或跑挂过)一个 agno Agent 的 run,想在某个历史消息位置重新接上去、或者从某个状态分叉出一条互不影响的新对话,就需要用/continue能力:用continue_from指定回退到哪个消息点,用fork=True显式 fork 出新的 sibling run,或用fork_session()把整个 session 复制到一个新 session。本文基于 cookbook 中的三个可运行示例(01_continue_from.py、02_fork_run.py、01_fork_session.py)给出完整操作路径。
前提:先让 run 可回退
/continue系列能力建立在持久化的 run 之上。按 18_checkpointing/README.md 的说明:
- 默认的
checkpoint="runs"只在终态(COMPLETED、PAUSED、CANCELLED、ERROR)写库; checkpoint="tool-batch"在每个 tool batch 之后写一次,这份中途持久化正是/continue能回退、能 fork 的基础;checkpoint="tools"(逐 tool 写)留给 3.0,目前会抛NotImplementedError。
所以示例中的 Agent 统一配置了db和checkpoint="tool-batch"。示例使用的模型为OpenAIResponses(id="gpt-5.4"),运行前需要OPENAI_API_KEY(TEST_LOG.md 中标注了示例依赖该 key)。01 示例用本地 SQLite(tmp/checkpoint_time_travel.db),02 示例用 Postgres:
from agno.agent import Agent from agno.db.sqlite import SqliteDb from agno.models.openai import OpenAIResponses agent = Agent( name="travel-agent", model=OpenAIResponses(id="gpt-5.4"), db=SqliteDb( session_table="checkpoint_demo", db_file="tmp/checkpoint_time_travel.db", ), checkpoint="tool-batch", tools=[get_population], # 示例中的 mock 工具 )continue_from 的三种边界写法
continue_from决定回退到哪个消息位置,有三种写法(见 20_time_travel/README.md):
| 写法 | 语义 |
|---|---|
"end"(默认) | 保留完整 transcript,等同于普通追问 |
"last_user" | 保留到最后一个 user 消息为止,丢弃 user 之后的尾部(tool 会被重新调用) |
K(int) | 保留messages[:K],精确的消息索引边界 |
无论选哪种边界,边界都会自动吸附到 tool-call 安全索引:如果截断会把 assistant 的 tool_call 与其 result 拆散,边界会回移到该 exchange 的开头,保证恢复后的 transcript 始终合法。
回退到历史消息点并继续
先跑一个基础 run,拿到run_id和session_id:
first = await agent.arun(input="What is the population of Paris?") # first.run_id / first.session_id 是后续所有 /continue 操作的定位参数然后用acontinue_run指定边界回退并传入新的input。回退到最后一条 user 消息之后、换一个问题:
rewound = await agent.acontinue_run( run_id=first.run_id, session_id=first.session_id, continue_from="last_user", input="Actually, what is the population of Tokyo instead?", )当"end"/"last_user"落不到你想要的位置时(比如还要多丢掉一组 tool 结果),用数字索引。示例中先用 1 起序号打印first.messages找出目标位置,再把索引从len(run.messages)往前数出 K 传入:
for i, m in enumerate(first.messages or [], start=1): print(f" [{i}] {m.role}: {(m.content or '')[:60]}") rewound_to_index = await agent.acontinue_run( run_id=first.run_id, session_id=first.session_id, continue_from=1, # 只保留第 1 条消息(原始用户问题) input="Instead, what is the population of Tokyo?", )需要说明:对COMPLETED的 run 执行/continue会自动 fork出一个新的 sibling run(保持 "1 run = 1 model loop" 约定),源 run 原样保留。所以这里的rewound并不是在原地改写first,而是同一 session 里的一个新 run。
显式 fork 出新的 run
如果对象不是 COMPLETED run(例如中途被取消、还在 RUNNING),想让/continue明确创建 sibling 而不是原地恢复,传fork=True。02 示例(02_fork_run.py)展示了从已知状态分叉的用法——例如从同一个状态出发跑不同 prompt 做 eval 或 A/B 对比:
fork = await agent.acontinue_run( run_id=original.run_id, session_id=original.session_id, continue_from="last_user", input="What's the weather in Tokyo and Lagos?", )fork 出来的 run 带三个可核对的字段:
run_id:全新的 run id;forked_from_run_id:指向源 run;forked_from_message_index:截断索引;session_id:与源 run 相同——fork 和源 run 共存于同一个 session,session 的runs数组因此形成一个 DAG(每个 fork 通过forked_from_run_id指回来源)。
对应的服务端入口是POST /runs/{run_id}/continue(见 21_fork_session/README.md 中的对照表)。
fork 出新的 session
run 级 fork 是"同一个 session 内多一条 run";如果你想把整个对话复制出去、开一条完全独立的对话线,用fork_session()。它会深拷贝源 session 的每个 run,生成全新的session_id和全新的run_id,源 session 不受影响(对应入口POST /sessions/{session_id}/fork):
original_sid = "trip-planning-original" user_id = "demo-user" # 先在源 session 里积累两轮对话 await agent.arun(input="I'm planning a trip to Japan...", session_id=original_sid, user_id=user_id) await agent.arun(input="Tell me more about Kyoto.", session_id=original_sid, user_id=user_id) # 分叉:拿到一个全新的 session_id new_sid = await agent.afork_session( source_session_id=original_sid, user_id=user_id, )之后可以在两个 session 中各自独立继续,互不影响:
forked_run = await agent.arun( input="Actually, what about Osaka's street food scene?", session_id=new_sid, user_id=user_id, ) original_run = await agent.arun( input="Which Kyoto temples are must-see?", session_id=original_sid, user_id=user_id, )两点注意:
- 源 session 按调用方的
user_id作用域读取——你只能 fork 自己的 session; - 血缘字段有两层:
run.forked_from_session_id记录 run 的最初源 session(跨嵌套 fork 保持不变);session.session_data["forked_from_session_id"]记录直接父 session(每次再 fork 会被覆盖)。对 root → mid → leaf 的链,leaf.runs[*].forked_from_session_id == root,而leaf.session.session_data["forked_from_session_id"] == mid。
fork 与 fork_session 的区分:
fork=True(run 级) | fork_session()(session 级) | |
|---|---|---|
| 粒度 | Run | Session |
| 结果 | 同 session 内的新 sibling run | 含所有 run 副本的新 session |
| 入口 | POST /runs/{run_id}/continue | POST /sessions/{session_id}/fork |
| 血缘字段 | run.forked_from_run_id | run.forked_from_session_id |
验证回退与 fork 的结果
三个示例都给出了同一类核对方式:直接查库里的 session,确认源 run 保留、fork 已加入。
run 级(01 / 02 示例末尾):
session = agent.db.get_session(session_id=first.session_id, session_type="agent") print(f"Runs in session: {len(session.runs or [])} (source preserved, forks added)") # 02 示例进一步逐条打印每个 run 的来源 for r in session.runs or []: marker = f" (forked from {r.forked_from_run_id})" if r.forked_from_run_id else "" print(f" - {r.run_id} [{r.status}]{marker}")判断依据:len(session.runs)随每次 fork 增加;新 run 的forked_from_run_id非空且指向源 run;源 run 的status保持原值。
session 级(01_fork_session.py 末尾):
for sid, label in [(original_sid, "original"), (new_sid, "forked")]: s = agent.db.get_session(session_id=sid, session_type="agent") forked_from_session_id = (s.session_data or {}).get("forked_from_session_id") print(f"{label}: {sid} ({len(s.runs or [])} runs)" + (f" forked_from_session_id={forked_from_session_id}" if forked_from_session_id else ""))判断依据:original 的 run 数不变、无forked_from_session_id;forked session 携带forked_from_session_id,且 run 数与源一致(全部为副本)。
限制与相近能力
continue_from="last_user"会丢掉 user 之后的整段尾部(tool_calls、结果、回复),tool 会被重新调用;如果只想"重跑最后的总结、保留工具结果",用regenerate=True——它只丢弃末尾无 tool 调用的 assistant 回合,永不重调 tool,且总是 fork 出新 run(replace_original=False可让源 run 保持可见以便对比)。对照见 19_regenerate/README.md 中的比较表。- 数字边界
K的取值需要先发现合法索引:直接检查run.messages(示例做法),或调用 checkpoint timeline 接口(参考 03_checkpoint_endpoints.py)。 - 边界自动吸附到 tool-call 安全索引,无需手工保证 K 落在"安全"位置。
- 回退/fork 依赖
checkpoint="tool-batch"提供的中途持久化;它是session.runsJSON 列上的真实写放大,文档建议用于长研究类 run,而非高频闲聊 agent。
运行示例
.venvs/demo/bin/python cookbook/02_agents/20_time_travel/01_continue_from.py .venvs/demo/bin/python cookbook/02_agents/20_time_travel/02_fork_run.py .venvs/demo/bin/python cookbook/02_agents/21_fork_session/01_fork_session.py需要进一步看 checkpoint 与/continue的接口细节,可继续读 18_checkpointing/;只想重做最后一条回答则看 19_regenerate/。
【免费下载链接】agnoBuild, run, and manage agent platforms.项目地址: https://gitcode.com/GitHub_Trending/ag/agno
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考