agno Team 回答重生成(Regenerate)实战:重新生成团队最终回答而不重跑成员委派
【免费下载链接】agnoBuild, run, and manage agent platforms.项目地址: https://gitcode.com/GitHub_Trending/ag/agno
团队型 Agent 在多次与用户交互时,往往会出现"委派结果都对,但最后的总结答得不好"的情形。agno 为 Team 提供了一等公民的Regenerate(重生成)能力:通过Team.continue_run(..., regenerate=True)只丢弃上一次运行末尾的纯文本回答,保留中间全部工具交换(即各成员 Agent 的委派结果),用一个全新的run_id重新生成一份最终答案——成员不会被重复委派、中间结果不会被浪费。本文基于仓库中的 Team Regenerate 官方示例,完整讲解它的触发方式、replace_original可见性语义、fork 血统与成员行的保留规则,并下沉到 libs/agno/agno/team/_run.py 与 libs/agno/agno/run/team.py 的源码实现,帮助你在自己的多 Agent 应用中安全地使用"只重写结论、不重跑过程"。
一、什么是 Team Regenerate:能力边界一图看懂
根据 24_regenerate 目录下的 README,regenerate=True的语义可拆成三点:
- 只丢弃尾部的助手回答:Team 运行中会产生多条消息,其中末尾那一条"不含任何 tool_calls 的纯文本 assistant 消息"就是最终总结。Regenerate 只把它丢掉。
- 保留中间工具交换:成员委派在 Team 视角下就是"调用了一个工具",成员输出以 tool-role 消息形式出现在 Team 会话中。这些中间消息原样保留,因此成员委派不会重新执行,模型基于同一份委派结果生成全新总结。
- 总是 fork:重生成不是在原 run 上原地改写,而是产生一个新的
run_id与全新的RunMetrics,源 run 永远被保留在存储中。
一句话概括:Regenerate = "同样的事实(成员输出),换一种说法(新的最终总结)"。
二、运行前准备与示例文件结构
示例位于 cookbook/03_teams/24_regenerate/01_regenerate.py,该目录同时包含 TEST_LOG.md 用于记录验证状态。运行方式:
.venvs/demo/bin/python cookbook/03_teams/24_regenerate/01_regenerate.py需要注意两点前提:
- 示例使用
OpenAIResponses(id="gpt-5.4"),依赖OPENAI_API_KEY环境变量;据 TEST_LOG 记录,该脚本在无 Key 环境下仅做语法/编译校验(Status: NOT RUN)。 - Regenerate 依赖会话持久化,成员与 Team 都配置了
SqliteDb;该功能建立在 23_checkpointing 团队断点续跑 基础之上,README 亦明确标注 Foundation 为该目录。
三、分步实战:原始运行 → Regenerate → 会话检查
示例脚本演示了"旅游问答团队"(天气 + 人口两个子 Agent)的完整重生成链路,值得逐段拆解。
3.1 构造成员与团队(含持久化)
weather_agent = Agent( name="weather-agent", role="Answers weather questions.", model=OpenAIResponses(id="gpt-5.4"), tools=[get_weather], db=SqliteDb(session_table="team_demo", db_file=DB_FILE), ) population_agent = Agent( name="population-agent", role="Answers population questions.", model=OpenAIResponses(id="gpt-5.4"), tools=[get_population], db=SqliteDb(session_table="team_demo", db_file=DB_FILE), ) team = Team( name="travel-team", model=OpenAIResponses(id="gpt-5.4"), members=[weather_agent, population_agent], db=SqliteDb(session_table="team_demo", db_file=DB_FILE), instructions=( "Delegate weather questions to weather-agent and population " "questions to population-agent. Summarize in one sentence." ), )要点:三个对象共享同一个SqliteDb(session_table="team_demo", db_file=DB_FILE),其中DB_FILE = f"tmp/team_regenerate_{int(time.time())}.db"按时间戳生成独立库文件,便于反复试验;团队成员get_weather/get_population是纯内存 mock 函数(Paris/Tokyo/Lagos 三城市),确保流程可离线重现。
3.2 STEP 1:创建原始 run
original = await team.arun( input="What's the weather and population of Paris?", session_id="team-sess-1", ) print(f" run_id: {original.run_id}") print(f" content: {original.content}")这一轮中,Team 的模型循环会依次把问题委派给weather-agent与population-agent,两个成员的返回以 tool-role 消息沉淀进会话,最后模型生成一句总结。返回的original.run_id是后续重生成的"源坐标"。
3.3 STEP 2:带"转向指令"的 Regenerate
forked = await team.acontinue_run( run_id=original.run_id, session_id="team-sess-1", regenerate=True, additional_instructions="This time, only mention weather. Skip population.", ) print(f" run_id: {forked.run_id} (new)") print(f" forked_from_run_id: {forked.forked_from_run_id}") print(f" regenerated_from: {forked.regenerated_from}") print(f" content: {forked.content}")这是全篇最关键的一次调用,注意三个细节:
- 同步 API 是
team.continue_run(...),异步 API 是team.acontinue_run(...);两者的方法签名在 libs/agno/agno/team/team.py 中成对出现(continue_run定义在 1113 行附近,acontinue_run定义在 1198 行附近),并都接受regenerate、replace_original、additional_instructions、input、continue_from、fork等快照分发参数。 regenerate=True触发重生成;additional_instructions="This time, only mention weather..."提供转向指引,代码注释称之为steering(转向)——不传时则按原样重述一次结论。- 结果对象
forked同时携带两类血统字段:forked_from_run_id(来自 fork 机制)与regenerated_from(专用于标识"从哪个 run 重生成而来"),都指向源 run。
3.4 STEP 3:会话内验证明细
session = team.db.get_session(session_id="team-sess-1", session_type="team") team_runs = [r for r in (session.runs or []) if hasattr(r, "member_responses")] agent_runs = [r for r in (session.runs or []) if not hasattr(r, "member_responses")]脚本用"是否含member_responses字段"区分 Team 行与成员 Agent 行,然后打印两类结论:
- Team 行 = 2(original + fork,且都持久化):源 run 与重生成 run 各自作为一条独立 run 记录存在,用
forked_from_run_id前缀输出能看出从属关系。 - 成员行 = 0 新增:成员 run不会被克隆,仍然挂在原始 Team 之下(通过
parent_run_id标识其归属)。
这正是 Regenerate 与"整段复制"的分水岭:已经发生的委派是历史事实(fact of history),不是需要拷贝的状态(state to copy)。README 的表述与脚本输出完全一致:"Member rows the original team produced stay attached to the original team"。
四、replace_original:源 run 的"可见性开关"
README 明确指出 Regenerate总是 fork——新run_id+ 新指标,源 run 永远保留。那么"重生成后旧的总结还显不显示?"就由replace_original控制,它只影响源 run 在历史中的可见性:
replace_original | 源 run 状态 | 历史可见性 | 典型用途 |
|---|---|---|---|
True(默认) | 被标记为REGENERATED | 隐藏,由新 run 顶替其位置 | "旧答案作废,只展示重写后的" |
False | 保持COMPLETED | 保留,两个尝试都显示 | "AB 对比,让用户/裁判选择" |
结合源码,libs/agno/agno/team/_run.py 的_normalize_regenerate_params_team(7252 行起)还会做这些一致性校验:
replace_original只在regenerate=True时有意义,否则抛ValueError("replace_original only makes sense with regenerate=True");regenerate=True时不允许再传fork=True——因为"是否 fork"已由replace_original推导,README 注释称之为1-run-1-loop invariant;regenerate=True要求调用前已加载到目标 run 的run_response,否则无法计算截断点;additional_instructions与input互斥,二者只能选其一作为转向输入。
五、源码深读:截断点如何计算、成员为何不被重跑
5.1 边界计算:_find_regenerate_checkpoint_team
Regenerate 的落点是一个消息截断索引,由 libs/agno/agno/team/_run.py 中的_find_regenerate_checkpoint_team(7158 行起)计算:
messages = run_response.messages or [] i = len(messages) while i > 0 and messages[i - 1].role == "assistant" and not messages[i - 1].tool_calls: i -= 1 if i == 0: raise ValueError("Cannot regenerate: team run has no non-assistant messages to regenerate from.") return i逻辑是:从消息尾部向前扫描,凡是"assistant 角色且不含 tool_calls"的消息一律剔除(这通常正是末尾的最终总结,中间推理性质的 assistant 消息同理被剥掉),遇到第一条带工具调用的 assistant 消息或非 assistant 消息即停。因此成员委派(对 Team 而言就是工具调用)及其 tool-role 结果消息全部留在边界之前。
值得对照的是同一文件中的_find_last_user_message_index_team(7177 行起):它对应continue_from="last_user"的语义——回退到最后一条 user 消息,把其后的中间工具交换也一并丢弃。源码 docstring 明确点出二者差异:"last_user"与regenerate=True语义不同,前者把中间工具调用也丢掉。也就是说:
continue_from="last_user"→ 从"用户提问后"整段重来(重跑委派);regenerate=True→ 只重写"末尾总结"(不重跑委派)。
5.2 fork 血统与成员引用:Team 行的深拷贝,成员行的零拷贝
_resolve_continue_from_team里regenerate=True会自动推导边界(不允许手工指定continue_from,否则报错)。而真正执行时,fork 出的新 run 会携带两类血统字段,它们定义在 libs/agno/agno/run/team.py 的 TeamRun 模型上(810–819 行附近):
forked_from_run_id/forked_from_message_index:fork 血统与截断坐标;regenerated_from:本 run 由哪个 run 重生成而来(即"父 run 的run_id")。
关于成员数据的处理,01_regenerate.py 顶部的 docstring 说得非常直白:fork 出的 Team 其member_responses字段引用(deep-copy)了同一份成员数据,但不会向会话写入任何新的成员 run 行。这与 Agent fork 不克隆"工具执行行"完全对仗——成员之于 Team,就像工具之于 Agent,是一个被调度的资源而非被复制的状态。同时深拷贝也保证了 fork 无法污染源数据。
5.3 状态流转与默认替换
regenerate=True且未显式关闭替换时,源码在写新 run 的同时会调用_mark_team_run_regenerated(7354 行起)把父 run 状态翻转为RunStatus.regenerated并持久化,使历史构建器(history builders)在拼上下文时跳过它——这正是replace_original=True默认行为的落点。示例代码forked_from_run_id[:8]与 run 状态的一并打印,可以让开发者直接观察这条状态变迁。
六、同步/异步双 API 与团队模型循环语义
Regenerate 是/continue快照分发能力(snapshot dispatch)的一部分,参数在 libs/agno/agno/team/team.py 的continue_run与acontinue_run中被透传:
continue_run(同步,1078–1160 行签名区):可直接传入上次的run_response,或以run_id + session_id定位;acontinue_run(异步,1163–1247 行签名区):同样的参数集,适用于async def main()场景(示例即采用此写法,最后以asyncio.run(main())收尾)。
无论哪种形态,重生成都是让 fork 出的 Team 用新的run_id重放 Team 自己的模型循环(仅一次模型循环 + 复用既有成员输出),因此 fork run 会获得一套全新的RunMetrics(token 消耗、耗时、工具调用数等),便于与原始 run 做质量对比。
七、成员为何"不在作用域内":与 Agent 面的 Parity 设计
整个 Regenerate 机制最核心的设计决策是:members are out of scope。团队断点(checkpoint)只作用于 Team 自身的状态——从 Team 视角看,成员只是它委派出去的工具,成员的输出只是会话中一条 tool-role 消息。这一点在 23_checkpointing 的 README 中被表述为 "Fork / regenerate / time-travel operate on the team's own state, not member state"。
由此带来一个工程师必须养成的习惯:如果你希望"重生成时连委派过程一起重来",那不应使用 Regenerate,而应使用同目录族的time-travel / fork / 新的 run能力(详见 03_teams 下的25_time_travel/与26_fork_session/兄弟目录)。Regenerate 的适用场景非常聚焦:委派结果有效、需要换一种总结口径或修正措辞——例如示例中"这次只提天气、跳过人口"的转向需求。
八、测试支撑与验证手段
该功能不是孤例代码,而是有系统化单测覆盖的:
- 单元测试位于 libs/agno/tests/unit/team/test_team_checkpointing.py,其中
TestTeamRegenerateSugar与TestRegenerateSugarNormalization两组用例分别覆盖"重生成语法糖的端到端行为"与"参数规范化/校验"; - TEST_LOG 说明 01 号脚本在无 Key 环境下仅做语法/编译验证,其运行时行为交由上述单测保障。
因此,即便你的环境没有模型 API Key,也可以先跑单测确认 Regenerate 语义;再配置OPENAI_API_KEY后运行示例脚本做端到端观察。推荐关注的输出点有三个:forked.run_id != original.run_id、forked.regenerated_from == original.run_id、以及会话中 Team 行数变为 2 而成员行数不变。
结语
Team Regenerate 是 agno 把"Agent 的 fork/regenerate 能力"推广到"多 Agent 团队"后的自然延伸:它通过只截断无工具调用的尾部助手消息实现低成本改写,通过总是 fork + 源 run 保留保证可追溯,通过成员行不克隆守住"已发生的委派是事实"的边界。配合replace_original开关,你既可以"旧答案作废、新答案顶上",也可以"两次尝试并存、供人审阅对比"。在需要反复打磨团队最终输出、又不想为每次微调都付出整轮委派成本的生产场景里,这是一个值得优先纳入工具箱的 API。
【免费下载链接】agnoBuild, run, and manage agent platforms.项目地址: https://gitcode.com/GitHub_Trending/ag/agno
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考