作者:互联网 时间: 2026-07-01 08:59:53
Eino 底层用 compose 包搭图——你手动加节点、连边、配分支、编译、运行。强大,但代码量不小。

ADK 是建在 compose 之上的高层封装,目标是:只需配置就能建 Agent,不用关心图的结构。包路径是 github.com/cloudwego/eino/adk。
ADK 和 compose 的关系,类似 Web 框架和 HTTP 库的关系:底层是同一套机制,上层帮你省掉重复的"建图→编译→运行"三步。
ChatModelAgent 是 ADK 里使用最频繁的类型。核心逻辑:配置一个模型,可选配工具,剩下的 ADK 自动处理。
复制代码// 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:
复制代码用户输入 → [system prompt + messages] → ChatModel → 输出
只调用一次模型,不循环。
给 ToolsConfig 传工具,ChatModelAgent 自动升级为 ReAct Agent:
复制代码// 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()},
},
},
})
内部结构变成:
复制代码用户输入
↓
ChatModel → 要用工具?
├── 是 → ToolsNode(执行工具)→ 回 ChatModel
└── 否 → 输出结果
(最多循环 MaxIterations 次,默认 20)
两种模式的切换是自动的:有工具就 ReAct,没工具就直通。你不需要改代码结构。
Agent 本身只负责执行逻辑。Runner 是执行入口,负责生命周期管理:启动、流式输出、断点持久化、恢复。
复制代码// 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() 接收字符串,自动包装成 UserMessage;runner.Run() 接收 []Message,适合多轮对话场景。
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
}
完整流程:
复制代码① Agent 调用工具
② 工具返回 compose.NewInterruptAndRerunErr("你想看什么类型的书?")
③ Runner 检测到中断 → 状态存入 CheckPointStore → 事件流结束
④ 用户看到问题,输入答案
⑤ 调用 runner.Resume(),注入用户答案,从断点继续
恢复代码:
复制代码// 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 即可。
当 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/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