【Eino 框架入门】Graph Tool 复杂工作流:把多步骤流水线封装成一个 Tool

4 阅读3分钟

【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-3answer: 生成答案
    ↓
输出: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,但 QuestionSTART,中间隔了好几个节点。

解决方案:用 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 确定了,answerSTART 拿数据只是为了取值,不需要影响执行顺序。

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 配合实现中断恢复。