前面《 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 条件)。

有两点要特别强调:

  1. LoopAgent 自己不连 LLM
    • 它和 SequentialAgent 一样,只负责调度;
    • 里面的子 Agent(比如 Writer、Critic)才会用 LLM。
  2. 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 时,它做的事大致是:

  1. 在每一轮中,按顺序执行 sub_agents

    • 就像 SequentialAgent 一样:
      • 第一个子 Agent → 第二个子 Agent → …
    • 这一串称作一个 “iteration(迭代轮次)”。
  2. 迭代结束后,检查是否应该继续下一轮

    • 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:

  1. CriticAgent(评论家)
    • 从 state[“current_document”] 里取出当前版本故事;
    • 如果还可以明显改进:
      • 输出具体、简短的改进建议(写入 state[“criticism”]);
    • 如果故事已经“足够合理”,就输出一个 非常固定的短语,比如:
      • “No major issues found.”
        同样写入 state[“criticism”]。
# 步骤 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"
)
  1. RefinerAgent(改稿者 & 负责退出循环的 Agent)
  • 同时读取:

    • state[“current_document”](当前版本故事)
    • state[“criticism”](上一步的评论)
  • 如果 criticism 的内容 恰好是那个完成短语:

    • 它就调用 exit_loop 工具,触发 tool_context.actions.escalate = True,
      告诉 LoopAgent:可以停了;
    • 不输出任何文本。
  • 否则(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
)
  1. 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 发挥威力的时候了。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐