【Eino 框架入门】Graph Tool 复杂工作流:把多步骤流水线封装成一个 Tool
普通的 Tool 只能干一件事。但有些任务需要多个步骤协同:读取文件 → 分块 → 评分 → 筛选 → 生成答案。
Graph Tool 就是把这种"流水线"封装成一个 Tool,让 Agent 一行调用就能跑完整流程。
简单 Tool vs Graph Tool
简单 Tool:单步操作,比如读取文件
func read_file(path string) (string, error)
Graph Tool:流水线,多步骤协同
输入:file_path, question
↓
load: 读取文件
↓
chunk: 分块(800 字一块)
↓
score: 并行评分(5 个并发)
↓
filter: 筛选 top-3
↓
answer: 生成答案
↓
输出:answer, sources
compose.Workflow 基础
Workflow 是 Eino 构建工作流的核心:
wf := compose.NewWorkflow[Input, Output]()
// 添加节点
wf.AddLambdaNode("load", loadFunc).AddInput(compose.START)
wf.AddLambdaNode("chunk", chunkFunc).AddInput("load")
wf.AddLambdaNode("score", scoreFunc).AddInput("chunk")
// 连接到结束节点
wf.End().AddInput("score")
核心概念:
compose.START:工作流入口compose.END(通过wf.End()):工作流出口- 节点之间的边:
AddInput定义数据流向
实战:构建 RAG 工作流
目标:从大文档中检索相关内容,生成答案。
1. 定义输入输出
type Input struct {
FilePath string `json:"file_path" jsonschema:"description=文档绝对路径"`
Question string `json:"question" jsonschema:"description=要回答的问题"`
}
type Output struct {
Answer string `json:"answer"`
Sources []string `json:"sources"` // 引用的原文片段
}
2. 构建工作流
func buildWorkflow(cm model.BaseChatModel) *compose.Workflow[Input, Output] {
wf := compose.NewWorkflow[Input, Output]()
// load: 读取文件
wf.AddLambdaNode("load", compose.InvokableLambda(
func(ctx context.Context, in Input) ([]*schema.Document, error) {
data, err := os.ReadFile(in.FilePath)
if err != nil {
return nil, err
}
return []*schema.Document{{Content: string(data)}}, nil
},
)).AddInput(compose.START)
// chunk: 分块
wf.AddLambdaNode("chunk", compose.InvokableLambda(
func(ctx context.Context, docs []*schema.Document) ([]*schema.Document, error) {
var out []*schema.Document
for _, d := range docs {
out = append(out, splitIntoChunks(d.Content, 800)...)
}
return out, nil
},
)).AddInput("load")
// ... score, filter, answer 节点
wf.End().AddInput("answer")
return wf
}
3. 并行评分:BatchNode
评分要对每个 chunk 调用模型,串行太慢。用 BatchNode 并行处理:
scorer := batch.NewBatchNode(&batch.NodeConfig[scoreTask, scoredChunk]{
Name: "ChunkScorer",
InnerTask: scoreWorkflow, // 单个任务的处理逻辑
MaxConcurrency: 5, // 最大并发数
})
// 在工作流中使用
tasks := make([]scoreTask, len(chunks))
for i, c := range chunks {
tasks[i] = scoreTask{Text: c.Content, Question: question}
}
return scorer.Invoke(ctx, tasks) // 并行执行
工作原理:BatchNode 接收任务列表,并行执行每个任务(受 MaxConcurrency 限制),收集所有结果返回。
4. 跨节点传数据:FieldMapping
问题:answer 节点需要 Question,但 Question 在 START,中间隔了好几个节点。
解决方案:用 AddInputWithOptions 直接从 START 取数据:
wf.AddLambdaNode("answer", answerFunc).
AddInputWithOptions("filter", // 从 filter 节点拿 TopK
[]*compose.FieldMapping{compose.ToField("TopK")},
compose.WithNoDirectDependency()).
AddInputWithOptions(compose.START, // 直接从 START 拿 Question
[]*compose.FieldMapping{compose.MapFields("Question", "Question")},
compose.WithNoDirectDependency())
为什么需要 WithNoDirectDependency?
正常情况下,AddInput 会建立依赖关系,决定执行顺序。但这里执行顺序已经由 load → chunk → score → filter → answer 确定了,answer 从 START 拿数据只是为了取值,不需要影响执行顺序。
5. 封装为 Tool
func BuildTool(ctx context.Context, cm model.BaseChatModel) (tool.BaseTool, error) {
wf := buildWorkflow(cm)
return graphtool.NewInvokableGraphTool[Input, Output](
wf,
"answer_from_document",
"Search a large document for relevant content and synthesize an answer.",
)
}
6. 注册到 Agent
ragTool, _ := rag.BuildTool(ctx, cm)
agent, _ := deep.New(ctx, &deep.Config{
// ...
ToolsConfig: adk.ToolsConfig{
ToolsNodeConfig: compose.ToolsNodeConfig{
Tools: []tool.BaseTool{ragTool},
},
},
})
完整流程图
START{FilePath, Question}
│
▼
[load] 读取文件 → []*Document
│
▼
[chunk] 分块 → []*Document
│
├──────────────────────────────────────┐
▼ │
[score] 并行评分 (MaxConcurrency=5) │ Question
│ │
▼ │
[filter] 筛选 top-3 │
│ │
├──────────────────────────────────────┘
▼
[answer] ◄─ Question (from START)
│
▼
END{Answer, Sources}
关键代码总结
// 1. 创建工作流
wf := compose.NewWorkflow[Input, Output]()
// 2. 添加节点,定义边
wf.AddLambdaNode("load", loadFunc).AddInput(compose.START)
wf.AddLambdaNode("chunk", chunkFunc).AddInput("load")
// 3. 跨节点传数据
wf.AddLambdaNode("answer", answerFunc).
AddInputWithOptions("filter", fieldMapping, compose.WithNoDirectDependency()).
AddInputWithOptions(compose.START, fieldMapping, compose.WithNoDirectDependency())
// 4. 结束节点
wf.End().AddInput("answer")
// 5. 封装为 Tool
graphtool.NewInvokableGraphTool[Input, Output](wf, "tool_name", "description")
小结
| 概念 | 作用 |
|---|---|
| compose.Workflow | 构建工作流,定义节点和边 |
| BatchNode | 并行处理多个任务 |
| FieldMapping | 跨节点传递数据 |
| Graph Tool | 把工作流封装成 Agent 可调用的 Tool |
Graph Tool 的本质:把"确定性业务流程"封装成 Tool,支持并行、分支、状态管理,还能和 Interrupt/Resume 配合实现中断恢复。