承接上一篇的实战内容,前面示例都是基于 adk.ChatModelAgent 间接调用大模型。
Agent 本质是上层封装,内部最终依旧依赖 ChatModel 组件完成 LLM 请求。本篇下沉至最底层核心组件 ChatModel,完整拆解模型组件的抽象设计、接口规范、消息结构体、同步/流式调用、通用配置、多模型适配,最后提供可直接运行的 Demo,不带 Agent,直接操作 ChatModel,弄懂 Eino 所有上层组件的底层根基。
一、ChatModel 在 Eino 整体架构中的定位
先回顾 Eino 分层架构:
数据层(schema) → 组件层(components) → 编排层(compose) → Agent层(adk)
ChatModel 隶属于 components/model,属于最基础、最核心的可插拔组件。
- 1. adk 的 ChatModelAgent 只是上层业务封装,接收 BaseChatModel 接口实例;
- 2. compose 编排链路里可以直接放入 ChatModel 作为链路节点;
- 3. 所有第三方大模型服务商(DeepSeek、OpenAI、通义千问、Ollama)全部适配这套统一接口;
- 4. 上层 Agent、Chain、Graph 不需要关心底层是哪家模型,只面向抽象接口编程。
组件层两层分离:抽象接口 + 厂商实现
Eino 采用经典的接口与实现分离设计:
- 1. 抽象接口包
github.com/cloudwego/eino/components/model
定义接口标准,规定所有聊天模型必须具备哪些能力,业务代码依赖接口; - 2. 厂商实现包
eino‑ext
github.com/cloudwego/eino-ext/components/model/openai、ollama、ark 等适配层。
只要服务商兼容 OpenAI‑API 协议,都可以直接复用 openai 适配组件,只修改接口地址、密钥、模型名称。
简单理解:BaseChatModel 是插座;各个厂商的 ChatModel 是插头;上层 Agent、编排链路只管插座,不用关心插头品牌。
二、ChatModel 整套接口体系详解
2.1 基础顶层接口 BaseChatModel
type BaseChatModel interface {
Generate(ctx context.Context, messages []*schema.Message) (*schema.Message, error)
Stream(ctx context.Context, messages []*schema.Message) (schema.StreamReader[*schema.Message], error)
}两个最核心方法:
- 1. Generate:同步一次性请求,等待模型返回完整应答;适合后台任务、一次性文本处理;
- 2. Stream:流式返回,返回 StreamReader 迭代器,逐个读取 token 分片;适合网页对话、终端实时输出,降低首字等待时延。
在此之上衍生出继承接口:
- 1.
ToolCallingChatModel:继承 BaseChatModel,新增绑定工具集、支持函数调用; - 2.
AgenticModel:面向复杂 Agent 场景,支持 Agent‑消息块、多工具类型。
日常普通对话场景只需要使用 BaseChatModel 即可。
三、底层数据载体 schema.Message
所有传入模型、模型返回的数据统一使用 *schema.Message,也是整套框架的数据标准:
消息角色常量
schema.SystemMessage(content string) // 系统提示词,设定助手人设、规则
schema.UserMessage(content string) // 用户提问消息
schema.AssistantMessage(content string) // AI 返回消息
schema.ToolMessage(content string, toolCallID string) // 工具返回结果Message 内置核心字段
- 1. Role:消息角色;
- 2. Content:普通文本内容;
- 3. MultiContent:多模态消息,存放图片、音频等;
- 4. ToolCalls:模型发起的工具调用请求;
- 5. ResponseMeta:模型返回元信息,最常用 Token‑消耗用量;
重点优势:不同厂商 SDK 的消息结构体互不兼容,Eino 使用 schema.Message 做统一封装,切换模型不需要改动消息组装逻辑。
四、openai 适配层 ChatModelConfig 常用配置项
eino_openai.ChatModelConfig 为 OpenAI 协议模型通用配置,关键参数说明:
- 1. BaseURL:服务商接口地址,DeepSeek 需要填写
https://api.deepseek.com; - 2. APIKey:服务商密钥;
- 3. Model:模型名称;
- 4. Temperature:*float32 指针,随机性,0 精准,1 高随机;传 nil 使用模型默认参数;
- 5. MaxTokens:单次回答最大 token;
- 6. TopP、Stop、PresencePenalty 等原生 OpenAI 请求参数全部支持。
框架内所有可选数值配置全部使用指针类型,用来区分「用户主动设置数值」和「使用默认值」,所以项目通用泛型指针工具
ptr[T any]()。
五、同步调用 Generate 和流式调用 Stream 的适用场景
5.1 Generate(同步一次性返回)
执行流程:
- 1. 组装好完整的 []*schema.Message 对话上下文;
- 2. 发起 HTTP 请求,阻塞等待模型全部输出完毕;
- 3. 返回完整 *schema.Message,可读取 Content、Token 消耗元数据。
适用场景:
- • 后台批量文本处理、文档摘要;
- • 不需要实时打字效果、能够接受整体等待;
- • 需要一次性拿到完整响应再进入下一步 compose 编排节点。
5.2 Stream(流式分片读取)
执行流程:
- 1. 调用 model.Stream 获取流读取器 StreamReader;
- 2. 使用 Recv() 循环读取每一块增量 token;
- 3. io.EOF 代表流正常结束;
- 4. 必须 defer stream.Close(),释放网络连接、goroutine 资源,防止内存泄漏。
适用场景:
- • 网页聊天、终端交互、聊天机器人;
- • 需要降低首 token 延迟,给到用户即时反馈。
六、ChatModel 组件的可插拔特性与模型切换演示
得益于面向接口编程,上层代码只依赖 model.BaseChatModel,更换大模型只改动模型初始化函数,消息组装、同步/流式调用代码完全不用改动。
- • DeepSeek:BaseURL = https://api.deepseek.com
- • OpenAI:BaseURL = https://api.openai.com
- • Ollama 本地部署模型:更换为 ollama 的 ChatModel 构造函数
七、完整可运行 Demo:直接调用 ChatModel,不使用 Agent
演示内容:通用指针工具、初始化模型、组装 schema 消息、同步 Generate、流式 Stream、首 token 计时、Token‑用量统计、流资源关闭。
package main
import (
"context"
"errors"
"fmt"
"io"
"os"
"time"
eino_openai "github.com/cloudwego/eino-ext/components/model/openai"
"github.com/cloudwego/eino/schema"
)
// ==================== Eino 框架:ChatModel 直接调用 ====================
// 本 demo 演示不经过 Agent,直接使用 ChatModel 的 Generate(同步)和 Stream(流式)。
const (
baseURL = "https://api.deepseek.com"
modelName = "deepseek-v4-flash" // DeepSeek 模型,若不存在可改 deepseek-chat
)
// ptr 泛型指针工具函数,快速获取任意类型变量的指针
func ptr[T any](v T) *T {
return &v
}
// newChatModel 创建 DeepSeek ChatModel 实例(eino-ext 提供 OpenAI 兼容实现)。
func newChatModel(ctx context.Context) (*eino_openai.ChatModel, 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: baseURL,
APIKey: apiKey,
Model: modelName,
Temperature: ptr(float32(0.5)),
})
}
// buildMessages 构造对话消息:system 设定角色,user 提出问题。
func buildMessages() []*schema.Message {
return []*schema.Message{
schema.SystemMessage("你是一个 Go 语言学习高级助手,回答用户关于 Go 的问题,保持专业"),
schema.UserMessage("简单概括 Go 语言的优势有哪些"),
}
}
// generateDemo 演示同步生成:一次调用返回完整回复,适合对延迟不敏感的场景。
func generateDemo(ctx context.Context, model *eino_openai.ChatModel, messages []*schema.Message) {
fmt.Println("============ 同步生成 ============")
start := time.Now()
resp, err := model.Generate(ctx, messages)
if err != nil {
fmt.Printf("Generate 失败:%v\n", err)
return
}
fmt.Println(resp.Content)
if resp.ResponseMeta != nil && resp.ResponseMeta.Usage != nil {
u := resp.ResponseMeta.Usage
fmt.Printf("Prompt tokens: %d Completion tokens: %d Total tokens: %d\n",
u.PromptTokens, u.CompletionTokens, u.TotalTokens)
}
fmt.Printf("[总耗时] %d ms\n\n", time.Since(start).Milliseconds())
}
// streamDemo 演示流式生成:逐 token 输出,首 token 延迟低,适合交互场景。
func streamDemo(ctx context.Context, model *eino_openai.ChatModel, messages []*schema.Message) {
fmt.Println("============ 流式输出 ============")
start := time.Now()
stream, err := model.Stream(ctx, messages)
if err != nil {
fmt.Printf("Stream 失败:%v\n", err)
return
}
defer stream.Close()
firstChunk := true
for {
chunk, err := stream.Recv()
if errors.Is(err, io.EOF) {
break // 流正常结束
}
if err != nil {
fmt.Printf("\n流式读取出错:%v\n", err)
return
}
if firstChunk {
firstChunk = false
fmt.Printf("[首 token 时间] %d ms\n", time.Since(start).Milliseconds())
}
fmt.Print(chunk.Content)
}
fmt.Printf("\n[总耗时] %d ms\n", time.Since(start).Milliseconds())
fmt.Println("============ 流式输出结束 ============")
}
func main() {
ctx := context.Background()
chatModel, err := newChatModel(ctx)
if err != nil {
fmt.Printf("初始化模型失败:%v\n", err)
return
}
messages := buildMessages()
// 1. 同步生成:完整回复 + token 用量统计
generateDemo(ctx, chatModel, messages)
// 2. 流式生成:首 token 耗时 + 逐字输出
streamDemo(ctx, chatModel, messages)
}八、Demo‑代码逐段要点解析
8.1 泛型 ptr 指针工具
Eino 大量配置字段选用指针类型,用来区分「未赋值(nil)」和「主动设置数值」;
泛型 ptr 可以适配 float32、int、bool、string 所有类型,是项目通用工具。
8.2 newChatModel 模型实例初始化
- 1. 读取环境变量密钥,做前置校验;
- 2. 填充 OpenAI‑兼容配置结构体;
- 3. 返回具体厂商实现的 ChatModel,该结构体实现 BaseChatModel 接口。
8.3 buildMessages 构建标准化对话上下文
全部采用 schema 包内置构造函数生成消息切片,system 设置助手身份,user 设置用户提问;
后续不管同步还是流式调用,传入的消息结构体永远是统一标准。
8.4 generateDemo 同步调用
- 1. 记录开始时间用于耗时统计;
- 2. 调用 Generate 一次性获取完整应答消息;
- 3. 打印回答文本,读取 ResponseMeta.Usage 获取 Token 消耗;
Token 统计在计费、日志、性能监控场景非常关键。
8.5 streamDemo 流式调用重点注意事项
- 1. Stream 返回 StreamReader,defer stream.Close() 强制关闭流;
- 2. 循环 Recv() 获取增量分片;
- 3. io.EOF 是流正常结束标记,不属于异常错误;
- 4. 捕获第一个分片的耗时,观测首 token 延迟;
- 5. 循环打印分片 Content,实现终端打字机效果。
九、ChatModel 组件的工程价值总结
- 1. 统一抽象:BaseChatModel 屏蔽各个大模型服务商 SDK 的差异化,消息结构、调用方法全部标准化;
- 2. 组件可替换:面向接口编程,业务代码无需改动即可切换 DeepSeek、OpenAI、本地 Ollama;
- 3. 两种原生调用模式:同步 Generate 用于离线任务,流式 Stream 用于交互式对话;
- 4. 上层所有高级能力,ChatModelAgent、Chain、Graph 本质上都是对 ChatModel 的二次封装;
- 5. 配套标准的消息结构体 schema.Message,支持普通文本、多模态、工具调用、Token 元数据。
MiaoAll