Eino - 让系统具备工程化扩展能力
Eino - 让系统具备工程化扩展能力前言在大型 AI 应用开发中工程化能力是保障系统稳定、可维护、可扩展的关键。Eino 框架通过一系列设计模式让开发者能够轻松扩展系统能力。本篇文章将基于 lab03/callback/option_callback.go 的实现详细解析如何利用 Option 模式、Callback 机制以及 Go 高阶语法构建具备工程化扩展能力的 AI 应用。一、设计原则1.1 组合优于继承在传统面向对象设计中我们常用继承来扩展功能。但在大模型应用中模型类型繁多继承层次会变得复杂且脆弱。Eino 框架采用了组合优于继承的设计理念typeWrappedChatModelstruct{inner*openai.ChatModel// 组合封装内部模型modelNamestringcallbacks[]ChatCallback// 组合注入回调机制}优势可以包装任意实现了ChatModel接口的类型回调机制可以动态添加/移除功能可以自由组合1.2 依赖注入通过构造函数将依赖注入而不是在内部创建funcNewWrappedChatModel(cfg*Config,callbacks...ChatCallback)(*WrappedChatModel,error){// 依赖通过参数传入而非硬编码inner,err:openai.NewChatModel(context.Background(),openai.ChatModelConfig{APIKey:cfg.Model.APIKey,Model:cfg.Model.ModelName,BaseURL:cfg.Model.BaseURL,})// ...}优势便于单元测试可以注入 mock 对象配置与代码分离灵活性高1.3 开放封闭原则系统对扩展开放对修改封闭。通过 Option 和 Callback 机制无需修改核心代码即可扩展功能。// 添加新功能只需定义新的 Option 或 Callback_,errw.Generate(ctx,msgs,WithRetryCount(2),// 新增重试次数WithTimeout(60*time.Second),// 新增超时控制)二、Option 模式实现2.1 为什么需要 Option 模式Go 语言的函数不支持默认参数和命名参数当函数参数过多时调用变得不友好// 不好的设计参数过多调用困难funcNewChatModel(apiKey,model,baseURLstring,timeoutint,temperaturefloat32,maxTokensint,topPfloat32,...)// 好的设计使用 Option 模式funcNewChatModel(apiKey,model,baseURLstring,opts...Option)chatModel,_:NewChatModel(key,gpt-4,WithTimeout(30*time.Second),WithTemperature(0.7),)2.2 Eino 的 Option 结构typeOptionstruct{// Has unexported fields.}Option 是 Eino 封装的不可变对象通过WrapImplSpecificOptFn函数创建funcWithRetryCount(countint)model.Option{returnmodel.WrapImplSpecificOptFn(func(o*MyChatModelOptions){o.RetryCountcount})}2.3 自定义选项实现// 1. 定义自定义选项结构typeMyChatModelOptionsstruct{RetryCountintTimeout time.Duration}// 2. 创建 Option 构造函数funcWithRetryCount(countint)model.Option{returnmodel.WrapImplSpecificOptFn(func(o*MyChatModelOptions){o.RetryCountcount})}funcWithTimeout(timeout time.Duration)model.Option{returnmodel.WrapImplSpecificOptFn(func(o*MyChatModelOptions){o.Timeouttimeout})}2.4 解析 OptionEino 提供了公开 API 来解析选项// 解析通用选项Temperature, MaxTokens 等标准选项commonOpts:model.GetCommonOptions(nil,opts...)// 解析实现特定选项自定义选项myOpts:model.GetImplSpecificOptions(MyChatModelOptions{},opts...)完整使用示例func(m*WrappedChatModel)Generate(ctx context.Context,messages[]*schema.Message,opts...model.Option)(*schema.Message,error){// 解析选项myOpts:model.GetImplSpecificOptions(MyChatModelOptions{},opts...)// 使用选项ifmyOpts.Timeout0{ctx,cancelcontext.WithTimeout(ctx,myOpts.Timeout)defercancel()}// ...}三、Callback 机制实现3.1 Callback 核心思想Callback回调是一种通知机制允许在特定事件发生时执行自定义逻辑。在大模型调用中我们需要在开始时记录日志、收集请求信息结束时记录响应、处理异常、统计性能3.2 定义 Callback 接口// 输入信息typeCallbackInputstruct{ModelstringMessages[]*schema.Message Extramap[string]any// 扩展信息}// 输出信息typeCallbackOutputstruct{Message*schema.Message Usage*schema.TokenUsage Extramap[string]any}// Callback 接口定义typeChatCallbackinterface{OnStart(ctx context.Context,in CallbackInput)OnEnd(ctx context.Context,out CallbackOutput,errerror)}3.3 实现 LoggingCallbacktypeLoggingCallbackstruct{}func(LoggingCallback)OnStart(ctx context.Context,in CallbackInput){fmt.Println( [callback] start )fmt.Println(model:,in.Model)fori,m:rangein.Messages{fmt.Printf( [%d] role%s content%q\n,i,m.Role,m.Content)}iflen(in.Extra)0{fmt.Println(extra:,in.Extra)}}func(LoggingCallback)OnEnd(ctx context.Context,out CallbackOutput,errerror){fmt.Println( [callback] end )iferr!nil{fmt.Println(error:,err)return}ifout.Message!nil{fmt.Println(assistant:,out.Message.Content)}ifout.Usage!nil{fmt.Printf(usage: prompt%d completion%d total%d\n,out.Usage.PromptTokens,out.Usage.CompletionTokens,out.Usage.TotalTokens)}}3.4 在 Generate 中集成 Callbackfunc(m*WrappedChatModel)Generate(ctx context.Context,messages[]*schema.Message,opts...model.Option)(*schema.Message,error){// 1. 解析选项myOpts:model.GetImplSpecificOptions(MyChatModelOptions{},opts...)// 2. 构建 callback 输入in:CallbackInput{Model:m.modelName,Messages:messages,Extra:map[string]any{retry_count:myOpts.RetryCount,timeout:myOpts.Timeout.String(),},}// 3. 执行 OnStart 回调for_,cb:rangem.callbacks{cb.OnStart(ctx,in)}// 4. 执行业务逻辑重试生成// ... 生成逻辑 ...// 5. 构建 callback 输出out:CallbackOutput{Message:resp,Usage:usage,Extra:map[string]any{latency_ms:time.Since(start).Milliseconds(),},}// 6. 执行 OnEnd 回调for_,cb:rangem.callbacks{cb.OnEnd(ctx,out,lastErr)}returnresp,nil}3.5 Callback 执行流程┌─────────────────────────────────────────────────────────┐ │ Generate │ ├─────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────┐ │ │ │ OnStart │ ◀── 遍历所有 callbacks │ │ │ callbacks │ (LoggingCallback 等) │ │ └──────┬──────┘ │ │ │ │ │ ▼ │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ 执行 │ ──▶ │ 失败? │──▶ 退避重试 │ │ │ Generate │ └─────────────┘ │ │ │ └──────┬─────┘ │ │ │ │成功 │ │ │ ▼ │ │ │ ┌─────────────┐ │ │ │ │ OnEnd │ ◀── 遍历所有 callbacks │ │ │ │ callbacks │ (带错误信息) └─────────────┘ │ └─────────────┘ │ │ └─────────────────────────────────────────────────────────┘四、Go 高阶语法应用4.1 泛型WrapImplSpecificOptFnfuncWrapImplSpecificOptFn[T any](optFnfunc(*T))Option这是 Go 1.18 的泛型应用。[T any]表示 T 可以是任意类型// 使用示例funcWithRetryCount(countint)model.Option{returnmodel.WrapImplSpecificOptFn(func(o*MyChatModelOptions){o.RetryCountcount})}泛型优势编译时类型检查安全可靠无需反射性能更好代码复用性高4.2 闭包Option 函数返回的func(o *MyChatModelOptions)是一个闭包funcWithRetryCount(countint)model.Option{// count 是自由变量被闭包捕获returnmodel.WrapImplSpecificOptFn(func(o*MyChatModelOptions){o.RetryCountcount// 闭包使用外部变量})}闭包特点可以访问外部函数作用域的变量变量是被捕获的引用而非副本常用于回调和异步场景4.3 可变参数...funcNewWrappedChatModel(cfg*Config,callbacks...ChatCallback)(*WrappedChatModel,error)callbacks ...ChatCallback表示可以接收零个或多个ChatCallback参数// 调用方式灵活w1:NewWrappedChatModel(cfg)// 无 callbackw2:NewWrappedChatModel(cfg,LoggingCallback{})// 一个 callbackw3:NewWrappedChatModel(cfg,cb1,cb2,cb3)// 多个 callbacks4.4 defer 与资源管理ifmyOpts.Timeout0{callCtx,cancelcontext.WithTimeout(ctx,myOpts.Timeout)defercancel()// 确保超时后取消 context}defer 特点在函数返回前执行即使发生 panic 也会执行常用于资源清理关闭文件、释放锁、取消 context4.5 错误处理与 sentinel erroriferrors.Is(callCtx.Err(),context.DeadlineExceeded)||errors.Is(callCtx.Err(),context.Canceled){break}错误处理模式使用errors.Is判断错误类型保留错误链fmt.Errorf(重试失败: %w, err)区分不同错误类型采取不同策略4.6 切片与动态功能扩展typeWrappedChatModelstruct{callbacks[]ChatCallback// 切片支持动态添加}// 支持多个 callbackfor_,cb:rangem.callbacks{cb.OnStart(ctx,in)}切片优势长度可变零到多个可以组合多个 callback顺序执行互不影响4.7 结构体标签与 YAML 解析typeModelConfigstruct{BaseURLstringyaml:base_urlAPIKeystringyaml:api_keyModelNamestringyaml:model_nameTimeoutintyaml:timeoutTemperaturefloat64yaml:temperature}结构体标签用途yaml:base_url指定 YAML 字段映射编译时检查字段对应关系解析结果自动填充到结构体五、完整代码解析5.1 代码结构总览callback/ ├── Config / ModelConfig / AppConfig # 配置结构体 ├── loadConfig() # 配置加载 ├── MyChatModelOptions # 自定义选项结构 ├── WithRetryCount / WithTimeout # Option 构造函数 ├── CallbackInput / CallbackOutput # Callback 数据结构 ├── ChatCallback interface # Callback 接口 ├── LoggingCallback # Callback 实现 ├── WrappedChatModel # 封装模型 │ ├── NewWrappedChatModel() # 构造函数 │ └── Generate() # 生成方法含重试逻辑 └── main() # 入口5.2 核心流程funcmain(){// 1. 加载配置cfg,_:loadConfig(config.yml)// 2. 创建封装模型注入 callbackw,_:NewWrappedChatModel(cfg,LoggingCallback{})// 3. 调用生成传入 optionsw.Generate(ctx,msgs,WithRetryCount(2),WithTimeout(60*time.Second),)}5.3 重试机制forattempt:0;attemptretries;attempt{r,err:m.inner.Generate(callCtx,messages)iferrnil{resprbreak}lastErrerr// Context 取消/超时则停止重试iferrors.Is(callCtx.Err(),context.DeadlineExceeded){break}// 指数退避time.Sleep(time.Duration(attempt1)*250*time.Millisecond)}六、最佳实践总结6.1 Option 模式最佳实践实践说明不可变性Option 创建后不可修改命名规范With前缀如WithTimeout合理默认值在结构体中设置默认值文档注释说明参数含义和取值范围6.2 Callback 机制最佳实践实践说明单一职责每个 Callback 只做一件事错误处理OnEnd 中正确处理 err性能意识避免在 callback 中做耗时操作扩展性使用接口而非具体类型6.3 工程化建议配置外置敏感信息和环境差异通过配置文件管理错误分类区分可重试错误和不可重试错误日志规范记录足够上下文便于排查问题超时控制防止无限等待合理设置超时时间资源清理使用 defer 确保资源释放七、扩展阅读7.1 更多 Callback 场景场景实现方式埋点统计在 OnEnd 中上报数据敏感词过滤在 OnStart 中检查输入速率限制在 OnStart 中检查配额链路追踪在 OnStart/OnEnd 中记录 trace7.2 相关 Go 语法Go 泛型入门Go 错误处理Go Context 模式八、运行示例# 运行代码go run option_callback.go-config../config.yml输出示例配置加载成功: base_urlhttps://api.minimaxi.com/v1, modelMiniMax-M2.7 [callback] start model: MiniMax-M2.7 [0] rolesystem content你是一个简洁、专业的篮球教练。 [1] roleuser content我每周打球2次想提升运球和终结请给我一周训练计划。 extra: map[retry_count:2 timeout:60s] [callback] end assistant: (AI 回复内容) usage: prompt39 completion500 total539 extra: map[latency_ms:12067]通过本文的学习你应该掌握了设计原则组合优于继承、依赖注入、开放封闭Option 模式自定义选项、灵活配置Callback 机制生命周期钩子、扩展功能Go 高阶语法泛型、闭包、可变参数、defer、错误处理这些技术组合在一起让 Eino 框架具备了强大的工程化扩展能力。