ChatModel 是 Eino 框架中對對話大模型的抽象,它提供了統一的介面來與不同的大模型服務(如 OpenAI、Ollama 等)進行互動。
這個元件在以下場景中發揮重要作用:
- 自然語言對話
- 文字生成和補全
- 工具呼叫的參數生成
- 多模態互動(文字、圖片、音訊等)
元件定義
介面定義
程式碼位置:eino/components/model/interface.go
go
type ChatModel 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)
BindTools(tools []*schema.ToolInfo) error
}Generate 方法
- 功能:生成完整的模型回應
- 參數:
- ctx:上下文物件,用於傳遞請求層級的資訊,同時也用於傳遞 Callback Manager
- input:輸入訊息列表
- opts:可選參數,用於配置模型行為
- 回傳值:
*schema.Message:模型生成的回應訊息- error:生成過程中的錯誤資訊
Stream 方法
- 功能:以串流方式生成模型回應
- 參數:與 Generate 方法相同
- 回傳值:
*schema.StreamReader[*schema.Message]:模型回應的串流讀取器- error:生成過程中的錯誤資訊
BindTools 方法
- 功能:為模型綁定可用的工具
- 參數:
- tools:工具資訊列表
- 回傳值:
- error:綁定過程中的錯誤資訊
核心定位 該介面是對話模型的核心抽象層,支援兩種呼叫模式:
- Generate :同步生成完整回應(適合常規對話場景)
- Stream :串流回應處理(適合長文字生成/即時互動)
架構特性
go
type ChatModel interface {
// 同步生成(典型AI对话模式)
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)
// 工具绑定机制(支持功能扩展)
BindTools(tools []*schema.ToolInfo) error
}關鍵設計點 :
- 多模型支援 :透過介面抽象實現不同 AI 引擎(OpenAI/MAAS)的相容
- 上下文感知 :使用 context.Context 支援逾時控制、鏈路追蹤等
- 可擴展參數 : ...Option 可變參數為不同實作提供配置擴展能力
- 工具熱綁定 : BindTools 實現執行時功能增強(推測支援 Function Calling 等特性)
工程實踐 :
透過 //go:generate 指令自動生成 ChatModelMock 模擬實作,說明:
- 介面優先設計原則
- 完善的單元測試支援
- 依賴注入能力(方便不同環境下的測試)
注意事項 :
- 並發安全 :註釋明確提示
BindTools與Generate存在非原子性操作,暗示需要同步控制 - 訊息協定 :依賴<schema.Message>定義的訊息格式(需結合具體協定分析)
- 串流生命週期 :
StreamReader需要配合 Close 操作確保資源釋放
Message 結構體
程式碼位置:eino/schema/message.go
go
type Message struct {
// Role 表示消息的角色(system/user/assistant/tool)
Role RoleType
// Content 是消息的文本内容
Content string
// MultiContent 是多模态内容,支持文本、图片、音频等
MultiContent []ChatMessagePart
// Name 是消息的发送者名称
Name string
// ToolCalls 是 assistant 消息中的工具调用信息
ToolCalls []ToolCall
// ToolCallID 是 tool 消息的工具调用 ID
ToolCallID string
// ResponseMeta 包含响应的元信息
ResponseMeta *ResponseMeta
// Extra 用于存储额外信息
Extra map[string]any
}Message 結構體是模型互動的基本結構,支援:
- 多種角色:system(系統)、user(使用者)、assistant(ai)、tool(工具)
- 多模態內容:文字、圖片、音訊、視訊、檔案
- 工具呼叫:支援模型呼叫外部工具和函式
- 元資訊:包含回應原因、token 使用統計等
公共 Option
Model 元件提供了一組公共 Option 用於配置模型行為:
程式碼位置:eino/components/model/option.go
go
type Options struct {
// Temperature 控制输出的随机性
Temperature *float32
// MaxTokens 控制生成的最大 token 数量
MaxTokens *int
// Model 指定使用的模型名称
Model *string
// TopP 控制输出的多样性
TopP *float32
// Stop 指定停止生成的条件
Stop []string
}可以透過以下方式設定 Option:
go
// 设置温度
WithTemperature(temperature float32) Option
// 设置最大 token 数
WithMaxTokens(maxTokens int) Option
// 设置模型名称
WithModel(name string) Option
// 设置 top_p 值
WithTopP(topP float32) Option
// 设置停止词
WithStop(stop []string) Option使用方式
單獨使用
go
import (
"context"
"fmt"
"io"
"github.com/cloudwego/eino-ext/components/model/openai"
"github.com/cloudwego/eino/components/model"
"github.com/cloudwego/eino/schema"
)
// 初始化模型 (以openai为例)
cm, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{
// 配置参数
})
// 准备输入消息
messages := []*schema.Message{
{
Role: schema.System,
Content: "你是一个有帮助的助手。",
},
{
Role: schema.User,
Content: "你好!",
},
}
// 生成响应
response, err := cm.Generate(ctx, messages, model.WithTemperature(0.8))
// 响应处理
fmt.Print(response.Content)
// 流式生成
streamResult, err := cm.Stream(ctx, messages)
defer streamResult.Close()
for {
chunk, err := streamResult.Recv()
if err == io.EOF {
break
}
if err != nil {
// 错误处理
}
// 响应片段处理
fmt.Print(chunk.Content)
}在編排中使用
go
import (
"github.com/cloudwego/eino/schema"
"github.com/cloudwego/eino/compose"
)
/*** 初始化ChatModel
* cm, err := xxx
*/
// 在 Chain 中使用
c := compose.NewChain[[]*schema.Message, *schema.Message]()
c.AppendChatModel(cm)
// 在 Graph 中使用
g := compose.NewGraph[[]*schema.Message, *schema.Message]()
g.AddChatModelNode("model_node", cm)Option 和 Callback 使用
Option 使用示例
go
import "github.com/cloudwego/eino/components/model"
// 使用 Option
response, err := cm.Generate(ctx, messages,
model.WithTemperature(0.7),
model.WithMaxTokens(2000),
model.WithModel("gpt-4"),
)Callback 使用示例
go
import (
"context"
"fmt"
"github.com/cloudwego/eino/callbacks"
"github.com/cloudwego/eino/components/model"
"github.com/cloudwego/eino/compose"
"github.com/cloudwego/eino/schema"
callbacksHelper "github.com/cloudwego/eino/utils/callbacks"
)
// 创建 callback handler
handler := &callbacksHelper.ModelCallbackHandler{
OnStart: func(ctx context.Context, info *callbacks.RunInfo, input *model.CallbackInput) context.Context {
fmt.Printf("开始生成,输入消息数量: %d\n", len(input.Messages))
return ctx
},
OnEnd: func(ctx context.Context, info *callbacks.RunInfo, output *model.CallbackOutput) context.Context {
fmt.Printf("生成完成,Token 使用情况: %+v\n", output.TokenUsage)
return ctx
},
OnEndWithStreamOutput: func(ctx context.Context, info *callbacks.RunInfo, output *schema.StreamReader[*model.CallbackOutput]) context.Context {
fmt.Println("开始接收流式输出")
defer output.Close()
return ctx
},
}
// 使用 callback handler
helper := callbacksHelper.NewHandlerHelper().
ChatModel(handler).
Handler()
/*** compose a chain
* chain := NewChain
* chain.appendxxx().
* appendxxx().
* ...
*/
// 在运行时使用
runnable, err := chain.Compile()
if err != nil {
return err
}
result, err := runnable.Invoke(ctx, messages, compose.WithCallbacks(helper))已有實作
- OpenAI ChatModel: 使用 OpenAI 的 GPT 系列模型 ChatModel - OpenAI
- Ollama ChatModel: 使用 Ollama 本地模型 ChatModel - Ollama
- ARK ChatModel: 使用 ARK 平臺的模型服務 ChatModel - ARK
自行實作參考
實作自訂的 ChatModel 元件時,需要注意以下幾點:
- 注意要實作公共的 option
- 注意實作 callback 機制
- 在串流輸出時記得完成輸出後要 close writer
Option 機制
自訂 ChatModel 如果需要公共 Option 以外的 Option,可以利用元件抽象的工具函式實作自訂的 Option,例如:
go
import (
"time"
"github.com/cloudwego/eino/components/model"
)
// 定义 Option 结构体
type MyChatModelOptions struct {
Options *model.Options
RetryCount int
Timeout time.Duration
}
// 定义 Option 函数
func WithRetryCount(count int) model.Option {
return model.WrapImplSpecificOptFn(func(o *MyChatModelOptions) {
o.RetryCount = count
})
}
func WithTimeout(timeout time.Duration) model.Option {
return model.WrapImplSpecificOptFn(func(o *MyChatModelOptions) {
o.Timeout = timeout
})
}Callback 處理
ChatModel 實作需要在適當的時機觸發回調,以下結構由 ChatModel 元件定義:
go
import (
"github.com/cloudwego/eino/schema"
)
// 定义回调输入输出
type CallbackInput struct {
Messages []*schema.Message
Model string
Temperature *float32
MaxTokens *int
Extra map[string]any
}
type CallbackOutput struct {
Message *schema.Message
TokenUsage *schema.TokenUsage
Extra map[string]any
}完整實作示例
go
import (
"context"
"errors"
"net/http"
"time"
"github.com/cloudwego/eino/callbacks"
"github.com/cloudwego/eino/components/model"
"github.com/cloudwego/eino/schema"
)
type MyChatModel struct {
client *http.Client
apiKey string
baseURL string
model string
timeout time.Duration
retryCount int
}
type MyChatModelConfig struct {
APIKey string
}
func NewMyChatModel(config *MyChatModelConfig) (*MyChatModel, error) {
if config.APIKey == "" {
return nil, errors.New("api key is required")
}
return &MyChatModel{
client: &http.Client{},
apiKey: config.APIKey,
}, nil
}
func (m *MyChatModel) Generate(ctx context.Context, messages []*schema.Message, opts ...model.Option) (*schema.Message, error) {
// 1. 处理选项
options := &MyChatModelOptions{
Options: &model.Options{
Model: &m.model,
},
RetryCount: m.retryCount,
Timeout: m.timeout,
}
options.Options = model.GetCommonOptions(options.Options, opts...)
options = model.GetImplSpecificOptions(options, opts...)
// 2. 开始生成前的回调
ctx = callbacks.OnStart(ctx, &model.CallbackInput{
Messages: messages,
Config: &model.Config{
Model: *options.Options.Model,
},
})
// 3. 执行生成逻辑
response, err := m.doGenerate(ctx, messages, options)
// 4. 处理错误和完成回调
if err != nil {
ctx = callbacks.OnError(ctx, err)
return nil, err
}
ctx = callbacks.OnEnd(ctx, &model.CallbackOutput{
Message: response,
})
return response, nil
}
func (m *MyChatModel) Stream(ctx context.Context, messages []*schema.Message, opts ...model.Option) (*schema.StreamReader[*schema.Message], error) {
// 1. 处理选项
options := &MyChatModelOptions{
Options: &model.Options{
Model: &m.model,
},
RetryCount: m.retryCount,
Timeout: m.timeout,
}
options.Options = model.GetCommonOptions(options.Options, opts...)
options = model.GetImplSpecificOptions(options, opts...)
// 2. 开始流式生成前的回调
ctx = callbacks.OnStart(ctx, &model.CallbackInput{
Messages: messages,
Config: &model.Config{
Model: *options.Options.Model,
},
})
// 3. 创建流式响应
// Pipe产生一个StreamReader和一个StreamWrite,向StreamWrite中写入可以从StreamReader中读到,二者并发安全。
// 实现中异步向StreamWrite中写入生成内容,返回StreamReader作为返回值
// ***StreamReader是一个数据流,仅可读一次,组件自行实现Callback时,既需要通过OnEndWithCallbackOutput向callback传递数据流,也需要向返回一个数据流,需要对数据流进行一次拷贝
// 考虑到此种情形总是需要拷贝数据流,OnEndWithCallbackOutput函数会在内部拷贝并返回一个未被读取的流
// 以下代码演示了一种流处理方式,处理方式不唯一
sr, sw := schema.Pipe[*model.CallbackOutput](1)
// 4. 启动异步生成
go func() {
defer sw.Close()
// 流式写入
m.doStream(ctx, messages, options, sw)
}()
// 5. 完成回调
_, nsr := callbacks.OnEndWithStreamOutput(ctx, sr)
return schema.StreamReaderWithConvert(nsr, func(t *model.CallbackOutput) (*schema.Message, error) {
return t.Message, nil
}), nil
}
func (m *MyChatModel) BindTools(tools []*schema.ToolInfo) error {
// 实现工具绑定逻辑
return nil
}
func (m *MyChatModel) doGenerate(ctx context.Context, messages []*schema.Message, opts *MyChatModelOptions) (*schema.Message, error) {
// 实现生成逻辑
return nil, nil
}
func (m *MyChatModel) doStream(ctx context.Context, messages []*schema.Message, opts *MyChatModelOptions, sr *schema.StreamWriter[*model.CallbackOutput]) {
// 流式生成文本写入sr中
return
}