M11 Eino 框架
前十个模块已经把 Agent 的关键能力手写了一遍:模型接入、消息与提示词、Agent 循环、工具调用、RAG、多 Agent 编排、上下文治理、可观测、评估和安全。本章开始进入框架篇,引入 CloudWeGo Eino,并把框架里的主要概念逐一映射回前面写过的代码。
学习框架不是把原理交给黑盒,而是把已经理解的机制交给框架统一实现。ChatModel 对应 M02 的 Provider,Tool 对应 M04/M06的工具接口,Retriever 对应 M07 的检索器,Graph 对应 M04/M08的编排,Eino ADK 的 ChatModelAgent 对应 M04 的 ReAct / Function Calling 循环。理解这些对应关系后,学习 Eino 的重点就不再是背 API,而是判断框架在什么位置替换了手写代码。
go.mod 锁定版本对应的官方文档为准。模块概述
学完本章,你应该能够:
- 跟随快速示例搭建一个基于 Eino 的 Go 技术问答应用;
- 说明为什么需要 Agent 框架,以及框架的价值和风险边界;
- 理解 Eino 的 Components、Compose、ADK 三层架构;
- 把
ChatModel、ChatTemplate、Tool、Retriever映射回前面手写的 Provider、模板、工具和检索器; - 使用 Chain 与 Graph 做编排,理解
compose.NewGraph[I, O]的类型化设计; - 理解 Eino 对流式输入输出的统一处理方式,并与 M01-M02的手写流式管线对照;
- 理解 Eino ADK 的
ChatModelAgent、协作原语、Runner、中断和检查点; - 用 Eino 重写 M04 的命令行助手,对比手写版与框架版;
- 了解 Eino、Google ADK-Go、tRPC-Agent-Go、LangChainGo 和 Genkit Go 的定位。
本章是框架篇 M11-M12 的开端。知识篇 M01-M10 已经覆盖 Agent 的核心原理,本章用 Eino 把这些原理映射到框架抽象;M12将继续讲 Google ADK-Go 与 A2A。到 M13 之后的项目篇,再使用 Eino 搭建完整平台。
建议先完成 M01-M10。本章会频繁引用前面手写的 Provider、Tool、Retriever、Agent 循环、多 Agent 拓扑、状态持久化和可观测代码。
配套练习是使用 Eino ADK 重写 M04 的命令行助手,使其继续支持多轮对话、计算器、时间工具和流式输出,并完成手写版与框架版的逐项对照。
11.1 快速开始:第一个 Eino AI 应用
在展开框架架构之前,先用 Eino 实现一个可以直接运行的 Go 技术问答助手。这个示例不引入工具调用、RAG 或多 Agent,只完成最小闭环:
命令行问题
↓
ChatTemplate:把问题填入消息模板
↓
ChatModel:调用 OpenAI 兼容模型
↓
Chain / Runnable:组织并执行完整流程
↓
输出模型回答跑通这个示例后,你会先对 Eino 的 Components、Compose 和 Runnable 建立直观认识,再回头理解它们的设计。
准备环境
示例要求:
- Go 1.24 或更高版本;
- 一个支持 OpenAI 兼容协议的模型服务;
- 对应服务的 API Key、Base URL 和模型名称。
下面使用 Eino v0.9.8 与 OpenAI 组件 v0.1.13。这组版本已经在 2026 年 6 月 19 日完成编译验证。为了保证示例可复现,入门阶段先固定版本,后续升级再阅读 migration notes。
创建项目并安装依赖:
mkdir eino-first-app
cd eino-first-app
go mod init example.com/eino-first-app
go get github.com/cloudwego/eino@v0.9.8
go get github.com/cloudwego/eino-ext/components/model/openai@v0.1.13配置模型。下面以 DeepSeek 的 OpenAI 兼容端点为例,模型名称应以当前控制台或 Models API 返回结果为准:
export OPENAI_API_KEY="你的 API Key"
export OPENAI_BASE_URL="https://api.deepseek.com/v1"
export OPENAI_MODEL_NAME="deepseek-v4-pro"如果使用其他 OpenAI 兼容服务,只需要替换这三个环境变量,代码不需要改动。
编写应用
新建 main.go:
package main
import (
"context"
"fmt"
"log"
"os"
"strings"
"github.com/cloudwego/eino-ext/components/model/openai"
"github.com/cloudwego/eino/components/prompt"
"github.com/cloudwego/eino/compose"
"github.com/cloudwego/eino/schema"
)
func main() {
ctx := context.Background()
apiKey := os.Getenv("OPENAI_API_KEY")
baseURL := os.Getenv("OPENAI_BASE_URL")
modelName := os.Getenv("OPENAI_MODEL_NAME")
if apiKey == "" || baseURL == "" || modelName == "" {
log.Fatal("请设置 OPENAI_API_KEY、OPENAI_BASE_URL 和 OPENAI_MODEL_NAME")
}
chatModel, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{
APIKey: apiKey,
BaseURL: baseURL,
Model: modelName,
})
if err != nil {
log.Fatalf("创建 ChatModel 失败: %v", err)
}
chatTemplate := prompt.FromMessages(
schema.FString,
schema.SystemMessage("你是一名 Go 技术助手,回答要准确、简洁,并给出可执行建议。"),
schema.UserMessage("{question}"),
)
chain := compose.NewChain[map[string]any, *schema.Message]()
chain.
AppendChatTemplate(chatTemplate).
AppendChatModel(chatModel)
app, err := chain.Compile(ctx)
if err != nil {
log.Fatalf("编译 Chain 失败: %v", err)
}
question := strings.Join(os.Args[1:], " ")
if question == "" {
question = "用三句话解释 Eino 的 Components、Compose 和 ADK。"
}
answer, err := app.Invoke(ctx, map[string]any{"question": question})
if err != nil {
log.Fatalf("调用模型失败: %v", err)
}
fmt.Println(answer.Content)
}运行应用:
go run . "Go 项目中 context.Context 应该如何传递?"模型输出具有随机性,但程序应打印一段围绕问题生成的回答。如果不传命令行参数,程序会使用代码中的默认问题。
理解这段代码
这个小程序已经覆盖 Eino 的核心工作方式:
openai.NewChatModel创建一个 Component,负责调用模型;prompt.FromMessages创建另一个 Component,负责把运行时变量转换为消息列表;compose.NewChain声明模板和模型的执行顺序;chain.Compile(ctx)把声明转换为可复用的Runnable;app.Invoke(...)使用非流式方式执行 Runnable。
Chain 中的数据类型变化如下:
map[string]any
└─ ChatTemplate → []*schema.Message
└─ ChatModel → *schema.Message这就是一个最简单的 Eino 应用:组件负责能力,Compose 负责连接,Runnable 负责运行。后面讲 Graph、流式和 ADK 时,仍然是在这个基础上增加分支、循环、工具和状态管理。
常见问题
- 提示缺少环境变量:确认三个
OPENAI_*变量在当前终端中已经导出; - 返回 401 或 403:检查 API Key、服务地址和账号权限;
- 提示 model not found:模型名称已经变化或当前账号没有权限,应查询服务商当前模型列表;
- 依赖升级后编译失败:重新使用示例中的固定版本,或阅读 Eino migration notes 后再升级;
- 希望流式输出:将
app.Invoke改为app.Stream,并按 11.5 的方式循环读取StreamReader。
ChatModelAgent。先理解 ChatModel、ChatTemplate、Chain 和 Runnable,再学习 ADK,才能看明白 Agent 封装替你完成了哪些工作。11.2 为什么需要框架
之前我们选择手写大模型调用相关逻辑,是因为学习原理时需要看清数据结构、控制流和失败路径。但当系统扩展到几十个节点、复杂分支、流式输出、持久化、可观测和人工介入时,纯手写会逐渐暴露出工程成本。
- 样板代码增加:每个 Agent 都要实现相似的循环、工具执行、停止条件和状态管理。M08中的不同拓扑也各自包含调度样板。
- 流式处理复杂:M01-M02只处理了单个模型节点的 channel 和 SSE。真实系统还要处理多个节点之间的流拼接、合并、复制与汇总。
- 编排难以维护:M04 的循环和 M08 的拓扑可以用命令式代码实现,但节点增加后,连接关系会被并发与状态代码淹没。
- 横切能力重复:回调、可观测、中断恢复、检查点和人工介入如果分散在各个 Agent 中,会产生大量重复实现。
框架的作用是集中解决这些重复问题。开发者声明组件、节点和连接关系,框架负责数据流转、流式适配、执行计划、错误传播、回调、中断和检查点。它相当于提供了一个经过统一整理的 Agent harness,对应 M04 中手写的运行骨架。
框架同时也存在边界:抽象可能遮蔽实现细节,使开发者只能调用 API,却无法判断错误来自模型、工具、流处理还是编排。前十章手写核心能力的意义,正是在使用框架时仍然能够定位问题并扩展默认行为。
11.3 Eino 框架系统
Eino 的定位
Eino 是字节跳动开源、归属 CloudWeGo 生态的 Go LLM / Agent 应用开发框架。CloudWeGo 官方材料将其描述为字节内部 LLM 应用基础设施的开源版本,并提到 Doubao、TikTok、Coze 等业务线和数百个服务已经接入。设计上,Eino 借鉴了 LangChain、Google ADK 等框架的思想,同时保持 Go 的工程习惯。
Eino 的核心设计可以概括为四点:
- 强类型:通过 Go 泛型表达编排输入、输出和组件接口;
- 显式错误:可能失败的操作通过
error返回; - 组合优先:组件通过接口实现,再由 Chain、Graph 或 ADK 组合;
- 流式优先:统一支持非流式与流式调用,并在编排过程中完成必要的形态转换。
截至 2026 年 6 月,Eino 仍处于
0.x快速迭代阶段,Go module 已有v0.9.x稳定线与v0.10.0-alpha预发布版本。核心抽象已经形成,但 API 仍可能调整。正式项目需要锁定具体版本,升级时阅读 release 和 migration 说明。
还需要明确 Eino 的能力边界:
- 它是开发库,不是包含部署、UI 和多租户能力的 Agent SaaS 平台;
- 它封装模型调用,但不替代 OpenAI、Anthropic、DeepSeek、豆包等模型服务;
- 它定义 Retriever 等接口,但不是 pgvector、Qdrant 或 Milvus 一类向量数据库;
- Components、Compose 和 ADK 可以按需使用,不要求所有项目都采用完整三层。
Components、Compose 与 ADK
Eino 可以分为 Components、Compose 和 ADK 三层:
┌─────────────────────────────────────────────────────────────┐
│ ADK 层:ChatModelAgent、多 Agent 协作原语 │ ← 对应你的 M04/M05/M08
│ (开箱即用的 Agent 与拓扑,Plan-Execute / Host / ...) │
├─────────────────────────────────────────────────────────────┤
│ Compose 层:Chain / Graph / Workflow 编排 │ ← 对应你手搓的循环与拓扑
│ (声明式描述节点与数据流,自动处理流式,类型安全连接) │
├─────────────────────────────────────────────────────────────┤
│ Components 层:ChatModel / Tool / Retriever / Embedding 等 │ ← 对应你的 Provider / 工具 / 检索
│ (可复用的原子构件,含各家模型的官方实现) │
└─────────────────────────────────────────────────────────────┘
Components 提供模型、模板、工具、检索和 embedding 等原子构件;Compose 使用 Chain、Graph 和 Workflow 描述节点与数据流;ADK 再向上提供可直接运行的 Agent 与协作机制。三层自底向上组合,但开发者可以只使用其中一层。例如只使用 ChatModel 直接调用模型,不必引入 Compose 或 ADK。
| Eino 层 | 课程对应 | 已手写的内容 |
|---|---|---|
| Components | M02 / M06 / M07 | Provider、Tool、Embedder、Retriever |
| Compose | M04 / M05 / M08 | Agent 循环、链式处理、多 Agent 拓扑 |
| ADK | M04 / M05 / M08 / M09 | ReAct、Plan-and-Execute、Supervisor、上下文治理 |
五类核心能力
ChatModel
ChatModel 是 M02 Provider 接口的框架实现。eino-ext/components/model/* 提供多种模型接入,包括 OpenAI 兼容协议、Claude、Gemini、Ollama、火山方舟和其他供应商实现。具体包名与支持范围应以当前 eino-ext 仓库为准。
OpenAI 兼容实现可接入 OpenAI 本身,以及 DeepSeek、豆包、智谱等提供兼容接口的服务。原生协议实现则用于需要供应商特有能力的场景。
Chain、Graph 与 Workflow
compose 包提供多种编排原语:
- Chain:用于 A → B → C 一类线性数据流;
- Graph:用于条件分支、循环、并行和状态机;
- Workflow:用于更高层的工作流组合,包括子图、分支和同步。
它们分别对应 M05 的链式处理、M04 的 Agent 状态机和 M08 的多 Agent 拓扑。
ADK
adk 包提供 ChatModelAgent 等 Agent 抽象,以及 Runner、事件流、中断、恢复和多 Agent 协作能力。
主要能力包括:
ChatModelAgent:封装 ReAct / Function Calling 循环;- Plan-and-Execute:对应 M04/M05的规划与执行模式;
- Host / Supervisor:对应 M08 的主管式拓扑;
- 摘要、工具裁剪、Skill 和 Human-in-the-Loop 等中间件能力。
RAG 组件
RAG 相关组件包括:
Retriever:根据查询检索文档,可对接 pgvector、Qdrant、Milvus 等后端;Embedding:对应 M07 的 Embedder;Indexer:负责文档索引;- Document Loader 与 Transformer:负责从 PDF、Markdown、URL 等来源加载并处理文档。
这些接口可以直接使用 eino-ext 中的实现,也可以包装 M07 手写的混合检索和 rerank。
横切能力
Eino 还提供跨组件的工程能力:
- Callback:连接日志、OpenTelemetry、Langfuse 或自定义回调;
- Interrupt / Checkpoint:支持执行中断、保存状态和恢复;
- 流式形态转换:在编排节点之间适配流式与非流式输入输出;
- 类型与结构检查:在图构建和 Compile 阶段尽早暴露连接错误。
Eino Dev
Eino Dev 是 Eino 生态中的开发工具,面向编排图可视化和节点级调试。官方文档将其定位为 IDE 插件与 DevOps 能力的一部分,产品形态、安装方式和版本兼容性仍在变化。正式使用前应查看当前 Eino Dev、DevOps 与可视化调试文档。
类型化图编排
Eino 与动态语言框架的显著区别之一,是使用 Go 泛型表达输入输出类型,并在图构建和 Compile 阶段校验编排结构。
LangChain Python 的组合形式可以写成:
chain = prompt | model | parser # Python:运行时才知道类型对不上Eino 使用类型参数声明整条链的输入和输出:
chain := compose.NewChain[InputType, OutputType]().
AppendChatTemplate(template).
AppendChatModel(model).
AppendLambda(parser)
runnable, err := chain.Compile(ctx) // 编译时就报错:类型对不上[InputType, OutputType] 让 Go 编译器能够检查一部分静态类型关系,Eino 的 Compile(ctx) 则进一步检查已经声明的节点、边和执行结构。这里的 Compile 是框架运行时执行的编排构建步骤,不等同于 go build 的编译期检查;两者共同把部分错误提前到实际请求执行之前。
Eino 编排通常经历三个阶段:
1. 描述阶段: Chain / Graph 拼出来,只是元数据(没真跑)
2. Compile 阶段: 调 .Compile(ctx),做类型检查、流式派生、优化,产出 Runnable
3. Run 阶段: Runnable 可被任意多次调用,内部图已就绪Compile 后得到的 Runnable 可以重复调用,不需要每次请求都重新声明图。工程上应在应用启动或依赖装配阶段完成 Compile,并对错误进行处理。
流式适配是 Compose 的重要能力。下面的简化代码说明只实现一种调用形态时的派生思想:
// 组件只实现 Generate
func (m *MyModel) Generate(ctx, msgs) (*Message, error) { ... }
// 当编排以 Stream 方式运行、而该节点只有 Generate 时,Eino 自动:
// 调用 Generate → 把结果"装箱"成单元素 stream → 交给下游反向转换的思路是消费完整流并合并为非流式结果。具体哪些组件接口允许只实现一种形态、由哪一层完成转换,应以所使用版本的组件接口和 Compose 文档为准。稳定的教学重点是:框架会统一处理多个节点之间的流式与非流式数据形态,减少 M01 流式建模到 M04 Agent 事件流中手写的适配代码。
安装与最小运行
前面的快速开始已经用 ChatTemplate、ChatModel 和 Chain 跑通了完整应用。本节保留原讲义中的最小 ChatModel 示例,用于对比“直接调用 Component”和“通过 Compose 运行”的区别。
Eino 是普通 Go module。核心库与扩展组件分别安装;如果已经完成快速开始,不需要重复执行:
go get github.com/cloudwego/eino # 核心:类型、编排、Agent 抽象
go get github.com/cloudwego/eino-ext/components/model/openai # OpenAI 兼容 ChatModel 实现下面的示例不经过 Chain,直接创建 OpenAI 兼容 ChatModel、构造消息并调用 Generate。它与 M02 的 Provider.Chat基本对应,也说明 Components 可以脱离 Compose 单独使用。
package main
import (
"context"
"fmt"
"os"
"github.com/cloudwego/eino-ext/components/model/openai"
"github.com/cloudwego/eino/schema"
)
func main() {
ctx := context.Background()
cm, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{
BaseURL: "https://api.deepseek.com/v1", // OpenAI 兼容,换任意国产模型只改这三行
APIKey: os.Getenv("DEEPSEEK_API_KEY"),
Model: "deepseek-v4-pro",
})
if err != nil {
panic(err)
}
msg, err := cm.Generate(ctx, []*schema.Message{
schema.SystemMessage("你是一个严谨的 AI 助手。"),
schema.UserMessage("用一句话解释 Eino 的组件设计解决了什么问题。"),
})
if err != nil {
panic(err)
}
fmt.Println(msg.Content)
}DeepSeek 官方文档在 2026-04-24 的更新中提供了
deepseek-v4-pro与deepseek-v4-flash,并说明旧的deepseek-chat/deepseek-reasoner将在 2026-07-24 后停用。运行示例前,应通过 DeepSeek Models API、账号控制台或当前官方文档确认模型名称。
如果想最快体验完整 Agent,可以使用官方 eino-examples 仓库的 quickstart/chatwitheino。它从 Console 到 Web 逐步展示 ChatModel、Agent/Runner、Memory、Tools、Callback、Interrupt、Graph、Skill 和 Web 版本。
git clone https://github.com/cloudwego/eino-examples.git
cd eino-examples/quickstart/chatwitheino
export OPENAI_API_KEY=... OPENAI_MODEL=gpt-4.1-mini
go run ./cmd/ch01 -- "用一句话解释 Eino 的组件设计解决了什么问题"Eino 项目结构
Eino 不强制项目结构,它是普通 Go 库。推荐继续沿用 M01 1.1 的工程分层,把 Eino 当成 internal 中的一层实现。
myagent/
├── cmd/
│ └── server/main.go # 入口:装配组件、Compile 编排图、起 HTTP/CLI
├── internal/
│ ├── model/ # ChatModel 装配(选模型、读密钥)
│ ├── tools/ # 业务工具(用 eino 的 Tool 抽象封装)
│ ├── rag/ # Retriever / Indexer / Embedding 装配
│ ├── agent/ # 用 compose.Graph 或 adk 组装 Agent
│ └── graph/ # 复杂编排图的定义
├── configs/ # 模型 / 检索 / 编排的配置
└── go.mod要点和 M01 1.1一致:cmd 只做装配与启动,真正的能力按职责分在 internal 各包。Eino 的组件、图和 Agent 都在 internal 中创建和编排,入口处把它们 Compile() 成可运行对象后再启动服务或命令行程序。
配套生态
使用 Eino 时重点关注四处:
cloudwego/eino:核心库,包含schema、compose、ADK、流机制与切面能力;cloudwego/eino-ext:扩展实现,包括模型、检索、工具、回调上报、评估器和提示词优化器等;cloudwego/eino-examples:成体系的可运行示例,适合跟着跑;- Eino Dev:面向图可视化与可视化调试的开发工具。
eino-ext 应在 go.mod 中锁定具体版本。不要在课程或项目代码里默认追随最新版本,否则 ADK、middleware、DevOps 或组件配置字段的变化可能导致示例不可复现。一个工程细节需要提前说明:Eino 使用自己的 schema.Message 作为消息类型,角色通过 schema.SystemMessage、schema.UserMessage、schema.AssistantMessage、schema.ToolMessage 等构造。它和 M02 的 llm.Message是同一个概念,只是类型不同。如果要把知识篇的代码和 Eino 混用,在边界处做一次 llm.Message ↔ schema.Message 的转换即可。
与其他 Go Agent 框架对比
Go 生态的 LLM / Agent 框架不止 Eino。横向比较的目的不是证明某个框架永远最好,而是明确 Eino 的定位和本课选择它的原因。
主流框架全景
| 框架 | 出品 | 定位 | 特色 | 当前状态(2026-06) |
|---|---|---|---|---|
| Eino | 字节 CloudWeGo | 类型化图编排,生产级 | Components / Compose / ADK 三层完整 | v0.9.x 稳定线,已出现 v0.10.0-alpha |
| ADK-Go | A2A 参考实现 + Agent 抽象 | 原生 OTel、Request Confirmation、A2A | v1.x 稳定线 | |
| tRPC-Agent-Go | 腾讯 tRPC | 图工作流 + A2A/MCP/AG-UI | 与腾讯 RPC 生态打通 | v1.x 稳定线,迭代快 |
| LangChainGo | 社区 | LangChain 的 Go 端口 | LangChain 概念迁移,集成较多 | v0.1.x,pre-1.0 |
| Genkit Go | Google Firebase | Firebase / GCP 集成 | 与 Google Cloud 生态结合 | v1.x 稳定线 |
这些版本状态会变化,项目选型前应重新查看 GitHub releases、pkg.go.dev 和官方迁移文档。版本号也不等于成熟度:Eino 虽仍在 0.x,但官方材料说明其在字节内部有大规模实践;v1.x 也不代表一定更适合当前项目。
关键差异
Eino vs LangChainGo
| Eino | LangChainGo | |
|---|---|---|
| 设计取向 | Go-first,强类型 | LangChain 概念迁移 |
| 类型问题暴露 | 静态类型 + 装配 / Compile 阶段 | 更多依赖运行期 |
| 流式适配 | Compose 层统一处理 | 更多手写 |
| 编排可视化 | Eino Dev | 无原生同级工具 |
| 中文生态 | 字节与 CloudWeGo 生态 | 跟随 LangChain 社区 |
Eino vs ADK-Go
| Eino | ADK-Go | |
|---|---|---|
| 定位 | 通用 Go Agent 框架 | A2A 协议与 Agent 构建工具 |
| 优势场景 | 内部复杂编排、类型化图 | 对外 A2A 暴露、跨语言互联、人工确认 |
| 本课用法 | 项目内部主框架 | M12用于 A2A 与对外协作 |
Eino vs Genkit Go
Genkit Go 与 Firebase / Google Cloud 生态结合更深。如果项目已经运行在 Google Cloud 上,并希望复用 Genkit 的开发工具、Flow、Prompt 和 Developer UI,可以考虑它。Eino 更中立,适合在任意 Go 服务环境中做内部 Agent 编排。
Eino vs tRPC-Agent-Go
tRPC-Agent-Go 更适合已经采用腾讯 tRPC 体系的团队。它提供 Agent、Chain、Parallel、Cycle、Graph、SubAgent、A2A/MCP/AG-UI 等能力;不在 tRPC 生态中时,Eino 的通用性更强。
本课选择 Eino 的理由
本课选择 Eino 的理由包括:
- Go-first 设计,符合 Go 工程习惯;
- 类型化编排能够提前暴露一部分连接错误;
- 官方材料显示其在字节内部有生产实践;
eino-ext生态覆盖模型、工具、检索和回调等扩展;- Compose 对流式形态做了统一处理;
- 中文文档和示例相对完整。
何时不选 Eino
不选择 Eino 的典型场景也很明确:
- 已在 Google Cloud / Firebase 生态中,可以优先评估 Genkit;
- 已在腾讯 tRPC 生态中,可以优先评估 tRPC-Agent-Go;
- 只想跟随 LangChain 教程快速迁移,可以评估 LangChainGo;
- 需要对外暴露 A2A 服务,本课倾向于 Eino + ADK-Go 组合。
11.4 组件层
Eino 的 components 包定义了一组核心组件接口。它们并不陌生,只是把知识篇手写过的能力标准化了。
ChatModel 对应 M02 的 Provider。 它的核心接口可以简化理解为两个方法:Generate 用于非流式输出,Stream 用于流式输出。
// Eino 的 ChatModel 接口(简化示意)
type BaseChatModel interface {
Generate(ctx context.Context, input []*schema.Message, opts ...Option) (*schema.Message, error)
Stream(ctx context.Context, input []*schema.Message, opts ...Option) (*schema.StreamReader[*schema.Message], error)
}Generate 对应 M02 2.2的 Chat,Stream 对应 M02 2.7的 ChatStream。StreamReader[T] 是 Eino 对流式产出 T 的统一抽象,和 M01 1.2中用 <-chan StreamChunk 表达的概念类似。
创建一个接 DeepSeek 的 ChatModel,可以使用 Eino 的 OpenAI 兼容实现:
import "github.com/cloudwego/eino-ext/components/model/openai"
cm, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{
BaseURL: "https://api.deepseek.com/v1",
APIKey: os.Getenv("DEEPSEEK_API_KEY"),
Model: "deepseek-v4-pro",
})
// 直接用:
msg, err := cm.Generate(ctx, []*schema.Message{
schema.SystemMessage("你是一个严谨的 AI 助手。"),
schema.UserMessage("用一句话解释什么是向量检索。"),
})
fmt.Println(msg.Content)openai.NewChatModel 对应 M02 2.3里手写的 openai.New(...),schema.SystemMessage 和 schema.UserMessage 对应手工构造 llm.Message{Role: ...}。区别是模型接入和消息类型由 Eino 统一维护。
ChatTemplate 对应 M03 3.2 的提示词模板。 它把变量填入消息模板,产出 []*schema.Message。这和使用 text/template 渲染系统提示词的思路一致,只是输出直接变成模型可消费的消息数组。
Tool 对应 M04/M06的工具接口。 Eino 的工具接口同样要求工具提供名称、描述、参数 Schema 和调用入口。Eino 也提供了从类型化 Go 函数推断 Schema 的辅助函数,对应 M06 手写的泛型 TypedTool。
import "github.com/cloudwego/eino/components/tool/utils"
type weatherArgs struct {
City string `json:"city" jsonschema:"description=城市名"`
}
// 从一个类型化函数推断出工具(Schema 自动生成)——对照 M06 的 TypedTool
weatherTool, err := utils.InferTool("get_weather", "查询城市天气",
func(ctx context.Context, a weatherArgs) (string, error) {
return a.City + ":晴", nil
})这段代码和 M06 6.2的 tool.NewTypedTool 思路相同:以类型化函数为核心,通过反射生成参数 Schema,再把函数包装成可被模型调用的工具。实际使用时,应按 Eino 当前版本确认 utils.InferTool、InferOptionableTool、结构体 tag 和 JSON Schema 支持范围。
jsonschema:"description=..." 可能与 tag 字段分隔产生歧义。Eino 官方更推荐使用独立的 jsonschema_description:"..." 描述字段,required、enum 等约束仍可按当前版本文档使用 jsonschema:"..."。Retriever 对应 M07 的检索器。 它的核心能力是根据 query 返回 []*schema.Document。Eino 可以直接使用 eino-ext 里的实现,也可以把 M07 手写的混合检索、rerank和 pgvector 封装成一个 Eino Retriever。
下面用一张表格把 eino 组件与我们之前手写的内容进行映射:
| Eino 组件 | 已手写过的内容 |
|---|---|
BaseChatModel (Generate / Stream) | M02 的 llm.Provider(Chat / ChatStream) |
ChatTemplate | M03 的 prompt.Template |
tool.BaseTool + utils.InferTool | M04 的 tool.Tool + M06 的 TypedTool |
Retriever | M07 的 rag.Retriever |
schema.Message | M02 的 llm.Message |
11.5 编排层
组件是积木,编排负责把积木组织成应用。Eino 提供 Chain 和 Graph 两类常用编排方式。
Chain 用于线性串联。 当数据就是从模板到模型、再到解析器这样一站一站流动时,用 Chain:
import "github.com/cloudwego/eino/compose"
chain, err := compose.NewChain[map[string]any, *schema.Message]().
AppendChatTemplate(prompt). // 第一站:填模板
AppendChatModel(cm). // 第二站:调模型
Compile(ctx) // 编译成可运行对象
out, err := chain.Invoke(ctx, map[string]any{"query": "你们几点上班?"})NewChain[map[string]any, *schema.Message] 声明整条链的输入类型是模板变量,输出类型是模型回复。它对应 M05 5.3中手写的 RunChain,但把类型衔接与执行计划交给框架处理。
Graph 用于分支、循环和状态机。 当流程不是直线,而是包含条件分支、循环或并行时,用 compose.NewGraph[I, O]()。
g := compose.NewGraph[map[string]any, *schema.Message]()
_ = g.AddChatTemplateNode("tpl", prompt) // 加节点:模板
_ = g.AddChatModelNode("model", cm) // 加节点:模型
_ = g.AddEdge(compose.START, "tpl") // 连边:开始 → 模板
_ = g.AddEdge("tpl", "model") // 模板 → 模型
_ = g.AddEdge("model", compose.END) // 模型 → 结束
runnable, err := g.Compile(ctx) // 编译:检查类型衔接、构建执行计划
out, err := runnable.Invoke(ctx, map[string]any{"query": "..."})这正好对应 M08 手写多 Agent 拓扑时遇到的问题:节点、边、数据流、并发和错误传播都混在命令式代码里。Graph 把“有哪些节点、怎么连接”显式声明出来,再由框架根据图执行。
AddEdge 和 Compile 是类型安全的关键位置。它们会在装配阶段检查上游输出和下游输入是否匹配,整张图的入口和出口是否符合 NewGraph[I, O] 声明的类型。如果类型不匹配,会以 error 提前暴露,而不是等到线上请求跑到一半才失败。
需要注意边界:Graph 节点间的检查通常发生在图构建或 Compile(ctx) 调用时,属于程序运行期的装配阶段,不是 Go 编译器在 go build 那一刻完成的所有检查。它的价值在于把错误前移到处理真实业务请求之前。

11.6 流式自动处理
M01-M02为了实现流式输出,手写了 channel 建模、SSE 解析、生产者关闭、select ctx.Done() 和错误传播。那还只是单个模型节点。真实系统里数据要经过多个节点:模板节点可能产出完整消息,模型节点产出流,下游节点可能要消费流,也可能只接受完整结果。
Eino 的 Runnable 用四个方法覆盖输入/输出与流/非流的组合:
| 方法 | 输入 | 输出 | 对应场景 |
|---|---|---|---|
Invoke | 非流 | 非流 | 普通一问一答 |
Stream | 非流 | 流 | 流式回答,对应 M01 的 ChatStream |
Collect | 流 | 非流 | 把上游流汇总成完整结果 |
Transform | 流 | 流 | 流进流出的中间处理 |
// 同一个编译好的 runnable,要流式输出就调 Stream:
sr, err := runnable.Stream(ctx, map[string]any{"query": "讲个长故事"})
defer sr.Close()
for {
chunk, err := sr.Recv()
if err == io.EOF {
break
}
if err != nil { /* 处理错误 */ }
fmt.Print(chunk.Content) // 逐段输出
}当多个节点组成图时,Eino 会在节点之间处理流的拼接、装箱、合并和复制。例如非流节点的输出需要喂给期待流式输入的下游时,框架可以把结果装箱为单元素流;流式输出需要给多个下游消费时,框架负责复制。具体支持范围仍要看当前 Compose 版本和组件接口,但课程重点是:这些从 M01 流式建模到 M04 AgentEvent里手写过的流式适配,在 Eino 中变成了编排层的公共能力。
11.7 Eino ADK
Components 和 Compose 提供了“搭积木”的能力。每次从 ChatModel、ToolsNode 和循环图开始手搭 ReAct Agent,仍然会产生重复。Eino ADK 在更高层提供开箱即用的 Agent 与协作原语,把 M04 Agent 循环、M05 设计模式和 M08 多 Agent 拓扑封装成现成件。
ChatModelAgent 对应 M04 的 ReAct / Function Calling 循环。 它封装了“调用模型 → 判断是否需要工具 → 执行工具 → 把结果喂回模型 → 再次调用模型”的完整过程。
import (
"github.com/cloudwego/eino/adk"
"github.com/cloudwego/eino/compose"
)
agent, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Name: "assistant",
Instruction: "你是一个严谨的 AI 助手,依据提供的资料回答,不编造。", // 即系统提示词
Model: cm, // 11.3 的 ChatModel
ToolsConfig: adk.ToolsConfig{
ToolsNodeConfig: compose.ToolsNodeConfig{
Tools: []tool.BaseTool{weatherTool, searchTool}, // 11.3 的工具
},
},
})这几行配置对应 M04 4.5中较长的 Function Calling 循环。前面已经手写过这套流程,因此这里要关注的是配置如何映射到循环中的模型、工具和系统提示词,而不是把它当作不可理解的黑盒。
| Eino ADK 原语 | 已手写过的内容 |
|---|---|
ChatModelAgent | M04 ReAct / Function Calling 循环 |
| Sequential | M05 Prompt Chaining |
| Parallel | M05 Parallelization / Sectioning |
| Loop | M05 Evaluator-Optimizer反思循环 |
| Supervisor | M08 Supervisor 拓扑 |
| Plan-Execute | M04 Plan-and-Execute / M05 模式选型 |
// 把两个子 Agent 串成顺序流程(示意,签名以官方为准)
seq, err := adk.NewSequentialAgent(ctx, &adk.SequentialAgentConfig{
SubAgents: []adk.Agent{plannerAgent, executorAgent},
})NewSequentialAgent 代码用于说明模式映射。Eino v0.9 迁移说明已将 Workflow Agent 与 Supervisor 标记为不推荐新项目使用;新项目更推荐 ChatModelAgent + AgentTool,或使用 DeepAgent 做结构化子任务委派。确定性固定流程仍可结合 Workflow Agent 或 Compose 使用,但要按锁定版本确认 API。Runner、中断和检查点对应 M04 的事件流与状态持久化。 ADK 用 Runner 驱动 Agent 执行,产出事件流;中断与检查点机制则对应 Human-in-the-Loop:执行到需要人工介入的位置时,Agent 中断、保存状态,人处理完成后再恢复执行。M12 12.3会把这部分与 A2A 的 input-required 生命周期连接起来。
// 用 Runner 驱动,消费事件流(示意;与 M14/M15 的项目代码保持一致)
runner := adk.NewRunner(ctx, adk.RunnerConfig{Agent: agent})
iter := runner.Query(ctx, "北京明天要带伞吗?") // 产出事件流(异步迭代器)
for {
ev, ok := iter.Next()
if !ok {
break // 流结束
}
// ev 携带这一步的类型与内容,对照 M04 的 AgentEvent 消费
handle(ev)
}Runner、NewSequentialAgent、中断和检查点相关字段仍可能随 Eino 版本变化。学习时要记住它们对应哪段手写逻辑;写项目时要按锁定版本核对签名。

11.8 实战:用 Eino 重写命令行助手
本节把 M04 的命令行助手改写为 Eino ADK 版本。M04 手写版包含 Function Calling 循环、工具注册与执行、停止条件、流式 AgentEvent 和状态持久化。Eino 版的骨架如下:
func main() {
ctx := context.Background()
// 1) 模型(对应你 M02 的 Provider)
cm, _ := openai.NewChatModel(ctx, &openai.ChatModelConfig{
BaseURL: "https://api.deepseek.com/v1",
APIKey: os.Getenv("DEEPSEEK_API_KEY"),
Model: "deepseek-v4-pro",
})
// 2) 工具(对应你 M06 的 TypedTool)
calcTool, _ := utils.InferTool("calc", "做四则运算",
func(ctx context.Context, a struct {
Expr string `json:"expr" jsonschema:"description=算式,如 1+2*3"`
}) (string, error) {
return eval(a.Expr) // 你的求值实现
})
timeTool, _ := utils.InferTool("now", "返回当前时间",
func(ctx context.Context, _ struct{}) (string, error) {
return time.Now().Format(time.RFC3339), nil
})
// 3) Agent(对应你 M04 的整个 Function Calling 循环)
agent, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Name: "assistant",
Instruction: "你是命令行助手,可调用工具。",
Model: cm,
ToolsConfig: adk.ToolsConfig{
ToolsNodeConfig: compose.ToolsNodeConfig{
Tools: []tool.BaseTool{calcTool, timeTool},
},
},
})
// 4) 运行并消费事件流(对应你 M04 的 RunStream + AgentEvent)
runner := adk.NewRunner(ctx, adk.RunnerConfig{Agent: agent})
iter := runner.Query(ctx, "现在几点?再算一下 12*8")
for {
ev, ok := iter.Next()
if !ok {
break
}
printEvent(ev) // 对照 M04 消费 AgentEvent
}
}这段代码是结构示意,不是可直接编译运行的完整程序。为了突出“框架对应手写的哪段”,示例省略了多处错误处理,eval(a.Expr) 和 printEvent(ev) 也没有实现;多轮会话历史存储同样没有展开。项目篇必须把这些补齐,并在锁定的 Eino 版本下通过 go build ./... 和 go test ./...。
手写版和 Eino 版可以这样对照:
| 关注点 | M04 手写版 | Eino 版 |
|---|---|---|
| Function Calling 循环 | 手写循环与停止条件 | ChatModelAgent 配置 |
| 工具 Schema 生成 | 泛型 TypedTool | utils.InferTool |
| 流式事件 | AgentEvent + channel | runner.Query 事件迭代器 |
| 状态持久化 / 中断 | 自行设计存储与暂停点 | Runner / Interrupt / Checkpoint |
| 代码规模 | 多个模块协同 | 主要是装配代码 |
练习时建议把 M04 的手写助手和本节 Eino 版并排放置,逐项确认 Eino 的每个 API 对应手写版中的哪段代码。这个对照比单纯跑通示例更重要。
11.9 框架全景
Go 的 Agent 框架不止 Eino。简单认识其他框架,是为了知道何时坚持 Eino,何时换用或组合其他工具。
- Eino:本课项目内部的主框架。优势是类型化图编排、Go-first 设计、国内生态活跃和流式编排能力。
- Google ADK-Go:M12 12.2会详细讲。它是 Google ADK 的 Go 实现,适合 A2A、跨语言互联、Request Confirmation 和对外 Agent 服务。
- tRPC-Agent-Go:提供 LLMAgent、Chain、Parallel、Cycle、Graph、SubAgent、A2A/MCP/AG-UI 等能力,适合已经采用腾讯 tRPC 体系的团队。
- LangChainGo:LangChain 的 Go 实现,适合需要快速复用 LangChain 概念和集成的场景。
- Genkit Go:适合已经在 Firebase / Google Cloud 生态中的项目。
本课的默认判断是:项目内部复杂编排使用 Eino;对外 Agent 互联使用 Google ADK-Go 与 A2A。其余框架理解定位即可,需要时再深入。因为知识篇已经手写过底层抽象,所以切换框架时不需要重新学习 Agent 的基本原理,只需要把框架 API 映射回熟悉的概念。
配套练习:用 Eino 重写命令行助手
需求:用 Eino ADK 重写 M04 的命令行助手 assistant,功能对齐手写版,包括多轮对话、内置计算器、当前时间工具和流式输出。
验收点:
- 用
openai.NewChatModel接入模型,可指向 DeepSeek 或 Ollama; - 用
utils.InferTool定义两个工具,并对照 M06 6.2的TypedTool; - 用
adk.NewChatModelAgent和Runner驱动 Agent,消费事件流做流式输出; - 写一份对比笔记,列出 Eino 的哪个 API 对应 M04 手写版中的哪段代码,以及两者大致代码量;
- 思考并回答:Eino 帮你省掉的部分里,是否还有某处你不能解释其工作原理。
这个练习的目标不是单纯学会调用 Eino,而是确认你对框架有掌控感。对照笔记写完后,应能把框架里的模型、工具、事件和状态机制都映射回前面手写过的实现。
本章小结
| 本章内容 | 对应手写基础 |
|---|---|
| 快速开始:ChatTemplate + ChatModel + Chain | 第一个可运行的 Eino AI 应用 |
| Eino 三层架构:Components / Compose / ADK | Provider / 编排 / Agent 与拓扑 |
| ChatModel / ChatTemplate / Tool / Retriever | M02 Provider与 M03 模板,M06 工具,M07 检索 |
| Chain / Graph 类型化编排 | M05 链式处理,M04 循环,M08 拓扑 |
| Runnable 四范式与流式处理 | M01-M02手写流式管线 |
| ADK ChatModelAgent、协作原语和 Runner | M04 ReAct,M05 模式,M08 拓扑,状态持久化 |
| Go Agent 框架全景与选型 | 为项目篇选型做准备 |
思考题
- Eino ADK 适合在一个进程内编排多个 Agent。如果某个能力由另一个团队用 Java 实现,并运行在另一台机器上,Eino 的图编排无法直接覆盖它。此时需要什么跨进程、跨语言的 Agent 通信标准?这正是 M12 12.3 A2A 协议。
- 11.7 提到 ADK 的中断与检查点是 Human-in-the-Loop 的基础。当工具需要人工审批时,Agent 中断、保存检查点并等待人处理。如果这个过程跨越 HTTP 请求边界,应如何表达任务处于
input-required状态?这会在 M12 12.3 A2A Task 生命周期中展开。 - Eino 的
Compile可以在构建期暴露一部分类型和结构问题,但模型输出内容仍然是运行期才知道的。类型安全保证“管道接得对”,不保证“模型答得对”。运行期如何持续评估 Agent 答得好不好?这需要回到 M10 10.4 的评估体系。