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

相关推荐
咬_咬5 小时前
go语言学习(数组与切片)
开发语言·学习·golang·数组·切片
人工智能培训6 小时前
多模态AI模型融合难?核心问题与解决思路
人工智能·机器学习·prompt·agent·智能体
xwz小王子6 小时前
智元发布 GO-2:动作空间推理 + 全生命周期闭环,让机器人稳定可靠落地
开发语言·golang·机器人
GDAL6 小时前
Go Channel `make()` 深入全面讲解
golang·make·通道
XMYX-08 小时前
06 - Go 的切片、字典与遍历:从原理到实战
后端·golang
正在走向自律9 小时前
AI Agent:从概念到实践,下一代人工智能的核心形态
人工智能·ai agent·智能体
qq_3961534510 小时前
docker ddns-go 忘记密码
docker·容器·golang
XMYX-010 小时前
04 - Go 的变量和常量:零值、类型推导与枚举
开发语言·golang
千桐科技10 小时前
从“找答案”到“解决问题”:qKnow 如何用“预置+迭代”体系打通 AI 落地最后一公里?
大模型·知识图谱·知识库·智能体·智能应用·qknow