Go 语言架构|Google ADK Go 深入拆解:把 Agent 写进 Go 的工程流水线

原文:Agent Development Kit (ADK) for Go 作者:Google ADK Go 团队(Google 官方开源项目维护者) 来源:GitHub,发布于 2025 年 11 月 06 日

Google ADK Go:把 Agent 开发拉进 Go 的工程流水线

我最近重新看了一遍 Google 的 ADK Go。它吸引我的地方,并不是“Go 也有一个 AI 框架”,而是它试图回答一个更实际的问题:一个会调用模型、工具和其他 Agent 的系统,能不能像普通 Go 服务一样被编译、被测试、被部署和被审查?

这篇文章不打算把官方 API 逐个翻译一遍。我会沿着自己的理解,把 Agent、工具、工作流、协议、状态和上下文串起来,也会特别标出 v1 与 v2 的边界。文中的蓝色表示工具或事实,绿色表示我的实践判断红色表示容易踩的坑黄色底色表示可以直接拿去用的做法

  1. 先看 ADK Go 为什么值得单独关注。 我认为,ADK Go 的重点不是证明“Go 比 Python 快”,而是让 Agent 代码进入成熟的软件工程流程。Go 项目已有的编译器、静态检查、单元测试、依赖管理、CI 和容器化能力,都可以继续使用。

    官方仓库把它定位为一个“code-first”的 Go 工具包:开发者用 Go 定义 Agent、工具和编排关系,再交给 Runner 或 Launcher 执行。ADK Go 首个 v0.1.0 版本于 2025 年 11 月 06 日发布;v2.0.0 于 2026 年 06 月 30 日发布,并将模块路径改为 google.golang.org/adk/v2,同时要求 Go 1.25 或更高版本。所以,阅读旧文章或旧示例时,第一件事不是复制代码,而是先确认它针对的是 v1 还是 v2。

    从抽象上看,我会把 ADK Go 先拆成八个部分:

    1. Agent:执行任务的基本单元,既可以是 LLM Agent,也可以是自定义 Agent。
    2. Tool:Agent 可以调用的函数、API 或外部能力。
    3. Session:一次用户与 Agent 交互的会话及其事件历史。
    4. Memory:跨会话检索的长期记忆能力。
    5. Artifact:文件、图片、报告等不适合直接塞进文本上下文的大对象。
    6. Model:模型适配层,官方示例包含 Gemini 和 OpenAI 兼容模型。
    7. Workflow:把 Agent、函数和工具组织成有向图,处理顺序、分支、并行和汇合。
    8. Telemetry:把 Agent、模型调用和工具调用接入可观测性系统。

    v2 的一个最小 Agent 大致会是下面这个形状。为了突出结构,我省略了部分错误处理;真正项目里,模型、工具和 Agent 的构造错误都应该显式返回并记录。

    model, err := gemini.NewModel(ctx, "gemini-flash-latest", &genai.ClientConfig{
        APIKey: os.Getenv("GOOGLE_API_KEY"),
    })
    if err != nil {
        return err
    }
    
    weatherTool, err := functiontool.New(functiontool.Config{
        Name:        "get_weather",
        Description: "查询指定城市的天气",
    }, getWeather)
    if err != nil {
        return err
    }
    
    root, err := llmagent.New(llmagent.Config{
        Name:        "weather_agent",
        Model:       model,
        Description: "回答天气问题",
        Instruction: "当用户询问城市天气时,调用 get_weather 并整理结果。",
        Tools:       []tool.Tool{weatherTool},
    })
    if err != nil {
        return err
    }
    

    这里最值得关注的不是函数名,而是边界:模型、工具、Agent 都是显式对象;错误在构造阶段就能暴露;运行时再由 runnerlauncher 接管。我的判断是,Agent 越接近普通服务的依赖图,越容易做代码审查、替换模型和编写测试。

  2. 再看 v2 的工作流变化。 原稿中使用 sequential.Newparallel.New 的写法,更接近 v1 的模板式工作流。v2 的主线已经转向 图工作流:Agent、工具和普通 Go 函数都可以成为节点,节点之间通过边连接,运行时负责调度。

    这带来几种常见的组合方式:

    1. 顺序执行A → B → C,适合“先抓取、再处理、最后汇总”。
    2. 条件路由:节点发出路由信息后,进入不同的后续节点。
    3. 扇出与汇合:一个节点同时触发多个分支,再由 Join 节点统一收集结果。
    4. 循环与重试:对需要迭代或可能暂时失败的节点设置边界。
    5. 人工介入:在付款、审批或高风险操作前暂停工作流,等待外部输入后恢复。

    v1 的模板式工作流并没有因此失去价值。SequentialAgentParallelAgentLoopAgent 仍适合表达固定流程;只是对于需要条件分支、动态节点和汇合的系统,v2 的图模型更自然。不要把 v1 示例中的构造器直接当成 v2 的推荐写法,也不要只因为能串起几个 Agent,就把所有逻辑都交给一个“协调 Agent”决定。

    我更喜欢把确定性的部分交给代码,把需要理解自然语言的部分交给模型。例如,“库存查询完成后才能判断是否通知”是代码层面的顺序约束;“这次延误是否需要升级处理”才可能交给 LLM Agent。这样做的好处是,流程在 review 中看得见,模型只负责它真正擅长的判断。

    ADK Go 的事件流使用 Go 的 iter.Seq2 表达。调用方可以用 for range 消费事件,同时把 context.Context 沿着调用链传下去,统一处理取消、超时和追踪。这不是“用了 Go 就自动安全”,但它确实给了我们一个更明确的审查入口:每个异步任务都要回答什么时候结束、谁负责取消、错误向哪里传播。

  3. 协议要按问题选择,而不是按名词收集。 围绕 Agent 的协议不少,但 ADK Go 当前最值得先看的还是 MCP 和 A2A;其余更多属于相邻生态,我会先按职责分层:

    1. MCP解决 Agent 如何接入外部工具和数据源。ADK Go 提供 MCP Toolset,适合把外部 MCP Server 暴露给 LLM Agent。
    2. A2A解决跨进程、跨服务的 Agent 通信。需要把本地 Agent 委派给远程 Agent 时,再考虑它。
    3. AP2 与 UCP更偏交易和商业场景,涉及支付、商品、库存和订单时才有必要引入。
    4. A2UI 与 AG-UI更接近表达层,关注 Agent 如何向前端传递结构化界面或交互状态。

    我自己的判断顺序很简单:先问 Agent 要不要调用外部工具,再问是否需要跨服务通信,接着问是否进入交易,最后才考虑 UI 协议。大多数刚开始做的项目,先把 MCP 的工具边界和 A2A 的服务边界设计清楚,就已经足够了。

    这里还有一个容易被忽略的工程问题:协议只描述通信方式,不替你定义权限。MCP 工具需要做白名单、参数校验、超时和审计;A2A 需要考虑身份、授权、重试和幂等。“协议接通了”不等于“系统可以放心上线”。

  4. 上下文管理决定 Agent 能跑多远。 我会把状态分成三个作用域来理解:

    1. 应用级状态:系统提示词、模型选择和默认配置等所有用户共享的内容。
    2. 用户级状态:语言、时区和长期偏好等跨会话信息。
    3. 临时状态:当前会话里的中间结果、短期变量和工作流分支数据。

    这三个作用域的价值,不只是让字段命名更整齐,而是帮助我们回答“这条信息应该被谁看到、保存多久、什么时候清掉”。并行分支尤其需要注意隔离:一个分支产生的临时结果,不应该无意间污染另一个分支的上下文。

    关于历史压缩,我建议把版本说准确。ADK Go 后续 v2.x 已经提供了 session/compaction 相关能力,可以按滑动窗口或 token 阈值生成摘要;它的设计是追加一条摘要事件,并在后续组装 prompt 时用摘要替代被覆盖的旧事件,原始历史并不会被直接删除。但这不是“打开一个配置就万事大吉”:摘要质量、保留多少原始事件、摘要失败如何处理,都需要结合业务验证。

    Skill 也可以看成上下文管理的一部分。开发者可以把“如何查 BigQuery”“如何操作 Cloud Storage”这类指令放在独立 Skill 文件中,再通过 Skill Toolset 按需加载。指令和工具分开后,Agent 的基础提示词不会被所有业务细节塞满,能力也更容易被复用和 review。

    我会把 trace、metric 和事件日志一起看,而不是只统计模型耗时。至少要知道:哪个 Agent 被调用了、调用了几次模型、用了哪些工具、在哪个节点失败、上下文是否持续膨胀。只有这样,出了问题才能区分是模型判断错了、工具返回错了,还是工作流路由错了。

  5. 用一个餐厅供应链例子把这些抽象串起来。 假设用户说:“看看今天有没有供应商延误;如果有,就发邮件,并把账单挂到仪表盘。”我会把它拆成四个叶子 Agent 或函数节点:

    1. InventoryAgent:调用 query_inventory 查询库存和供应商状态。
    2. EmailAgent:调用 send_email 发送通知。
    3. BillingAgent:调用 create_invoice 生成账单。
    4. DashboardAgent:调用 push_dashboard 更新仪表盘。

    流程可以表达成:

    InventoryAgent
        ├─ 无延误 → 结束
        └─ 有延误 → EmailAgent
                       ├─ BillingAgent
                       └─ DashboardAgent
    

    这里的关键不是“用了几个 Agent”,而是把依赖关系写清楚:库存查询是前置条件;发邮件是条件分支;账单和仪表盘互不依赖,可以并行;付款或真正发送通知前,还可以插入人工确认节点。

    代码即文档在这种场景里才真正成立:读者从节点和边就能理解流程,不需要先读一大段“如果……那么……”的说明。与此同时,每个节点都应该拥有自己的超时、重试、错误和权限边界,而不是把这些责任藏在根 Agent 的长提示词里。

    如果准备开始使用,我会按下面的顺序推进:

    1. 先跑通一个最小 Agent:一个模型、一个工具、一个 Session,再接入 Runner 或 Launcher。
    2. 先写清楚工具的输入、输出、权限和失败方式,再让反射机制生成 schema。 functiontool 支持结构体参数,但复杂参数仍然应该配好 JSON tag,并用测试确认生成的 schema 符合模型预期。
    3. 固定流程优先用确定性的工作流,动态分派才交给协调 Agent。
    4. 不要把长篇业务规则全部塞进 Instruction。 基础行为放 Instruction,按需加载的知识放 Skill,真正的校验和权限放工具实现。
    5. 部署前至少接入取消、超时、错误日志、trace、metric 和 mock 工具测试。 Agent 能回答问题只是起点,能在失败时收敛、在中断时退出、在升级后保持可观察,才接近一个可维护的服务。

给读者的启示

回到开头的问题,ADK Go 的价值并不是“用 Go 重写一个 Python 框架”,而是把 Agent 系统里的不确定部分,尽可能放进可审查的工程边界里:模型负责理解和生成,工具负责执行,工作流负责顺序和路由,Session 与 Memory 负责状态,Telemetry 负责解释系统发生了什么。

如果你的 Agent 只是一次性的脚本,ADK Go 可能显得偏重;如果它要长期运行、调用多个外部系统、经历多轮会话,或者需要被团队持续维护,那么它的 Go 风格抽象就值得认真评估。我会把“显式、可取消、可测试、可观测”当作选择 Agent 框架时比“示例是否炫”更重要的四个标准。

最后提醒一句:ADK Go 的 API 仍在快速演进,特别是 v1 到 v2 的工作流和上下文模型变化较大。动手前要锁定模块版本,并以对应版本的官方文档和示例为准。本文参考了 ADK Go 官方仓库v2.0.0 发布说明ADK Go 安装文档工作流文档上下文压缩 API 文档