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 应用场景。

相关推荐
Luca_kill6 小时前
深度解析 DeerFlow:字节跳动开源的长时程 AI 超级智能体架构
人工智能·智能体·大模型应用·ai架构·deerflow
xixixi777779 小时前
英伟达Agent专用全模态模型出击,仿冒AI智能体泛滥成灾,《AI伦理安全指引》即将落地——AI治理迎来“技术-风险-规范”三重奏
人工智能·5g·安全·ai·大模型·英伟达·智能体
码点滴12 小时前
私有 Gateway 接入企业 IM:从消息路由到多租户隔离——Hermes Agent 工程实战
人工智能·架构·gateway·prompt·智能体·hermes
lolo大魔王12 小时前
Go语言的并发、协调创建和通信机制
开发语言·golang
geovindu13 小时前
go:Template Method Pattern
开发语言·后端·设计模式·golang·模板方法模式
怪我冷i13 小时前
多租户管理系统,用户表,IsSuperAdmin,IsTenantAdmin,IsCompanyAdmin,IsDeptAdmin需要吗?
golang·llm·多租户·skill
开心码农1号15 小时前
Go 语言深度剖析:指针、unsafe.Pointer 与 uintptr 底层原理、区别与实战避坑
开发语言·后端·golang
初心未改HD15 小时前
Go语言Error处理与errors包深度解析
开发语言·golang
济61716 小时前
Ai智能体专栏---从零搭建完全本地、无依赖、可离线的个人知识库---Ollama+RAGFlow 保姆级教程
人工智能·ai·智能体