news 2026/10/2 16:40:39

adk-go HITL 最小实战指南:用 RequestInput 与 Resume 构建无 LLM 的人工在环工作流

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
adk-go HITL 最小实战指南:用 RequestInput 与 Resume 构建无 LLM 的人工在环工作流
  • 人工智能
  • 大模型
  • AI Agent
  • Agent 框架
  • 多智能体
  • 工具调用
  • MCP Clients
  • Agent 记忆

【免费下载链接】adk-go

An open-source, code-first Go toolkit for building, evaluating, and deploying sophisticated AI agents with flexibility and control.

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

导读

本文讲解 adk-go 开源 Go Agent 开发套件中最小化的 Human-in-the-Loop(HITL,人工在环)工作流实现。以 examples/workflow/hitl_simple/README.md 为骨架,本文将带你理解工作流节点如何通过RequestInput事件暂停运行、等待人工输入,以及控制台启动器如何完成暂停/恢复(pause/resume)闭环——整个过程不需要 LLM、不需要 API Key、也不需要流式传输,仅仅依赖两个普通的工作流节点即可跑通。

读完本文,你将掌握:RequestInput事件与ErrNodeInterrupted的暂停协议、InterruptID的关联机制、NodeConfig.RerunOnResume的两种恢复模式(handoff 与 re-entry),并能直接运行该示例观察完整会话。

什么是 HITL 最小工作流?

概念与定位

hitl_simple是 adk-go 中"最小的端到端 HITL 工作流"示例:两个节点构成一条链路——第一个节点暂停并请求人工输入,第二个节点消费并响应人工的回复。它的定位极其纯粹:

  • 概念:两节点 HITL 交接(handoff)——用RequestInput暂停,在下一个节点恢复执行;
  • 需要 LLM 吗?不需要。

这是验证控制台启动器(console launcher)暂停/恢复能力的最小样本,也是理解 adk-go 工作流 HITL 机制的最佳入门点。它的孪生变体是 examples/workflow/hitl_rerun,把同样的场景压缩成单个重入节点,本文末尾会做对照说明。

目标

示例展示了最简单的 HITL 模式:

  1. ask_name节点发射一个RequestInput事件,然后返回ErrNodeInterrupted——这会让工作流暂停;
  2. 控制台启动器渲染出提示语(prompt);
  3. 用户的回复以类型化输入(typed input)的形式投递给下一个节点greet。

这里没有任何模型调用,暂停与恢复完全由工作流引擎的调度器承担。

工作流结构与执行时序

节点图

示例的工作流拓扑如下:

四个执行步骤

上图中编号的连线代表用户与应用之间按顺序发生的四次交互:

  1. ask_name:一个EmittingFunctionNode(发射型函数节点),它产出RequestInput事件(携带每次请求全新的InterruptID)并中断本次运行;
  2. greet:一个普通的FunctionNode,接收回复字符串并返回问候语。

对应到代码里,工作流通过 workflow.Chain 连接三个端点:

Edges: workflow.Chain(workflow.Start, ask, greet),

即Start → ask_name → greet的严格串行链路。关于Chain、Start端点等边构建细节,可参考 workflow/edgebuilder.go 与 workflow/graph.go。

运行示例

启动命令

示例通过 launcher 的 console 子命令运行:

go run ./examples/workflow/hitl_simple/ console

go run会在仓库根目录 go.mod 声明的模块google.golang.org/adk/v2下编译并执行该示例;console子命令由 cmd/launcher/full 提供的完整启动器解析,该启动器封装了 cmd/launcher 下的通用启动逻辑。示例本身通过 agent.NewSingleLoader 注册单 Agent,再交给full.NewLauncher().Execute(...)启动,见 examples/workflow/hitl_simple/main.go。

一次完整的示例会话

控制台中会出现如下往返:

User -> hello Agent -> What's your name? User -> Alice Agent -> Hello, Alice!
  • 输入任意内容(如hello)启动运行,工作流从Start走到ask_name;
  • ask_name发出RequestInput事件并暂停,控制台渲染出What's your name?;
  • 用户输入Alice,引擎把该回复按InterruptID路由回工作流;
  • greet收到回复并输出Hello, Alice!,运行结束。

代码拆解:暂停与恢复的两大核心机制

1. 暂停协议:NewRequestInputEvent+ErrNodeInterrupted

ask_name节点是暂停的发起者,其核心实现(examples/workflow/hitl_simple/main.go):

ask := workflow.NewEmittingFunctionNodeany, any error) (any, error) { // 每个请求一个全新 InterruptID if err := emit(workflow.NewRequestInputEvent(ctx, session.RequestInput{ InterruptID: "ask_name-" + uuid.NewString(), Message: "What's your name?", })); err != nil { return nil, err } return nil, workflow.ErrNodeInterrupted }, workflow.NodeConfig{}, )

两处关键点:

  • workflow.NewRequestInputEvent构造一个带RequestedInput字段的session.Event。该事件还会合成一个名为adk_request_input的 FunctionCall 部件(其ID等于req.InterruptID),并把InterruptID写入LongRunningToolIDs。这一形态有双重作用:其一,让Event.IsFinalResponse()返回 true,从而在请求产出后终止外层 Agent 循环;其二,让通用的"按 FunctionCall ID 分发 FunctionResponse"机制能够把人工回复路由回发出请求的工作流 Agent。常量定义见 workflow/request_input.go 中的WorkflowInputFunctionCallName。
  • workflow.ErrNodeInterrupted是约定的中断哨兵错误。节点在发射完请求事件后返回它,调度器观察到Event.RequestedInput就会把节点置为NodeWaiting(等待状态),并停止调度其后继节点——详见 workflow/hitl_test.go 中TestScheduler_HitlNode_PausesAndForwardsRequest的断言:等待暂停时downstream绝不运行。

2. 唯一性要求:为什么InterruptID必须"每次全新"

代码注释明确强调:复用的 ID 会让 Dev UI 把一次稍后运行的提示误判为已经回答。这与 Python 版 adk 的RequestInput.interrupt_id默认生成全新 UUID 的行为保持一致。

从 session/session.go 中RequestInput结构体的定义可以看到,InterruptID的职责是"将本次请求与恢复它的响应关联起来",回复正是靠匹配这个 ID 被路由回去的。引擎对它的约定是:

  • 留空时,引擎自动填充一个全新 UUID(推荐默认);
  • 也可以用"可读前缀 + UUID"自行构造;
  • 不要在同一个 session 的多次运行之间复用同一个固定字面量——客户端(尤其 Dev UI)会按此 ID 记录"已回答的请求",复用会导致后续运行不再弹出输入框。

RequestInput还支持Message(UI 上展示的提示文本)、ResponseSchema(人工回复必须满足的 JSON Schema)和Payload(随提示透传给 UI 的上下文,引擎不做解释)。Payload必须是 JSON 可编码的;二进制数据建议通过agent.Artifacts存放,在Payload里只放 URI 字符串。

3. 恢复协议:Workflow.Resume

暂停之后如何恢复?答案是 workflow/resume.go 中的(*Workflow).Resume,其签名是:

func (w *Workflow) Resume( ctx agent.Context, state *RunState, responses map[string]any, ) iter.Seq2[*session.Event, error]

responses把RequestInput.InterruptID映射到用户提供的回复载荷。Resume对每个处于NodeWaiting且InterruptID命中的节点执行:

  1. 校验:若PendingRequest.ResponseSchema非空,先校验载荷,不匹配则以ErrInvalidResumeResponse报错,节点保持NodeWaiting、PendingRequest不变,便于调用方修正后重试;
  2. 消费:先清空PendingRequest、把状态置为NodePending,再重新调度——这样对同一InterruptID的重复Resume调用会变成无害的空操作;
  3. 路由:把回复像"提问者自己的输出"一样投递给提问者的后继节点(handoff 模式),提问者自身不会重新执行。

Resume还定义了ErrNothingToResume:当responses非空但没有等待节点匹配时返回,用于区分"成功恢复"与"回复投递没有生效"(通常是目标已过期/已消费,或工作流图已演进出该等待节点)。空responses(或 nil 状态)则视为干净的空操作,不报错。

NodeConfig:控制 HITL 恢复行为的开关

两种 HITL 变体的差异本质上是 workflow/config.go 中NodeConfig.RerunOnResume字段的控制:

取值恢复模式行为
&truere-entry(重入)恢复时从头重跑被中断的节点,回复经ctx.ResumedInput交付给该节点本体
&falsehandoff(交接)把回复作为该节点的输出,路由给后继节点作为输入,提问者自身不重跑
nil跟随引擎默认当前引擎把 nil 视为 handoff

hitl_simple使用的是默认的 handoff 语义(ask_name提问、greet消费回复,提问者不重跑);hitl_rerun则显式设置NodeConfig{RerunOnResume: &rerun}(其中rerun := true)走 re-entry 模式。

re-entry 模式的一站式封装:ResumeOrRequestInput

单节点重入变体(examples/workflow/hitl_rerun/main.go)把"提问—暂停—恢复后消费回复"压缩成一个调用:

reply, err := workflow.ResumeOrRequestInput(nc, emit, session.RequestInput{ InterruptID: "ask_name-" + nc.InvocationID(), Message: "What's your name?", })

workflow.ResumeOrRequestInput 的实现逻辑(workflow/request_input.go):

  • 第一次执行:ctx.ResumedInput(req.InterruptID)查不到已恢复输入,于是发射NewRequestInputEvent并返回ErrNodeInterrupted(暂停、无输出);
  • 恢复后重跑:同一调用通过ctx.ResumedInput拿到人工回复并返回它,函数体随即把它转成最终输出。

注意此处InterruptID内嵌了调用 ID(nc.InvocationID()):在单次运行的多次重入之间保持稳定,使回复仍能正确关联;同时对不同运行又是唯一的,保证 Dev UI 在后续运行中会重新弹出提示。

机制层面的佐证:调度器测试

workflow/hitl_test.go 用一组测试精确刻画了调度器对 HITL 事件的语义,可作为阅读源码时的对照:

  • TestScheduler_HitlNode_PausesAndForwardsRequest:节点产出一个RequestInput事件后退出,引擎把事件向下游转发、不调度后继节点,工作流以提问者停驻(parked)状态干净收尾;
  • TestScheduler_HitlNode_AutoGeneratesInterruptID:节点作者未提供InterruptID时,引擎会补一个非空 UUID;
  • TestScheduler_HitlNode_PreservesExplicitInterruptID:显式提供的 ID 原样穿透到消费方;
  • TestScheduler_HitlNode_MultipleRequestsPark:一个节点可在单次激活中抛出多个中断,全部记录在NodeState.Interrupts,节点停驻在NodeWaiting;
  • TestScheduler_HitlNode_ErrorAfterRequestFails:先记录请求又返回错误的节点按失败处理,而非等待——失败优先于已记录的请求;
  • TestScheduler_HitlNode_ConcurrentBranches_PausesOnlyWhenAllNonRunning:并行分支中,非 HITL 分支照常完成,HITL 分支的后继不调度,全部活节点完成或进入NodeWaiting后才终止。

两种 HITL 模式怎么选?

把hitl_simple(handoff)与hitl_rerun(re-entry)放在一起对比,选择依据一目了然:

维度hitl_simple(handoff)hitl_rerun(re-entry)
节点数量两个(提问 + 消费)一个(提问 + 消费合一)
恢复路径回复流向后继节点回复流回同节点并从头重跑
关键 APINewRequestInputEvent+ErrNodeInterruptedResumeOrRequestInput
关键配置默认(nil 即 handoff)RerunOnResume: &true
适用场景提问与处理解耦、处理逻辑较长或需要独立命名提问与最终输出强耦合、节点体量小的场景

进一步探索

  • 运行孪生变体:go run ./examples/workflow/hitl_rerun/ console,对比单节点重入与双节点交接的会话差异;
  • 在真实会话中体验暂停/恢复的持久化语义,可阅读 session/session.go 中RequestInput的 JSON 序列化注释(它会跨暂停/恢复回合持久化到会话状态中);
  • 若你的 HITL 节点需要合法化回复格式,为RequestInput设置ResponseSchema,结合ErrInvalidResumeResponse实现"回复格式错误可重试"的交互;
  • 若需要在多步审批等真实业务中落地,可参考更复杂的 examples/workflow/complex 与 examples/workflow/hitl_rerun/README.md,它们展示了 HITL 与更丰富工作流图组合的用法。
  • 人工智能
  • 大模型
  • AI Agent
  • Agent 框架
  • 多智能体
  • 工具调用
  • MCP Clients
  • Agent 记忆

【免费下载链接】adk-go

An open-source, code-first Go toolkit for building, evaluating, and deploying sophisticated AI agents with flexibility and control.

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

相关推荐

上一篇:LikeC4 DSL 样式令牌与颜色体系:从语义色板到图标的完整配色指南
下一篇:ClickHouse v24.8.6.70-lts 版本发布详解:LTS 分支的关键修复与稳定性保障

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 16:39:16

飞机失控赖宇宙射线?

2025年10月30日,捷蓝航空一架A320,航班B61230,注册号N6-05JB,从墨西哥坎昆飞纽约纽瓦克。飞机正常巡航在35000英尺左右,天气也没什么特别的,结果突然出现俯仰异常,短时掉高大约100英尺。100英尺…

作者头像 李华
网站建设 2026/10/2 16:38:26

Claude Code 终端使用教程:把 settings 改到 TaoToken 的完整配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华