ADK 入门:不写图也能搭 Agent

时间:2026-07-02 08:54:42 来源:互联网

ADK作为Eino框架中compose包之上的高级封装,通过配置即可构建智能体,无需手动构图。这种设计大幅简化了开发流程,让开发者能快速搭建功能强大的Agent。

读完这篇你会知道


一、ADK 是什么

在Eino底层,compose包负责构建计算图,开发者需手动添加节点、连接边、配置分支,再经历编译和运行。这种方式虽然灵活,但代码量较大。

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

ADK建立在compose包之上,是一种高层封装,其设计目标是通过配置即可创建智能体,无需关心底层图结构。该功能的包路径为github.com/eino/adk。ADK与compose的关系可以类比为Web框架与HTTP库:两者共享底层机制,上层则省去了重复的建图、编译和运行步骤。


二、ChatModelAgent:最常用的类型

ChatModelAgent是ADK中使用频率最高的类型。其核心逻辑是:配置一个模型,可选配工具,其余部分由ADK自动处理。

无工具模式

 复制代码// 示例: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,仅调用一次模型,不会循环。

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

加工具:自动变成 ReAct 循环

在ToolsConfig中传入工具,ChatModelAgent会自动升级为ReAct Agent。其内部结构会从简单chain变为包含循环逻辑的复杂流程:

 复制代码// 示例: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()},
        },
    },
})

内部结构会变成ReAct循环:

 复制代码用户输入

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

两种模式的切换是自动进行的:有工具时采用ReAct,无工具时直通。开发者无需修改代码结构。


三、Runner:怎么跑起来

Agent本身只负责执行逻辑,而Runner是实际的执行入口,负责管理生命周期,包括启动、流式输出、断点持久化以及恢复等操作。

 复制代码// 示例: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,其包含以下内容:

  1. event.Output.MessageOutput:模型输出或工具结果
  2. event.Action:特殊动作,例如中断或退出
  3. event.Err:错误信息

runner.Query()接收字符串,会自动将其包装成UserMessage;而runner.Run()则接收[]Message,更适合多轮对话场景。


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

ADK的中断机制允许工具在执行中途暂停整个Agent,等待用户输入后继续执行。书籍推荐Agent中的ask_for_clarification工具演示了这一机制(源码位于subagents/ask_for_clarification.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
}

完整流程包括以下步骤:

  1. Agent调用工具
  2. 工具返回compose.NewInterruptAndRerunErr,例如"你想看什么类型的书?"
  3. Runner检测到中断,将状态存入CheckPointStore,结束事件流
  4. 用户看到问题并输入答案
  5. 调用runner.Resume()注入用户答案,从断点继续执行

恢复代码示例如下:

 复制代码// 示例: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接口:

 复制代码// 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):

 复制代码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完全一致:

 复制代码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即可。


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

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

 复制代码package mainimport (
    "context"
    "fmt"
    "log"
    "os"    "github.com/eino/adk"
    "github.com/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提供从无工具链式到自动ReAct循环的灵活切换,配合统一的Runner执行入口和内置中断恢复机制,覆盖大多数智能体应用场景。日常开发中,ChatModelAgent可处理大部分需求,需要完全控制时则实现Agent接口。下篇将继续深入探讨。


代码来源:eino/eino-examples