Eino-Graph 实战详解

Eino-Graph 实战详解

概述

本文详细讲解 Eino 框架中 Graph 的概念、初始化、编排和编译过程。Graph 是 Eino 框架中用于构建复杂 AI 流程编排的核心组件,支持有向无环图(DAG)结构的节点编排,源码链接

一、Eino 框架中 Graph 的概念

1.1 什么是 Graph

Graph(有向无环图)是 Eino 框架中用于编排 AI 流程的核心数据结构。与 Chain 的线性结构不同,Graph 支持更复杂的分支、循环和条件跳转逻辑,可以构建多阶段、多分支的复杂 AI 应用。

1.2 Graph vs Chain vs Workflow

特性 Chain Workflow Graph
结构 线性 分支/条件 DAG
节点连接 顺序执行 可条件分支 任意连接
适用场景 简单流水线 中等复杂流程 复杂AI应用
灵活性

1.3 Graph 的核心组成

go 复制代码
// Graph 的类型定义
graph := compose.NewGraph[map[string]any, *schema.Message]()
  • 泛型参数[map[string]any, *schema.Message] 表示输入是 map,输出是 schema.Message
  • 节点类型:ChatTemplateNode、ChatModelNode、ToolsNode、LambdaNode 等
  • 边(Edge):连接节点的边,定义数据流向

二、openai.NewChatModel 方法使用

2.1 引入依赖

go 复制代码
import (
    "github.com/cloudwego/eino-ext/components/model/openai"
)

2.2 配置参数

go 复制代码
chatModel, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{
    APIKey:  cfg.Model.APIKey,    // API密钥
    Model:   cfg.Model.ModelName,  // 模型名称
    BaseURL: cfg.Model.BaseURL,    // API基础地址
})

2.3 工具绑定

大模型可以通过 BindTools 方法绑定工具,使其具备调用外部函数的能力:

go 复制代码
info, err := playerInfoTool.Info(ctx)
if err != nil {
    return fmt.Errorf("获取工具信息失败: %w", err)
}
if err := chatModel.BindTools([]*schema.ToolInfo{info}); err != nil {
    return fmt.Errorf("绑定工具失败: %w", err)
}

三、如何初始化 Graph

3.1 创建 Graph 实例

go 复制代码
func initGraph(cfg *Config) error {
    ctx := context.Background()
    g := compose.NewGraph[map[string]any, *schema.Message]()
    // ...
}

3.2 定义提示词模板

go 复制代码
systemTpl := `你是一名篮球教练与比赛分析师。你需要结合用户的基本信息与训练习惯,
使用 player_info API,为其补全信息,然后给出适合他的训练计划、位置建议与一套简单战术建议。`

chatTpl := prompt.FromMessages(schema.FString,
    schema.SystemMessage(systemTpl),
    schema.MessagesPlaceholder("histories", true),
    schema.UserMessage("{user_query}"),
)

提示词模板说明

  • schema.SystemMessage() - 系统消息,设置 AI 角色
  • schema.MessagesPlaceholder("histories", true) - 历史消息占位符
  • schema.UserMessage("{user_query}") - 用户消息模板,{user_query} 会动态替换

3.3 创建工具节点

go 复制代码
playerInfoTool := utils.NewTool(
    &schema.ToolInfo{
        Name: "player_info",
        Desc: "根据用户的姓名和邮箱,查询用户的篮球相关信息",
        ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
            "name":  {Type: "string", Desc: "用户的姓名"},
            "email": {Type: "string", Desc: "用户的邮箱"},
        }),
    },
    func(ctx context.Context, input *playerInfoRequest) (*playerInfoResponse, error) {
        return &playerInfoResponse{
            Name:        input.Name,
            Email:       input.Email,
            Role:        "锋线",
            HeightCM:    182,
            WeightKG:    78,
            PlayStyle:   "偏投射+无球空切,偶尔持球突破",
            WeeklyHours: 4,
        }, nil
    },
)

toolsNode, err := compose.NewToolNode(ctx, &compose.ToolsNodeConfig{
    Tools: []tool.BaseTool{playerInfoTool},
})

3.4 创建 Lambda 节点

Lambda 节点用于数据转换,实现自定义的数据处理逻辑:

go 复制代码
// 从 toolsNode 输出中提取工具结果,转为普通 user 文本
extractToolOps := func(
    ctx context.Context,
    input *schema.StreamReader[[]*schema.Message],
) (*schema.StreamReader[*schema.Message], error) {
    return schema.StreamReaderWithConvert(input, func(msgs []*schema.Message) (*schema.Message, error) {
        if len(msgs) == 0 {
            return nil, errors.New("no messages from tools node")
        }

        type msgLite struct {
            Role    string `json:"role,omitempty"`
            Content string `json:"content,omitempty"`
        }
        lites := make([]msgLite, 0, len(msgs))
        for _, m := range msgs {
            if m == nil {
                continue
            }
            lites = append(lites, msgLite{
                Role:    string(m.Role),
                Content: m.Content,
            })
        }
        b, _ := json.MarshalIndent(lites, "", "  ")

        text := "工具执行完成,返回信息如下(结构化摘要):\n" + string(b)
        return schema.UserMessage(text), nil
    }), nil
}
extractToolLambda := compose.TransformableLambda[[]*schema.Message, *schema.Message](extractToolOps)

四、如何编排 Graph

4.1 节点类型

Eino Graph 支持以下几种核心节点类型:

节点类型 方法 说明
ChatTemplateNode AddChatTemplateNode 提示词模板节点
ChatModelNode AddChatModelNode 大模型节点
ToolsNode AddToolsNode 工具节点
LambdaNode AddLambdaNode 自定义转换节点

4.2 添加节点

go 复制代码
const (
    promptNodeKey        = "prompt"
    chatNodeKey          = "chat"
    toolsNodeKey         = "tools"
    extractNodeKey       = "extract_tool_result"
    lambdaPromptNodeKey  = "build_recommend_prompt"
    recommendChatNodeKey = "chat_recommend"
)

_ = g.AddChatTemplateNode(promptNodeKey, chatTpl)
_ = g.AddChatModelNode(chatNodeKey, chatModel)
_ = g.AddToolsNode(toolsNodeKey, toolsNode)
_ = g.AddLambdaNode(extractNodeKey, extractToolLambda)
_ = g.AddLambdaNode(lambdaPromptNodeKey, buildPromptLambda)
_ = g.AddChatModelNode(recommendChatNodeKey, chatModel)

4.3 添加边(连接)

go 复制代码
_ = g.AddEdge(compose.START, promptNodeKey)          // START -> prompt
_ = g.AddEdge(promptNodeKey, chatNodeKey)           // prompt -> chat
_ = g.AddEdge(chatNodeKey, toolsNodeKey)            // chat -> tools
_ = g.AddEdge(toolsNodeKey, extractNodeKey)        // tools -> extract
_ = g.AddEdge(extractNodeKey, lambdaPromptNodeKey)  // extract -> build_prompt
_ = g.AddEdge(lambdaPromptNodeKey, recommendChatNodeKey)  // build_prompt -> chat_recommend
_ = g.AddEdge(recommendChatNodeKey, compose.END)    // chat_recommend -> END

4.4 Graph 编排流程图

复制代码
START
  │
  ▼
┌─────────────────┐
│   promptNode    │  (ChatTemplate: 篮球教练系统提示词)
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│   chatNode      │  (ChatModel: 第一次模型调用,决定是否调用工具)
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│   toolsNode      │  (ToolsNode: player_info 工具)
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  extractNode    │  (Lambda: 提取工具结果,转为用户消息)
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ buildPromptNode │  (Lambda: 构造第二次模型输入,添加推荐模板)
└────────┬────────┘
         │
         ▼
┌──────────────────────┐
│  recommendChatNode   │  (ChatModel: 第二次模型调用,生成最终推荐)
└────────┬─────────────┘
         │
         ▼
        END

五、如何编译 Graph

5.1 编译方法

go 复制代码
func compileGraph() error {
    ctx := context.Background()
    r, err := graph.Compile(ctx)
    if err != nil {
        return fmt.Errorf("编译 Graph 失败: %w", err)
    }
    runnable = r
    log.Printf("Graph 编译成功")
    return nil
}

5.2 执行 Graph

编译后的 Graph 是一个 Runnable 对象,可以调用 Invoke 方法执行:

go 复制代码
output, err := runnable.Invoke(ctx, map[string]any{
    "histories":  []*schema.Message{},
    "user_query": req.UserQuery,
})

5.3 响应结构

go 复制代码
resp := GraphResponse{
    Content:          output.Content,           // 模型回复内容
    ReasoningContent: output.ReasoningContent, // 思考内容(部分模型支持)
}

if output.ResponseMeta != nil && output.ResponseMeta.Usage != nil {
    resp.PromptTokens = output.ResponseMeta.Usage.PromptTokens      // 输入token数
    resp.OutputTokens = output.ResponseMeta.Usage.CompletionTokens  // 输出token数
    resp.TotalTokens = output.ResponseMeta.Usage.TotalTokens        // 总token数
}

六、完整项目结构

复制代码
lab02/
├── config.yml              # 配置文件
├── graph/
│   ├── graph_chat.go       # 主程序
│   ├── graph_chat          # 编译产物
│   └── docs/               # Swagger文档
│       ├── docs.go
│       ├── swagger.json
│       └── swagger.yaml

配置文件 config.yml

yaml 复制代码
model:
  base_url: "https://api.minimaxi.com/v1"
  api_key: "your-api-key"
  model_name: "MiniMax-M2.7"

app:
  host: "0.0.0.0"
  port: 8080

七、运行项目

启动服务

bash 复制代码
cd lab02/graph
go run graph_chat.go -log graph.log

API 调用

bash 复制代码
curl -X POST http://localhost:8080/graph \
  -H "Content-Type: application/json" \
  -d '{"user_query": "我叫morning,邮箱是lumworn@gmail.com,帮我制定训练计划"}'

访问 Swagger UI

复制代码
http://localhost:8080/swagger/index.html

八、总结

本文通过 graph_chat.go 实战项目,详细讲解了:

  1. Graph 概念:Eino 框架中用于构建复杂 AI 流程编排的核心组件,基于 DAG 结构
  2. openai.NewChatModel:通过配置创建 OpenAI 兼容的 ChatModel,支持工具绑定
  3. 初始化 Graph:创建 Graph 实例、定义模板、创建工具和 Lambda 节点
  4. 编排 Graph :使用 AddNode 方法添加节点,AddEdge 方法连接节点
  5. 编译 Graph :调用 Compile 方法将编排好的 Graph 编译为可执行对象

Graph 相比 Chain 和 Workflow 提供了更灵活的编排能力,适用于需要多分支、复杂数据流转的 AI 应用场景。

相关推荐
Java研究者19 小时前
AI智能体研发 | 什么是OpenAI API协议
人工智能·大模型·openai·api·agent·智能体
何以解忧,唯有..15 天前
Go语言循环语句详解:for、range与循环控制
开发语言·算法·golang
踏着七彩祥云的小丑15 天前
Go学习第9天:并发编程 + 文件操作 + 正则表达式
学习·golang·正则表达式·go
JCGKS15 天前
Go `init` 函数:包初始化顺序到底是怎样的
golang·init·init执行顺序
何以解忧,唯有..15 天前
Go语言中的const:常量声明与iota枚举详解
java·开发语言·golang
星释15 天前
鸿蒙智能体开发实战:2.创建单Agent
harmonyos·智能体
MicrosoftReactor15 天前
技术速递|从一次性提示到标准化工作流:如何在 GitHub Copilot CLI 中使用自定义智能体
github·copilot·cli·智能体
geovindu15 天前
go: Reactor Pattern
开发语言·后端·设计模式·golang·反应器模式
星释15 天前
鸿蒙智能体开发实战:3.创建工作流
华为·harmonyos·智能体
記億揺晃着的那天15 天前
Java 调用外部 Go 程序的实践:ProcessBuilder 在生产环境中的应用
java·golang·processbuilder