adk-go-agent
adk-go Agent 类详解
ADK for Go 在 2026 年 3 月底发布 1.0 后,重心已经不只是“怎么写一个 Agent”,而是“怎么把 Agent 放进生产环境运行”。这篇文章按 1.0/1.1.x 的能力把几个关键点补齐:结构化工作流 Agent、Plugin/OTel 可观测性、Retry and Reflect 自愈、Human-in-the-Loop 工具确认流、YAML 配置化,以及多模态内容审核场景里的 SafetySettings。
基础接口
在 ADK 框架中,agent.Agent 是所有 Agent 的基础接口。它定义了 Agent 的基本属性和运行逻辑:
1 | type Agent interface { |
核心方法可以这样理解:
Name() string:返回 Agent 名称。同一棵 Agent 树里必须唯一,且不能是保留名user。Description() string:返回 Agent 能力描述。多 Agent 系统里,LLM 会依赖它判断是否把任务委托给这个 Agent,建议保持一行内的短描述。SubAgents() []Agent:返回直接子 Agent 列表,定义 Agent 的层级关系。框架会基于它建立父子关系,支持跨 Agent 路由。Run(InvocationContext) iter.Seq2[*session.Event, error]:定义 Agent 的执行逻辑。InvocationContext包含当前调用、会话、状态、内存等信息,返回值会持续产出执行事件或错误。
如果要写完全自定义的 Agent,可以用 agent.Config:
1 | customAgent, err := agent.New(agent.Config{ |
agent.Config 适合 API 聚合、确定性任务、编排胶水代码等场景。对大多数业务来说,真正高频使用的是下面的 llmagent.New。
llmagent 介绍
大多数 Agent 不会手写 Run,而是通过 llmagent.New 创建 LLM 驱动的 Agent。常见配置如下:
Name:Agent 名称,必须非空。Model:底层模型,必填。Description:Agent 能力描述,主要用于父 Agent 动态路由。Instruction/InstructionProvider:局部系统提示词,支持静态字符串或动态函数。GlobalInstruction/GlobalInstructionProvider:全局指令,只在根 Agent 上生效。Tools/Toolsets:可调用工具集合。BeforeModelCallbacks/AfterModelCallbacks:模型调用前后的钩子。BeforeToolCallbacks/AfterToolCallbacks:工具调用前后的钩子。GenerateContentConfig:温度、token 上限、安全策略、结构化输出等模型生成参数。InputSchema/OutputSchema:Agent 被调用或输出时的 JSON Schema 约束。SubAgents:可委托的子 Agent。IncludeContents:是否把对话历史带入模型请求。DisallowTransferToParent/DisallowTransferToPeers:限制向父 Agent 或同级 Agent 转移。OutputKey:把 Agent 输出写入 session state,便于后续 Agent 使用。
一个典型的初始化如下:
1 | agent, err := llmagent.New(llmagent.Config{ |
这里要注意一个 1.0/1.1.x 很容易写错的点:Plugins 不在 llmagent.Config 上,而是在 runner.Config.PluginConfig 上;HITL 也不是一个统一的 Confirmers 字段,而是通过工具级的 RequireConfirmation / RequireConfirmationProvider,或者 tool.WithConfirmation 包装 Toolset 来实现。也就是说,Agent 负责业务能力,Runner 和 Tool 负责生产级横切能力。
Model
1.0 之后的官方示例已经更偏向 model/gemini 包,而不是旧文章里常见的 provider/googleai 写法。生产代码里不要把 API Key 写死,Gemini API Key 场景建议显式从环境变量读取;如果走 Vertex AI/ADC,再按部署环境传对应的 genai.ClientConfig。
1 | import ( |
如果项目使用 Vertex AI/ADC,可以把认证交给运行环境,并在 ClientConfig 中只保留后端、区域等必要配置。最小化示意如下:
1 | model, err := gemini.NewModel(ctx, "gemini-flash-latest", &genai.ClientConfig{}) |
这样做的好处是代码不携带密钥,容器镜像也不需要因为 API Key 变化而重新构建。
提示词
Instruction 和 GlobalInstruction 的区别可以按作用域理解:
| 项目 | 作用范围 | 生成时机 | 一句话总结 |
|---|---|---|---|
Instruction |
单个 Agent | 编译期固化 | 当前 Agent 的角色和任务边界 |
InstructionProvider |
单个 Agent | 每次运行前动态生成 | 根据 session state、环境、时间等动态拼提示词 |
GlobalInstruction |
整棵 Agent 树 | 编译期固化 | 全局红线和所有 Agent 都要遵守的规则 |
GlobalInstructionProvider |
整棵 Agent 树 | 每次运行前动态生成 | 把当前环境、日期、租户等动态信息广播给全体 Agent |
局部指令示例:
1 | import ( |
全局指令示例:
1 | import ( |
如果同时设置 Instruction 和 InstructionProvider,动态 Provider 通常会接管静态指令。GlobalInstruction 也同理。实际项目里建议把不可变的安全边界放在静态指令里,把环境、租户、日期、灰度策略放在 Provider 里。
GenerateContentConfig
GenerateContentConfig 用来配置底层模型生成参数,例如温度、token 上限、停止词、JSON 输出和安全过滤。注意:工具不要写在这里,工具应该写到 llmagent.Config.Tools 或 Toolsets。
1 | genConfig := &genai.GenerateContentConfig{ |
核心字段:
| 字段名 | 作用机制 | 最佳实践与场景举例 |
|---|---|---|
Temperature |
控制生成随机性 | 内容审核、信息抽取、路由决策建议 0.0 到 0.2;创意生成可以更高 |
MaxOutputTokens |
限制单次输出长度 | 控制成本和接口耗时,例如后端同步接口限制在 500 token 左右 |
ResponseMIMEType / ResponseSchema |
强制结构化输出 | 企业后端建议输出 JSON,方便 json.Unmarshal 和下游校验 |
TopP / TopK |
控制采样候选范围 | 一般不单独调,优先调 Temperature |
StopSequences |
遇到指定字符串停止生成 | Few-shot 或协议化输出时防止模型继续模拟用户 |
SafetySettings |
控制底层安全拦截策略 | 内容审核/有害梗图分析场景必须认真配置,否则模型可能因为安全过滤拒绝分析违规图片 |
有害梗图分析是一个特殊场景:系统的目标正是识别违规内容,因此需要让模型“看见”违规内容并输出标签,而不是直接被安全层拦截。可以在审核链路里对相关 harm category 设置 BLOCK_NONE,但不要把这个配置无差别用于普通聊天入口。更稳妥的做法是把审核 Agent 部署在隔离链路里,输出强制走 Schema,并在业务层继续做权限、日志脱敏和人工复核。
Schema 约束
Agent 进入后端业务链路后,不应依赖自然语言输出。输入输出都应该用 Schema 收敛成结构化数据。
1 | type MemeAnalyzeInput struct { |
OutputSchema 的价值是把模型输出从自然语言收敛到 Go 可以校验和解析的数据结构里。需要注意:在当前 ADK Go API 中,InputSchema 和 OutputSchema 的字段类型是 *genai.Schema。如果你在项目里用 struct tag 表达 Schema,需要先通过本地的 JSON Schema 工具或封装函数转换成 *genai.Schema,再传给 llmagent.Config。
1 | agent, err := llmagent.New(llmagent.Config{ |
还要记住一个重要限制:配置 OutputSchema 后,这个 Agent 通常只能直接回复结构化结果,不能再自由调用工具、RAG 或转交给其他 Agent。实践中更推荐把“会调用工具的编排 Agent”和“最终结构化输出 Agent”拆开:前者负责取证和调用,后者只负责把最终结论压成 Schema。
工具调用与 HITL
1.0/1.1.x 里,HITL 的核心是 Tool Confirmation。对敏感工具设置确认要求后,Agent 在执行工具前会暂停,发出确认事件,等待人类在后台或前端批准、修改参数或拒绝,然后再继续。
典型场景包括删除数据库、大额转账、线上配置变更、批量封禁用户、发送外部邮件等。这类操作不应该只依赖模型判断。
1 | import ( |
如果只有部分请求需要确认,可以用动态判断:
1 | transferTool, err := functiontool.New(functiontool.Config{ |
这比在 BeforeToolCallback 里自己拦截更清晰,因为确认流会进入 ADK 的标准事件协议,前端、后台任务和审计日志都能围绕同一个事件模型实现。
子 Agent 与结构化工作流
老版本文章里通常只讲一种模式:父 llmagent 把子 Agent 包成可调用工具,由 LLM 根据 Instruction 和子 Agent 的 Description 动态决定调用谁。这个模式仍然重要,适合意图识别、动态路由和开放式任务拆解。
1 | ocrAgent, _ := llmagent.New(llmagent.Config{ |
1.0/1.1.x 之后,更推荐把确定性强的流程改成结构化 Workflow Agent,而不是完全交给 LLM 自己决定控制流。
SequentialAgent
SequentialAgent 按 SubAgents 的顺序执行一次,适合固定流水线,例如 OCR -> 文本审核 -> 报告汇总。
1 | import ( |
ParallelAgent
ParallelAgent 并发执行多个子 Agent,适合需要多视角判断或多个独立步骤的任务,例如 OCR、图像内容分类、相似图检索并行执行,然后交给汇总 Agent。
1 | import "google.golang.org/adk/agent/workflowagents/parallelagent" |
LoopAgent
LoopAgent 会按顺序反复执行子 Agent,直到达到最大迭代次数或被子 Agent 终止,适合“生成 -> 评审 -> 修正”的迭代流程。
1 | import "google.golang.org/adk/agent/workflowagents/loopagent" |
后端开发的经验法则是:控制流能确定就用 Workflow Agent,任务边界开放才交给 LLM 路由。这样更容易测试、监控和排障。
