AI - LoopAgent 实战:用 ADK 做一个 “写作-批改-再写” 的迭代写作智能体
AI - LoopAgent 实战:用 ADK 做一个 “写作-批改-再写” 的迭代写作智能体
前面《 AI - 认识 Workflow Agent:给 LLM 团队配一个靠谱的项目经理》认识了 Workflow Agent 、《 AI - SequentialAgent 实战:用 ADK 把「写代码 → 审查 → 重构」串成一条流水线》实战了 SequentialAgent(排队办事型)。
今天轮到 LoopAgent 出场——它是专门负责“反复尝试、迭代优化”的那位。
先用一句话把 LoopAgent 讲清楚:
LoopAgent = 把一组子 Agent 变成“循环执行”的内层循环,
一遍一遍跑,直到满足退出条件或者次数上限。
和 SequentialAgent 一次性 A→B→C 不同,LoopAgent 更像:
while (还没达到要求 && 没超过最大次数) {
先跑 A,再跑 B,再跑 C
}
非常适合那种:
- “先做一版 → 看看哪里不行 → 再改一版 → 再看 → 再改……”
- 人类日常干得最多的那种活 😅
一、LoopAgent 是什么?
根据 ADK 文档 的定义:
- LoopAgent 是一种 Workflow Agent;
- 它会重复执行自己的 sub_agents 列表:
- 一次循环 = 依次跑完列表里的所有子 Agent;
- 循环会一直进行,直到:
- 达到你设置的最大循环次数(max_iterations),或者
- 某个子 Agent / 工具“发出退出信号”(你自己设计的 termination 条件)。
有两点要特别强调:
- LoopAgent 自己不连 LLM
- 它和 SequentialAgent 一样,只负责调度;
- 里面的子 Agent(比如 Writer、Critic)才会用 LLM。
- LoopAgent 是确定性的(deterministic)
- 它不会“随机决定”下一轮要不要跑;
- 怎么循环、循环几次,完全取决于你设的 max_iterations 和退出逻辑。
二、什么时候应该用 LoopAgent?
一个很好理解的例子:数香蕉 🍌
你有一个“生成食物图片”的工具(Generate Image),
还有一个“数图片里有多少食物”的工具(Count Food Items)。
比如想生成一张“5 根香蕉”的图,但模型有时会画成 7 根,
你就想:生成 → 数一数 → 不对就重来 → 再数……直到数量正确或尝试次数到上限。
这个场景非常典型:
- 每一轮要做的事情是固定的(先生成图,再数一数);
- 但是轮数不确定:可能一次就成功,也可能要试很多次;
- 人类其实就是这么干的:多试几次直到看起来够好了。
另一个常见场景:迭代改写文档 / 代码:
- 一个 Writer Agent 负责写 / 改;
- 一个 Critic Agent 负责挑问题;
- 两个 Agent 在 LoopAgent 的控制下反复“互相配合”,
一直到 Critic 说“没啥大问题了”或者循环次数用完。
总结一下,遇到这些句式,就可以考虑 LoopAgent:
- “我要反复改,直到满意”;
- “每轮都做同样几步,只是结果在变”;
- “想构建一个自我修正 / 自我迭代的 Agent”。
三、LoopAgent 是怎么“循环”的?
当你调用 LoopAgent 的 run_async 时,它做的事大致是:
-
在每一轮中,按顺序执行 sub_agents
- 就像 SequentialAgent 一样:
- 第一个子 Agent → 第二个子 Agent → …
- 这一串称作一个 “iteration(迭代轮次)”。
- 就像 SequentialAgent 一样:
-
迭代结束后,检查是否应该继续下一轮
- LoopAgent 自己不会用 LLM 来“思考要不要停”;
- 你必须提供一个“终止机制”,防止它无限循环。
两类常见的终止策略:
- Max Iterations(最大循环次数)
- 设置 max_iterations,比如 5;
- 就算你忘了写退出逻辑,最多只会跑 5 轮。
- 由子 Agent 决定退出(Escalation from sub-agent)
- 让某个子 Agent 自己判断“是否已经够好”;
- 如果满足条件,则:
- 设置某个 state 标记;
- 或者设置 tool_context.actions.escalate = True;
- 或返回一个特殊值,让外部逻辑识别后停掉循环。
重点:LoopAgent 自己不会“聪明地”决定啥时候停,
你必须给出“什么时候停”的规则。
四、终止循环的几种常见方式
我们可以把 LoopAgent 的“停几次”理解为三类:
1 只靠 max_iterations(硬上限)
最简单粗暴的方式:
refinement_loop = LoopAgent(
name="RefinementLoop",
sub_agents=[writer_agent, critic_agent],
max_iterations=5,
)
- 这表示最多跑 5 轮:
第 1 轮:写 → 评;
第 2 轮:写 → 评;
…… - 不管文档质量如何,第 5 轮之后必定停止。
优点:
- 实现简单,不怕忘记退出。
缺点:
- 不够智能:文档早就够好了,还要白跑几轮;
- 文档一直很烂,也不会提前停(除非子 Agent 自己在内部拒绝继续改)。
2 靠 state 标志位自己判断
你可以在 Critic Agent 里写一个逻辑:
- 如果觉得当前结果已经“够好”,
就在会话 state 中写一个标志,比如:
invocation_context.state["done"] = True
- 然后在 LoopAgent 外或回调里检测:
- 如果发现 state[“done”] == True,就停止循环。
就是借助“共享上下文(state 或事件)”,让子 Agent 把“停止信号”写进去。
3 用工具 + actions.escalate = True(文档示例里的 exit_loop)
用的是一个挺优雅的模式:“exit_loop 工具”。
大概长这样(我用伪代码形容):
def exit_loop(tool_context: ToolContext):
"""
只在确定不需要再迭代时调用,负责告诉 LoopAgent:可以退出了。
"""
tool_context.actions.escalate = True
return {}
- 这个函数本身啥也不做,只是把 actions.escalate 设为 True;
- ADK 在看到这个 escalate 标志后,会把它当成“从循环里往外升级一个信号”,用来终止 LoopAgent 的后续迭代。
配合这个工具,Refiner Agent 的 instruction 会写:
- 如果 Critic 的反馈是“没啥大问题了”(固定短语),
你就必须调用 exit_loop 工具,不要输出文本;- 否则就根据建议修改文档,并输出新版本。
这样:
- 每一轮都先 Critic → 再 Refiner;
- 当 Critic 觉得“OK 了”并输出固定短语时,
- Refiner 在看到这个短语后就调用 exit_loop → 触发 escalate → LoopAgent 知道该停了。
这个模式非常通用:你可以在任何“需要退出循环”的场景里,用一个这样的工具作为“退出开关”。
五、完整示例:循环改写文档(Iterative Document Improvement)
LoopAgent 文档里给了一个比较完整的例子,非常值得仔细研究:
目标:
给定一个初始 topic(比如 “一只迷路的小猫”),
- 先写出第一版短篇故事草稿;
- 然后反复做:
- Critic 审稿,给出修改建议或说“没什么大问题”;
- Refiner 根据建议改写故事,或者在足够好时调用 exit_loop;
- 一直到故事质量 OK,或者循环次数达到上限(比如 5 轮)。
工作流结构:
SequentialAgent(root)
├─ InitialWriterAgent # 第一次写初稿(只执行一次)
└─ RefinementLoop # LoopAgent:在里面反复执行
├─ CriticAgent # 给出批评/改进建议
└─ RefinerAgent # 按建议改稿 或 触发 exit_loop
项目结构:
adk_loop_demo/
├─ .env # 配置 google api key 或 proxy gateway
├─ agent.py # 定义所有 Agent + LoopAgent
└─ run_loop.py # 用 InMemoryRunner 跑起来(可选)
整体结构可以拆成三步:
5.1 第一步:InitialWriterAgent(只跑一次)
- 类型:LlmAgent
- 功能:接受用户输入为 story 或 document;
- 输出存到 state[“current_document”]。
注意:这个 Agent 不在 LoopAgent 里,而是先由 SequentialAgent 跑一次,给后面循环提供起点。
import os
from google.adk.agents import LoopAgent, LlmAgent, SequentialAgent
from google.adk.tools.tool_context import ToolContext
from google.adk.models.lite_llm import LiteLlm
import litellm
# 步骤 1:初始文稿 Agent(在开始时运行一次)
initial_writer_agent = LlmAgent(
name="InitialWriterAgent",
model=proxy_model,
# 直接将用户最近一条消息视为完整的故事/文档,不做任何改写
instruction=f"""你是一个“原文直通”助手。
用户最近一条消息已经是一篇完整的故事或文档内容。
你的任务:
- 原样输出这段文本,不能做任何修改;
- 不要增删任何字符,不要翻译、润色、总结或重排顺序;
- 不要加引号,也不要包裹在代码块中;
- 只输出这段文本本身,除此之外不要输出任何内容。
""",
description="将用户最近一条消息原样写入 state['current_document'] 作为初稿。",
output_key="current_document"
)
5.2 第二步:RefinementLoop(LoopAgent 内部)
LoopAgent 里面有两个子 Agent:
- CriticAgent(评论家)
- 从 state[“current_document”] 里取出当前版本故事;
- 如果还可以明显改进:
- 输出具体、简短的改进建议(写入 state[“criticism”]);
- 如果故事已经“足够合理”,就输出一个 非常固定的短语,比如:
- “No major issues found.”
同样写入 state[“criticism”]。
- “No major issues found.”
# 步骤 2a:审查 Agent(循环中的审查环节)
critic_agent_in_loop = LlmAgent(
name="CriticAgent",
model=proxy_model,
include_contents='none',
# Instruction:专注检查语法、拼写和句子逻辑等基础问题,只指出问题本身
instruction=f"""你是一个“文稿审查助手”,负责检查一段短篇故事或文档草稿(通常为 2-6 句)。
**待审查文稿:**
```
{{current_document}}
```
**任务:**
请仔细检查这段文字中是否存在:
- 拼写错误或错别字;
- 语法和标点错误;
- 表达不清或别扭的句子;
- 句内或句间的逻辑问题(例如主语、时态、指代或含义前后不一致等)。
如果你发现任何**非非常轻微**的问题(拼写、语法、用词或句子逻辑):
- 清楚指出这些问题在哪,以及问题本身是什么;
- 可以用项目符号或分条列出问题;
- 不要提供修改建议或替代句子,只描述问题本身;
- 只输出你的审查意见文本。
否则(如果这段文字在拼写、语法和句子逻辑上没有明显问题,整体表达已经足够清晰):
- 请只输出完全相同的一句话:"No major issues found.",不要添加其他任何内容。
不要解释你自己的身份或任务,只输出审查意见或上述固定短语。
""",
description="审查当前草稿的拼写、语法和句子逻辑问题;如无明显问题则返回固定短语表示通过审查。",
output_key="criticism"
)
- RefinerAgent(改稿者 & 负责退出循环的 Agent)
-
同时读取:
- state[“current_document”](当前版本故事)
- state[“criticism”](上一步的评论)
-
如果 criticism 的内容 恰好是那个完成短语:
- 它就调用 exit_loop 工具,触发 tool_context.actions.escalate = True,
告诉 LoopAgent:可以停了; - 不输出任何文本。
- 它就调用 exit_loop 工具,触发 tool_context.actions.escalate = True,
-
否则(criticism 里面是真实的建议):
- 它根据建议改写 current_document,输出新版本故事;
- 并写回 state[“current_document”](覆盖旧的),供下一轮使用。
# --- 工具定义 ---
def exit_loop(tool_context: ToolContext):
"""当审查结果表明无需进一步修改时调用,用于结束循环修订过程。"""
print(f" [Tool Call] exit_loop triggered by {tool_context.agent_name}")
tool_context.actions.escalate = True
# Return empty dict as tools should typically return JSON-serializable output
return {}
# 步骤 2b:修订/退出 Agent(循环中的修订环节)
refiner_agent_in_loop = LlmAgent(
name="RefinerAgent",
model=proxy_model,
# Relies solely on state via placeholders
include_contents='none',
instruction=f"""你是一个“文稿修订助手”,会根据审查意见对文稿进行修改,或在无需修改时结束流程。
**当前文稿:**
```
{{current_document}}
```
**审查意见:**
{{criticism}}
**任务:**
阅读“审查意见”内容。
- 如果审查意见的内容**完全等于** "No major issues found.":
- 必须调用工具函数 `exit_loop`;
- 不要输出任何文本。
- 否则(审查意见中包含具体的修改建议):
- 在“当前文稿”的基础上,按照审查意见对文本进行修改;
- 尽量只改正拼写、语法、用词和句子逻辑问题,避免加入与原意无关的大幅新内容;
- 输出修改后的完整文稿文本,仅输出文稿本身。
不要解释你的行为,不要输出关于工具调用的说明,只需输出修订后的文稿或触发退出。
""",
description="根据审查意见对当前草稿进行修订;若审查通过则调用 exit_loop 结束循环。",
tools=[exit_loop], # Provide the exit_loop tool
output_key="current_document" # Overwrites state['current_document'] with the refined version
)
- LoopAgent 的定义类似:
# 步骤 2:循环审查与修订 Agent
refinement_loop = LoopAgent(
name="RefinementLoop",
# 子 Agent 顺序很重要:先审查,再修订/决定是否退出
sub_agents=[
critic_agent_in_loop,
refiner_agent_in_loop,
],
max_iterations=5 # 限制最多循环次数,防止无限循环
)
)
也就是说:
- 第 1 轮:Critic → Refiner
- 第 2 轮:Critic → Refiner
- ……
- 在某一轮,Refiner 调用了 exit_loop → 循环提前结束;
- 或者一直没有满足退出条件 → 跑满 5 轮自动结束。
5.3 第三步:SequentialAgent 外层包一层
最后,还有一个最外层的 SequentialAgent 把整个流程串起来:
# 步骤 3:整体顺序流水线
# 为了兼容 ADK 工具,根 Agent 的变量名必须为 `root_agent`
root_agent = SequentialAgent(
name="IterativeWritingPipeline",
sub_agents=[
initial_writer_agent, # 首先接收用户原始文稿
refinement_loop # 然后在循环中反复审查并修订
],
description="先接收用户原始文稿,然后在循环中反复审查并修订,直到没有明显问题为止。"
)
整个执行路径就是:
- InitialWriterAgent:写第一版;
- RefinementLoop:
- Critic → Refiner → Critic → Refiner → ……
- 直到 exit_loop or max_iterations。
你可以把它看成是:
“外层 SequentialAgent 负责整个流程的大框架,
内层 LoopAgent 负责局部的迭代优化过程。”
这是非常常见的一种 Workflow Agent 组合模式。
5.4 会话效果
将以上代码放放 Agent.py 文件,配置好 .env,就可以执行命令:
adk web --port 8001
等 adk web servier 启动起来后就可以访问 Web UI:http://127.0.0.1:8001 进行会话。
循环 2 次结束
用户输入:”去超时,买,做饭“
refinement_loop 循环了 2 次,直到没有问题发现才执行 exist_loop tool 终止循环
循环 3 次结束
用户输入:“I go to the supermarket near my home, buy some foot, then I go home and make food”
refinement_loop 循环了 3 次,直到没有问题发现才执行 exist_loop tool 终止循环
六、LoopAgent 和 SequentialAgent 的区别(顺便复习一下)
简单对比一下两个 Workflow Agent 的定位:
-
SequentialAgent
- 执行一次:A → B → C;
- 很适合作为“整体流程”的骨架,比如:
- “写初稿 → 进入迭代优化环节 → 最后生成摘要”。
-
LoopAgent
- 执行多次:
- while (not done) { A → B → C };
- 更适合用在“某个局部需要反复尝试 / 改进”的地方:
- “反复 Critic → Refine 文档”;
- “反复生成图片 → 检查是否满足条件”;
- “反复写代码 → 跑测试 → 看是否通过”。
- 执行多次:
在整体架构上,你可以非常自然地组合它们:
Root SequentialAgent
├─ Step1: 数据准备(一个 LlmAgent / SequentialAgent)
├─ Step2: 局部 LoopAgent(反复优化 / 重试)
└─ Step3: 最终汇总 / 报告生成
七、小结:一句话记住 LoopAgent
如果用一句话总结 LoopAgent:
SequentialAgent 管的是“只跑一遍的有序流程”,
LoopAgent 管的是“反复跑几遍、每次都按同样顺序走的内层循环”。
当你碰到这类需求:
- 要迭代改进一个东西(文档 / 代码 / 图片);
- 每一轮都要“先看一眼 → 再改一版”;
- 想让系统自己判断“差不多了,就停”;
那就是 LoopAgent 发挥威力的时候了。
更多推荐


所有评论(0)