跳至内容

M11 Eino 框架

前十个模块已经把 Agent 的关键能力手写了一遍:模型接入、消息与提示词、Agent 循环、工具调用、RAG、多 Agent 编排、上下文治理、可观测、评估和安全。本章开始进入框架篇,引入 CloudWeGo Eino,并把框架里的主要概念逐一映射回前面写过的代码。

学习框架不是把原理交给黑盒,而是把已经理解的机制交给框架统一实现。ChatModel 对应 M02 的 ProviderTool 对应 M04/M06的工具接口,Retriever 对应 M07 的检索器Graph 对应 M04/M08的编排,Eino ADK 的 ChatModelAgent 对应 M04 的 ReAct / Function Calling 循环。理解这些对应关系后,学习 Eino 的重点就不再是背 API,而是判断框架在什么位置替换了手写代码。

Eino 仍在快速演进。本章 API 以 2026 年中原讲义和官方文档为基准,示例重点用于说明概念映射;具体函数签名、配置字段、模型名称和 ADK 推荐用法,必须以项目 go.mod 锁定版本对应的官方文档为准。

模块概述

学完本章,你应该能够:

  1. 跟随快速示例搭建一个基于 Eino 的 Go 技术问答应用;
  2. 说明为什么需要 Agent 框架,以及框架的价值和风险边界;
  3. 理解 Eino 的 Components、Compose、ADK 三层架构;
  4. ChatModelChatTemplateToolRetriever 映射回前面手写的 Provider、模板、工具和检索器;
  5. 使用 Chain 与 Graph 做编排,理解 compose.NewGraph[I, O] 的类型化设计;
  6. 理解 Eino 对流式输入输出的统一处理方式,并与 M01-M02的手写流式管线对照;
  7. 理解 Eino ADK 的 ChatModelAgent、协作原语、Runner、中断和检查点;
  8. 用 Eino 重写 M04 的命令行助手,对比手写版与框架版;
  9. 了解 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 的核心工作方式:

  1. openai.NewChatModel 创建一个 Component,负责调用模型;
  2. prompt.FromMessages 创建另一个 Component,负责把运行时变量转换为消息列表;
  3. compose.NewChain 声明模板和模型的执行顺序;
  4. chain.Compile(ctx) 把声明转换为可复用的 Runnable
  5. 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,却无法判断错误来自模型、工具、流处理还是编排。前十章手写核心能力的意义,正是在使用框架时仍然能够定位问题并扩展默认行为。

框架用于减少重复实现代码,不能替代对 Agent 循环、工具协议、流式处理和状态管理的理解。

11.3 Eino 框架系统

Eino 的定位

Eino 是字节跳动开源、归属 CloudWeGo 生态的 Go LLM / Agent 应用开发框架。CloudWeGo 官方材料将其描述为字节内部 LLM 应用基础设施的开源版本,并提到 Doubao、TikTok、Coze 等业务线和数百个服务已经接入。设计上,Eino 借鉴了 LangChain、Google ADK 等框架的思想,同时保持 Go 的工程习惯。

Eino 的核心设计可以概括为四点:

  1. 强类型:通过 Go 泛型表达编排输入、输出和组件接口;
  2. 显式错误:可能失败的操作通过 error 返回;
  3. 组合优先:组件通过接口实现,再由 Chain、Graph 或 ADK 组合;
  4. 流式优先:统一支持非流式与流式调用,并在编排过程中完成必要的形态转换。

截至 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 / 工具 / 检索
│  (可复用的原子构件,含各家模型的官方实现)                          │
└─────────────────────────────────────────────────────────────┘

Eino 三层架构

Components 提供模型、模板、工具、检索和 embedding 等原子构件;Compose 使用 Chain、Graph 和 Workflow 描述节点与数据流;ADK 再向上提供可直接运行的 Agent 与协作机制。三层自底向上组合,但开发者可以只使用其中一层。例如只使用 ChatModel 直接调用模型,不必引入 Compose 或 ADK。

Eino 层课程对应已手写的内容
ComponentsM02 / M06 / M07Provider、Tool、Embedder、Retriever
ComposeM04 / M05 / M08Agent 循环、链式处理、多 Agent 拓扑
ADKM04 / M05 / M08 / M09ReAct、Plan-and-Execute、Supervisor、上下文治理

五类核心能力

ChatModel

ChatModelM02 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 等中间件能力。
Eino v0.9 迁移说明已将基于 Agent Transfer 的 Workflow Agent 与 Supervisor 标记为不推荐新项目采用。正文保留 Sequential、Parallel、Loop、Supervisor 和 Plan-Execute 的模式映射,用于理解与手写拓扑的关系;实际编码前应根据锁定版本选择官方推荐 API。

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-prodeepseek-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:核心库,包含 schemacompose、ADK、流机制与切面能力;
  • cloudwego/eino-ext:扩展实现,包括模型、检索、工具、回调上报、评估器和提示词优化器等;
  • cloudwego/eino-examples:成体系的可运行示例,适合跟着跑;
  • Eino Dev:面向图可视化与可视化调试的开发工具。
Eino 与 eino-ext 应在 go.mod 中锁定具体版本。不要在课程或项目代码里默认追随最新版本,否则 ADK、middleware、DevOps 或组件配置字段的变化可能导致示例不可复现。

一个工程细节需要提前说明:Eino 使用自己的 schema.Message 作为消息类型,角色通过 schema.SystemMessageschema.UserMessageschema.AssistantMessageschema.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-GoGoogleA2A 参考实现 + Agent 抽象原生 OTel、Request Confirmation、A2Av1.x 稳定线
tRPC-Agent-Go腾讯 tRPC图工作流 + A2A/MCP/AG-UI与腾讯 RPC 生态打通v1.x 稳定线,迭代快
LangChainGo社区LangChain 的 Go 端口LangChain 概念迁移,集成较多v0.1.x,pre-1.0
Genkit GoGoogle FirebaseFirebase / GCP 集成与 Google Cloud 生态结合v1.x 稳定线

这些版本状态会变化,项目选型前应重新查看 GitHub releases、pkg.go.dev 和官方迁移文档。版本号也不等于成熟度:Eino 虽仍在 0.x,但官方材料说明其在字节内部有大规模实践;v1.x 也不代表一定更适合当前项目。

关键差异

Eino vs LangChainGo

EinoLangChainGo
设计取向Go-first,强类型LangChain 概念迁移
类型问题暴露静态类型 + 装配 / Compile 阶段更多依赖运行期
流式适配Compose 层统一处理更多手写
编排可视化Eino Dev无原生同级工具
中文生态字节与 CloudWeGo 生态跟随 LangChain 社区

Eino vs ADK-Go

EinoADK-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 的理由包括:

  1. Go-first 设计,符合 Go 工程习惯;
  2. 类型化编排能够提前暴露一部分连接错误;
  3. 官方材料显示其在字节内部有生产实践;
  4. eino-ext 生态覆盖模型、工具、检索和回调等扩展;
  5. Compose 对流式形态做了统一处理;
  6. 中文文档和示例相对完整。

何时不选 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.2ChatStream 对应 M02 2.7ChatStreamStreamReader[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.SystemMessageschema.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.2tool.NewTypedTool 思路相同:以类型化函数为核心,通过反射生成参数 Schema,再把函数包装成可被模型调用的工具。实际使用时,应按 Eino 当前版本确认 utils.InferToolInferOptionableTool、结构体 tag 和 JSON Schema 支持范围。

当描述中包含逗号时,jsonschema:"description=..." 可能与 tag 字段分隔产生歧义。Eino 官方更推荐使用独立的 jsonschema_description:"..." 描述字段,requiredenum 等约束仍可按当前版本文档使用 jsonschema:"..."

Retriever 对应 M07 的检索器。 它的核心能力是根据 query 返回 []*schema.Document。Eino 可以直接使用 eino-ext 里的实现,也可以把 M07 手写的混合检索、rerank和 pgvector 封装成一个 Eino Retriever。

下面用一张表格把 eino 组件与我们之前手写的内容进行映射:

Eino 组件已手写过的内容
BaseChatModel (Generate / Stream)M02 的 llm.ProviderChat / ChatStream
ChatTemplateM03 的 prompt.Template
tool.BaseTool + utils.InferToolM04 的 tool.Tool + M06 的 TypedTool
RetrieverM07 的 rag.Retriever
schema.MessageM02 的 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 把“有哪些节点、怎么连接”显式声明出来,再由框架根据图执行。

AddEdgeCompile 是类型安全的关键位置。它们会在装配阶段检查上游输出和下游输入是否匹配,整张图的入口和出口是否符合 NewGraph[I, O] 声明的类型。如果类型不匹配,会以 error 提前暴露,而不是等到线上请求跑到一半才失败。

需要注意边界:Graph 节点间的检查通常发生在图构建或 Compile(ctx) 调用时,属于程序运行期的装配阶段,不是 Go 编译器在 go build 那一刻完成的所有检查。它的价值在于把错误前移到处理真实业务请求之前。

Compose 编译与运行流程

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 循环。前面已经手写过这套流程,因此这里要关注的是配置如何映射到循环中的模型、工具和系统提示词,而不是把它当作不可理解的黑盒。

协作原语对应 M05 的设计模式M08 的拓扑

Eino ADK 原语已手写过的内容
ChatModelAgentM04 ReAct / Function Calling 循环
SequentialM05 Prompt Chaining
ParallelM05 Parallelization / Sectioning
LoopM05 Evaluator-Optimizer反思循环
SupervisorM08 Supervisor 拓扑
Plan-ExecuteM04 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)
}

RunnerNewSequentialAgent、中断和检查点相关字段仍可能随 Eino 版本变化。学习时要记住它们对应哪段手写逻辑;写项目时要按锁定版本核对签名。

Eino ADK ReAct 循环

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 生成泛型 TypedToolutils.InferTool
流式事件AgentEvent + channelrunner.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.2TypedTool
  • adk.NewChatModelAgentRunner 驱动 Agent,消费事件流做流式输出;
  • 写一份对比笔记,列出 Eino 的哪个 API 对应 M04 手写版中的哪段代码,以及两者大致代码量;
  • 思考并回答:Eino 帮你省掉的部分里,是否还有某处你不能解释其工作原理。

这个练习的目标不是单纯学会调用 Eino,而是确认你对框架有掌控感。对照笔记写完后,应能把框架里的模型、工具、事件和状态机制都映射回前面手写过的实现。

本章小结

本章内容对应手写基础
快速开始:ChatTemplate + ChatModel + Chain第一个可运行的 Eino AI 应用
Eino 三层架构:Components / Compose / ADKProvider / 编排 / Agent 与拓扑
ChatModel / ChatTemplate / Tool / RetrieverM02 ProviderM03 模板M06 工具M07 检索
Chain / Graph 类型化编排M05 链式处理M04 循环M08 拓扑
Runnable 四范式与流式处理M01-M02手写流式管线
ADK ChatModelAgent、协作原语和 RunnerM04 ReActM05 模式M08 拓扑状态持久化
Go Agent 框架全景与选型为项目篇选型做准备

思考题

  1. Eino ADK 适合在一个进程内编排多个 Agent。如果某个能力由另一个团队用 Java 实现,并运行在另一台机器上,Eino 的图编排无法直接覆盖它。此时需要什么跨进程、跨语言的 Agent 通信标准?这正是 M12 12.3 A2A 协议
  2. 11.7 提到 ADK 的中断与检查点是 Human-in-the-Loop 的基础。当工具需要人工审批时,Agent 中断、保存检查点并等待人处理。如果这个过程跨越 HTTP 请求边界,应如何表达任务处于 input-required 状态?这会在 M12 12.3 A2A Task 生命周期中展开。
  3. Eino 的 Compile 可以在构建期暴露一部分类型和结构问题,但模型输出内容仍然是运行期才知道的。类型安全保证“管道接得对”,不保证“模型答得对”。运行期如何持续评估 Agent 答得好不好?这需要回到 M10 10.4 的评估体系

参考资料

最后更新于 • Q1mi