ADK 入门:不写图,也能搭 Agent

系列「企业级 AI Agent 实现拆解」E18 篇。上一篇 E17 拆解了 deer-go 深度研究团队的完整实现------八节点图、共享状态、AnyPredecessor 循环。那条路的门槛不低:要手动建图、注册节点、配分支、编译运行。这篇介绍 Eino 的 ADK(Agent Development Kit)------一套专门为"不想手搓图"的场景准备的更高层封装。

读完这篇你会知道

  • ADK 是什么:它和 compose/graph 的关系
  • ChatModelAgent:最常用的 Agent,两种运行模式
  • Runner:如何启动、流式输出、断点续跑
  • Custom Agent:自定义 Agent 的最小实现
  • Interrupt/Resume:一个工具怎么暂停整个 Agent 等人回复
  • 三种类型怎么选

一、ADK 是什么

Eino 底层用 compose 包搭图------你手动加节点、连边、配分支、编译、运行。强大,但代码量不小。

ADK 是建在 compose 之上的高层封装 ,目标是:只需配置就能建 Agent,不用关心图的结构。包路径是 github.com/cloudwego/eino/adk

ADK 和 compose 的关系,类似 Web 框架和 HTTP 库的关系:底层是同一套机制,上层帮你省掉重复的"建图→编译→运行"三步。


二、ChatModelAgent:最常用的类型

ChatModelAgent 是 ADK 里使用最频繁的类型。核心逻辑:配置一个模型,可选配工具,剩下的 ADK 自动处理

无工具模式

go 复制代码
// eino-examples/adk/helloworld/helloworld.go
agent, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
    Name:        "hello_agent",
    Description: "A friendly greeting assistant",
    Instruction: "You are a friendly assistant. Please respond in a warm tone.",
    Model:       model,
})

无工具时,ChatModelAgent 内部是一条简单 chain

css 复制代码
用户输入 → [system prompt + messages] → ChatModel → 输出

只调用一次模型,不循环。

加工具:自动变成 ReAct 循环

ToolsConfig 传工具,ChatModelAgent 自动升级为 ReAct Agent:

go 复制代码
// eino-examples/adk/intro/chatmodel/subagents/agent.go
agent, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
    Name:        "BookRecommender",
    Description: "An agent that can recommend books",
    Instruction: `You are an expert book recommender. Use "search_book" to find books.`,
    Model:       model.NewChatModel(),
    ToolsConfig: adk.ToolsConfig{
        ToolsNodeConfig: compose.ToolsNodeConfig{
            Tools: []tool.BaseTool{NewBookRecommender(), NewAskForClarificationTool()},
        },
    },
})

内部结构变成:

markdown 复制代码
用户输入
    ↓
 ChatModel → 要用工具?
    ├── 是 → ToolsNode(执行工具)→ 回 ChatModel
    └── 否 → 输出结果
(最多循环 MaxIterations 次,默认 20)

两种模式的切换是自动的:有工具就 ReAct,没工具就直通。你不需要改代码结构。


三、Runner:怎么跑起来

Agent 本身只负责执行逻辑。Runner 是执行入口,负责生命周期管理:启动、流式输出、断点持久化、恢复。

go 复制代码
// eino-examples/adk/intro/chatmodel/chatmodel.go
runner := adk.NewRunner(ctx, adk.RunnerConfig{
    EnableStreaming:  true,
    Agent:           a,
    CheckPointStore: store.NewInMemoryStore(),
})

iter := runner.Query(ctx, "recommend a book to me", adk.WithCheckPointID("1"))
for {
    event, ok := iter.Next()
    if !ok { break }
    if event.Err != nil { log.Fatal(event.Err) }
    prints.Event(event)
}

每次 iter.Next() 返回一个 AgentEvent

  • event.Output.MessageOutput --- 模型输出或工具结果
  • event.Action --- 特殊动作(中断、退出)
  • event.Err --- 错误

runner.Query() 接收字符串,自动包装成 UserMessagerunner.Run() 接收 []Message,适合多轮对话场景。


四、Interrupt/Resume:工具让 Agent 暂停等人

ADK 的中断机制允许一个工具在执行中途暂停整个 Agent,等待用户输入后继续

书籍推荐 Agent 里的 ask_for_clarification 工具演示了这个机制(源码 subagents/ask_for_clarification.go):

go 复制代码
func NewAskForClarificationTool() tool.InvokableTool {
    t, _ := utils.InferOptionableTool(
        "ask_for_clarification",
        "Call this when the user's request is ambiguous.",
        func(ctx context.Context, input *AskForClarificationInput, opts ...tool.Option) (string, error) {
            o := tool.GetImplSpecificOptions[askForClarificationOptions](nil, opts...)
            if o.NewInput == nil {
                // 没有用户回复 → 触发中断,把问题文本作为 payload
                return "", compose.NewInterruptAndRerunErr(input.Question)
            }
            // 有用户回复 → 继续执行,返回用户答案
            return *o.NewInput, nil
        })
    return t
}

完整流程

vbscript 复制代码
① Agent 调用工具
② 工具返回 compose.NewInterruptAndRerunErr("你想看什么类型的书?")
③ Runner 检测到中断 → 状态存入 CheckPointStore → 事件流结束
④ 用户看到问题,输入答案
⑤ 调用 runner.Resume(),注入用户答案,从断点继续

恢复代码:

go 复制代码
// eino-examples/adk/intro/chatmodel/chatmodel.go:61
iter, err := runner.Resume(ctx, "1",
    adk.WithToolOptions([]tool.Option{subagents.WithNewInput(nInput)}))

中断恢复靠 CheckPointID (上面的 "1"):它绑定一次对话会话,Query 时创建,Resume 时读取。CheckPointStore 负责在两次调用之间持久化状态------示例里用内存 Map,生产环境换成 Redis 即可。


五、Custom Agent:从头实现 Agent 接口

ChatModelAgent 不够用------需要混合调用多个模型、自定义流控、注入外部状态------直接实现 Agent 接口:

go 复制代码
// adk/interface.go
type Agent interface {
    Name(ctx context.Context) string
    Description(ctx context.Context) string
    Run(ctx context.Context, input *AgentInput, options ...AgentRunOption) *AsyncIterator[*AgentEvent]
}

最小实现(源码 adk/intro/custom/myagent.go):

go 复制代码
type MyAgent struct{}

func (m *MyAgent) Name(_ context.Context) string        { return "MyAgent" }
func (m *MyAgent) Description(_ context.Context) string { return "My custom agent" }

func (m *MyAgent) Run(ctx context.Context, input *adk.AgentInput, opts ...adk.AgentRunOption) *adk.AsyncIterator[*adk.AgentEvent] {
    iter, gen := adk.NewAsyncIteratorPair[*adk.AgentEvent]()
    go func() {
        defer func() {
            if e := recover(); e != nil {
                gen.Send(&adk.AgentEvent{Err: fmt.Errorf("panic: %v", e)})
            }
            gen.Close()  // 必须 Close,否则 iter.Next() 永远阻塞
        }()

        // 你的任意逻辑:调模型、查 DB、调 API......
        gen.Send(&adk.AgentEvent{
            Output: &adk.AgentOutput{
                MessageOutput: &adk.MessageVariant{
                    Message: &schema.Message{Role: schema.Assistant, Content: "hello world"},
                    Role:    schema.Assistant,
                },
            },
        })
    }()
    return iter
}

实现后传给 Runner,用法与 ChatModelAgent 完全一致:

go 复制代码
runner := adk.NewRunner(ctx, adk.RunnerConfig{Agent: &MyAgent{}})

ADK 不关心 Agent 内部实现 ,只要遵守接口契约:通过 AsyncIterator 异步推送 AgentEvent,最后 gen.Close()


六、三种类型怎么选

ChatModelAgent(无工具) ChatModelAgent(有工具) Custom Agent
内部结构 简单 chain 自动 ReAct 循环 完全自定义
代码量 最少,纯配置 少,多加一个 ToolsConfig 多,实现接口
中断/恢复 内置支持 内置支持 需自行集成
适用场景 问答、摘要、翻译 需要工具的任务 混合多模型、自定义流控

还有一种 TypedChatModelAgent[*schema.AgenticMessage],专给支持原生工具调用的模型(如 Claude Computer Use 系列)------模型内部处理工具,Eino 只做单次调用,不跑外部 ReAct 循环。一般场景用默认 ChatModelAgent 即可。


七、完整代码:从零到可运行

把前面拆开的部分组装成最小完整示例:

go 复制代码
package main

import (
    "context"
    "fmt"
    "log"
    "os"

    "github.com/cloudwego/eino/adk"
    "github.com/cloudwego/eino-ext/components/model/openai"
)

func main() {
    ctx := context.Background()

    // 1. 初始化模型
    m, _ := openai.NewChatModel(ctx, &openai.ChatModelConfig{
        APIKey:  os.Getenv("OPENAI_API_KEY"),
        Model:   os.Getenv("OPENAI_MODEL"),
        BaseURL: os.Getenv("OPENAI_BASE_URL"),
    })

    // 2. 创建 Agent(无工具 = 简单 chain)
    agent, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
        Name:        "demo_agent",
        Description: "A demo agent",
        Instruction: "You are a helpful assistant.",
        Model:       m,
    })

    // 3. 创建 Runner
    runner := adk.NewRunner(ctx, adk.RunnerConfig{
        Agent:          agent,
        EnableStreaming: true,
    })

    // 4. 运行并消费事件
    iter := runner.Query(ctx, "Hello, who are you?")
    for {
        event, ok := iter.Next()
        if !ok {
            break
        }
        if event.Err != nil {
            log.Fatal(event.Err)
        }
        if msg, err := event.Output.MessageOutput.GetMessage(); err == nil {
            fmt.Println(msg.Content)
        }
    }
}

五步:初始化模型 → 创建 Agent → 创建 Runner → 调用 Query → 消费事件流。不需要手工建图。


小结

ADK 是 Eino compose 层之上的高层封装。ChatModelAgent 处理日常大多数场景:无工具时是 chain,有工具时自动变 ReAct,中断恢复开箱即用。需要完全控制时实现 Agent 接口。Runner 统一管理执行入口,流式、断点、恢复都通过它来做。

下篇继续。


代码来源:cloudwego/eino · cloudwego/eino-examples

相关推荐
阿里云云原生2 小时前
AI 开发新常态:当 Cursor、Claude、Codex 并行,如何统一管理散落的 Skill 资产?
云原生·ai编程
kyriewen2 小时前
今天的科技圈,全在抢英伟达的饭碗
前端·面试·ai编程
Databend3 小时前
在 AWS 中国峰会逛了一天,我在 Databend 展台看到了 Agent 数据基础设施的新思路
数据库·人工智能·agent
米小虾3 小时前
从 Prompt 到 Loop:2026 年 AI 工程师必须掌握的 Loop Engineering 实战指南
人工智能·agent
槑有老呆3 小时前
Token 与 Embedding:大模型理解语言的起点
agent
七牛开发者4 小时前
MCP 到底是什么?为什么 Agent 都想接上它
算法·aigc·agent
ServBay4 小时前
拒绝当二等公民,Windows 开发者如何无痛开启 Claude Code 本地全栈运维?
后端·ai编程·mcp
埃菲尔铁桶4 小时前
我和大模型一起做了个本地知识库——用户也是我和大模型
人工智能·ai编程