This commit is contained in:
2026-05-24 11:42:38 +08:00
commit 30d312ac35
521 changed files with 146481 additions and 0 deletions
+399
View File
@@ -0,0 +1,399 @@
---
Description: ""
date: "2026-03-02"
lastmod: ""
tags: []
title: 概述
weight: 1
---
## 简介
**Eino['aino]** (近似音: i know,希望框架能达到 "i know" 的愿景) 旨在提供基于 Go 语言的终极大模型应用开发框架。 它从开源社区中的诸多优秀 LLM 应用开发框架,如 LangChain 和 LlamaIndex 等获取灵感,同时借鉴前沿研究成果与实际应用,提供了一个强调简洁性、可扩展性、可靠性与有效性,且更符合 Go 语言编程惯例的 LLM 应用开发框架。
Eino 提供的价值如下:
- 精心整理的一系列 **组件(component)** 抽象与实现,可轻松复用与组合,用于构建 LLM 应用。
- **智能体开发套件(ADK)**,提供构建 AI 智能体的高级抽象,支持多智能体编排、人机协作中断机制以及预置的智能体模式。
- 强大的 **编排(orchestration)** 框架,为用户承担繁重的类型检查、流式处理、并发管理、切面注入、选项赋值等工作。
- 一套精心设计、注重简洁明了的 **API**。
- 以集成 **流程(flow)** 和 **示例(example)** 形式不断扩充的最佳实践集合。
- 一套实用 **工具(DevOps tools)**,涵盖从可视化开发与调试到在线追踪与评估的整个开发生命周期。
借助上述能力和工具,Eino 能够在人工智能应用开发生命周期的不同阶段实现标准化、简化操作并提高效率:
<a href="/img/eino/eino_project_structure_and_modules.png" target="_blank"><img src="/img/eino/eino_project_structure_and_modules.png" width="100%" /></a>
[Eino Github 仓库链接](https://github.com/cloudwego/eino)
## 快速上手
直接使用组件:
```go
model, _ := openai.NewChatModel(ctx, config) // create an invokable LLM instance
message, _ := model.Generate(ctx, []*Message{
SystemMessage("you are a helpful assistant."),
UserMessage("what does the future AI App look like?")})
```
当然,你可以这样用,Eino 提供了许多开箱即用的有用组件。但通过使用编排功能,你能实现更多,原因有三:
- 编排封装了大语言模型(LLM)应用的常见模式。
- 编排解决了处理大语言模型流式响应这一难题。
- 编排为你处理类型安全、并发管理、切面注入以及选项赋值等问题。
Eino 提供了三组用于编排的 API:
<table>
<tr><td>API</td><td>特性和使用场景</td></tr>
<tr><td>Chain</td><td>简单的链式有向图,只能向前推进。</td></tr>
<tr><td>Graph</td><td>有向有环或无环图。功能强大且灵活。</td></tr>
<tr><td>Workflow</td><td>有向无环图,支持在结构体字段级别进行数据映射。</td></tr>
</table>
我们来创建一个简单的 chain: 一个模版(ChatTemplate)接一个大模型(ChatModel)。
<a href="/img/eino/chain_simple_llm.png" target="_blank"><img src="/img/eino/chain_simple_llm.png" width="100%" /></a>
```go
chain, _ := NewChain[map[string]any, *Message]().
AppendChatTemplate(prompt).
AppendChatModel(model).
Compile(ctx)
chain.Invoke(ctx, map[string]any{"query": "what's your name?"})
```
现在,我们来创建一个 Graph,一个 ChatModel,要么直接输出结果,要么最多调一次 Tool。
<a href="/img/eino/eino_take_first_toolcall_output.png" target="_blank"><img src="/img/eino/eino_take_first_toolcall_output.png" width="100%" /></a>
```go
graph := NewGraph[map[string]any, *schema.Message]()
_ = graph.AddChatTemplateNode("node_template", chatTpl)
_ = graph.AddChatModelNode("node_model", chatModel)
_ = graph.AddToolsNode("node_tools", toolsNode)
_ = graph.AddLambdaNode("node_converter", takeOne)
_ = graph.AddEdge(START, "node_template")
_ = graph.AddEdge("node_template", "node_model")
_ = graph.AddBranch("node_model", branch)
_ = graph.AddEdge("node_tools", "node_converter")
_ = graph.AddEdge("node_converter", END)
compiledGraph, err := graph.Compile(ctx)
if err != nil {
return err
}
out, err := compiledGraph.Invoke(ctx, map[string]any{"query":"Beijing's weather this weekend"})
```
现在,我们来创建一个 Workflow,它能在字段级别灵活映射输入与输出:
<a href="/img/eino/graph_node_type1.png" target="_blank"><img src="/img/eino/graph_node_type1.png" width="100%" /></a>
```go
type Input1 struct {
Input string
}
type Output1 struct {
Output string
}
type Input2 struct {
Role schema.RoleType
}
type Output2 struct {
Output string
}
type Input3 struct {
Query string
MetaData string
}
var (
ctx context.Context
m model.BaseChatModel
lambda1 func(context.Context, Input1) (Output1, error)
lambda2 func(context.Context, Input2) (Output2, error)
lambda3 func(context.Context, Input3) (*schema.Message, error)
)
wf := NewWorkflow[[]*schema.Message, *schema.Message]()
wf.AddChatModelNode("model", m).AddInput(START)
wf.AddLambdaNode("lambda1", InvokableLambda(lambda1)).
AddInput("model", MapFields("Content", "Input"))
wf.AddLambdaNode("lambda2", InvokableLambda(lambda2)).
AddInput("model", MapFields("Role", "Role"))
wf.AddLambdaNode("lambda3", InvokableLambda(lambda3)).
AddInput("lambda1", MapFields("Output", "Query")).
AddInput("lambda2", MapFields("Output", "MetaData"))
wf.End().AddInput("lambda3")
runnable, err := wf.Compile(ctx)
if err != nil {
return err
}
our, err := runnable.Invoke(ctx, []*schema.Message{
schema.UserMessage("kick start this workflow!"),
})
```
Eino 的**图编排**开箱即用地提供以下能力:
- **类型检查**:在编译时确保两个节点的输入和输出类型匹配。
- **流处理**:如有需要,在将消息流传递给 ChatModel 和 ToolsNode 节点之前进行拼接,以及将该流复制到 callback handler 中。
- **并发管理**:由于 StatePreHandler 是线程安全的,共享的 state 可以被安全地读写。
- **切面注入**:如果指定的 ChatModel 实现未自行注入,会在 ChatModel 执行之前和之后注入回调切面。
- **选项赋值**:调用 Option 可以全局设置,也可以针对特定组件类型或特定节点进行设置。
例如,你可以轻松地通过回调扩展已编译的图:
```go
handler := NewHandlerBuilder().
OnStartFn(
func(ctx context.Context, info *RunInfo, input CallbackInput) context.Context) {
log.Infof("onStart, runInfo: %v, input: %v", info, input)
}).
OnEndFn(
func(ctx context.Context, info *RunInfo, output CallbackOutput) context.Context) {
log.Infof("onEnd, runInfo: %v, out: %v", info, output)
}).
Build()
compiledGraph.Invoke(ctx, input, WithCallbacks(handler))
```
或者你可以轻松地为不同节点分配选项:
```go
// assign to All nodes
compiledGraph.Invoke(ctx, input, WithCallbacks(handler))
// assign only to ChatModel nodes
compiledGraph.Invoke(ctx, input, WithChatModelOption(WithTemperature(0.5))
// assign only to node_1
compiledGraph.Invoke(ctx, input, WithCallbacks(handler).DesignateNode("node_1"))
```
现在,咱们来创建一个 “ReAct” 智能体:一个 ChatModel 绑定了一些 Tool。它接收输入的消息,自主判断是调用 Tool 还是输出最终结果。Tool 的执行结果会再次成为聊天模型的输入消息,并作为下一轮自主判断的上下文。
<a href="/img/eino/eino_adk_react_illustration.png" target="_blank"><img src="/img/eino/eino_adk_react_illustration.png" width="100%" /></a>
Eino 的**智能体开发套件(ADK)**提供了开箱即用的 `ChatModelAgent` 来实现这一模式:
```go
agent, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Name: "assistant",
Description: "A helpful assistant that can use tools",
Model: chatModel,
ToolsConfig: adk.ToolsConfig{
ToolsNodeConfig: compose.ToolsNodeConfig{
Tools: []tool.BaseTool{weatherTool, calculatorTool},
},
},
})
runner := adk.NewRunner(ctx, adk.RunnerConfig{Agent: agent})
iter := runner.Query(ctx, "What's the weather in Beijing this weekend?")
for {
event, ok := iter.Next()
if !ok {
break
}
// process agent events (model outputs, tool calls, etc.)
}
```
ADK 在内部处理 ReAct 循环,为智能体推理过程的每个步骤发出事件。
除了基本的 ReAct 模式,ADK 还提供了构建生产级智能体系统的强大能力:
**多智能体与上下文管理**:智能体可以将控制权转移给子智能体,或被封装为工具。框架会自动管理跨智能体边界的对话上下文:
```go
// 设置智能体层级 - mainAgent 现在可以转移到子智能体
mainAgentWithSubs, _ := adk.SetSubAgents(ctx, mainAgent, []adk.Agent{researchAgent, codeAgent})
```
当 `mainAgent` 转移到 `researchAgent` 时,对话历史会自动重写,为子智能体提供适当的上下文。
智能体也可以被封装为工具,允许一个智能体在其工具调用工作流中调用另一个智能体:
```go
// 将智能体封装为可被其他智能体调用的工具
researchTool := adk.NewAgentTool(ctx, researchAgent)
```
**随处中断,直接恢复**:任何智能体都可以暂停执行以等待人工审批或外部输入,并从中断处精确恢复:
```go
// 在工具或智能体内部,触发中断
return adk.Interrupt(ctx, "Please confirm this action")
// 稍后,从检查点恢复
iter, _ := runner.Resume(ctx, checkpointID)
```
**预置智能体模式**:为常见架构提供开箱即用的实现:
```go
// Deep Agent:经过实战检验的复杂任务编排模式,
// 内置任务管理、子智能体委派和进度跟踪
deepAgent, _ := deep.New(ctx, &deep.Config{
Name: "deep_agent",
Description: "An agent that breaks down and executes complex tasks",
ChatModel: chatModel,
SubAgents: []adk.Agent{researchAgent, codeAgent},
ToolsConfig: adk.ToolsConfig{...},
})
// Supervisor 模式:一个智能体协调多个专家
supervisorAgent, _ := supervisor.New(ctx, &supervisor.Config{
Supervisor: coordinatorAgent,
SubAgents: []adk.Agent{writerAgent, reviewerAgent},
})
// 顺序执行:智能体依次运行
seqAgent, _ := adk.NewSequentialAgent(ctx, &adk.SequentialAgentConfig{
SubAgents: []adk.Agent{plannerAgent, executorAgent, summarizerAgent},
})
```
**可扩展的中间件系统**:在不修改核心逻辑的情况下为智能体添加能力:
```go
fsMiddleware, _ := filesystem.NewMiddleware(ctx, &filesystem.Config{
Backend: myFileSystem,
})
agent, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
// ...
Middlewares: []adk.AgentMiddleware{fsMiddleware},
})
```
## 关键特性
### 丰富的组件(Component)
- 将常见的构建模块抽象为**组件**,每个组件抽象都有多个可开箱即用的**组件实现**。
- 诸如聊天模型(ChatModel)、工具(Tool)、提示模板(PromptTemplate)、检索器(Retriever)、文档加载器(Document Loader)、Lambda 等组件抽象。
- 每种组件类型都有其自身的接口:定义了输入和输出类型、定义了选项类型,以及合理的流处理范式。
- 实现细节是透明的。在编排组件时,你只需关注抽象层面。
- 实现可以嵌套,并包含复杂的业务逻辑。
- ReAct 智能体(React Agent)、多查询检索器(MultiQueryRetriever)、主机多智能体(Host MultiAgent)等。它们由多个组件和复杂的业务逻辑构成。
- 从外部看,它们的实现细节依然透明。例如在任何接受 Retriever 的地方,都可以使用 MultiQueryRetriever。
## **智能体开发套件(ADK)**
**ADK** 包提供了针对构建 AI 智能体优化的高级抽象:
- **ChatModelAgent**:ReAct 风格的智能体,自动处理工具调用、对话状态和推理循环。
- **多智能体与上下文工程**:构建层级化智能体系统,对话历史在智能体转移和智能体作为工具调用时自动管理,实现专业智能体间的无缝上下文共享。
- **工作流智能体**:使用 `SequentialAgent`、`ParallelAgent` 和 `LoopAgent` 组合智能体,实现复杂的执行流程。
- **人机协作**:`Interrupt` 和 `Resume` 机制,支持检查点持久化,适用于需要人工审批或输入的工作流。
- **预置模式**:开箱即用的实现,包括 Deep Agent(任务编排)、Supervisor(层级协调)和 Plan-Execute-Replan。
- **智能体中间件**:可扩展的中间件系统,用于添加工具(文件系统操作)和管理上下文(token 缩减)。
### 强大的编排 (Graph/Chain/Workflow)
如需细粒度控制,Eino 提供**图编排**能力,数据从 Retriever / Document Loader / ChatTemplate 流向 ChatModel,接着流向 Tool ,并被解析为最终答案。
- 组件实例是图的 **节点(Node)** ,而 **边(Edge)** 则是数据流通道。
- 图编排功能强大且足够灵活,能够实现复杂的业务逻辑:
- **类型检查、流处理、并发管理、切面注入和选项分配**都由框架处理。
- 在运行时进行**分支(Branch)执行、读写全局状态(State)**,或者使用工作流进行字段级别的数据映射。
## **切面(Callbacks)**
**切面**处理日志记录、追踪、指标统计等横切关注点。切面可以直接应用于组件、编排图或 ADK 智能体。
- 支持五种切面类型:OnStart、OnEnd、OnError、OnStartWithStreamInput、OnEndWithStreamOutput。
- 可通过 Option 在运行时添加自定义回调处理程序。
### 完善的流处理(Streaming)
- 流数据处理(Stream Processing)很重要,因为 ChatModel 在生成消息时会实时输出完整消息的各个分片。在编排场景下会尤为重要,因为更多的组件需要处理分片的消息数据。
- 对于只接受非流式输入的下游节点(如 ToolsNode),Eino 会自动将流 **拼接(Concatenate)** 起来。
- 在图的执行过程中,当需要流时,Eino 会自动将非流式**转换**为流式。
- 当多个流汇聚到一个下游节点时,Eino 会自动 **合并(Merge)** 这些流。
- 当一个流传入到多个不同的下游节点或传递给回调处理器时,Eino 会自动 **复制(Copy)** 这些流。
- 如 **分支(Branch)** 、或 **状态处理器(StateHandler)** 等编排元素,也能够感知和处理流。
- 借助上述流数据处理能力,组件本身的“是否能处理流、是否会输出流”变的对用户透明。
- 经过编译的 Graph 可以用 4 种不同的流输入输出范式来运行:
<table>
<tr><td>流处理范式</td><td>解释</td></tr>
<tr><td>Invoke</td><td>接收非流类型 I ,返回非流类型 O</td></tr>
<tr><td>Stream</td><td>接收非流类型 I , 返回流类型 StreamReader[O]</td></tr>
<tr><td>Collect</td><td>接收流类型 StreamReader[I] , 返回非流类型 O</td></tr>
<tr><td>Transform</td><td>接收流类型 StreamReader[I] , 返回流类型 StreamReader[O]</td></tr>
</table>
## Eino 框架结构
<a href="/img/eino/eino_architecture_overview.png" target="_blank"><img src="/img/eino/eino_architecture_overview.png" width="100%" /></a>
Eino 框架由几个部分组成:
- [Eino](https://github.com/cloudwego/eino):包含类型定义、流数据处理机制、组件抽象定义、编排功能、切面机制等。
- [EinoExt](https://github.com/cloudwego/eino-ext):组件实现、回调处理程序实现、组件使用示例,以及各种工具,如评估器、提示优化器等。
> 💡
> 针对字节内部使用的组件,有对应的内部代码仓库:
- [Eino Devops](https://github.com/cloudwego/eino-ext/tree/main/devops):可视化开发、可视化调试等。
- [EinoExamples](https://github.com/cloudwego/eino-examples):是包含示例应用程序和最佳实践的代码仓库。
详见:[Eino 框架结构说明](/zh/docs/eino/overview/eino_框架结构说明)
## 详细文档
针对 Eino 的学习和使用,我们提供了完善的 Eino 用户手册,帮助大家快速理解 Eino 中的概念,掌握基于 Eino 开发设计 AI 应用的技能,赶快通过 [Eino 用户手册](https://www.cloudwego.io/zh/docs/eino/)尝试使用吧~。
若想快速上手,了解 通过 Eino 构建 AI 应用的过程,推荐先阅读 [Eino: 快速开始](https://www.cloudwego.io/zh/docs/eino/quick_start/)
完整 API Reference:[https://pkg.go.dev/github.com/cloudwego/eino](https://pkg.go.dev/github.com/cloudwego/eino)
## 依赖说明
- Go 1.18 及以上版本
## **代码规范**
本仓库开启了 `golangci-lint` 检查以约束基础代码规范,可通过以下命令在本地检查:
```bash
golangci-lint run ./...
```
主要规则包括:
- 导出的函数、接口、package 等需要添加注释,且注释符合 GoDoc 规范。
- 代码格式需符合 `gofmt -s` 规范。
- import 顺序需符合 `goimports` 规范(std -> third party -> local)。
## 安全
如果你在该项目中发现潜在的安全问题,或你认为可能发现了安全问题,请通过我们的[安全中心](https://security.bytedance.com/src)或[漏洞报告邮箱](mailto:sec@bytedance.com)通知字节跳动安全团队。
请**不要**创建公开的 GitHub Issue。
## 联系我们
- 如何成为 member: [COMMUNITY MEMBERSHIP](https://github.com/cloudwego/community/blob/main/COMMUNITY_MEMBERSHIP.md)
- Issues: [Issues](https://github.com/cloudwego/eino/issues)
- 飞书用户群([注册飞书](https://www.feishu.cn/)后扫码进群)
<a href="/img/eino/eino_lark_qr_code.png" target="_blank"><img src="/img/eino/eino_lark_qr_code.png" width="100%" /></a>
- 字节内部 OnCall 群
## 开源许可证
本项目依据 [[Apache-2.0 许可证](https://www.apache.org/licenses/LICENSE-2.0.txt)]授权。
@@ -0,0 +1,488 @@
---
Description: ""
date: "2026-03-03"
lastmod: ""
tags: []
title: 字节跳动大模型应用 Go 开发框架 —— Eino 实践
weight: 2
---
## 前言
开发基于大模型的软件应用,就像指挥一支足球队:**组件**是能力各异的队员,**编排**是灵活多变的战术,**数据**是流转的足球。Eino 是字节跳动开源的大模型应用开发框架,拥有稳定的内核,灵活的扩展性,完善的工具生态,可靠且易维护,背靠豆包、抖音等应用的丰富实践经验。初次使用 Eino,就像接手一支实力雄厚的足球队,即使教练是初出茅庐的潜力新人,也可以踢出高质量、有内容的比赛。
下面就让我们一起踏上新手上路之旅!
## 认识队员
Eino 应用的基本构成元素是功能各异的组件,就像足球队由不同位置角色的队员组成:
<table>
<tr><td>组件名</td><td>组件功能</td></tr>
<tr><td>ChatModel</td><td>与大模型交互,输入 Message 上下文,得到模型的输出 Message</td></tr>
<tr><td>Tool</td><td>与世界交互,根据模型的输出,执行对应的动作</td></tr>
<tr><td>Retriever</td><td>获取相关的上下文,让模型的输出基于高质量的事实</td></tr>
<tr><td>ChatTemplate</td><td>接收外界输入,转化成预设格式的 prompt 交给模型</td></tr>
<tr><td>Document Loader</td><td>加载指定的文本</td></tr>
<tr><td>Document Transformer</td><td>按照特定规则转化指定的文本</td></tr>
<tr><td>Indexer</td><td>存储文件并建立索引,供后续 Retriever 使用</td></tr>
<tr><td>Embedding</td><td>Retriever 和 Indexer 的共同依赖,文本转向量,捕获文本语义</td></tr>
<tr><td>Lambda</td><td>用户定制 function</td></tr>
</table>
这些组件抽象代表了固定的输入输出类型、Option 类型和方法签名:
```go
type ChatModel interface {
Generate(ctx context.Context, input []*schema.Message, opts ...Option) (*schema.Message, error)
Stream(ctx context.Context, input []*schema.Message, opts ...Option) (
*schema.StreamReader[*schema.Message], error)
BindTools(tools []*schema.ToolInfo) error
}
```
真正的运行,需要的是具体的组件**实现**:
<table>
<tr><td>组件名</td><td>官方组件实现</td></tr>
<tr><td>ChatModel</td><td>OpenAI, Claude, Gemini, Ark, Ollama...</td></tr>
<tr><td>Tool</td><td>Google Search, Duck Duck Go...</td></tr>
<tr><td>Retriever</td><td>Elastic Search, Volc VikingDB...</td></tr>
<tr><td>ChatTemplate</td><td>DefaultChatTemplate...</td></tr>
<tr><td>Document Loader</td><td>WebURL, Amazon S3, File...</td></tr>
<tr><td>Document Transformer</td><td>HTMLSplitter, ScoreReranker...</td></tr>
<tr><td>Indexer</td><td>Elastic Search, Volc VikingDB...</td></tr>
<tr><td>Embedding</td><td>OpenAI, Ark...</td></tr>
<tr><td>Lambda</td><td>JSONMessageParser...</td></tr>
</table>
Eino 的开发过程中,首先要做的是决定“我需要使用哪个组件抽象”,再决定“我需要使用哪个具体组件实现”。就像足球队先决定“我要上 1 个前锋”,再挑选“谁来担任这个前锋”。
组件可以像使用任何的 Go interface 一样单独使用。但要想发挥 Eino 这支球队真正的威力,需要多个组件协同编排,成为一个相互联结的整体。
## 制定战术
在 Eino 编排场景中,每个组件成为了“节点”(Node),节点之间 1 对 1 的流转关系成为了“边”(Edge),N 选 1 的流转关系成为了“分支”(Branch)。基于 Eino 开发的应用,经过对各种组件的灵活编排,就像一支足球队可以采用各种阵型,能够支持无限丰富的业务场景。
足球队的战术千变万化,但却有迹可循,有的注重控球,有的简单直接。对 Eino 而言,针对不同的业务形态,也有更合适的编排方式:
<table>
<tr><td>编排方式</td><td>特点和场景</td></tr>
<tr><td>Chain</td><td>链式有向图,始终向前,简单。适合数据单向流动,没有复杂分支的场景。</td></tr>
<tr><td>Graph</td><td>有向图,有最大的灵活性;或有向无环图,不支持分支,但有清晰的祖先关系。</td></tr>
</table>
Chain,如简单的 ChatTemplate + ChatModel 的 Chain:
<a href="/img/eino/simple_template_and_chatmodel.png" target="_blank"><img src="/img/eino/simple_template_and_chatmodel.png" width="80%" /></a>
```go
chain, _ := NewChain[map[string]any, *Message]().
AppendChatTemplate(prompt).
AppendChatModel(model).
Compile(ctx)
chain.Invoke(ctx, map[string]any{"query": "what's your name?"})
```
Graph,如最多执行一次 ToolCall 的 Agent:
<a href="/img/eino/eino_practice_graph_tool_call.png" target="_blank"><img src="/img/eino/eino_practice_graph_tool_call.png" width="100%" /></a>
```go
graph := NewGraph[map[string]any, *schema.Message]()
_ = graph.AddChatTemplateNode("node_template", chatTpl)
_ = graph.AddChatModelNode("node_model", chatModel)
_ = graph.AddToolsNode("node_tools", toolsNode)
_ = graph.AddLambdaNode("node_converter", takeOne)
_ = graph.AddEdge(START, "node_template")
_ = graph.AddEdge("node_template", "node_model")
_ = graph.AddBranch("node_model", branch)
_ = graph.AddEdge("node_tools", "node_converter")
_ = graph.AddEdge("node_converter", END)
compiledGraph, err := graph.Compile(ctx)
if err != nil {
return err
}
out, err := compiledGraph.Invoke(ctx, map[string]any{"query":"Beijing's weather this weekend"})
```
## 了解工具
现在想象下你接手的足球队用了一些黑科技,比如:在每个队员接球和出球的瞬间,身上的球衣可以自动的记录接球和出球的速度、角度并传递给场边的服务器,这样比赛结束后,就可以统计出每个队员触球的情况和处理球的时间。
在 Eino 中,每个组件运行的开始和结束,也可以通过 Callbacks 机制拿到输入输出及一些额外信息,处理横切面需求。比如一个简单的打日志能力:
```go
handler := NewHandlerBuilder().
OnStartFn(
func(ctx context.Context, info *RunInfo, input CallbackInput) context.Context {
log.Printf("onStart, runInfo: %v, input: %v", info, input)
return ctx
}).
OnEndFn(
func(ctx context.Context, info *RunInfo, output CallbackOutput) context.Context {
log.Printf("onEnd, runInfo: %v, out: %v", info, output)
return ctx
}).
Build()
// 注入到 graph 运行中
compiledGraph.Invoke(ctx, input, WithCallbacks(handler))
```
再想象一下,这个足球队的黑科技不止一种,还可以让教练在比赛前制作“锦囊”并藏在球衣里,当队员接球时,这个锦囊就会播放教练事先录制好的妙计,比如“别犹豫,直接射门!”。听上去很有趣,但有一个难点:有的锦囊是给全队所有队员的,有的锦囊是只给一类队员(比如所有前锋)的,而有的锦囊甚至是只给单个队员的。如何有效的做到锦囊妙计的分发?
在 Eino 中,类似的问题是 graph 运行过程中 call option 的分发:
```go
// 所有节点都生效的 call option
compiledGraph.Invoke(ctx, input, WithCallbacks(handler))
// 只对特定类型节点生效的 call option
compiledGraph.Invoke(ctx, input, WithChatModelOption(model.WithTemperature(0.5)))
// 只对特定节点生效的 call option
compiledGraph.Invoke(ctx, input, WithCallbacks(handler).DesignateNode("node_1"))
```
## 发现独门秘笈
现在,想象一下你的球队里有一些明星球员(中场大脑 ChatModel 和锋线尖刀 StreamableTool)身怀绝技,他们踢出的球速度如此之快,甚至出现了残影,看上去就像是把一个完整的足球切成了很多片!面对这样的“流式”足球,对手球员手足无措,不知道该如何接球,但是你的球队的所有队员,都能够完美的接球,要么直接一个片一个片的接收“流式”足球并第一时间处理,要么自动的把所有片拼接成完整的足球后再处理。身怀这样的独门秘笈,你的球队具备了面对其他球队的降维打击能力!
在 Eino 中,开发者只需要关注一个组件在“真实业务场景”中,是否可以处理流式的输入,以及是否可以生成流式的输出。根据这个真实的场景,具体的组件实现(包括 Lambda Function)就去实现符合这个流式范式的方法:
```go
// ChatModel 实现了 Invoke(输入输出均非流)和 Stream(输入非流,输出流)两个范式
type ChatModel interface {
Generate(ctx context.Context, input []*Message, opts ...Option) (*Message, error)
Stream(ctx context.Context, input []*Message, opts ...Option) (
*schema.StreamReader[*Message], error)
}
// Lambda 可以实现任意四种流式范式
// Invoke is the type of the invokable lambda function.
type Invoke[I, O, TOption any] func(ctx context.Context, input I, opts ...TOption) (
output O, err error)
// Stream is the type of the streamable lambda function.
type Stream[I, O, TOption any] func(ctx context.Context,
input I, opts ...TOption) (output *schema.StreamReader[O], err error)
// Collect is the type of the collectable lambda function.
type Collect[I, O, TOption any] func(ctx context.Context,
input *schema.StreamReader[I], opts ...TOption) (output O, err error)
// Transform is the type of the transformable lambda function.
type Transform[I, O, TOption any] func(ctx context.Context,
input *schema.StreamReader[I], opts ...TOption) (output *schema.StreamReader[O], err error)
```
Eino 编排能力会自动做两个重要的事情:
1. 上游是流,但是下游只能接收非流时,自动拼接(Concat)。
2. 上游是非流,但是下游只能接收流时,自动流化(T -> StreamReader[T])。
除此之外,Eino 编排能力还会自动处理流的合并、复制等各种细节,把大模型应用的核心——流处理做到了极致。
## 一场训练赛 -- Eino 智能助手
好了,现在你已经初步了解了 Eino 这支明星球队的主要能力,是时候通过队员(组件)、战术(编排)、工具(切面、可视化)来一场训练赛,去亲自体验一下它的强大。
### 场景设定
Eino 智能助手:根据用户请求,从知识库检索必要的信息并按需调用多种工具,以完成对用户的请求的处理。工具列表如下:
- DuckDuckGo:从 DuckDuckGo 搜索互联网信息
- EinoTool:获取 Eino 的工程信息,比如仓库链接、文档链接等
- GitClone:克隆指定仓库到本地
- 任务管理(TaskManager):添加、查看、删除 任务
- OpenURL:使用系统的默认应用打开文件、Web 等类型的链接
本文主要呈现一个 Demo 样例,用户可根据自己的场景,更换自己的知识库和工具,以搭建自己所需的智能助手。
先来一起看看「基于 Eino 搭建」起来的 Agent 助手能实现什么效果
<iframe height="400px" width="100%" src="https://player.bilibili.com/player.html?autoplay=0&bvid=BV1VZNRenEDs&t=0.4" ></iframe>
我们分两步来构建这个 Eino 智能助手:
- Knowledge Indexing(索引知识库):将我们在特定领域沉淀的知识,以分词、向量化等多种手段,构建成索引,以便在接收用户请求时,索引出合适的上下文。 本文采用向量化索引来构建知识库。
- Eino Agent(Eino 智能助手):根据用户的请求信息以及我们预先构建好的可调用的工具,让 ChatModel 帮我们决策下一步应该执行什么动作或输出最终结果。Tool 的执行结果会再次输入给 ChatModel,让 ChatModel 再一次判断下一步的动作,直至完成用户的请求。
### 任务工作流
#### **索引知识库(Knowledge Indexing)**
将 Markdown 格式的 Eino 用户手册,以合适的策略进行拆分和向量化,存入到 RedisSearch 的 VectorStore 中,作为 Eino 知识库。
<a href="/img/eino/eino_practice_index_flow.png" target="_blank"><img src="/img/eino/eino_practice_index_flow.png" width="50%" /></a>
#### **Eino 智能体(Eino Agent)**
根据用户请求,从 Eino 知识库召回信息,采用 ChatTemplate 构建消息,请求 React Agent,视需求循环调用对应工具,直至完成处理用户的请求。
<a href="/img/eino/eino_practice_agent_graph.png" target="_blank"><img src="/img/eino/eino_practice_agent_graph.png" width="60%" /></a>
### 所需工具
在从零开始构建「Eino 智能助手」这个实践场景中,需要下列工具:
<table>
<tr><td>工具集</td><td>是否必须</td><td>功能与作用</td><td>资源列表</td></tr>
<tr><td>Eino 框架</td><td>必须</td><td><li>全码开发 AI 应用的框架</li><li>提供 AI 相关的各种原子组件和编排能力</li></td><td><li>https://github.com/cloudwego/eino</li><li>https://github.com/cloudwego/eino-ext</li><li><a href="https://www.cloudwego.io/zh/docs/eino/">「</a><a href="https://www.cloudwego.io/zh/docs/eino/">Eino 用户手册</a><a href="https://www.cloudwego.io/zh/docs/eino/">」</a></li></td></tr>
<tr><td>EinoDev 插件(Goland、VSCode)</td><td>非必须</td><td><li>可视化拖拽编排 AI 应用,并生成全码</li><li>可视化对编排的 AI 应用进行调试</li></td><td><li><a href="https://www.cloudwego.io/zh/docs/eino/core_modules/devops/ide_plugin_guide/">「Eino Dev 插件安装」</a></li><li><a href="https://www.cloudwego.io/zh/docs/eino/core_modules/devops/visual_orchestration_plugin_guide/">「EinoDev 可视化编排插件功能指南」</a></li></td></tr>
<tr><td> 火山云豆包模型/向量化</td><td>必须</td><td><li>豆包模型:ArkChatModel,提供在线的对话文本推理能力</li><li>向量化:将文本进行向量化计算,用于对 Eino 知识库构建向量索引</li></td><td><a href="https://console.volcengine.com/ark">「火山引擎豆包模型」</a>:需要实名认证后购买使用,每人有 50万免费Tokens额度<a href="/img/eino/eino_practice_ark_create_model.png" target="_blank"><img src="/img/eino/eino_practice_ark_create_model.png" width="100%" /></a></td></tr>
<tr><td>Docker</td><td>非必须</td><td><li>通过 Docker 提供 RedisSearch 组件</li><li>也可自主进行手动部署</li></td><td><li><a href="https://docs.docker.com/get-started/">Docker 官方文档</a></li></td></tr>
<tr><td>Eino 智能助手代码示例</td><td>必须</td><td><li>本文的完整示例代码</li></td><td><li><a href="https://github.com/cloudwego/eino-examples/tree/main/quickstart/eino_assistant">示例代码仓库</a></li></td></tr>
</table>
### 索引知识库
> 示例的仓库路径:[https://github.com/cloudwego/eino-examples/tree/main/quickstart/eino_assistant](https://github.com/cloudwego/eino-examples/tree/main/quickstart/eino_assistant)
>
> 下文中,采用相对于此目录的相对路径来标识资源位置
构建一个命令行工具,递归遍历指定目录下的所有 Markdown 文件。按照标题将 Markdown 文件内容分成不同的片段,并采用火山云的豆包向量化模型逐个将文本片段进行向量化,存储到 Redis VectorStore 中。
> 指令行工具目录:cmd/knowledge_indexing
>
> Markdown 文件目录:cmd/knowledge_indexing/eino-dcos
开发「索引知识库」应用时,首先采用 Eino 框架提供的 Goland EinoDev 插件,以可视化拖拽和编排的形式构建 KnowledgeIndexing 的核心应用逻辑,生成代码到 eino_graph/knowledge_indexing 目录。
代码生成后,首先手动将该目录下的各组件的构造方法补充完整,然后在业务场景中,调用 BuildKnowledgeIndexing 方法,构建并使用 Eino Graph 实例。
接下来将逐步介绍,KnowledgeIndexing 的开发过程:
#### 大模型资源创建
火山引擎是字节跳动的云服务平台,可从中注册和调用豆包大模型(有大量免费额度)。
- 创建 doubao-embedding-large 作为知识库构建时的向量化模型,以及创建 doubao-pro-4k 资源作为 agent 对话时的模型。
- 「火山引擎在线推理」:[https://console.volcengine.com/ark](https://console.volcengine.com/ark)
<a href="/img/eino/model_create.gif" target="_blank"><img src="/img/eino/model_create.gif" width="100%" /></a>
#### 启动 Redis Stack
本文将使用 Redis 作为 Vector Database,为方便用户构建环境,提供 Docker 的快捷指令
- 在 eino-examples/quickstart/eino_assistant 提供 docker-compose.yml
- 在 eino-examples/quickstart/eino_assistant/data 目录下提供了 Redis 的初始知识库
直接用 redis 官方的 redis stack 镜像启动即可
```bash
# 切换到 eino_assistant 目录
cd xxx/eino-examples/quickstart/eino_assistant
docker-compose up -d
```
<a href="/img/eino/redis_start_up.gif" target="_blank"><img src="/img/eino/redis_start_up.gif" width="100%" /></a>
- 完成启动后,打开本地的 8001 可进入 redis stack 的 web 界面
> 在浏览器打开链接: [http://127.0.0.1:8001](http://127.0.0.1:8001)
#### 可视化开发
> 「Eino 可视化开发」是为了降低 Eino AI 应用开发的学习曲线,提升开发效率。对于熟悉 Eino 的开发者,也可选择跳过「Eino 可视化开发」阶段,直接基于 Eino 的 API 进行全码开发。
1. [安装 EinoDev 插件](/zh/docs/eino/core_modules/devops/ide_plugin_guide),并打开 Eino Workflow 功能
- Graph name: KnowledgeIndexing
- Node trigger mode: Triggered after all predecessor nodes are executed
- Input type: document.Source
- Import path of input type: github.com/cloudwego/eino/components/document
- Output type: []string
- 其他置空
<a href="/img/eino/eino_practice_debug_panel.png" target="_blank"><img src="/img/eino/eino_practice_debug_panel.png" width="100%" /></a>
2. 按照上文「**索引知识库**」中的流程说明,从 Eino Workflow 中选择需要使用的组件库,本文需要用到如下组件:
- document/loader/file
- 从指定 URI 加载文件,解析成文本内容,以 schema.Document 列表形式返回。
- document/transformer/splitter/markdown
- 将从 FileLoader 中加载到的文本内容,进一步拆分成合适的大小,以平衡向量化计算/存储的尺寸限制和召回的效果。
- indexer/redis
- 将 schema.Document 的原文、索引字段 存储在 Redis Vector Database 中
- embedding/ark
- 采用 Ark 平台的向量化模型,对 schema.Document 中的 Content 等内容进行向量化计算
3. 将选中的组件按照预期的拓扑结构进行编排,完成编排后,点击“生成代码”到指定目录。
- 「**索引知识库**」的代码生成到:eino_assistant/eino/knowledgeindexing
- 本示例可直接复制 eino/knowledge_indexing.json 中的 Graph Schema,来快速构建示例中的图
<table><tbody><tr>
<td>
<a href="/img/eino/eino_practice_indexing_graph.png" target="_blank"><img src="/img/eino/eino_practice_indexing_graph.png" width="100%" /></a>
</td><td>
<a href="/img/eino/eino_practice_indexing_show_codes.png" target="_blank"><img src="/img/eino/eino_practice_indexing_show_codes.png" width="100%" /></a>
</td></tr></tbody></table>
4. 按需完善各个组件的构造函数,在构造函数中补充创建组件实例时,需要的配置内容
<table><tbody><tr>
<td>
<a href="/img/eino/eino_practice_indexing_config.png" target="_blank"><img src="/img/eino/eino_practice_indexing_config.png" width="100%" /></a>
</td><td>
<a href="/img/eino/eino_practice_indexing_index_config.png" target="_blank"><img src="/img/eino/eino_practice_indexing_index_config.png" width="100%" /></a>
</td></tr></tbody></table>
5. 补充好组件的配置内容后,即可调用 BuildKnowledgeIndexing 方法,在业务场景使用
#### 完善代码
- 通过可视化开发,生成的 Eino 编排代码,无法保证可直接使用,需要人工阅读和检查下代码的完整性
- 生成核心函数是 BuildKnowledgeIndexing(),用户可在需要的地方调用此方法,创建实例进行使用
在「索引知识库」的场景下,需要将 BuildKnowledgeIndexing 封装成一个指令,从环境变量中读取模型配置等信息,初始化 BuildKnowledgeIndexing 的配置内容,扫描指定目录下的 Markdown 文件,执行对 Markdown 进行索引和存储的操作。
> 详细代码可查看:cmd/knowledgeindexing/main.go
<a href="/img/eino/eino_practice_indexing_new_runner.png" target="_blank"><img src="/img/eino/eino_practice_indexing_new_runner.png" width="100%" /></a>
#### 运行
> PS: 示例项目中,已经内置了 eino 的一部分文档向量化到 redis 中
1. 在 .env 文件中按照注释说明,获取并填写 ARK_EMBEDDING_MODEL 和 ARK_API_KEY 的值,按如下指令,运行 KnowledgeIndexing 指令
```bash
cd xxx/eino-examples/quickstart/eino_assistant # 进入 eino assistant 的 example 中
# 修改 .env 中所需的环境变量 (大模型信息、trace 平台信息)
source .env
# 因示例的Markdown文件存放在 cmd/knowledgeindexing/eino-docs 目录,代码中指定了相对路径 eino-docs,所以需在 cmd/knowledgeindexing 运行指令
cd cmd/knowledgeindexing
go run main.go
```
<a href="/img/eino/knowledgeindexing.gif" target="_blank"><img src="/img/eino/knowledgeindexing.gif" width="100%" /></a>
2. 执行运行成功后,即完成 Eino 知识库的构建,可在 Redis Web UI 中看到向量化之后的内容
> 在浏览器打开链接: [http://127.0.0.1:8001](http://127.0.0.1:8001)
>
<a href="/img/eino/redis_keys.jpeg" target="_blank"><img src="/img/eino/redis_keys.jpeg" width="100%" /></a>
### Eino 智能体
> 示例的仓库路径:[https://github.com/cloudwego/eino-examples/tree/main/quickstart/eino_assistant](https://github.com/cloudwego/eino-examples/tree/main/quickstart/eino_assistant)
>
> 下文中,采用相对于此目录的相对路径来标识资源位置
构建一个基于从 Redis VectorStore 中召回的 Eino 知识回答用户问题,帮用户执行某些操作的 ReAct Agent,即典型的 RAG ReAct Agent。可根据对话上下文,自动帮用户记录任务、Clone 仓库,打开链接 等。
#### 大模型资源创建
继续使用「索引知识库」章节中创建的 doubao-embedding-large 和 doubao-pro-4k
#### 启动 RedisSearch
继续使用「索引知识库」章节中启动的 Redis Stack
#### 可视化开发
<iframe height="400px" width="100%" src="https://player.bilibili.com/player.html?autoplay=0&bvid=BV15ZNRenEUf&t=1.2" ></iframe>
1. 打开 EinoDev 插件,进入到 Eino Workflow 页面,新建一张画布
- Graph Name: EinoAgent
- Node Trigger Mode: 任意前驱节点结束后触发
- Input Type Name: *UserMessage
- Input Package Path: ""
- Output Type Name: *schema.Message
- Output Import Path: github.com/cloudwego/eino/schema
- 其他置空
2. 按照上文「**Eino 智能体**」中的流程说明,从 Eino Workflow 中选择需要使用的组件库,本文需要用到如下组件:
- lambda: 将开发者任意的函数 func(ctx context.Context, input I) (output O, err error),转换成可被编排的节点,在 EinoAgent 中,有两个转换场景
- 将 *UserMessage 消息转换成 ChatTemplate 节点的 map[string]any
- 将 *UserMessage 转换成 RedisRetriever 的输入 query
- retriever/redis
- 根据用户 Query 从 Redis Vector Database 根据语义相关性,召回和 Query 相关的上下文,以 schema.Document List 的形式返回。
- prompt/chatTemplate
- 通过字符串字面量构建 Prompt 模板,支持 文本替换符 和 消息替换符,将输入的任意 map[string]any,转换成可直接输入给模型的 Message List。
- flow/agent/react
- 基于开发者提供的 ChatModel 和 可调用的工具集,针对用户的问题,自动决策下一步的 Action,直至能够产生最终的回答。
- model/ark
- Ark 平台提供的能够进行对话文本补全的大模型,例如豆包模型。作为 ReAct Agent 的依赖注入。
- 可调用的工具列表
- 互联网搜索工具(DuckDuckGo)、EinoTool、GitClone、任务管理(TaskManager)、 OpenURL
3. 将选中的组件按照预期的拓扑结构进行编排,完成编排后,点击“生成代码”到指定目录。
- 本示例中,「**Eino 智能体**」的代码生成到:eino/einoagent
- 本示例可直接复制 eino/eino_agent.json 中的 Graph Schema,来快速构建示例中的图
<table><tbody><tr>
<td>
<a href="/img/eino/eino_practice_graph.png" target="_blank"><img src="/img/eino/eino_practice_graph.png" width="100%" /></a>
</td><td>
<a href="/img/eino/eino_practice_agent_graph_codes.png" target="_blank"><img src="/img/eino/eino_practice_agent_graph_codes.png" width="100%" /></a>
</td></tr></tbody></table>
4. 按需完善各个组件的构造函数,在构造函数中补充创建组件实例时,需要的配置内容
<table><tbody><tr>
<td>
<a href="/img/eino/eino_practice_agent_lambda.png" target="_blank"><img src="/img/eino/eino_practice_agent_lambda.png" width="100%" /></a>
</td><td>
<a href="/img/eino/eino_practice_agent_model_config.png" target="_blank"><img src="/img/eino/eino_practice_agent_model_config.png" width="100%" /></a>
</td></tr></tbody></table>
5. 补充好组件的配置内容后,即可调用 BuildEinoAgent 方法,在业务场景使用
#### 完善代码
在「Eino 智能体」的场景下,BuildEinoAgent 构建的 Graph 实例可做到:根据用户请求和对话历史,从 Eino 知识库中召回上下文, 然后结合可调用的工具列表,将 ChatModel 循环决策下一步是调用工具或输出最终结果。
下图即是对生成的 BuildEinoAgent 函数的应用,将 Eino Agent 封装成 HTTP 服务接口:
<a href="/img/eino/eino_practice_agent_runner.png" target="_blank"><img src="/img/eino/eino_practice_agent_runner.png" width="100%" /></a>
#### 运行
1. 在 .env 文件中按照注释说明,获取并填写对应各变量的值,按如下指令,启动 Eino Agent Server
```bash
cd eino-examples/eino_assistant # 进入 eino assistant 的 example 中
# 修改 .env 中所需的环境变量 (大模型信息、trace 平台信息)
source .env
# 为了使用 data 目录,需要在 eino_assistant 目录下执行指令
go run cmd/einoagent/*.go
```
<a href="/img/eino/eino_agent.gif" target="_blank"><img src="/img/eino/eino_agent.gif" width="100%" /></a>
2. 启动后可访问如下链接,打开 Eino Agent Web
> Eino Agent Web:[http://127.0.0.1:8080/agent/](http://127.0.0.1:8080/agent/)
#### 观测(可选)
##### APMPlus
如果在运行时,在 .env 文件中指定了 `APMPLUS_APP_KEY`,便可在 [火山引擎 APMPlus](https://console.volcengine.com/apmplus-server) 平台中,登录对应的账号,查看 Trace 以及 Metrics 详情。
<a href="/img/eino/apm_plus_callback.gif" target="_blank"><img src="/img/eino/apm_plus_callback.gif" width="100%" /></a>
##### Langfuse
如果在运行时,在 .env 文件中指定了 `LANGFUSE_PUBLIC_KEY` 和 `LANGFUSE_SECRET_KEY`,便可在 Langfuse 平台中,登录对应的账号,查看请求的 Trace 详情。
<a href="/img/eino/langfuse_callback.gif" target="_blank"><img src="/img/eino/langfuse_callback.gif" width="100%" /></a>
## 相关链接
项目地址:[https://github.com/cloudwego/eino](https://github.com/cloudwego/eino),[https://github.com/cloudwego/eino-ext](https://github.com/cloudwego/eino-ext)
Eino 用户手册:[https://www.cloudwego.io/zh/docs/eino/](https://www.cloudwego.io/zh/docs/eino/)
项目官网:__[https://www.cloudwego.io](https://www.cloudwego.io)__
扫描二维码加入飞书社群:
<a href="/img/eino/eino_lark_qr_code_practice.png" target="_blank"><img src="/img/eino/eino_lark_qr_code_practice.png" width="50%" /></a>
+571
View File
@@ -0,0 +1,571 @@
---
Description: ""
date: "2026-03-24"
lastmod: ""
tags: []
title: Eino ADK:一文搞定 AI Agent 核心设计模式,从 0 到 1 搭建智能体系统
weight: 6
---
# 前言
当大语言模型突破了 “理解与生成” 的瓶颈,Agent 迅速成为 AI 落地的主流形态。从智能客服到自动化办公,几乎所有场景都需要 Agent 来承接 LLM 能力、执行具体任务。
但技术演进中痛点也随之凸显,有的团队因不懂如何衔接 LLM 与业务系统,导致 Agent 只能 “空谈”;有的因状态管理缺失,让 Agent 执行任务时频频 “失忆”,复杂的交互流程也进一步增加了开发难度。
为此,**Eino ADK(Agent Development Kit)应运而生,为 Go 开发者提供了一套完整、灵活且强大的智能体开发框架**,直接解决传统开发中的核心难题。
## 🙋 什么是 Agent?
Agent 代表一个独立的、可执行的智能任务单元,能够自主学习,适应与作出决策,主要功能包含:
- **推理**:Agent 可以分析数据、识别模式、使用逻辑和可用信息来得出结论、进行推断及解决问题。
- **行动**:Agent 根据决策、计划或外部输入采取行动或执行任务来实现目标。
- **观察**:Agent 自主收集相关的信息(例如计算机视觉、自然语言处理或传感器数据分析)来了解上下文,为做出明智的决策打下基础。
- **规划**:Agent 可以确定必要的步骤、评估潜在行动,并根据可用信息和预期结果选择最佳行动方案。
- **协作**:Agent 能够在复杂且动态的环境中,与他人(无论是人类还是其他 AI 智能体)进行有效协作。
你可以把它想象成一个能够理解指令、执行任务并给出回应的“智能体”。任何需要与大语言模型(LLM)交互的场景都可以抽象为一个 Agent。例如:
- 一个用于查询天气信息的 Agent。
- 一个用于预定会议的 Agent。
- 一个能够回答特定领域知识的 Agent。
## 🙋‍♂️ 什么是 Eino ADK?
[Eino ADK](https://github.com/cloudwego/eino) 是一个专为 Go 语言设计的 Agent 和 Multi-Agent 开发框架,设计上参考了 [Google-ADK](https://google.github.io/adk-docs/agents/) 中对 Agent 与协作机制的定义。
它不仅是一个工具库,更是一套完整的智能体开发体系:通过统一的抽象接口、灵活的组合模式和强大的协作机制,将复杂的 AI 应用拆解为独立、可组合的智能体单元,让开发者能够像搭建乐高积木一样构建复杂的智能体系统:
- **少写胶水**:统一接口与事件流,复杂任务拆解更自然。
- **快速编排**:预设范式 + 工作流,分分钟搭好管线。
- **更可控**:可中断、可恢复、可审计,Agent 协作过程“看得见”。
无论你是 AI 应用的新手,还是经验丰富的开发者,ADK 都能为你提供合适的工具和模式。它的设计哲学是"简单的事情简单做,复杂的事情也能做"——让开发者能够专注于业务逻辑的实现,而不必担心底层的技术复杂性。
# 核心构建
## 🧠 ChatModelAgent:智能决策的大脑
`ChatModelAgent` 是 ADK 中最重要的预构建组件,它封装了与大语言模型的交互逻辑,实现了经典的 [ReAct](https://react-lm.github.io/)(Reason-Act-Observe)模式,运行过程为:
1. 调用 LLM(Reason)
2. LLM 返回工具调用请求(Action)
3. ChatModelAgent 执行工具(Act)
4. 将工具结果返回给 LLM(Observation),结合之前的上下文继续生成,直到模型判断不需要调用 Tool 后结束。
<a href="/img/eino/eino_adk_chatmodel_agent.png" target="_blank"><img src="/img/eino/eino_adk_chatmodel_agent.png" width="100%" /></a>
ReAct 模式的核心是“**思考 → 行动 → 观察 → 再思考**”的闭环,解决传统 Agent “盲目行动”或“推理与行动脱节”的痛点,以下是几种可能的实践场景:
- **行业赛道分析**:使用 ReAct 模式避免了一次性搜集全部信息导致的信息过载,通过逐步推理聚焦核心问题;同时使用数据验证思考,而非凭空靠直觉决策,过程可解释,提升了生成报告的准确性。
- **Think-1**:判断赛道潜力,需要 “政策支持力度、行业增速、龙头公司盈利能力、产业链瓶颈”4 类信息。
- **Act-1**:调用 API 获取行业财报整体数据
- **Think-2**:分析数据,判断行业高增长 + 政策背书,但上游价格上涨可能挤压中下游利润,需要进一步验证是否有影响
- **Act-2**: 调用 API 获取供需、行业研报等详细数据
- **Think-3**: 整合结论生成分析报告,附关键数据来源
- **IT 故障运维**:使用 ReAct 模式逐步缩小问题范围,避免盲目操作;每一步操作有理有据,方便运维工程师实施解决方案前的二次验证,为后续复盘与制定预防措施提供基础。
- **Think-1**:理清故障的常见原因,例如宕机的常见原因是 “CPU 过载、内存不足、磁盘满、服务崩溃”,需要先查基础监控数据
- **Act-1**:调用「监控系统 API」查询服务器打点数据
- **Think-2**:判断主因,例如 CPU 利用率异常则进一步排查哪些进程 CPU 占用高
- **Act-2**:用「进程管理工具」查 TOP 进程,看是否有异常服务
- **Think-3**:发现日志服务异常,可能是 “日志文件过大” 或 “配置错误”,需要进一步查看日志服务的配置和日志文件大小
- **Act-3**:bash 执行命令,发现日志文件过大,同时配置未开启滚动,也未设置最大日志大小
- **Think-4**:向运维工程师提供可行的解决方案:清理日志,修改配置并开启滚动,重启日志服务与应用
`ChatModelAgent` 利用 LLM 强大的功能进行推理、理解自然语言、作出决策、生成响应、进行工具交互,**充当智能体应用程序 "思考" 的部分**。您可以使用 ADK 快速构建具有 `ReAct` 能力的 `ChatModelAgent`:
```go
import github.com/cloudwego/eino/adk
// 创建一个包含多个工具的 ReAct ChatModelAgent
chatAgent := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Name: "intelligent_assistant",
Description: "An intelligent assistant capable of using multiple tools to solve complex problems",
Instruction: "You are a professional assistant who can use the provided tools to help users solve problems",
Model: openaiModel,
ToolsConfig: adk.ToolsConfig{
Tools: []tool.BaseTool{
searchTool,
calculatorTool,
weatherTool,
},
}
})
```
## 🎭 WorkflowAgents:精密的流水线
Eino ADK 提供了专用于协调子 Agent 执行流程的 WorkflowAgents 模式,用于通过预定义逻辑管理 Agent 的运行方式,产生确定的执行过程,协助实现**可预测可控制的多 Agent 协作方式**。您可以按需对下列模式进行排列组合,结合 `ChatModelAgent` 构造出符合自身需求的完整工作流水线:
- **Sequential Agent**: 将配置中注册的 Agents 按顺序依次执行一次后结束,运行遵循以下原则:
- **线性执行**:严格按照 SubAgents 数组的顺序执行。
- **运行结果传递**:配置中的每个 Agent 都能够获取 Sequential Agent 的完整输入以及前序 Agent 的输出。
- **支持提前退出**:如果任何一个子 Agent 产生退出 / 中断动作,整个 Sequential 流程会立即终止。
- 可能的实践场景有:
- **数据 ETL**:`ExtractAgent`(从 MySQL 抽取订单数据)→ `TransformAgent`(清洗空值、格式化日期)→ `LoadAgent`(加载到数据仓库)
- **CI / CD 流水线**:`CodeCloneAgent`(从代码仓库拉取代码)→`UnitTestAgent`(运行单元测试,用例失败时返回错误与分析报告)→`CompileAgent`(编译代码)→`DeployAgent`(部署到目标环境)
```go
import github.com/cloudwego/eino/adk
// 依次执行 制定研究计划 -> 搜索资料 -> 撰写报告
sequential := adk.NewSequentialAgent(ctx, &adk.SequentialAgentConfig{
Name: "research_pipeline",
SubAgents: []adk.Agent{
planAgent, // 制定研究计划
searchAgent, // 搜索资料
writeAgent, // 撰写报告
},
})
```
<a href="/img/eino/eino_adk_sequential.png" target="_blank"><img src="/img/eino/eino_adk_sequential.png" width="100%" /></a>
- **Parallel Agent**: 将配置中注册的 Agents 并发执行,所有 Agent 执行完毕后结束,运行遵循以下原则:
- **并发执行**:所有子 Agent 同时启动,在独立的 goroutine 中并行执行。
- **共享输入**:所有子 Agent 接收调用 Pararllel Agent 时相同的初始输入。
- **等待与结果聚合**:内部使用 sync.WaitGroup 等待所有子 Agent 执行完成,收集所有子 Agent 的执行结果并按接收顺序输出到 `AsyncIterator` 中。
- 可能的实践场景有:
- **多源数据采集**:`MySQLCollector`(采集用户表)+ `PostgreSQLCollector`(采集订单表)+ `MongoDBCollector`(采集商品评论)
- **多渠道推送**:`WeChatPushAgent`(推送到微信公众号)+ `SMSPushAgent`(发送短信)+ `AppPushAgent`(推送到 APP)
```go
import github.com/cloudwego/eino/adk
// 并发执行 情感分析 + 关键词提取 + 内容摘要
parallel := adk.NewParallelAgent(ctx, &adk.ParallelAgentConfig{
Name: "multi_analysis",
SubAgents: []adk.Agent{
sentimentAgent, // 情感分析
keywordAgent, // 关键词提取
summaryAgent, // 内容摘要
},
})
```
<a href="/img/eino/eino_adk_parallel.png" target="_blank"><img src="/img/eino/eino_adk_parallel.png" width="100%" /></a>
- **Loop Agent**:将配置中注册的 Agents 按顺序依次执行并循环多次,运行遵循以下原则:
- **循环执行**:重复执行 SubAgents 序列,每次循环都是一个完整的 Sequential 执行过程。
- **运行结果累积**:每次迭代的结果都会累积,后续迭代的输入可以访问所有历史信息。
- **条件退出**:支持通过输出包含 `ExitAction` 的事件或达到最大迭代次数来终止循环,配置 `MaxIterations=0` 时表示无限循环。
- 可能的实践场景有:
- **数据同步**:`CheckUpdateAgent`(检查源库增量)→ `IncrementalSyncAgent`(同步增量数据)→ `VerifySyncAgent`(验证一致性)
- **压力测试**:`StartClientAgent`(启动测试客户端)→ `SendRequestsAgent`(发送请求)→ `CollectMetricsAgent`(收集性能指标)
```go
import github.com/cloudwego/eino/adk
// 循环执行 5 次,每次顺序为:分析当前状态 -> 提出改进方案 -> 验证改进效果
loop := adk.NewLoopAgent(ctx, &adk.LoopAgentConfig{
Name: "iterative_optimization",
SubAgents: []adk.Agent{
analyzeAgent, // 分析当前状态
improveAgent, // 提出改进方案
validateAgent, // 验证改进效果
},
MaxIterations: 5,
})
```
<a href="/img/eino/eino_adk_loop_controller.png" target="_blank"><img src="/img/eino/eino_adk_loop_controller.png" width="100%" /></a>
## 🛠️ 预构建的 Multi-Agent 范式
Eino ADK 基于日常 Multi-Agent 协作实践中沉淀的最佳工程经验,为用户提供**两种预构建的 Multi-Agent 范式**,无需从头设计协作逻辑即可开箱即用,覆盖「集中式协调」与「结构化问题解决」两大核心场景,高效支撑复杂任务的智能协作。
#### 🎯 Supervisor 模式:集中式协调
Supervisor Agent 是 ADK 提供的一种中心化 Multi-Agent 协作模式,旨在为集中决策与分发执行的通用场景提供解决方案,由一个 Supervisor Agent(监督者) 和多个 SubAgent (子 Agent)组成,其中:
- Supervisor Agent 负责任务的分配、子 Agent 完成后的结果汇总与下一步决策。
- 子 Agents 专注于执行具体任务,并在完成后自动将任务控制权交回 Supervisor。
<a href="/img/eino/eino_adk_supervisor_flow.png" target="_blank"><img src="/img/eino/eino_adk_supervisor_flow.png" width="100%" /></a>
Supervisor 模式有如下特点:
- **中心化控制**:Supervisor 统一管理子 Agent,可根据输入与子 Agent 执行结果动态调整任务分配。
- **确定性回调**:子 Agent 执行完毕后会将运行结果返回到 Supervisor Agent,避免协作流程中断。
- **松耦合扩展**:子 Agent 可独立开发、测试和替换,方便拓展与维护。
Supervisor 模式的这种层级化的结构非常适合于**动态协调多个专业 Agent 完成复杂任务**的场景,例如:
- **科研项目管理**:Supervisor 分配调研、实验、报告撰写任务给不同子 Agent。
- **客户服务流程**:Supervisor 根据用户问题类型,分配给技术支持、售后、销售等子 Agent。
```go
import github.com/cloudwego/eino/adk/prebuilt/supervisor
// 科研项目管理:创建一个监督者模式的 multi-agent
// 包含 research(调研),experimentation(实验),report(报告)三个子 Agent
supervisor, err := supervisor.New(ctx, &supervisor.Config{
SupervisorAgent: supervisorAgent,
SubAgents: []adk.Agent{
researchAgent,
experimentationAgent,
reportAgent,
},
})
```
#### 🎯 Plan-Execute 模式:结构化问题解决
Plan-Execute Agent 是 ADK 提供的基于「规划-执行-反思」范式的 Multi-Agent 协作模式(参考论文 **Plan-and-Solve Prompting**),旨在解决复杂任务的分步拆解、执行与动态调整问题,通过 Planner(规划器)、Executor(执行器)和 Replanner(重规划器) 三个核心智能体的协同工作,实现任务的结构化规划、工具调用执行、进度评估与动态重规划,最终达成用户目标,其中:
- **Planner**:根据用户目标,生成一个包含详细步骤且结构化的初始任务计划
- **Executor**:执行当前计划中的首个步骤
- **Replanner**:评估执行进度,决定是修正计划继续交由 Executor 运行,或是结束任务
<a href="/img/eino/eino_adk_plan_execute_replan_detail.png" target="_blank"><img src="/img/eino/eino_adk_plan_execute_replan_detail.png" width="100%" /></a>
Plan-Execute 模式有如下特点:
- **明确的分层架构**:通过将任务拆解为规划、执行和反思重规划三个阶段,形成层次分明的认知流程,体现了 “先思考再行动,再根据反馈调整” 的闭环认知策略,在各类场景中都能达到较好的效果。
- **动态迭代优化**:Replanner 根据执行结果和当前进度,实时判断任务是否完成或需调整计划,支持动态重规划。该机制有效解决了传统单次规划难以应对环境变化和任务不确定性的瓶颈,提升了系统的鲁棒性和灵活性。
- **职责分明且松耦合**:Plan-Execute 模式由多个智能体协同工作,支持独立开发、测试和替换。模块化设计方便扩展和维护,符合工程最佳实践。
- **具备良好扩展性**:不依赖特定的语言模型、工具或 Agent,方便集成多样化外部资源,满足不同应用场景需求。
Plan-Execute 模式的「规划 → 执行 → 重规划」闭环结构非常适合**需要多步骤推理、动态调整和工具集成的复杂任务场景**,例如:
- **复杂研究分析**:通过规划分解研究问题,执行多轮数据检索与计算,动态调整研究方向和假设,提升分析深度和准确性。
- **自动化工作流管理**:将复杂业务流程拆解为结构化步骤,结合多种工具(如数据库查询、API 调用、计算引擎)逐步执行,并根据执行结果动态优化流程。
- **多步骤问题解决**:适用于需要分步推理和多工具协作的场景,如法律咨询、技术诊断、策略制定等,确保每一步执行都有反馈和调整。
- **智能助理任务执行**:支持智能助理根据用户目标规划任务步骤,调用外部工具完成具体操作,并根据重规划思考结合用户反馈调整后续计划,提升任务完成的完整性和准确性。
```go
import github.com/cloudwego/eino/adk/prebuilt/planexecute
// Plan-Execute 模式的科研助手
researchAssistant := planexecute.New(ctx, &planexecute.Config{
Planner: adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Name: "research_planner",
Instruction: "制定详细的研究计划,包括文献调研、数据收集、分析方法等",
Model: gpt4Model,
}),
Executor: adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Name: "research_executor",
ToolsConfig: adk.ToolsConfig{
Tools: []tool.BaseTool{
scholarSearchTool,
dataAnalysisTool,
citationTool,
},
},
}),
Replanner: replannerAgent,
})
```
#### 🎯 DeepAgents 模式:规划驱动的集中式协作
DeepAgents 是一种在 Main Agent 统一协调下的 Multi-Agent 模式。Main Agent 借助具备工具调用能力的 ChatModel 以 ReAct 流程运行:
- 通过 WriteTodos 将用户目标拆解为结构化待办并记录进度
- 通过统一入口 TaskTool 选择并调用对应的 SubAgent 执行子任务;主/子代理上下文隔离,避免中间步骤污染主流程。
- 汇总各子代理返回的结果;必要时再次调用 WriteTodos 更新进度或进行重规划,直至完成。
<a href="/img/eino/eino_adk_deep_agents_overview.png" target="_blank"><img src="/img/eino/eino_adk_deep_agents_overview.png" width="100%" /></a>
DeepAgents 模式的特点为:
- **强化任务拆解与进度管理**:通过 WriteTodos 形成明确的子任务与里程碑,使复杂目标可分解、可跟踪。
- **上下文隔离更稳健**:子代理在“干净”上下文中执行,主代理仅汇总结果,减少冗余思维链和工具调用痕迹对主流程的干扰。
- **统一委派入口、易扩展**:TaskTool 将所有子代理与工具能力抽象为统一调用面,便于新增或替换专业子代理。
- **计划与执行的灵活闭环**:规划作为工具可按需调用;对简单任务可跳过不必要规划,从而降低 LLM 调用成本与耗时。
- **边界与权衡**:过度拆解会增加调用次数与成本;对子任务划分与提示词调优提出更高要求,模型需具备稳定的工具调用与规划能力。
DeepAgent 的核心价值在于自动化处理需要多步骤、多角色协作的复杂工作流。它不仅仅是单一功能的执行者,更是一个具备深度思考、规划和动态调整能力的“项目经理”,适配场景有:
- **多角色协作的复杂业务流程**:围绕研发、测试、发布、法务、运营多角色协作,集中委派子任务并统一汇总;每个阶段设定关口与回退策略,进度可视且可重试。
- **长流程的阶段性管理**:规划拆解清洗、校验、血缘分析、质检等步骤,子代理在隔离上下文中运行;出现异常时仅重跑相关阶段,产物统一对账与汇总。
- **需要严格上下文隔离的执行环境**:统一入口收集材料与请求,TaskTool 将法务、风控、财务等子任务分别路由;子任务之间边界清晰互不可见,进度与留痕可审计,失败可重试而不影响其他环节。
```go
import github.com/cloudwego/eino/adk/prebuilt/deep
agent, err := deep.New(ctx, &deep.Config{
Name: "deep-agent",
ChatModel: gpt4Model,
SubAgents: []adk.Agent{
LegalAgent,
RiskControlAgent,
FinanceAgent,
},
MaxIteration: 100,
})
```
# 基础设计
## 🎯 统一的 Agent 抽象
ADK 的核心是一个简洁而强大的 `Agent` 接口:
```go
type Agent interface {
Name(ctx context.Context) string
Description(ctx context.Context) string
Run(ctx context.Context, input *AgentInput, options ...AgentRunOption) *AsyncIterator[*AgentEvent]
}
```
每个 Agent 都有明确的身份(Name)、清晰的职责(Description)和标准化的执行方式(Run),为 Agent 之间的发现与调用提供了基础。无论是简单的问答机器人,还是复杂的多步骤任务处理系统,都可以通过这个统一的接口加以实现。
## ⚡ 异步事件驱动架构
ADK 采用了异步事件流设计,通过 `AsyncIterator[*AgentEvent]` 实现非阻塞的事件处理,并通过 `Runner` 框架运行 Agent:
- **实时响应**:`AgentEvent` 包含 Agent 执行过程中特定节点输出(Agent 回复、工具处理结果等等),用户可以立即看到 Agent 的思考过程和中间结果。
- **追踪执行过程**:`AgentEvent` 额外携带状态修改动作与运行轨迹,便于开发调试和理解 Agent 行为。
- **自动流程控制**:框架通过 `Runner` 自动处理中断、跳转、退出行为,无需用户额外干预。
## 🤝 灵活的协作机制
Eino ADK 支持处于同一个系统内的 Agent 之间以多种方式进行协作(交换数据或触发运行):
- **共享 Session**:单次运行过程中持续存在的 KV 存储,用于支持跨 Agent 的状态管理和数据共享。
```go
// 获取全部 SessionValues
func GetSessionValues(ctx context.Context) map[string]any
// 指定 key 获取 SessionValues 中的一个值,key 不存在时第二个返回值为 false,否则为 true
func GetSessionValue(ctx context.Context, key string) (any, bool)
// 添加 SessionValues
func AddSessionValue(ctx context.Context, key string, value any)
// 批量添加 SessionValues
func AddSessionValues(ctx context.Context, kvs map[string]any)
```
- **移交运行(Transfer)**:携带本 Agent 输出结果上下文,将任务移交至子 Agent 继续处理。适用于智能体功能可以清晰的划分边界与层级的场景,常结合 ChatModelAgent 使用,通过 LLM 的生成结果进行动态路由。结构上,以此方式进行协作的两个 Agent 称为父子 Agent:
<a href="/img/eino/eino_adk_transfer.png" target="_blank"><img src="/img/eino/eino_adk_transfer.png" width="100%" /></a>
```go
// 设置父子 Agent 关系
func SetSubAgents(ctx context.Context, agent Agent, subAgents []Agent) (Agent, error)
// 指定目标 Agent 名称,构造 Transfer Event
func NewTransferToAgentAction(destAgentName string) *AgentAction
```
- **显式调用(ToolCall)**:将 Agent 视为工具进行调用。适用于 Agent 运行仅需要明确清晰的参数而非完整运行上下文的场景,常结合 ChatModelAgent,作为工具运行后将结果返回给 ChatModel 继续处理。除此之外,ToolCall 同样支持调用符合工具接口构造的、不含 Agent 的普通工具。
<a href="/img/eino/eino_adk_agent_as_tool.png" target="_blank"><img src="/img/eino/eino_adk_agent_as_tool.png" width="100%" /></a>
```go
// 将 Agent 转换为 Tool
func NewAgentTool(_ context.Context, agent Agent, options ...AgentToolOption) tool.BaseTool
```
## 🔄 **中断与恢复机制**
Eino ADK 提供运行时中断与恢复的功能,允许正在运行中的 Agent 主动中断并保存其当前状态,并在未来从中断点恢复执行。该功能为长时间等待、可暂停或需要外部输入(Human in the loop)等场景下的开发提供协助。
- Agent 内部运行过程中,通过抛出含 `Interrupt Action` 的 `Event` 主动通知 `Runner` 中断运行,并允许携带额外信息供调用方阅读与使用。
- `Runner` 通过初始化时注册的 `CheckPointStore` 记录当前运行状态
- 重新准备好运行后,通过 `Resume` 方法携带恢复运行所需要的新信息,从断点处重新启动该 Agent 运行
```go
// 1. 创建支持断点恢复的 Runner
runner := adk.NewRunner(ctx, adk.RunnerConfig{
Agent: complexAgent,
CheckPointStore: memoryStore, // 内存状态存储
})
// 2. 开始执行
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)
}
if event.Action != nil {
// 3. 由 Agent 内部抛出 Interrupt 事件
if event.Action.Interrupted != nil {
ii, _ := json.MarshalIndent(event.Action.Interrupted.Data, "", "\t")
fmt.Printf("action: interrupted\n")
fmt.Printf("interrupt snapshot: %v", string(ii))
}
}
}
// 4. 从 stdin 接收用户输入
scanner := bufio.NewScanner(os.Stdin)
fmt.Print("\nyour input here: ")
scanner.Scan()
fmt.Println()
nInput := scanner.Text()
// 5. 携带用户输入信息,从断点恢复执行
iter, err := runner.Resume(ctx, "1", adk.WithToolOptions([]tool.Option{subagents.WithNewInput(nInput)}))
```
# 快速开始
## 安装
```go
go get github.com/cloudwego/eino@latest
```
## 项目开发经理智能体
下面的示例使用 Eino ADK 构建了一个项目开发经理智能体,面向多方面管理协同的场景:
- Project Manager Agent:项目经理智能体,整体使用 Supervisor 模式,各 Agent 的功能如下:
- `ResearchAgent`:调研 Agent,负责调研并生成可行方案,支持中断后从用户处接收额外的上下文信息来提高调研方案生成的准确性。
- `CodeAgent`:编码 Agent,使用知识库工具,召回相关知识作为参考,生成高质量的代码。
- `ReviewAgent`:评论 Agent,使用顺序工作流编排问题分析、评价生成、评价验证三个步骤,对调研结果 / 编码结果进行评审,给出合理的评价,供项目经理进行决策。
- `ProjectManagerAgent`:项目经理 Agent,根据动态的用户输入,路由并协调多个负责不同维度工作的子智能体开展工作。
- 该 Agent 可能的工作场景为:
- **从零开始实现项目**:项目经理从需求入手,经由调研、编码、评论三个 Agent 工作,最终完成项目交付。
- **对已有项目的完善**:项目经理从评论 Agent 获得项目仍旧需要完善的功能点,交由编码 Agent 进行实现,再交由评论 Agent 对修改后的代码进行评审。
- **开展技术调研**:项目经理要求调研 Agent 生成技术调研报告,然后由评论 Agent 给出评审意见。调用方结合返回的技术调研报告和评审意见,决定后续动作。
<a href="/img/eino/eino_adk_project_manager.png" target="_blank"><img src="/img/eino/eino_adk_project_manager.png" width="100%" /></a>
该示例的设计涵盖了文中介绍的大部分概念,您可以基于示例回顾之前的提到的种种设计理念。另外,请试想普通开发模式下如何完成该示例的编写,ADK 的优势便立刻凸显了出来:
<table>
<tr><td>设计点</td><td>传统开发模式</td><td>基于 Eino ADK 开发</td></tr>
<tr><td>Agent 抽象</td><td>没有统一定义,团队协作开发效率差,后期维护成本高</td><td>统一定义,职责独立,代码整洁,便于各 Agent 分头开发</td></tr>
<tr><td>输入输出</td><td>没有统一定义,输入输出混乱运行过程只能手动加日志,不利于调试</td><td>有统一定义,全部基于事件驱动运行过程通过 iterator 透出,所见即所得</td></tr>
<tr><td>Agent 协作</td><td>通过代码手动传递上下文</td><td>框架自动传递上下文</td></tr>
<tr><td>中断恢复能力</td><td>需要从零开始实现,解决序列化与反序列化、状态存储与恢复等问题</td><td>仅需在 Runner 中注册 CheckPointStore 提供断点数据存储介质</td></tr>
<tr><td>Agent 模式</td><td>需要从零开始实现</td><td>多种成熟模式开箱即用</td></tr>
</table>
核心代码如下,完整代码详见 Eino-Examples 项目中提供的[源码](https://github.com/cloudwego/eino-examples/tree/main/adk/multiagent/integration-project-manager):
```go
func main() {
ctx := context.Background()
// Init chat model for agents
tcm, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{
APIKey: os.Getenv("OPENAI_API_KEY"),
Model: os.Getenv("OPENAI_MODEL"),
BaseURL: os.Getenv("OPENAI_BASE_URL"),
ByAzure: func() bool {
return os.Getenv("OPENAI_BY_AZURE") == "true"
}(),
})
if err != nil {
log.Fatal(err)
}
// Init research agent
researchAgent, err := agents.NewResearchAgent(ctx, tcm)
if err != nil {
log.Fatal(err)
}
// Init code agent
codeAgent, err := agents.NewCodeAgent(ctx, tcm)
if err != nil {
log.Fatal(err)
}
// Init technical agent
reviewAgent, err := agents.NewReviewAgent(ctx, tcm)
if err != nil {
log.Fatal(err)
}
// Init project manager agent
s, err := agents.NewProjectManagerAgent(ctx, tcm)
if err != nil {
log.Fatal(err)
}
// Combine agents into ADK supervisor pattern
// Supervisor: project manager
// Sub-agents: researcher / coder / reviewer
supervisorAgent, err := supervisor.New(ctx, &supervisor.Config{
Supervisor: s,
SubAgents: []adk.Agent{researchAgent, codeAgent, reviewAgent},
})
if err != nil {
log.Fatal(err)
}
// Init Agent runner
runner := adk.NewRunner(ctx, adk.RunnerConfig{
Agent: supervisorAgent,
EnableStreaming: true, // enable stream output
CheckPointStore: newInMemoryStore(), // enable checkpoint for interrupt & resume
})
// Replace it with your own query
query := "please generate a simple ai chat project with python."
checkpointID := "1"
// Start runner with a new checkpoint id
iter := runner.Query(ctx, query, adk.WithCheckPointID(checkpointID))
interrupted := false
for {
event, ok := iter.Next()
if !ok {
break
}
if event.Err != nil {
log.Fatal(event.Err)
}
if event.Action != nil && event.Action.Interrupted != nil {
interrupted = true
}
prints.Event(event)
}
if !interrupted {
return
}
// interrupt and ask for additional user context
scanner := bufio.NewScanner(os.Stdin)
fmt.Print("\ninput additional context for web search: ")
scanner.Scan()
fmt.Println()
nInput := scanner.Text()
// Resume by checkpoint id, with additional user context injection
iter, err = runner.Resume(ctx, checkpointID, adk.WithToolOptions([]tool.Option{agents.WithNewInput(nInput)}))
if err != nil {
log.Fatal(err)
}
for {
event, ok := iter.Next()
if !ok {
break
}
if event.Err != nil {
log.Fatal(event.Err)
}
prints.Event(event)
}
}
```
# 结尾
Eino ADK 不仅仅是一个开发框架,更是一个完整的智能体开发生态。它通过统一的抽象、灵活的组合和强大的协作机制,让 Go 开发者能够轻松构建从简单对话机器人到复杂多智能体系统的各种 AI 应用。
> 💡
> **立即开始你的智能体开发之旅**
>
> - 📚 查看更多文档:[Eino ADK 文档](https://www.cloudwego.io/zh/docs/eino/core_modules/eino_adk/)
> - 🛠️ 浏览 ADK 源码:[Eino ADK 源码](https://github.com/cloudwego/eino/tree/main/adk)
> - 💡 探索全部示例:[Eino ADK Examples](https://github.com/cloudwego/eino-examples/tree/main/adk)
> - 🤝 加入开发者社区:与其他开发者交流经验和最佳实践
>
> Eino ADK,让智能体开发变得简单而强大!
<a href="/img/eino/eino_adk_user_group.png" target="_blank"><img src="/img/eino/eino_adk_user_group.png" width="100%" /></a>
+541
View File
@@ -0,0 +1,541 @@
---
Description: ""
date: "2025-12-02"
lastmod: ""
tags: []
title: 用 Eino ADK 构建你的第一个 AI 智能体:从 Excel Agent 实战开始
weight: 7
---
## 从 Excel Agent 详解 Eino ADK
本文将会向您介绍如何利用 **Eino ADK** (**Agent Development Kit**) 构建一个强大的多智能体系统,往期 Eino ADK 介绍链接:[Eino ADK:一文搞定 AI Agent 核心设计模式,从 0 到 1 搭建智能体系统](https://mp.weixin.qq.com/s/ffGjlDEzEzroo8w6knlLqw)
示例以 Excel Agent 这个实际业务场景为基础,Excel Agent 是一个能够“听懂你的话、看懂你的表格、写出并执行代码”的智能助手。它把复杂的 Excel 处理工作拆解为清晰的步骤,通过自动规划、工具调用与结果校验,稳定完成各项 Excel 数据处理任务。
接下来我们将从 Excel Agent 的完整架构与功能出发,向您展示该 Agent 是如何通过 Eino ADK 逐步搭建的,进而深入浅出的理解 Eino ADK 的核心设计特点,助您快速上手 Eino ADK,向构建自定义智能体与 AI 应用系统更进一步。
本示例完整代码位于 [Github](https://github.com/cloudwego/eino-examples/tree/main/adk/multiagent/integration-excel-agent),您可以随时浏览与下载。
### Excel Agent 是什么?
Excel Agent 是一个“看得懂 Excel 的智能助手”,它先把问题拆解成步骤,再一步步执行并校验结果。它能理解用户问题与上传的文件内容,提出可行的解决方案,并选择合适的工具(系统命令、生成并运行 Python 代码、网络查询等等)完成任务。
Excel Agent 整体是基于 Eino ADK 实现的 Multi-Agent 系统,完整架构如下图所示:
<a href="/img/eino/eino_adk_excel_agent_architecture.png" target="_blank"><img src="/img/eino/eino_adk_excel_agent_architecture.png" width="100%" /></a>
Excel Agent 内部包含的几个 Agent 功能分别为:
- **Planner**:分析用户输入,拆解用户问题为可执行的计划
- **Executor**:正确执行当前计划中的首个步骤
- **CodeAgent**:接收来自 Executor 的指令,调用多种工具(例如读写文件,运行 python 代码等)完成任务
- **WebSearchAgent**:接收来自 Executor 的指令,进行网络搜索
- **Replanner**:根据 Executor 执行的结果和现有规划,决定继续执行、调整规划或完成执行
- **ReportAgent**:根据运行过程与结果,生成总结性质的报告
### Excel Agent 的典型使用场景
在真实业务里,你可以把 Excel Agent 当成一位“Excel 专家 + 自动化工程师”。当你交付一个原始表格和目标描述,它会给出方案并完成执行:
- **数据清理与格式化**:从一个包含大量数据的 Excel 文件中完成去重、空值处理、日期格式标准化操作。
- **数据分析与报告生成**:从销售数据中提取每月的销售总额,聚合统计、透视,最终生成并导出图表报告。
- **自动化预算计算**:根据不同部门的预算申请,自动计算总预算并生成部门预算分配表。
- **数据匹配与合并**:将多个不同来源的客户信息表进行匹配合并,生成完整的客户信息数据库。
Excel Agent 的完整运行动线为:
<a href="/img/eino/eino_adk_excel_agent_complete.png" target="_blank"><img src="/img/eino/eino_adk_excel_agent_complete.png" width="100%" /></a>
> 💡
> **核心收益**:
>
> - **更少的人工操作**,把复杂繁琐的 Excel 处理工作交给 Agent 自动完成。
> - **更稳定的产出质量**,通过“规划—执行—反思”闭环减少漏项与错误。
> - **更强的可扩展性**,各 Agent 独立构建,低耦合利于迭代更新。
Excel Agent 既可以单独使用,也可以作为子 Agent,集成在一个复合的多专家系统中,由外部路由到此 Agent 上,解决 excel 领域相关的问题。
下面我们将逐步拆解 Excel Agent,深入了解 Eino ADK 的核心设计特点,以及如何利用这些特点构建高效、灵活的 AI 应用系统。
### ChatModelAgent:与 LLM 交互的基石
`ChatModelAgent` 是 Eino ADK 中的一个核心预构建的 Agent,内部使用了 [ReAct](https://react-lm.github.io/) 模式(一种让模型‘思考-行动-观察’的链式推理模式):
<a href="/img/eino/eino_adk_react_pattern.png" target="_blank"><img src="/img/eino/eino_adk_react_pattern.png" width="100%" /></a>
`ChatModelAgent` 旨在让 ChatModel 进行显式的、一步一步的“思考”,结合思考过程驱动行动,观测历史思考过程与行动结果继续进行下一步的思考与行动,最终解决复杂问题:
- 调用 ChatModel(Reason)
- LLM 返回工具调用请求(Action)
- ChatModelAgent 执行工具(Act)
- 将工具结果返回给 LLM(Observation),结合之前的上下文继续生成,直到模型判断不需要调用工具后结束
<a href="/img/eino/eino_adk_excel_chat_model_agent_view.png" target="_blank"><img src="/img/eino/eino_adk_excel_chat_model_agent_view.png" width="100%" /></a>
在 Excel Agent 中,每个 Agent 的核心都是这样一个 `ChatModelAgent`,以 Executor 运行【读取用户输入表格的头信息】这个步骤为例 ,我们可以通过观察完整的运行过程来理解 ReAct 模式在 `ChatModelAgent` 中的表现:
1. Executor:经过判断,将任务转交给 CodeAgent 运行
2. CodeAgent:接收到任务【读取用户输入表格的头信息】
1. **Think-1**:上下文未提供工作目录下的所有文件,需要查看
2. **Act-1**: 调用 Bash 工具,ls 查看工作目录下的所有文件
3. **Think-2**: 找到了用户输入的文件,判断需要编写 Python 代码读取 xlsx 表格的首行
4. **Act-2**: 调用 PythonRunner 工具,书写代码并运行,获取运行结果
5. **Think-3**: 获取到了 xlsx 首行,判断任务完成
3. 运行完成,将表格头信息返回给 Executor
### Plan-Execute Agent:基于「规划-执行-反思」的多智能体协作框架
Plan-Execute Agent 是 Eino ADK 中一种基于「规划-执行-反思」范式的多智能体协作框架,旨在解决复杂任务的分步拆解、执行与动态调整问题。它通过 **Planner(规划器)**、**Executor(执行器)**和 **Replanner(重规划器)** 三个核心智能体的协同工作,实现任务的结构化规划、工具调用执行、进度评估与动态 replanning,最终达成用户目标:
```go
// 完整代码: https://github.com/cloudwego/eino/blob/main/adk/prebuilt/planexecute/plan_execute.go
// NewPlanner creates a new planner agent based on the provided configuration.
func NewPlanner(_ context.Context, cfg *PlannerConfig) (adk.Agent, error)
// NewExecutor creates a new executor agent.
func NewExecutor(ctx context.Context, cfg *ExecutorConfig) (adk.Agent, error)
// NewReplanner creates a new replanner agent.
func NewReplanner(_ context.Context, cfg *ReplannerConfig) (adk.Agent, error)
// New creates a new plan-execute-replan agent with the given configuration.
func New(ctx context.Context, cfg *Config) (adk.Agent, error)
```
<a href="/img/eino/eino_adk_why_excel_plan_executor.png" target="_blank"><img src="/img/eino/eino_adk_why_excel_plan_executor.png" width="100%" /></a>
而 Excel Agent 的核心能力恰好为【解决用户在 excel 领域的问题】,与该智能体协作框架定位一致:
- **规划者**(**Planner**):明确目标,自动拆解可执行步骤
- **执行者(Executor)**:调用工具(Excel 读取、系统命令、Python 代码)完成规划中的每一个详细步骤
- **反思者(Replanner)**:根据执行进度决定继续、调整规划或结束
Planner 和 Replanner 会将用户模糊的指令拆解为清晰的、可执行的步骤清单,即包含多个步骤(Step)的计划(Plan),Eino ADK 为此提供了灵活的 Plan 接口定义,支持用户自定义 Plan 结构与细节:
```go
type Plan interface {
// FirstStep returns the first step to be executed in the plan.
FirstStep() string
// Marshaler serializes the Plan into JSON.
// The resulting JSON can be used in prompt templates.
json.Marshaler
// Unmarshaler deserializes JSON content into the Plan.
// This processes output from structured chat models or tool calls into the Plan structure.
json.Unmarshaler
}
```
默认情况下,框架会使用内置的 Plan 结构作为兜底配置,例如下面就是 Excel Agent 产生的一个完整运行计划:
```sql
### 任务计划
- [x] 1. Read the contents of '模拟出题.csv' from the working directory into a pandas DataFrame.
- [x] 2. Identify the question type (e.g., multiple-choice, short-answer) for each row in the DataFrame.
- [x] 3. For non-short-answer questions, restructure the data to place question, answer, explanation, and options in the same row.
- [x] 4. For short-answer questions, merge the answer content into the explanation column and ensure question and merged explanation are in the same row.
- [x] 5. Verify that all processed rows have question, answer (where applicable), explanation, and options (where applicable) in a single row with consistent formatting.
- [x] 6. Generate a cleaned report presenting the formatted questions with all relevant components (question, answer, explanation, options) in unified rows.
```
### Workflow Agents:可控的多 Agent 运行流水线
Excel Agent 中,存在一些需要按照特定顺序运行 agent 的情况:
1. **顺序运行**:先运行 Planner,再运行 Executor 和 Replanner;Planner 只运行一次。
2. **循环运行**:Executor 和 Replanner 需要按需循环运行多次,每次循环运行都是先运行 Executor 后运行 Replanner
3. **顺序运行**:Plan-Executor 整体运行完后,固定运行一次 ReportAgent 进行总结。
对于这些拥有固定执行流程的场景,Eino ADK 提供了三种流程编排方式,协助用户快速搭建可控的工作流:
- **SequentialAgent**:按照配置中提供的顺序,依次执行一系列子 Agent。每个子 Agent 执行完成后,其输出会通过 History 机制传递给下一个子 Agent,形成一个线性的执行链。
```go
import github.com/cloudwego/eino/adk
// 依次执行 制定研究计划 -> 搜索资料 -> 撰写报告
sequential := adk.NewSequentialAgent(ctx, &adk.SequentialAgentConfig{
Name: "research_pipeline",
SubAgents: []adk.Agent{
planAgent, // 制定研究计划
searchAgent, // 搜索资料
writeAgent, // 撰写报告
},
})
```
<a href="/img/eino/eino_adk_excel_agent_sequential.png" target="_blank"><img src="/img/eino/eino_adk_excel_agent_sequential.png" width="100%" /></a>
- **LoopAgent**:重复执行配置的子 Agent 序列,直到达到最大迭代次数或某个子 Agent 产生 ExitAction,每次迭代的结果都会累积,后续迭代的输入可以访问所有历史信息。LoopAgent 基于 SequentialAgent 实现。
```go
import github.com/cloudwego/eino/adk
// 循环执行 5 次,每次顺序为:分析当前状态 -> 提出改进方案 -> 验证改进效果
loop := adk.NewLoopAgent(ctx, &adk.LoopAgentConfig{
Name: "iterative_optimization",
SubAgents: []adk.Agent{
analyzeAgent, // 分析当前状态
improveAgent, // 提出改进方案
validateAgent, // 验证改进效果
},
MaxIterations: 5,
})
```
<a href="/img/eino/eino_adk_loop_agent_max_iterations_example.png" target="_blank"><img src="/img/eino/eino_adk_loop_agent_max_iterations_example.png" width="100%" /></a>
- **ParallelAgent**:允许多个子 Agent 基于相同的输入上下文并发执行。所有子 Agent 接收相同的初始输入,各自在独立的 goroutine(Go 语言中一种轻量级的并发执行单元) 运行,最终收集所有子 Agent 的执行结果并按顺序输出到 `AsyncIterator` 中。
```go
import github.com/cloudwego/eino/adk
// 并发执行 情感分析 + 关键词提取 + 内容摘要
parallel := adk.NewParallelAgent(ctx, &adk.ParallelAgentConfig{
Name: "multi_analysis",
SubAgents: []adk.Agent{
sentimentAgent, // 情感分析
keywordAgent, // 关键词提取
summaryAgent, // 内容摘要
},
})
```
<a href="/img/eino/eino_adk_yet_another_parallel.png" target="_blank"><img src="/img/eino/eino_adk_yet_another_parallel.png" width="100%" /></a>
### Agent 抽象:灵活定义 Agent 的基础
Eino ADK 的核心是一个简洁而强大的 Agent 接口,每个 Agent 都有明确的身份(Name)、清晰的职责(Description)和标准化的执行方式(Run),为 Agent 之间的发现与调用提供了基础。无论是简单的问答机器人,还是复杂的多步骤任务处理系统,都可以通过这个统一的接口加以实现。
- **统一的 Agent 抽象**:ADK 提供的预构建 Agent(ChatModelAgent,Plan-Execute Agent,Workflow Agents)都遵循该接口定义。您也可以基于该接口,书写自定义 Agent,完成定制化需求。
```go
type Agent interface {
Name(ctx context.Context) string
Description(ctx context.Context) string
Run(ctx context.Context, input *AgentInput, options ...AgentRunOption) *AsyncIterator[*AgentEvent]
}
```
- **标准化输入**:Agent 通常以 LLM 为核心,因此 Eino ADK 定义的 Agent 的输入与 LLM 接收的输入一致:
```go
type AgentInput struct {
Messages []Message
EnableStreaming bool
}
type Message = *schema.Message // *schema.Message 是模型输入输出的结构定义
```
- **异步事件驱动输出**:Agent 的输出是一个 AgentEvent 的异步迭代器,其中的 AgentEvent 表示 Agent 在其运行过程中产生的核心事件数据。其中包含了 Agent 的元信息、输出、行为和报错信息:
```go
type AgentEvent struct {
AgentName string // 产生 Event 的 Agent 名称(框架自动填充)
RunPath []RunStep // 到达当前 Agent 的完整运行轨迹(框架自动填充)
Output *AgentOutput // Agent 输出消息内容
Action *AgentAction // Agent 动作事件内容
Err error // Agent 报错
}
type AgentOutput struct {
MessageOutput *MessageVariant // 模型消息输出内容
CustomizedOutput any // 自定义输出内容
}
type MessageVariant struct {
IsStreaming bool // 是否为流式输出
Message Message // 非流式消息输出
MessageStream MessageStream // 流式消息输出
Role schema.RoleType // 消息角色
ToolName string // 工具名称
}
type AgentAction struct {
Exit bool // Agent 退出
Interrupted *InterruptInfo // Agent 中断
TransferToAgent *TransferToAgentAction // Agent 跳转
CustomizedAction any // 自定义 Agent 动作
}
```
异步迭代器允许 Agent 在运行过程中的任意时刻向迭代器发送消息(Agent 调用模型结果、工具运行结果、中间状态等等),同时调用方以一种有序、阻塞的方式消费这一系列事件:
```go
iter := myAgent.Run(ctx, "hello") // get AsyncIterator
for {
event, ok := iter.Next()
if !ok {
break
}
// handle event
}
```
### Agent 协作:隐藏在 Agent 后的数据传递
Excel Agent 架构图中的节点代表每个具体的 Agent,边代表了数据流通与任务转移。在构建多 Agent 系统时,让不同 Agent 之间高效、准确地共享信息至关重要。
这些信息不仅包含 Agent 的输入输出,还有全局的、部分可见的种种额外信息,例如:
- Executor 执行需要从 Planner / Replanner 拿到一个结构化的、可被拆分为详细步骤(Step)的计划(Plan),而非一段非结构化的 LLM 原始输出消息。
- ReportAgent 需要拿到完整的运行计划、运行过程与运行产物才能正确产生报告。
Eino ADK 包含两种基础的数据传递机制:
- **History**:每一个 Agent 产生的 AgentEvent 都会被保存到这个隐藏的 History 中,调用一个新 Agent 时 History 中的 AgentEvent 会被转换并拼接到 AgentInput 中。默认情况下,其他 Agent 的 Assistant 或 Tool Message,被转换为 User Message,这相当于在告诉当前的 LLM:“刚才, Agent_A 调用了 some_tool ,返回了 some_result 。现在,轮到你来决策了。”。 通过这种方式,其他 Agent 的行为被当作了提供给当前 Agent 的“外部信息”或“事实陈述”,而不是它自己的行为,从而避免了 LLM 的上下文混乱。
<a href="/img/eino/eino_adk_history.png" target="_blank"><img src="/img/eino/eino_adk_history.png" width="100%" /></a>
- **共享 Session**:单次运行过程中持续存在的 KV 存储,用于支持跨 Agent 的状态管理和数据共享,一次运行中的任何 Agent 可以在任何时间读写 SessionValues。以 Plan-Execute Agent 模式为例,Planner 生成首个计划并写入 Session;Executor 从 Session 读取计划并执行;Replanner 从 Session 读取当前计划后,结合运行结果,将更新后的计划写回 Session 覆盖当前的计划。
```go
// Agent 内获取全部 SessionValues
func GetSessionValues(ctx context.Context) map[string]any
// Agent 内指定 key 获取 SessionValues 中的值
func GetSessionValue(ctx context.Context, key string) (any, bool)
// Agent 内添加 SessionValues
func AddSessionValue(ctx context.Context, key string, value any)
// Agent 内批量添加 SessionValues
func AddSessionValues(ctx context.Context, kvs map[string]any)
// WithSessionValues 在 Agent 运行前由外部注入 SessionValues
func WithSessionValues(v map[string]any) AgentRunOption
```
<a href="/img/eino/eino_adk_plan_execute_replan_session.png" target="_blank"><img src="/img/eino/eino_adk_plan_execute_replan_session.png" width="100%" /></a>
除了完善的 Agent 间数据传递机制,Eino ADK 从实践出发,提供了多种 Agent 协作模式:
- **预设 Agent 运行顺序(Workflow)**:以代码中预设好的流程运行, Agent 的执行顺序是事先确定、可预测的。对应 Workflow Agents 章节提到的三种范式。
- **移交运行(Transfer)**:携带本 Agent 输出结果上下文,将任务移交至子 Agent 继续处理。适用于智能体功能可以清晰的划分边界与层级的场景,常结合 ChatModelAgent 使用,通过 LLM 的生成结果进行动态路由。结构上,以此方式进行协作的两个 Agent 称为父子 Agent:
<a href="/img/eino/eino_adk_excel_transfer.png" target="_blank"><img src="/img/eino/eino_adk_excel_transfer.png" width="100%" /></a>
```go
// 设置父子 Agent 关系
func SetSubAgents(ctx context.Context, agent Agent, subAgents []Agent) (Agent, error)
// 指定目标 Agent 名称,构造 Transfer Event
func NewTransferToAgentAction(destAgentName string) *AgentAction
```
- **显式调用(ToolCall)**:将 Agent 视为工具进行调用,适用于 Agent 运行仅需要明确清晰的参数而非完整运行上下文的场景。常结合 ChatModelAgent,将 Agent 作为工具运行后将结果返回给 ChatModel 继续处理。除此之外,ToolCall 同样支持调用符合工具接口构造的、不含 Agent 的普通工具。
<a href="/img/eino/eino_adk_agent_as_tool_case.png" target="_blank"><img src="/img/eino/eino_adk_agent_as_tool_case.png" width="100%" /></a>
```go
// 将 Agent 转换为 Tool
func NewAgentTool(_ context.Context, agent Agent, options ...AgentToolOption) tool.BaseTool
```
## Excel Agent 示例运行
### 配置环境与输入输出路径
- 环境变量:Excel Agent 运行依赖的完整环境变量可参考项目 README。
- 运行输入:包括一段用户需求描述和待处理的一系列文件,其中:
- `main.go` 中首行表示用户输入的需求描述,可自行修改:
```go
func main() {
// query := schema.UserMessage("统计附件文件中推荐的小说名称及推荐次数,并将结果写到文件中。凡是带有《》内容都是小说名称,形成表格,表头为小说名称和推荐次数,同名小说只列一行,推荐次数相加")
// query := schema.UserMessage("读取模拟出题.csv 中的内容,规范格式将题目、答案、解析、选项放在同一行,简答题只把答案写入解析即可")
query := schema.UserMessage("请帮我将 question.csv 表格中的第一列提取到一个新的 csv 中")
}
```
- `adk/multiagent/integration-excel-agent/playground/input` 为默认的附件输入路径,附件输入路径支持配置,参考 README。
- `adk/multiagent/integration-excel-agent/playground/test_data` 路径下提供了几个示例文件,您可以将文件复制到附件输入路径下来进行测试运行:
```go
% tree adk/multiagent/integration-excel-agent/playground/test_data
adk/multiagent/integration-excel-agent/playground/test_data
├── questions.csv
├── 推荐小说.txt
└── 模拟出题.csv
1 directory, 3 files
```
- 运行输出:Excel Agent 输入的附件、运行的中间产物与最终结果都会放置在工作路径下:`adk/multiagent/integration-excel-agent/playground/${uuid}`,输出路径支持配置,参考 README。
### 查看运行结果
Excel Agent 单次运行会在输出路径下创建一个新的工作目录,并在该目录下完成任务,运行时产生的中间产物与最终结果都会写到该目录下。
以 `请帮我将 question.csv 表格中的第一列提取到一个新的 csv 中` 这个任务为例,运行完成后在工作目录下的文件包含:
<a href="/img/eino/eino_adk_excel_directory.png" target="_blank"><img src="/img/eino/eino_adk_excel_directory.png" width="100%" /></a>
1. 原始输入:从输入路径获取到的 `question.csv`
2. Planner / Replanner 给出的运行计划:`plan.md`
```go
### 任务计划
- [x] 1. {"desc":"Read the 'questions.csv' file into a pandas DataFrame."}
- [x] 2. Save the extracted first column to a new CSV file.
```
3. Executor 中的 CodeAgent 书写的代码:`$uuid.py`
```go
import pandas as pd
df = pd.read_csv('questions.csv')
first_column = df.iloc[:, _0_]
first_column.to_csv('extracted_first_column.csv', index=_False_)
```
4. 运行中间产物:`extracted_first_column.csv` 和 `first_column.csv`
```go
type
multiple-choice
...
short-answer
```
5. 最终报告:`final_report.json`
```json
{
"is_success": true,
"result": "Successfully extracted the first column from questions.csv and saved it to first_column.csv.",
"files": [
{
"path": "/User/user/go/src/github.com/cloudwego/eino-examples/adk/multiagent/integration-excel-agent/playground/00f118af-4bd8-42f7-8d11-71f2801218bd/first_column.csv",
"desc": "A CSV file containing only the first column data from the original questions.csv."
}
]
}
```
### 运行过程输出
Excel Agent 会将每个步骤的运行结果输出到日志中。下面仍以 `请帮我将 question.csv 表格中的第一列提取到一个新的 csv 中` 这个任务为例,向您展示 Excel Agent 在运行过程中的几个关键步骤及其输出,并通过对步骤的解释,直观地呈现 Agent 的运行流程及其强大能力。:
- Planner 生成 JSON 格式的初始计划
```yaml
name: Planner
answer: {
**"steps"**: [
{
**"index"**: **1**,
**"desc"**: **"Read the 'questions.csv' file into a pandas DataFrame."**
},
{
**"index"**: **2**,
**"desc"**: **"Extract the first column from the DataFrame."**
},
{
**"index"**: **3**,
**"desc"**: **"Save the extracted first column to a new CSV file."**
}
]
}
```
- Executor 将 CodeAgent 作为工具进行调用,执行计划中的首个步骤
```yaml
name: Executor
tool name: CodeAgent
arguments: {"request":"Read the 'questions.csv' file into a pandas DataFrame using pandas. Use the pandas.read_csv function and store the result in a variable named df."}
```
- CodeAgent 使用 PythonRunner 工具运行代码,并使用 ReAct 模式自动纠错,修正代码中的错误
```yaml
# CodeAgent 使用 PythonRunner 工具运行代码
name: Executor
tool name: PythonRunner
arguments: {"code":"```python\nfirst_column = df.iloc[:, 0]\n```"}
# PythonRunner 代码运行报错
name: Executor
tool response: Traceback (most recent call last):
File "/User/user/go/src/github.com/cloudwego/eino-examples/adk/multiagent/integration-excel-agent/playground/00f118af-4bd8-42f7-8d11-71f2801218bd/00f118af-4bd8-42f7-8d11-71f2801218bd.py", line 1, in <module>
first_column = df.iloc[:, 0]
^^
NameError: name 'df' is not defined
# ReAct 模式自动纠错,修正无法运行的代码
name: Executor
answer: The error occurs because the DataFrame `df` is not defined. We need to first load the data from the existing CSV file `questions.csv` into `df`. Here's the corrected code:
tool name: PythonRunner
arguments: {"code":"```python\nimport pandas as pd\ndf = pd.read_csv('questions.csv')\nfirst_column = df.iloc[:, 0]\nprint(first_column.head()) # Verify the result\n```"}
# 代码运行成功,返回运行结果
name: Executor
path: [{SequentialAgent} {plan_execute_replan} {Planner} {execute_replan} {Executor}]
tool response:
0 multiple-choice
1 multiple-choice
2 multiple-choice
3 multiple-choice
4 multiple-choice
Name: type, dtype: object
```
- Replanner 判断计划完成,提交运行结果至 ReportAgent
```yaml
name: Replanner
answer: {
**"is_success"**: **true**,
**"result"**: **"已成功将'questions.csv'表格中的第一列提取到新的CSV文件'extracted_first_column.csv'中。"**,
**"files"**: [
{
**"desc"**: **"包含原表格第一列数据的新CSV文件"**,
**"path"**: **"extracted_first_column.csv"**
}
]
}
```
- ReportAgent 进行总结,结束执行
```yaml
name: Report
tool name: SubmitResult
arguments: {
**"is_success"**: **true**,
**"result"**: **"Successfully extracted the first column from questions.csv and saved it to first_column.csv."**,
**"files"**: [
{
**"path"**: **"/User/user/go/src/github.com/cloudwego/eino-examples/adk/multiagent/integration-excel-agent/playground/00f118af-4bd8-42f7-8d11-71f2801218bd/first_column.csv"**,
**"desc"**: **"A CSV file containing only the first column data from the original questions.csv."**
}
]
}
```
## 总结
Excel Agent 所呈现的并非“单一智能体”的技巧,而是一套以 Eino ADK 为底座的 Multi-Agent 系统工程化方法论:
- 以 ChatModelAgent 的 ReAct 能力为基石,让模型“可思考、会调用”。
- 以 WorkflowAgents 的编排能力,让 Multi-Agent 系统中的每个 Agent 以用户预期的顺序运行。
- 以 Planner–Executor–Replanner 的闭环,让复杂任务“可拆解、能纠错”。
- 以 History / Session 的数据传递机制,让多 Agent “能协作、可回放”。
> 💡
> **立即开始你的智能体开发之旅**
>
> - ⌨️ 查看 Excel Agent 源码:[Github Excel Agent 源码](https://github.com/cloudwego/eino-examples/tree/main/adk/multiagent/integration-excel-agent)
> - 📚 查看更多文档:[Eino ADK 文档](https://www.cloudwego.io/zh/docs/eino/core_modules/eino_adk/)
> - 🛠️ 浏览 ADK 源码:[Eino ADK 源码](https://github.com/cloudwego/eino/tree/main/adk)
> - 💡 探索 ADK 全部示例:[Eino ADK Examples](https://github.com/cloudwego/eino-examples/tree/main/adk)
> - 🤝 加入开发者社区:与其他开发者交流经验和最佳实践
>
> Eino ADK,让智能体开发变得简单而强大!
<a href="/img/eino/eino_adk_excel_agent_user_group.png" target="_blank"><img src="/img/eino/eino_adk_excel_agent_user_group.png" width="100%" /></a>
+187
View File
@@ -0,0 +1,187 @@
---
Description: ""
date: "2025-12-01"
lastmod: ""
tags: []
title: 大语言模型应用开发框架 —— Eino 正式开源!
weight: 3
---
今天,经过字节跳动内部半年多的使用和迭代,基于 Golang 的大模型应用综合开发框架 —— Eino,已在 CloudWeGo 正式开源啦!
Eino 基于明确的“组件”定义,提供强大的流程“编排”,覆盖开发全流程,旨在帮助开发者以最快的速度实现最有深度的大模型应用。
你是否曾有这种感受:想要为自己的应用添加大模型的能力,但面对这个较新的领域,不知如何入手;想持续的站在研究的最前沿,应用最新的业界成果,但使用的应用开发框架却已经数月没有更新;想看懂项目里的用 Python 写的代码,想确定一个变量或者参数的类型,需要反复查看上下文确认;不确定模型生成的效果是否足够好,想用又不太敢用;在调试、追踪、评测等开发之外的必要环节,还需要额外探索学习其他配套的工具。如果是,欢迎了解和尝试 Eino,因为 Eino 作为旨在覆盖 devops 全流程的大模型应用开发框架,具有如下特点:
- 内核稳定,API 简单易懂,有明确的上手路径,平滑的学习曲线。
- 极致的扩展性,研发工作高度活跃,长期可持续。
- 基于强类型语言 Golang,代码能看懂,易维护,高可靠。
- 背靠字节跳动核心业务线的充分实践经验。
- 提供开箱即用的配套工具。
Eino 已成为字节跳动内部大模型应用的首选全代码开发框架,已有包括豆包、抖音、扣子等多条业务线、数百个服务接入使用。
项目地址:[https://github.com/cloudwego/eino](https://github.com/cloudwego/eino),[https://github.com/cloudwego/eino-ext](https://github.com/cloudwego/eino-ext)
未来,我们将以 Eino 开源库为核心代码仓库,坚持**内外用一套代码**,与社区共建最优秀的大模型应用开发框架。
## 快速认识 Eino
Eino 是覆盖 devops 全流程的大模型应用开发框架,从最佳实践样例的 Eino Examples,到各环节的工具链,都是 Eino 的领域:
<a href="/img/eino/eino_project_structure_and_modules.png" target="_blank"><img src="/img/eino/eino_project_structure_and_modules.png" width="100%" /></a>
那么 Eino 具体能做什么?首先,Eino 由一个个大模型领域的“**组件**”组成,比如最核心的是与大模型交互的 Chat Model:
```go
model, _ := ark.NewChatModel(ctx, config) // 创建一个豆包大模型
message, _ := model.Generate(ctx, []*Message{
SystemMessage("you are a helpful assistant."),
UserMessage("what does the future AI App look like?")}
```
像上面这样一个个的直接使用组件,当然没问题,Eino 提供了大量有用的组件实现供选择。但是,大模型应用有它们自身的特点和规律,比如:
- 核心是大模型,业务逻辑围绕“如何给大模型充分、有效的上下文”以及“如何让大模型的输出可靠的影响环境”,核心的组件类型、数据类型和交互模式是可以枚举的,整体可以由有向图来描述。
- 大模型输出的特点是流式输出,意味着模型的下游都需要有效的处理流式数据,包括流的实时处理、流的复制、多个流的合并、单个流的拼接等。
- 以有向图为基础,衍生出并发处理、扇入扇出、通用横切面、option 分配等一系列子问题。
Eino 的编排能力,是上述通用问题的充分解决方案。
以 ReAct Agent 为例:一个 ChatModel(大模型),“绑定”了 Tool(工具),接收输入的 Message,由 ChatModel 自主判断是否调用 Tool 或输出最终结果。Tool 执行结果会再次成为给到 ChatModel 的 Message,并作为下一轮自主判断的上下文。
<a href="/img/eino/eino_graph_nodes_of_react_agent.png" target="_blank"><img src="/img/eino/eino_graph_nodes_of_react_agent.png" width="100%" /></a>
上述基于 ChatModel 进行自主决策和选路的 ReAct Agent,便是基于 Eino 的 组件 和 Graph 编排 来实现, 代码清晰简洁,可与流程图清晰对应。
- 代码实现详见:[flow/agent/react](https://github.com/cloudwego/eino/blob/main/flow/agent/react/react.go) 的实现
- ReAct Agent 用户手册详见:[react_agent_manual](https://www.cloudwego.io/zh/docs/eino/core_modules/flow_integration_components/react_agent_manual/)
在 Eino 中,这是几十行代码的图编排:
```go
// 构建一个 ReAct Agent,编译为一个输入为 []*Message,输出为 *Message 的 Runnable
// 创建包含 state 的 Graph,用户存储请求维度的 Message 上下文
graph = NewGraph[[]*Message, *Message](
WithGenLocalState(func(ctx context.Context) *state {
return &state{Messages: make([]*Message, 0, config.MaxStep+1)}
}))
// 将一个轮次中的上下文和响应,存储到 Graph 的临时状态中
modelPreHandle = func(ctx context.Context, input []*Message, state *state) ([]*Message, error) {
state.Messages = append(state.Messages, input...)
return state.Messages, nil
}
_ = graph.AddChatModelNode(nodeKeyModel, chatModel, WithStatePreHandler(modelPreHandle))
_ = graph.AddEdge(START, nodeKeyModel)
_ = graph.AddToolsNode(nodeKeyTools, toolsNode)
// chatModel 的输出可能是多个 Message 的流
// 这个 StreamGraphBranch 根据流的首个包即可完成判断,降低延迟
modelPostBranch = NewStreamGraphBranch(
func(_ context.Context, sr *schema.StreamReader[*Message]) (endNode string, err error) {
defer sr.Close()
if msg, err := sr.Recv(); err != nil {
return "", err
} else if len(msg.ToolCalls) == 0 {
return END, nil
}
return nodeKeyTools, nil
}, map[string]bool{nodeKeyTools: true, END: true})
_ = graph.AddBranch(nodeKeyModel, modelPostBranch)
// toolsNode 执行结果反馈给 chatModel
_ = graph.AddEdge(nodeKeyTools, nodeKeyModel)
// 编译 Graph:类型检查、callback 注入、自动流式转换、生成执行器
agent, _ := graph.Compile(ctx, WithMaxRunSteps(config.MaxStep))
```
在上面这几十行代码的背后,Eino 自动做了一些事情:
- 类型检查,在 compile 时确保相邻的节点的类型对齐。
- 流式封装,编译出的 Runnable 既可以 Invoke 调用,也可以 Stream 调用,无论内部的 Tool 是否支持流。
- 并发管理,对 state 这个公共状态的读写是并发安全的。
- 横切面注入,如果某个组件(比如一个 tool)没有实现 callbacks 注入,则 Eino 自动注入。
- Option 分配,编译出的 Runnable 可以灵活接收并把 option 分配给指定的节点。
## Eino 的独特优势
基于大语言模型的软件应用正处于快速发展阶段,新技术、新思路、新实践不断涌现,我们作为应用开发者,一方面需要高效、可靠的把业界共识的最佳实践应用起来,另一方面需要不断学习和提升认知,从而能够整体理解这个新领域的可能性。因此,一个优秀的大模型应用开发框架,既需要**封装领域内“不变”的通用核心要素**,又需要基于最新进展**敏捷的横向和纵向扩展**。
另一方面,目前较为主流的框架如 LangChain,LlamaIndex 等,都基于 Python,虽然能借助 Python 较为丰富的生态快速实现多样的功能,但是同时也继承了 Python 作为动态语言所带来的“弱类型检验”和“长期维护成本高”等问题。在大模型应用快速进入大规模线上运行阶段的当下,基于 Golang 这一强类型语言而实现的**高可靠性**和**高可维护性**,逐渐具有更大的价值。
基于大模型的应用开发是相对较新的领域,有时需要摸着石头过河,靠实践来检验认知。依托字节跳动高频应用豆包、抖音等的多样场景、快速迭代和海量反馈,Eino 在**实践驱动设计**方面有独特的优势。
最后,生产级的框架需要面对真实、复杂的业务场景,因此,除了直观易用的 API 设计之外,提供有针对性设计的开发**工具**可以有效的帮助开发者理解和应对复杂性、加速开发过程。
### 内核稳定
我们认为,存在一个常见的组件列表,共同构成了大模型应用的常见组成部分。每类组件作为一个 interface,有完善、稳定的定义:具体的输入输出类型,明确的运行时 option,以及明确的流处理范式。
在明确的组件定义基础之上,我们认为,大模型应用开发存在通用的基座性质的能力,包括但不限于:处理模型输出的流式编程能力;支持横切面功能以及透出组件内部状态的 Callback 能力;组件具体实现超出组件 interface 定义范围的 option 扩展能力。
在组件定义和通用基座能力的基础上,我们认为,大模型应用开发存在相对固定的数据流转和流程编排范式:以 ChatModel(大模型)为核心,通过 ChatTemplate 注入用户输入和系统 prompt,通过 Retriever、Document Loader & Transformer 等注入上下文,经过 ChatModel 生成,输出 Tool Call 并执行,或输出最终结果。基于此,Eino 提供了上述组件的不同编排范式:Chain,链式有向无环图;Graph,有向图或有向无环图;Workflow,有字段映射能力的有向无环图。
上述设计和功能共同构成了 Eino 的稳定内核:
<a href="/img/eino/eino_features_and_design.png" target="_blank"><img src="/img/eino/eino_features_and_design.png" width="100%" /></a>
### 敏捷扩展
每类组件都可以横向扩展出不同的实现,比如 ChatModel 组件可以有 OpenAI、Gemini、Claude 等不同的实现等。这些具体的实现,在实现组件 interface 从而可作为组件参与编排的基础上,可以实现和持续扩展自身的特殊功能。
当实际业务场景中,出现需要进入编排但是不对应任何组件定义的功能时,Eino 支持将自定义 function 声明为 Lambda 类型。Lambda 有用户声明的输入输出以及 option 类型,可支持全部的流处理范式,具备完整的 Callback 能力,在编排视角等价于官方组件。
在大模型应用开发领域,存在并且持续会涌现多个组件的特定编排范式,这些范式封装了验证有效的研究成果或实践经验,比如 ReAct Agent,Host Multi-Agent 等。这些开箱即用的封装,浓缩了大模型应用开发领域的最佳实践,会随着我们认知的提升持续纵向扩展。
在组件和图执行过程中,开发者可以在固定的时机嵌入自定义的回调逻辑,用于注入横切面功能。
综上所述,Eino 框架具备充分的可扩展性:
<a href="/img/eino/eino_modules_types.png" target="_blank"><img src="/img/eino/eino_modules_types.png" width="100%" /></a>
### 高可靠易维护
基于 Golang 写 Eino 代码时,开发者可以充分利用 Golang 的强类型特性,为所有的组件、Lambda、编排产物等声明具体类型。这像是为代码绘制了一幅精确的地图,开发者可以沿着清晰的路径进行维护和扩展,即使在项目规模不断扩大、功能持续迭代的情况下,依然能够保有较高的可维护性。
同时,Eino 编排能力也充分利用了强类型系统的编译时校验能力,尽可能将类型匹配问题暴露的时机提前到 graph 的编译时,而不是 graph 的运行时。尽早并明确的暴露类型匹配问题,有助于开发者迅速定位和修复,减少因类型错误在运行时引发的难以排查的故障和性能问题。
另一方面,Eino 遵循模块化设计,核心库以及各组件实现是单独的 go module,每个 go module 做到依赖最小化。同时,API 设计以“精简”、"直观"和“同构性”为原则,辅以由浅入深的全面文档,尽可能让学习曲线更平滑。最重要的是,Eino 采用清晰的分层设计,每层职责明确、功能内聚,在提升维护性的同时能更好的保证稳定性。
Eino 框架结构图:
<a href="/img/eino/eino_structure.png" target="_blank"><img src="/img/eino/eino_structure.png" width="100%" /></a>
### 实践驱动
Eino 框架的设计开发过程,扎根于 “满足真实需求” 与 “实践驱动设计” 这两大基石之上。功能的演进过程与字节跳动各业务线的接入过程紧密结合,始终倾听开发者的声音,并通过实际使用效果来检验设计的合理性。比如我们收到来自抖音的“希望能够以字段为粒度在图中映射和传递数据”的需求,以此为基础设计了 Workflow;倾听来自豆包的使用痛点,增强作为模型输入输出类型的 Message 结构体。在未来的开源生态共建过程中,我们会继续坚持上述原则,满足更广大的用户和开发者的真实需求,并在更大的范围内认真实践和精进。
<a href="/img/eino/eino_practice_cognition_loop.png" target="_blank"><img src="/img/eino/eino_practice_cognition_loop.png" width="100%" /></a>
### 工具生态
链路追踪、调试、可视化,是编排引擎的三个重要辅助工具。Eino 内置了 tracing callback,并与 APMPlus 和 Langfuse 平台做了集成。同时提供了 IDE 插件,可以在写代码的过程中随时可视化查看编排出的 graph,并进行调试运行,甚至可以通过 UI 拖拽的方式快速构建 graph 并导出为 Eino 代码。
## 快速上手
针对 Eino 的学习和使用,我们提供了完善的 Eino 用户手册,帮助大家快速理解 Eino 中的概念,掌握基于 Eino 开发设计 AI 应用的技能,赶快通过「[Eino: 快速开始](https://www.cloudwego.io/zh/docs/eino/quick_start/)」尝试使用吧~。
如有任何问题,可通过下方的飞书群或者 [Eino Issues](https://github.com/cloudwego/eino/issues) 和我们沟通、反馈~
## 相关链接
项目地址:[https://github.com/cloudwego/eino](https://github.com/cloudwego/eino),[https://github.com/cloudwego/eino-ext](https://github.com/cloudwego/eino-ext)
项目官网:__[https://www.cloudwego.io](https://www.cloudwego.io)__
扫描二维码加入飞书社群:
<a href="/img/eino/eino_lark_qr_code.png" target="_blank"><img src="/img/eino/eino_lark_qr_code.png" width="100%" /></a>
+348
View File
@@ -0,0 +1,348 @@
---
Description: ""
date: "2026-03-24"
lastmod: ""
tags: []
title: Agent 还是 Graph?AI 应用路线辨析
weight: 8
---
## 引言:两种并存的 AI 交互范式
许多应用程序的界面都集成了不同形态的 AI 功能,如下图所示:
<a href="/img/eino/eino_ai_app_form.png" target="_blank"><img src="/img/eino/eino_ai_app_form.png" width="100%" /></a>
这张看似简单的截图,代表了“AI 应用”的两种形态:
- 以“聊天框”为代表性标志的“Agent(智能体)”。**Agent 以 LLM(大语言模型)为决策中心,自主规划并能进行多轮交互**,天然适合处理开放式、持续性的任务,表现为一种“对话”形态。
- 以“按钮”或者“API”为代表性标志的“Graph(流程图)”。比如上面的“录音纪要”这个“按钮”,其背后的 Graph 大概是“录音”-》“LLM 理解并总结” -》“保存录音”这种固定流程。**Graph 的核心在于其流程的确定性与任务的封闭性**,通过预定义的节点和边来完成特定目标,表现为一种“功能”形态。举个例子,视频生成是 “API”形态 AI 应用:
<a href="/img/eino/eino_complex_workflow_as_api.png" target="_blank"><img src="/img/eino/eino_complex_workflow_as_api.png" width="100%" /></a>
```mermaid
flowchart TD
linkStyle default stroke-width:2px,stroke:#000000
classDef startend_style fill:#EAE2FE,stroke:#000000,stroke-width:2px,color:#1f2329
classDef process_style fill:#F0F4FC,stroke:#000000,stroke-width:2px,color:#1f2329
classDef decision_style fill:#FEF1CE,stroke:#000000,stroke-width:2px,color:#1f2329
classDef subgraph_style fill:#f5f5f5,stroke:#bbbfc4,stroke-width:1px,color:#000000
S(["AI 应用形态"])
D{"任务特征"}
A("Agent")
G("Graph")
A1("LLM 决策中心")
A2("多轮交互")
G1("预设拓扑结构")
G2("确定性输出")
S --> D
D -->|"开放式或不确定"| A
D -->|"封闭且确定"| G
A --> A1
A --> A2
G --> G1
G --> G2
class S startend_style
class D decision_style
class A,G,A1,A2,G1,G2 process_style
```
本文详细探讨了 Agent 和 Graph 两种 AI 应用形态的区别和联系,提出“两者的最佳结合点,在于将 Graph 封装为 Agent 的 Tool(工具)”,并为 [Eino](https://github.com/cloudwego/eino) 开发者给出建议的使用姿势。
## 核心概念辨析
### 基础定义
- **Graph**: 一个由开发者**预先定义**的、具有明确拓扑结构的流程图。它的节点可以是代码函数、API 调用或 LLM,输入和输出通常是结构化的。**核心特征是“确定性”**,即给定相同输入,其执行路径和最终产出是可预测的。
- **Agent**: 一个以 LLM 为核心,能够**自主规划、决策和执行**任务的实体。它通过与环境(Tool、用户、其他 Agent)的**动态交互**来完成目标,其行为具有不确定性。**核心特征是“自主性”**。
- **Tool**: Agent 可以调用的任何外部能力,通常是一个**封装了特定功能的函数或 API**。Tool 本身可以是同步或异步的、有状态或无状态的。它只负责执行,不具备自主决策能力。
- **编排**: **组织和协调多个计算单元(节点、Agent)协同工作**的过程。在本文中,特指通过 Graph 的方式来预定义静态流程。
### 深度对比
<table>
<tr><td>特征维度</td><td>Agent</td><td>Graph</td></tr>
<tr><td><strong>核心驱动力</strong></td><td><strong>LLM 自主决策</strong></td><td><strong>开发者预设流程</strong></td></tr>
<tr><td><strong>输入</strong></td><td><strong>非结构化</strong>的自然语言、图像等</td><td><strong>结构化</strong>的数据</td></tr>
<tr><td><strong>交付物</strong></td><td><strong>过程与结果并重</strong></td><td><strong>聚焦最终结果</strong></td></tr>
<tr><td><strong>状态管理</strong></td><td><strong>长时程、跨执行</strong></td><td><strong>单次执行、stateless</strong></td></tr>
<tr><td><strong>运行模式</strong></td><td>偏向<strong>异步</strong></td><td>偏向<strong>同步</strong></td></tr>
</table>
总结:Agent 可认为是自主的,整体由 LLM 驱动,以 Tool Call 的形式使用外部能力。Graph 是确定性的,以明确拓扑结构串联外部能力,同时在局部利用 LLM 做决策/生成等。
```mermaid
flowchart TD
subgraph AIApp["AI 应用"]
Agent["Agent (自主性)"]
Graph["Graph (确定性)"]
end
subgraph CoreDrive["智能来源"]
LLM["LLM (决策/生成)"]
end
subgraph ExternalCap["外部能力"]
Tool["External Capacity<br>(函数/API)"]
end
Agent -- "驱动力来源" --> LLM
Graph -- "包含节点" --> LLM
Agent -- "工具调用" --> Tool
Graph -- "包含节点" --> Tool
classDef agent fill:#EAE2FE,stroke:#000000
classDef graphClass fill:#F0F4FC,stroke:#000000
classDef llm fill:#FEF1CE,stroke:#000000
classDef tool fill:#DFF5E5,stroke:#000000
class Agent agent
class Graph graphClass
class LLM llm
class Tool tool
```
## 历史视角:从确定性走向自主性
当 Langchain 框架在 2022 年首次发布时,LLM 世界的 API 范式还是 OpenAI 的 [Completions API](https://platform.openai.com/docs/guides/completions),一个简单的“文本进,文本出”的 API。发布之初,Langchain 的口号是“[connect LLMs to external sources of computation and data](https://blog.langchain.com/langchain-second-birthday/)”。典型的“Chain”可能是这样的:
```mermaid
flowchart LR
S[retrievers, loaders, <br>prompt templates, etc...]
L[LLM]
P[output parsers, other handlers, etc...]
S-->L-->P
```
随后,[ReAct](https://react-lm.github.io/)(Reasoning and Acting)范式的提出,首次系统性地展示了如何让 LLM 不仅生成文本,更能通过“思考-行动-观察”的循环来与外部交互,解决复杂问题。这一突破为 Agent 的自主规划能力奠定了理论基础。近乎同时,OpenAI 推出了 [ChatCompletions API](https://platform.openai.com/docs/api-reference/chat),推动了 LLM 交互能力从“单次的文本输入输出”向“多轮对话”转变。之后 [Function Calling](https://platform.openai.com/docs/guides/function-calling)(函数调用) 能力出现,LLM 具备了标准的与外部函数和 API 交互的能力。至此,我们已经可以搭建出“多轮对话并与可以与外界自主交互”的 LLM 应用场景,即 Agent。在这个背景下,AI 应用框架产生了两个重要发展:
- Langchain 推出了 Langgraph:静态编排由简单的输入输出 Chain 向复杂拓扑结构转变。这类编排框架非常契合“Graph”类型的 AI 应用形态:“任意”的结构化输入,以“最终结果”为核心交付物,将消息历史等状态管理机制与核心编排逻辑解耦,可支持各种拓扑结构的灵活编排能力,以及以 LLM、知识库为代表的各种节点/组件。
- Agent 及 Multi-Agent 框架大量出现:比如 AutoGen,CrewAI,Google ADK 等。这些 Agent 框架的共同点,是尝试解决“LLM 驱动流程”、“上下文传递”、“记忆管理”以及“Multi-Agent 通用模式”等问题,与编排类框架尝试解决的“在复杂流程中连接 LLM 与外部系统”问题并不相同。
即便定位不同,使用编排框架也可以实现 ReAct Agent 或者其他 Multi-Agent 模式,因为“Agent”是“LLM 与外部系统交互”的一种特殊形式,而“LLM 驱动流程”可以通过“静态分支穷举”等方式来实现。然而,这种实现方式本质上是一种“模拟”,就像用 Word 写代码,可以写,但不匹配。编排框架的设计初衷是管理确定性的 Graph,而 Agent 的核心是响应动态变化的‘思考链’。将后者强行适配于前者,必然会在交付物、运行模式等方面产生“错位”。例如,在实际使用中,可能会发现一些痛点:
- 交付物的不匹配:编排出的 ReAct Agent 的输出是“最终结果”,而实际应用往往关注各种中间过程。用 Callback 等方案可以解决,足够完备,但依然属于“补丁”。
```mermaid
flowchart LR
A[ReAct Agent]
P@{ shape: processes, label: "全过程数据" }
A--o|关注|P
G[Graph]
F[最终结果]
G-->|主流程输出,<br>但被旁路输出涵盖|F
G-.->|旁路抽取|P
```
- 运行模式的不匹配:由于是同步运行,所以“为了尽快把 LLM 的回复展示给用户”,要求 ReAct Agent 编排内的各节点都尽量“快”,这主要是“在判断 LLM 的输出是否包含 ToolCall”的分支判断逻辑中,要尽可能根据第一个包或者前几个包完成判断。这个分支判断逻辑可以自定义,比如“读流式输出直到看到 Content,才判断为没有 ToolCall”,但有时并不能完全解决问题,只能通过 Callback 这样的“旁路”手动切换“同步”为“异步”。
```mermaid
flowchart LR
L[LLM 节点]
S@{ shape: processes, label: "流式内容"}
L-->|生成|S
B{是否包含<br>工具调用}
D@{ shape: processes, label: "流式内容"}
B-->|否,上屏展示|D
S-->|逐帧判断|B
```
这些痛点源于两者本质的差异。一个为确定性流程(Graph)设计的框架,很难原生支持一个以动态“思考链”为核心的自主系统(Agent)。
## 融合路径探索:Agent 与 Graph 的关系
Eino 框架的目标是同时支持 Graph 和 Agent 两种场景。我们的演进路径是从 Graph 和编排框架(eino-compose)做起,并在编排框架之外引入了相对独立的 Agent 能力(eino-adk)。这看上去会有些不必要的割裂,似乎“作为编排框架的 Eino”和“作为 Agent 框架的 Eino”是相互独立的,开发经验无法共享。现状确实如此,长期来看“相对独立”的状态会一直持续,但同时也会有局部的深度融合。
下面我们从下列三个角度分析“Agent”和“Graph”两个形态在 Eino 框架中的具体关系:
- Multi-Agent 的编排
- Agent 作为节点
- Graph 作为 Tool
### Multi-Agent 与编排
虽然“Agent”和“Graph”两个形态有本质的差异,那是否存在一些场景,属于两个形态的“交叉融合”,没法非黑即白的做选择呢?一个典型的场景是 Multi-Agent,即多个 Agent 以“某种方式”进行交互,对用户呈现的效果是一个完整的 Agent。这里的“某种交互方式”,可以理解为“Graph 编排”吗?
下面我们依次观察几种主流的协作模式:
- 层级调用(Agent as Tool):这是最常见的模式(参考 Google ADK 的[定义](https://google.github.io/adk-docs/agents/multi-agents/#c-explicit-invocation-agenttool)和[举例](https://google.github.io/adk-docs/agents/multi-agents/#hierarchical-task-decomposition))。一个上层 Agent 将特定子任务委托给专门的“Tool Agent”。例如,一个主 Agent 负责与用户交互,当需要执行代码时,它会调用一个“代码执行 Agent”。在这种模式下,子 Agent 通常是无状态的,不与主 Agent 共享记忆,其交互是一个简单的 Function Call。上层 Agent 和子 Agent 只有一种关系:调用与被调用。因此,我们可以得出,Agent as Tool 的 Multi-Agent 模式,不是“Graph 编排”中的“节点流转”关系。
```mermaid
flowchart LR
subgraph 主 Agent
L[主 Agent 的 LLM]
T1[子 Agent 1]
T2[子 Agent 2]
L-->|工具调用|T1
L-->|工具调用|T2
end
```
- 预设流程:对于一些成熟的协作模式,如“规划-执行-反思”(Plan-Execute-Replan)(参考 Langchain 的[样例](https://langchain-ai.github.io/langgraph/tutorials/plan-and-execute/plan-and-execute/)),Agent 间的交互顺序和角色是固定的。框架(如 Eino adk)可以将这些模式封装为“预制 Multi-Agent 模式”,开发者可以直接使用,无需关心内部的细节,也不需要手动设置或调整子 Agent 之间的流程关系。因此,我们可以得出,针对成熟的协作模式,“Graph 编排”是封装在预制模式内部的实现细节,开发者不感知。
```mermaid
flowchart LR
subgraph Plan-Execute-Replan
P[planner]
E[executor]
R[Replanner]
P-->E
E-->R
R-->E
end
user -->|整体使用| Plan-Execute-Replan
```
- 动态协作:在更复杂的场景中,Agent 的协作方式是动态的(参考 Google ADK 的[定义](https://google.github.io/adk-docs/agents/multi-agents/#b-llm-driven-delegation-agent-transfer)和[举例](https://google.github.io/adk-docs/agents/multi-agents/#coordinatordispatcher-pattern)),可能涉及竞价、投票或由一个“协调者 Agent”在运行时决定。这种模式下,Agent 之间的关系是“Agent 流转”,与“Graph 编排”中的“节点流转”有相似之处,都是“控制权”由 A 到 B 的完全转交。但是,这里的“Agent 流转”可以是完全动态的,其动态特性不仅体现在“可以流转到哪些 Agent”,更体现在“如何做出流转到哪个 Agent 的决策”上,都不是由开发者预设的,而是 LLM 的实时动态行为。这与“Graph 编排”的静态确定性形成了鲜明的对比。因此,我们可以得出,动态协作的 Multi-Agent 模式,从本质上与“Graph 编排”完全不同,更适合在 Agent 框架层面给出独立的解决方案。
```mermaid
flowchart LR
A[Agent 1]
B[Agent 2]
C[Agent 3]
A-.->|动态转交|B-.->|动态转交|C
```
综上所述,Multi-Agent 的协作问题,或可通过“Agent as Tool”模式降维解决,或可由框架提供固化模式,或是本质上完全动态的协作,其对“编排”的需求与 Graph 的静态的、确定性的流程编排有着本质区别。
### Agent 作为 Graph 的节点
在探讨完“Multi-Agent 与 Graph 编排的关系”后,我们可以从另一个角度提出问题:在 Graph 编排中是否需要使用 Agent?换句话说,Agent 是否可以作为一个“节点”进入到一个 Graph 中?
我们先回忆下 Agent 和 Graph 各自的特点:
- Agent 的输入来源更为多样,除了能接收来自上游节点的结构化数据外,还严重依赖于自身的会话历史(Memory)。这与 Graph 节点严格依赖其上游输出作为唯一输入的特性形成了鲜明对比。
- Agent 的输出是异步的全过程数据。这意味着其他节点很难使用“Agent 节点”的输出。
```mermaid
flowchart LR
U[前置节点]
A[Agent 节点]
D[后置节点]
M[Memory]
U-->|不是全部输入<br>|A
M-.->|外部状态注入|A
A-->|全过程数据<br>面向用户或 LLM<br>|D
```
因此,向 Graph 中加入 Agent 节点,意味着将一个需要多轮交互、长时记忆和异步输出的 Agent 强行嵌入到一个确定性的、同步执行的 Graph 节点中,这通常是不优雅的。Agent 的启动可以被 Graph 编排,但其内部的复杂交互不应阻塞主流程。
实际上,在 Graph 中我们需要的并非一个完整的 Agent 节点,而是一个功能更纯粹的**“LLM 节点”**。该节点负责在确定性流程中,接收特定输入,完成意图识别或内容生成,并产出结构化的输出,从而为流程注入智能。
同时,如果简单的“LLM”节点确实不满足需求,确实需要“Agent”,更合适的做法也许不是把 Agent 塞到静态预定义的 Graph 中,而是给“Agent”增加前置处理、后置处理等各种“插件”,把具体的业务逻辑嵌入到 Agent 内部。
综上所述:将 Agent 简单视为 Graph 的一个节点是**低效**的,更好的方式是使用 LLM 节点,或将业务逻辑作为插件注入 Agent。
### 融合之道:将 Graph 封装为 Agent 的 Tool
既然 Agent 和 Graph 在微观层面(节点)的直接融合存在困难,那么它们是否在宏观层面有更优雅的结合方式呢?答案是肯定的,这座桥梁就是“Tool”。如果观察 Graph 和 Tool 的含义,能发现很多相似之处:
<table>
<tr><td>特征维度</td><td>Graph</td><td>Tool</td></tr>
<tr><td>输入</td><td><strong>结构化的数据</strong></td><td><strong>结构化的数据</strong></td></tr>
<tr><td>交付物</td><td><strong>聚焦最终结果</strong></td><td><strong>聚焦最终结果</strong></td></tr>
<tr><td>状态管理</td><td><strong>单次执行、stateless</strong></td><td><strong>单次执行、stateless</strong></td></tr>
<tr><td>运行模式</td><td><strong>整体是同步</strong></td><td> <strong>LLM 的视角 Tool 是同步的</strong></td></tr>
</table>
这些相似之处,意味着“Graph 在表现形式上,与 Tool 的要求非常匹配,因此将 Graph 封装成 Tool 是直观、简单的”。因此,绝大多数 Graph 都适合通过 Tool 机制加入到 Agent 中,成为 Agent 能力的一部分。这样一来,Agent 可以明确的使用 Graph 的大部分能力,包括对“任意”业务拓扑的高效编排,对大量相关组件的生态集成,以及配套的框架和治理能力(流处理、callback、中断恢复等)。
“Agent”与“Graph”的“路线之争”,实现了对立统一。
```mermaid
flowchart TD
subgraph Agent ["Agent"]
A["LLM 决策"] --> B{"调用工具?"}
B -- "是" --> C["Tool: my_graph_tool"]
end
subgraph Tool ["Tool"]
C -- "封装" --> D["Graph: my_graph"]
end
subgraph Graph ["Graph"]
D -- "执行" --> E["节点1"]
E --> F["节点2"]
F --> G["返回结果"]
end
G -- "输出" --> C
C -- "结果" --> A
classDef agent fill:#EAE2FE,stroke:#000000
classDef tool fill:#DFF5E5,stroke:#000000
classDef graphGroup fill:#F0F4FC,stroke:#000000
class A,B agent
class C tool
class D,E,F,G graphGroup
```
Graph-Tool-Agent 关系图
## 结论
Agent 与 Graph 并非路线之争,而是能力互补的两种 AI 应用范式。
- Graph 是构建可靠、确定性 AI 功能的基石。 它擅长将复杂的业务逻辑、数据处理管道和 API 调用编排成可预测、可维护的工作流。当你需要一个“功能按钮”或一个稳定的后端服务时,Graph 是不二之选。
- Agent 是实现通用智能与自主探索的未来。 它以 LLM 为核心,通过动态规划和 Tool 来解决开放式问题。当你需要一个能与人对话、能自主完成复杂任务的“智能助理”时,Agent 是核心方向。
两者的最佳结合点,在于将 Graph 封装为 Agent 的 Tool。
通过这种方式,我们可以充分利用 Graph 在流程编排和生态集成上的强大能力,来扩展 Agent 的 Tool 列表。一个复杂的 Graph 应用(如一套完整的 RAG 流程、一个数据分析管道)可以被简化成 Agent 的一个原子能力,被其在合适的时机动态调用。
对于 Eino 的开发者而言,这意味着:
- 用 eino-compose 编写你的 Graph,将确定性的业务逻辑封装成“功能模块”。
- 用 eino-adk 构建你的 Agent,赋予它思考、规划和与用户交互的能力。
- 将前者作为后者的 Tools,最终实现“1+1 > 2”的效果。
代码示意:
```go
// NewInvokableGraphTool converts ANY Graph to the `InvokableTool` interface.
func NewInvokableGraphTool[I, O any](graph compose.Graph[I, O],
name, desc string,
opts ...compose.GraphCompileOption,
) (*InvokableGraphTool[I, O], error) {
tInfo, err := utils.GoStruct2ToolInfo[I](name, desc)
if err != nil {
return nil, err
}
return &InvokableGraphTool[I, O]{
graph: graph,
compileOptions: opts,
tInfo: tInfo,
}, nil
}
func (g *InvokableGraphTool[I, O]) InvokableRun(ctx context.Context, input string,
opts ...tool.Option) (output string, err error) {
// trigger callbacks where needed
// compile the graph
// convert input string to I
// run the graph
// handle interrupt
// convert output O to string
}
func (g *InvokableGraphTool[I, O]) Info(_ context.Context) (*schema.ToolInfo, error) {
return g.tInfo, nil
}
```
[eino-example 项目链接](https://github.com/cloudwego/eino-examples/tree/main/adk/common/tool/graphtool)