Go Micro Agent Loops 实战:用 `micro.FlowLoop` 构建有护栏的迭代式 Agent
2026/9/20 18:17:05 网站建设 项目流程
  • 后端
  • 微服务
  • AI Agent
  • RPC框架

【免费下载链接】go-micro

A Go agent harness and service framework

项目地址:https://gitcode.com/gh_mirrors/go/go-micro
点击查看免费下载

Go Micro 将"循环"(loop)实现为一个普通 flow 步骤:micro.FlowLoop在有序、可断点续跑(checkpointed)的步骤列表中反复运行一个 body 步骤,直到停止条件触发或达到迭代上限。本文围绕 agent-loops.md 指南,结合 flow/loop.go、flow/loop_test.go 与完整示例 examples/flow-loop/main.go,讲清 Loop 的停止条件、硬性上限、进度观测与持久化语义,并给出可复制运行的代码与改造成真实 Agent 循环的路径。

为什么需要 Loop:从"一问一答"到"持续工作"

大多数 Agent 调用是一次性的:输入一个 prompt,得到一个答案。而智能体(agentic)系统的下一步是循环——让 Agent 反复执行某个步骤,持续工作直到目标达成,而不是一轮就停。典型场景包括:

  • 一个 Agent 持续改进架构、另一个删除重复抽象,两者不断提交 PR;
  • 一篇文章反复打磨直到"足够好";
  • 一个构建反复修复、重跑直到全绿。

循环的代价是成本与失控风险:循环"烧 token 的速度远快于简单的问答式聊天机器人",而"继续干直到完成"这种非确定性的停止条件没有任何天然上限。因此一个可用的循环必须同时具备两样东西:

  1. 停止条件(stop condition)——决定循环何时算"做完";
  2. 硬性上限(hard cap)——一个保证循环必然终止的护栏。

Go Micro 把这两者打包成一个 flow 步骤:micro.FlowLoop

Loop 的形状:一个普通 flow 步骤

micro.FlowLoopStepFunc,所以它可以像其他任何步骤一样嵌入 flow 的有序步骤列表。它反复运行一个body步骤,把 flow 的State从一轮传递给下一轮,直到停止条件触发或迭代上限被命中——以先到者为准。

f := micro.NewFlow("refactor", micro.FlowProvider("anthropic"), micro.FlowSteps( micro.FlowStep{Name: "improve", Run: micro.FlowLoop( micro.FlowDispatch("coder"), // the body: an agent does one pass micro.FlowUntilLLM("Is the refactor complete with no duplicated abstractions left?"), micro.FlowLoopMax(5), // the ceiling: never more than 5 passes )}, ), )

从源码看,micro.FlowLoop只是 micro.go 中对flow.Loop的薄封装,FlowLoopMax/FlowUntil/FlowUntilLLM/FlowOnIteration则分别透传到flow.LoopMax/flow.Until/flow.UntilLLM/flow.OnIteration。真正的引擎在 flow/loop.go:

func Loop(body StepFunc, opts ...LoopOption) StepFunc { o := LoopOptions{Max: 10} // ...选项解析:Max<=0 时重置为 10 return func(ctx context.Context, in State) (State, error) { if body == nil { return in, fmt.Errorf("flow: Loop requires a body step") } cur := in for iter := 1; iter <= o.Max; iter++ { out, err := body(ctx, cur) if err != nil { return cur, fmt.Errorf("loop iteration %d: %w", iter, err) } cur = out if o.OnIter != nil { o.OnIter(iter, cur) } done, err := loopDone(ctx, o, cur, iter) if err != nil { return cur, err } if done { return cur, nil } } return cur, nil } }

注意几个实现细节:

  • 状态是流动的cur = out让每次迭代的输入都是上一轮的输出,因此 body 每一步都能看到前一轮的结果;
  • body 为 nil 会报错flow: Loop requires a body step
  • body 出错立即中止:错误会被包装为loop iteration %d: ...并返回,同时返回当前状态;
  • 达到上限时返回最新状态而非报错:护栏完成任务后循环"正常退出",调用方拿到的是最近一轮的State

停止条件一:代码定义(FlowUntil

当"完成"可以用代码衡量时(测试通过、分数超过阈值、队列为空),用FlowUntil传入一个谓词。每轮迭代结束后引擎都会调用它,返回true即终止循环。谓词签名是LoopCondition(flow/loop.go):接收context.Context、最新State和刚完成的迭代序号(从 1 开始):

micro.FlowUntil(func(_ context.Context, s micro.FlowState, iter int) (bool, error) { var d Draft _ = s.Scan(&d) return d.Quality >= 90, nil })

这里的s.Scan(&d)把 flow 状态解码进一个结构化结构体(状态由State.Set/State.Scan承载)。谓词返回(bool, error),若返回错误则循环以该错误结束。

停止条件二:模型判断(FlowUntilLLM,受监督的 "Ralph" 循环)

当"完成"不好用代码量化时,FlowUntilLLM让 flow 的模型在每轮迭代后判断目标是否达成,得到肯定回答就停止。这正是受监督的 "Ralph" 循环:由 Agent 决定何时完成,而上限仍然保证它必然停止。

micro.FlowUntilLLM("Have all the failing tests been fixed?")

该选项要求 flow 配置了模型FlowProvider/FlowAPIKey)。底层实现见 flow/loop.go 的askDone:它把问题与最新状态拼成 prompt,要求模型"只回答 yes 或 no",然后用isAffirmative判断回答是否为肯定(flow/loop.go)。肯定词前缀匹配yesdonetruecompletefinished(大小写不敏感)。如果 flow 没有模型,会返回错误:flow: UntilLLM requires a flow model (set Provider/APIKey)

两种停止条件可以组合loopDone(flow/loop.go)先评估代码谓词,再评估 LLM 判断,任一触发即停止循环。

护栏:FlowLoopMax—— 永远设置的硬上限

FlowLoopMax(n)是循环的天花板。body 最多运行n次,因此即使停止条件永不触发,循环也必然终止。上限被命中时,循环返回最新状态而不是报错——护栏完成了它的职责。文档明确建议:总是设置它。

  • 默认值:即使不传FlowLoopMax,源码中LoopOptions{Max: 10}也会给出默认上限 10;若显式传入n <= 0,会被重置为 10(见 flow/loop.go)。
  • 验证:flow/loop_test.go 的TestLoopMaxCapStops构造了一个永不触发的谓词,配LoopMax(5),断言 body 恰好执行 5 次——这正是"护栏保证终止"的直接测试证据。

预算更紧时,把上限调低,并搭配两类配套机制防止后台循环烧出无上限的账单:

  • Agent 防护栏(guardrails):Agent Guardrails 指南 中的MaxSteps(单次Ask的总工具调用次数上限)、LoopLimit(同一工具同一参数重复调用的上限,默认开启)、ApproveTool(执行前门禁,可用于额度/审批策略)以及WrapTool(执行全生命周期钩子,可做计量与审计);
  • 付费工具(x402):Payments (x402) 指南 让每个端点成为"付费工具",按调用计费(per-call metering),进一步约束无限扩大的开销。

观察进度:FlowOnIteration

FlowOnIteration在每轮迭代后执行,用于日志记录或持久化摘要,方便观察长时间运行的循环进展:

micro.FlowOnIteration(func(iter int, s micro.FlowState) { log.Printf("pass %d: %s", iter, s.String()) })

回调在每轮 body 执行完毕、停止条件评估之前被调用(flow/loop.go),收到从 1 开始的迭代序号与最新状态。测试 flow/loop_test.go 的TestLoopOnIteration验证了回调按[1 2 3]顺序收到每次迭代。

持久化语义:循环是一个单一 flow 步骤

循环作为单个 flow 步骤运行。flow 通过其 Checkpoint 机制 在步骤前后保存循环的结果,恢复(resume)时会重新进入该步骤——所以循环 body 必须是可安全重复执行的(幂等)。

有两个关键边界(在 durability.md 中明确说明):

  • 循环内的迭代不会被引擎单独 checkpoint。引擎只保存整个循环步骤的开始/结束状态;
  • FlowOnIteration只是应用自有的进度记录回调,它不会改变引擎的恢复边界,也不会自动跳过已完成迭代。恢复后,整个循环步骤会从头(或从保存的状态)重新执行。

因此对于长循环,用FlowOnIteration记录进度;对于不可重复的外部副作用(支付、资源开通等),按 durability.md 的提示自行实现幂等或对账,因为这里"没有跨外部服务的精确一次(exactly-once)保证"。

完整可运行示例:离线跑通一个 Loop

仓库自带一个无需 API Key的离线示例 examples/flow-loop/main.go:body 和停止条件都是纯 Go 代码,每次迭代让草稿质量 +30,直到质量达到 90 停止,同时用FlowLoopMax(10)兜底:

func main() { const goodEnough = 90 f := micro.NewFlow("refine", micro.FlowSteps( micro.FlowStep{Name: "improve", Run: micro.FlowLoop( improve, // Stop early once the draft is good enough... micro.FlowUntil(func(_ context.Context, s micro.FlowState, iter int) (bool, error) { var d Draft _ = s.Scan(&d) fmt.Printf(" pass %d → quality %d\n", iter, d.Quality) return d.Quality >= goodEnough, nil }), // ...but never run the body more than 10 times (the ceiling). micro.FlowLoopMax(10), )}, ), micro.FlowDeleteOnSuccess(), ) fmt.Println("refining until quality >=", goodEnough) if err := f.Execute(context.Background(), `{"text":"initial draft","quality":0}`); err != nil { fmt.Println("flow error:", err) return } for _, r := range f.Results() { fmt.Printf("\ndone: %s\n", r.Answer) } }

运行方式(从仓库根目录):

go run ./examples/flow-loop/ # refining until quality >= 90 # pass 1 → quality 30 # pass 2 → quality 60 # pass 3 → quality 90 # done: {"text":"draft refined (quality 90)","quality":90}

示例中的improve每轮把Draft.Quality增加 30 并更新文本,然后通过in.Set(d)写回状态;第三轮质量达到 90,FlowUntil触发停止。FlowDeleteOnSuccess()表示成功后删除运行 checkpoint(失败运行始终保留)。输出中的done: ...来自f.Results()Answer字段。

把它改造成真实 Agent 循环只需两步:

  • 把 body 从纯函数换成micro.FlowDispatch("agent")(把状态交给已注册的 Agent 通过 RPC 处理)或micro.FlowLLM(...)(一次增强式 LLM 轮次,服务即工具);
  • 把停止判断换成micro.FlowUntilLLM("Is the work complete?")——即文首的受监督 "Ralph" 循环形态。

用测试验证行为

flow/loop_test.go 提供了五个覆盖核心行为的确定性测试,可作为理解语义的参考:

测试验证点
TestLoopUntil代码谓词在n >= 3时停止,输出状态为"3"
TestLoopMaxCapStops谓词永不触发时,上限 5 强制恰好执行 5 次
TestLoopOnIteration回调依次收到迭代[1 2 3]
TestLoopBodyErrorbody 返回的错误(如context.Canceled)向上传播
TestIsAffirmative模型回答的肯定/否定判定,覆盖yes/DONE/complete/no/空串等

可运行:

go test ./flow -run 'TestLoop' -v

相关阅读

  • Agents and Workflows — flow(预定义路径)与 agent(自主决策)的边界,理解 Loop 所处的编排层;
  • Agent Guardrails — 用MaxSteps/LoopLimit/ApproveTool/WrapTool约束循环能做什么、烧多少;
  • Plan & Delegate — 把工作拆分给多个 Agent,是 Loop body 内常见的协作模式;
  • Durability and Recovery — 断点续跑与恢复边界,理解循环步骤的持久化语义。

核心思路一句话:让 Agent 一直干到完成,但永远给它一个天花板。FlowUntil/FlowUntilLLM决定"何时算完",FlowLoopMax保证"必然终止",FlowOnIteration让长循环可观测,而单步骤 checkpoint 语义提醒你保持 body 可重复执行。

  • 后端
  • 微服务
  • AI Agent
  • RPC框架

【免费下载链接】go-micro

A Go agent harness and service framework

项目地址:https://gitcode.com/gh_mirrors/go/go-micro
点击查看免费下载

相关推荐

上一篇:Deep Agents Code Thread Inspector 深度解析:离线会话库的只读检视与对话重建原理
下一篇:Learn Julia the Hard Way:Julia绘图与数据可视化完整指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询