Eino 入门教程:用 Go 构建你的第一个 Chain(Prompt 模板 + 大模型节点串联实战)


引言:从「调 SDK」到「编排」

第 5 篇《Go AI 框架选型横评》的结论是:要建复杂 LLM 流水线就选 Eino。本篇进入实操——不聊 Agent、不聊图编排,先把 Eino 里使用频率最高的 Chain 吃透。

回顾第 1 篇《大模型 API 基础》,我们直接调 OpenAI SDK 完成了「一次调用」。但真实应用几乎从不只调一次:通常是「拼提示词 → 调模型 → 处理输出 → 再调模型」,或者「检索 → 拼提示词 → 调模型」。把这些步骤的胶水代码写在业务函数里,很快会变成一坨难测试、难复用的面条代码。

Eino 的 Chain 就是解决这个问题的最小抽象:把「一段固定顺序的步骤」声明出来,交给框架去执行。它本质上是链式语法糖,底下就是一张图(Graph),两个节点一条边,所以学会 Chain 也就等于学会了 Eino 图编排的一半。

一、Chain 的三个核心概念

概念 说明 对应 API
Chain 有类型的步骤序列,输入输出类型在创建时用泛型定死 compose.NewChain[I, O]()
节点 链上的每一步:ChatTemplate、ChatModel、Lambda 等 AppendChatTemplate / AppendChatModel / AppendLambda
Runnable Compile() 之后的可执行体,可 Invoke、Stream、复用 chain.Compile(ctx)

两个关键设计决定了它好用:

  1. 泛型定类型:NewChain[map[string]any, *schema.Message] 明确写出「输入一个 map,输出一条消息」,编译期就能发现类型不匹配,而不是运行到一半才 panic。
  2. 声明式串联:AppendA().AppendB().AppendC() 链式写法天然表达「先 A 后 B 再 C」,数据流就是代码的阅读顺序。

二、环境准备

go mod init mychain
go get 'github.com/cloudwego/eino@v0.9.23'
go get 'github.com/cloudwego/eino-ext/components/model/openai@v0.1.13'

两条 go get 都带明确版本号。Eino 主模块与 eino-ext 的组件(model、prompt、retriever 等)分开发版,安装时两边都要指定版本。

API Key 照旧放进环境变量 OPENAI_API_KEY;国内读者把 BaseURL 指向兼容 OpenAI 协议的端点即可,代码不用改。

三、第一个 Chain:Prompt 模板 → 大模型

先看不带框架时我们要手写什么:构造 messages 切片、管理 System Prompt、调模型、解析响应。用 Chain 之后,前两步变成一个声明:

package main

import (
	"context"
	"fmt"
	"os"

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

func main() {
	ctx := context.Background()

	// 1. 创建模型节点
	chatModel, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{
		Model:  "gpt-4o-mini", // 换成你服务商可用的模型名
		APIKey: os.Getenv("OPENAI_API_KEY"),
	})
	if err != nil {
		panic(err)
	}

	// 2. 创建 Prompt 模板:System 定角色,{xxx} 是 FString 占位符
	template := prompt.FromMessages(schema.FString,
		&schema.Message{Role: schema.System, Content: "你是一位资深 {extra}工程师,回答简洁准确。"},
		&schema.Message{Role: schema.User, Content: "用一句话解释什么是 {topic}"},
	)

	// 3. 声明 Chain:输入 map,输出消息切片
	chain := compose.NewChain[map[string]any, []*schema.Message]().
		AppendChatTemplate(template).
		AppendChatModel(chatModel)

	// 4. 编译
	runnable, err := chain.Compile(ctx)
	if err != nil {
		panic(err)
	}

	// 5. 运行:传变量,拿回 messages
	resp, err := runnable.Invoke(ctx, map[string]any{
		"extra": "Go 语言",
		"topic": "goroutine",
	})
	if err != nil {
		panic(err)
	}
	fmt.Println(resp[len(resp)-1].Content) // 最后一条即模型回复
}

逐段拆解:

  • 模板里的 {xxx}:schema.FString 采用 Python 风格的 {var} 占位符(Eino 还支持 GoTemplate 的 {{.Var}} 和 Jinja2 风格),运行时从输入 map 取同名 key 渲染。同一个 Chain 换个 map 值就是一次全新的对话上下文,模板复用、数据隔离。
  • 类型流转:Chain 的泛型参数 [map[string]any, []*schema.Message] 决定了第一步 ChatTemplate 的 Format 收到 map、吐出消息切片,第二步 ChatModel 恰好消费消息切片——类型对不上会在 Compile 时直接报错,而不是运行时。
  • 输出取最后一条:模型节点输出的是完整 messages 列表,最后一条才是本次回复。

四、给 Chain 加一步:Lambda 自定义节点

实际应用里经常在模型前后插业务逻辑:过滤敏感词、解析 JSON、记日志。用 AppendLambda 加一个普通 Go 函数节点:

// 在 AppendChatModel 之后追加一个后处理节点
chain = chain.AppendLambda(compose.InvokableLambda(func(ctx context.Context, msgs []*schema.Message) (string, error) {
	last := msgs[len(msgs)-1]
	// 例如:统一去掉首尾空白,或在这里做 JSON 解析、落库
	return "【AI】" + last.Content, nil
}))

此时 Chain 的输出类型从 []*schema.Message 变成了 string,所以创建时泛型要相应改成 compose.NewChain[map[string]any, string]。Lambda 让 Chain 的输入输出类型完全由你定义,框架只负责把中间各段的类型衔接好。

常见可插入的节点速查:

需求 节点 Append 方法
渲染提示词 ChatTemplate AppendChatTemplate
调大模型 ChatModel AppendChatModel
自定义业务逻辑 Lambda AppendLambda
调用工具(第 2 篇主题) ToolsNode AppendToolsNode
文本转向量(第 3 篇主题) Embedder AppendEmbedding
知识库检索(RAG 预告) Retriever AppendRetriever

五、Invoke 还是 Stream

第 4 篇《SSE 流式输出教程》我们手写了流式协议,Eino 里 Chain 编出来的 Runnable 直接支持:

// Invoke:一次性拿完整结果
resp, err := runnable.Invoke(ctx, map[string]any{"extra": "Go 语言", "topic": "channel"})

// Stream:流式拿增量
sr, err := runnable.Stream(ctx, map[string]any{"extra": "Go 语言", "topic": "channel"})
if err != nil {
	panic(err)
}
defer sr.Close()
for {
	chunk, err := sr.Recv()
	if errors.Is(err, io.EOF) {
		break
	}
	if err != nil {
		panic(err)
	}
	fmt.Print(chunk[len(chunk)-1].Content)
}

注意新版 eino 的流读取方法是 Recv()(旧版部分示例中的 Next() 已更名)。Compile 一次、Invoke/Stream 随便调多少次——Runnable 是可复用的,这也是 Chain 比「每次重新拼代码」省心的地方。

六、什么时候该上 Graph

Chain 适合固定顺序的流水线。一旦需要分支(「用户问技术问题走 A 链,闲聊走 B 链」)、循环(多轮工具调用)、并行(同时查库和查缓存),就该用 compose.NewGraph 显式编排了,那是本系列后续图编排篇的内容。选择标准一句话:数据流是直线用 Chain,是图用 Graph;Chain 编不下去的那天,迁移成本几乎为零,因为 Chain 本来就是 Graph。

版本说明

本文代码 API 均于 2026-11-13 逐字核对自以下官方源码与发布页,安装请以下列版本为准:

依赖 版本 安装命令
eino v0.9.23(2026-11-09 发布,官方 releases 页核实) go get github.com/cloudwego/eino@v0.9.23
eino-ext OpenAI 组件 v0.1.13(官方 tags 页核实) go get github.com/cloudwego/eino-ext/components/model/openai@v0.1.13

官方文档:cloudwego.io/docs/eino;源码核对仓库:github.com/cloudwego/eino(compose/chain.go、components/prompt/chat_template.go、schema/message.go、schema/stream.go)与 github.com/cloudwego/eino-ext(components/model/openai/chatmodel.go @ v0.1.13)。文中 NewChain / AppendChatTemplate / AppendChatModel / AppendLambda / Compile / Recv 等 API 签名均抄录自上述源码。


本文是「Go AI 应用开发」系列第 6 篇(共 18 篇规划),阶段二「Go AI 框架」第二篇。上一篇:《Go AI 框架选型横评:Eino vs LangChainGo vs Genkit vs GoAI SDK》;系列前篇:《大模型 API 基础》、《Function Calling 教程》、《Embedding 与语义检索》、《SSE 流式输出教程》。下一篇预告:《Eino 图编排:分支、循环与人机协同中断恢复》。

🛒 站长推荐(京东联盟)

以上链接含京东联盟推广代码;通过链接购买本站可能获得少量佣金,不影响你的购买价格。

📮 关注公众号「Go2AI之路」
Go2AI之路 公众号二维码

每周更新 Go + AI 应用开发实战:AI 面试官完整项目、MCP、Agent 工程化,完整源码随文章首发。

微信扫码,或搜索「Go2AI之路」关注,第一时间获取系列更新与源码,不再错过任何一篇。

如有疑问关注公众号给我留言
wx

关注公众号

©2017-2026 鲁ICP备17023316号-1 Powered by Hugo · 隐私政策