第十七篇:Eino Chain线性流程

2026-09-26 46 0

前面我们学习完基础组件:ChatModel、ChatTemplate、ToolsNode、ChatModelAgent。如果业务需要把多个组件按固定先后顺序串联执行,手动写大量变量传递、错误处理会很繁琐。Eino compose包提供Chain(线性链式编排),专门处理无分支、无循环、顺序执行的流水线任务。

📌重要:Chain是Graph的简化特化版本,只支持单向顺序执行;没有条件分支、没有循环。当业务需要分支、判断、回环的时候,就要升级到Graph组件。

💡概念区分

  1. 1. 直接调用组件:业务手写代码,调用A拿到结果,传给B,再调用B;变量、错误全部自己管理。
  2. 2. Chain编排:声明式把多个节点Append追加,框架自动处理数据流转、类型校验、错误透传。

通俗比喻:

  • • 手写调用 = 手工流水线,每一步自己搬运物料;
  • • Chain = 自动化传送带,物料从前一个工位输出,自动送到下一个工位输入,按顺序跑完整条流水线。

Chain输入节点1节点2节点3Chain输出

一、Chain核心原理

compose.NewChain[Input, Output]() 通过泛型声明整条Chain的输入类型与最终输出类型。

  1. 1. 使用AppendXXX()系列方法依次追加节点,节点严格按Append顺序执行;
  2. 2. 前一个节点返回值直接作为下一个节点入参;
  3. 3. 调用Compile(ctx)编译Chain,编译阶段校验上下游节点输入输出类型是否匹配,类型不匹配直接报错,不用等到运行时;
  4. 4. Compile后得到Runnable执行器,调用Invoke()同步执行,或者Stream()流式执行;
  5. 5. 任意节点发生error,整个Chain立刻终止,错误向上返回调用方。

⚠️关键点:每一个Append追加的节点,输出类型必须和下一个节点输入类型对齐,Compile做静态类型检查,Go泛型约束保证安全。

Chain支持追加哪些节点:

追加方法对应组件
AppendChatTemplatePrompt模板组件
AppendChatModel大模型调用组件
AppendToolsNode工具执行节点
AppendLambda自定义Go逻辑(数据转换、清洗、解析)
AppendChain嵌套子Chain
AppendGraph嵌套Graph复杂子流程

二、Chain完整可运行Demo

业务场景:简单文本摘要流水线
原始用户query → Lambda预处理填充参数 → ChatTemplate组装提示词 → ChatModel调用大模型做摘要 → Lambda提取content字符串输出。

package main

import (
    "context"
    "fmt"
    "os"

    eino_openai "github.com/cloudwego/eino-ext/components/model/openai"
    "github.com/cloudwego/eino/components/model"
    "github.com/cloudwego/eino/compose"
    "github.com/cloudwego/eino/prompt"
    "github.com/cloudwego/eino/schema"
)

// ptr 辅助函数,返回指针
func ptr[T any](v T) *T { return &v }

// newChatModel 创建模型实例
func newChatModel(ctx context.Context) (model.BaseChatModel, error) {
    apiKey := os.Getenv("DEEPSEEK_API_KEY")
    if apiKey == "" {
        return nil, fmt.Errorf("请设置环境变量 DEEPSEEK_API_KEY")
    }

    return eino_openai.NewChatModel(ctx, &eino_openai.ChatModelConfig{
        BaseURL:    "https://api.deepseek.com",
        APIKey:     apiKey,
        Model:      "deepseek-v4-flash",
        Temperature: ptr(float32(0.3)),
    })
}

// buildSummaryChain 构建摘要线性Chain
func buildSummaryChain(ctx context.Context) (compose.Runnable[string, string], error) {
    chatModel, err := newChatModel(ctx)
    if err != nil {
        return nil, err
    }

    // 1. 定义Prompt模板,变量 {user_text}
    tpl, err := prompt.FromMessages(schema.FString,
        schema.SystemMessage("你是文本摘要助手,把用户输入的文本精简成2-3句话摘要。"),
        schema.UserMessage("待摘要文本:{user_text}"),
    )
    if err != nil {
        return nil, err
    }

    // 构建Chain:输入string,输出string
    chain := compose.NewChain[string, string]()
    chain.
        // 节点1 Lambda:把输入字符串包装成map,给ChatTemplate使用
        AppendLambda(compose.InvokableLambda(func(ctx context.Context, input string) (map[string]any, error) {
            return map[string]any{
                "user_text": input,
            }, nil
        })).
        // 节点2:模板渲染,map输入,输出[]*schema.Message
        AppendChatTemplate(tpl).
        // 节点3:调用大模型,[]*schema.Message输入,输出*schema.Message
        AppendChatModel(chatModel).
        // 节点4 Lambda:提取message的content,输出string
        AppendLambda(compose.InvokableLambda(func(ctx context.Context, msg *schema.Message) (string, error) {
            return msg.Content, nil
        }))

    // Compile编译,做类型校验
    runnable, err := chain.Compile(ctx)
    if err != nil {
        return nil, fmt.Errorf("chain编译失败: %w", err)
    }
    return runnable, nil
}

func main() {
    ctx := context.Background()
    run, err := buildSummaryChain(ctx)
    if err != nil {
        fmt.Printf("❌ 创建chain失败:%v\n", err)
        return
    }

    userInput := `Eino是CloudWeGo开源的Go原生大模型应用开发框架,提供组件化抽象,
支持ChatModel、Prompt、Tool、编排能力,包含Chain、Graph、Workflow三种编排方式,
可以快速构建Agent、RAG等大模型工程化应用。`

    result, err := run.Invoke(ctx, userInput)
    if err != nil {
        fmt.Printf("❌ chain执行失败:%v\n", err)
        return
    }

    fmt.Println("===== Chain执行结果 =====")
    fmt.Println(result)
}

执行内部流程(框架自动处理)

输入字符串
    ↓ Lambda1
map[string]any{"user_text": "..."}
    ↓ ChatTemplate
[]*schema.Message 组装好的消息数组
    ↓ ChatModel
*schema.Message 模型返回完整消息
    ↓ Lambda2
string 最终摘要文本输出

三、Chain关键知识点

1. Compile编译做了什么

  1. 1. 遍历所有Append节点,校验相邻节点输入输出类型是否兼容,类型不匹配直接返回编译错误;
  2. 2. 构建内部可执行拓扑;
  3. 3. 注册回调、中间件钩子点;

优势:大部分类型错误开发阶段就暴露,而不是线上运行才panic。

2. Invoke同步 / Stream流式两种执行模式

编译得到Runnable对象支持两套接口:

  • • Invoke(ctx, input):同步调用,等待全部执行完毕返回最终输出;
  • • Stream(ctx, input):流式执行,返回*schema.StreamReader[T],适合模型流式输出场景。

示例流式调用片段:

streamR, err := run.Stream(ctx, userInput)
if err != nil {
    return err
}
defer streamR.Close()

for {
    chunk, err := streamR.Read()
    if err != nil {
        break
    }
    fmt.Print(chunk)
}

3. Chain嵌套

Chain节点可以嵌套子Chain,使用AppendChain(subChain)。适合把一段复用流水线封装子链,在多个主流程复用。

subChain := compose.NewChain[map[string]any, *schema.Message]()
// ...子链节点配置

mainChain.AppendChain(subChain)

四、Chain、Graph、Workflow三者选型对比

组件能力适用场景
Chain仅线性顺序执行,无分支循环;Append追加节点;编译强类型校验简单流水线:摘要、翻译、预处理→prompt→模型;步骤固定无判断逻辑
Graph有向图;支持分支、并行、循环回环;AddNode+AddEdge显式连线Agent、ReAct循环、条件路由;需要做判断分支逻辑
WorkflowDAG有向无环图;支持结构体字段级映射;面向业务结构体工程化业务服务,多字段映射,对接数据库、API服务层

开发建议:能Chain就优先Chain;出现分支/循环再升级Graph;业务是结构体驱动的后端业务,选Workflow。

五、新手避坑清单

❌ 前后节点输入输出类型不匹配,Compile直接报错。一定要看每个组件输入输出类型。
❌ Chain不要写条件分支逻辑,分支逻辑交给Graph;Chain只适合纯顺序流水线。
❌ 忘记调用Compile(),直接调用Invoke会报错;Append只是声明流程,Compile才生成执行器。
❌ Lambda函数输入输出类型写错,泛型校验失败。
✅ 调试技巧:给Runnable增加Callback处理器,打印每一个节点的入参出参,观察流水线数据流转。
✅ 复杂逻辑不要硬塞Lambda,优先拆成子Chain或者升级Graph。

六、组件知识串联总结

组件层级回顾:

  1. 1. 基础原子组件:ChatModel、ChatTemplate、ToolsNode、InvokableTool
  2. 2. 编排层:
    • • Chain:线性顺序流水线
    • • Graph:支持分支循环通用图编排
  3. 3. 高层封装ADK:ChatModelAgent,内部基于Graph实现ReAct循环。

执行链路示例(Chain串联原子组件):

业务输入 → Lambda(数据转换) → ChatTemplate → ChatModel → Lambda(结果解析) → 输出

Chain本质:把手写的“调用A拿结果传给B”的业务代码,改成声明式编排,框架处理流转、类型、错误、回调。

相关文章

第十六篇:Eino入门——核心组件总览与场景选型总结
第十五篇:Eino ChatModelAgent 模型自己决定是否调用工具
第十四篇:Eino ToolsNode 工具调用执行器
第十三篇:InvokableTool-把Go函数包装成模型可调用的工具
第十二篇:Eino ToolInfo工具-给模型看的说明书
第十一篇:Eino Message与Prompt上下文编排

发布评论