02搭建开发环境创建第一个 Eino 项目《Eino 实战用 Go 构建 AI 应用与智能体》· 第 02 篇关键词Go Modules、依赖版本、eino-ext、ChatModel、环境变量、流式输出前置条件已安装 Go 1.18 或更高版本本文在 Go 1.25 上验证能够访问你选择的模型服务。上一篇我们建立了整体印象Eino 负责组织模型、检索和工具而不是替代它们。这一篇动手搭建工程骨架跑通第一次真实调用并顺手把配置、版本和流式输出这几件容易埋坑的事处理好。读完本篇你应该拥有一个可以继续往上加的 Go 项目而不是停留在复制粘贴的示例片段。一、开始之前先想清楚三个问题在敲下go mod init之前先确认用哪个模型服务是官方云服务、自建网关还是本地部署这决定了是否需要自定义BaseURL。密钥从哪里来绝不要写进源码、提交到版本库或打印进日志。用环境变量或密钥管理服务。失败时怎么办超时、限流、网络中断都会发生。第一篇里我们只做了一次简单调用项目起点就应该带上context超时和错误处理。这三个问题不解决代码能跑起来也走不远。二、创建项目mkdirticket-assistantcdticket-assistant go mod init example.com/ticket-assistantmodule名称按你所在团队的规范来例如公司域名反写。这里用示例域名便于直接复制。建议一开始就规划目录但不要过度设计ticket-assistant/ ├── go.mod ├── go.sum ├── cmd/ │ └── assistant/ │ └── main.go # 程序入口 └── internal/ └── model/ └── chatmodel.go # 模型初始化与封装internal目录能防止被外部模块导入适合放模型封装这类内部实现。目录会随功能增长下一篇开始加入检索和工具时再扩展。三、安装依赖并固定版本Eino 的核心与各组件实现分属不同仓库和模块。安装时先把核心框架和模型适配器一起引入go get github.com/cloudwego/einov0.9.21 go get github.com/cloudwego/eino-ext/components/model/openaiv0.1.13这里明确写了版本号。如果只写latest不同时间点拉到的版本可能不同示例里的接口也可能对不上。版本固定下来问题才可复现。写入代码后让 Go 整理依赖关系go mod tidy几个需要注意的点核心与扩展版本不联动。github.com/cloudwego/eino和github.com/cloudwego/eino-ext/...是独立模块各自有版本号不存在“框架是 v0.9.21扩展也必须是 v0.9.21”的对应关系。eino-ext不是单一模块。模型、检索器等实现以独立模块发布各自有自己的go.mod。go.mod与go.sum都应提交。它们共同锁定依赖缺一不可。关于 Go 版本Eino 核心与本文使用的 OpenAI 适配器都声明要求go 1.18及以上。这属于依赖声明的最低要求不代表你可以在任意更老的 Go 上获得相同的工具链体验例如泛型相关的编译错误提示。四、配置模型接入1. 用环境变量保存配置密钥、地址和模型名称都应从外部注入。启动时设置exportOPENAI_API_KEY你的密钥exportOPENAI_MODELgpt-4.1-mini# 使用兼容 OpenAI 协议的自建网关或第三方服务时填写官方地址可留空exportOPENAI_BASE_URL在生产环境里这些值应由配置中心或密钥管理服务下发而不是写进 shell 脚本。2. 封装模型初始化把初始化逻辑集中到一个文件业务代码就不必到处重复处理密钥和地址packagemodelimport(contexterrorsfmtostimegithub.com/cloudwego/eino-ext/components/model/openai)// NewChatModel 从环境变量读取配置并创建 ChatModel。funcNewChatModel(ctx context.Context)(*openai.ChatModel,error){apiKey:os.Getenv(OPENAI_API_KEY)modelName:os.Getenv(OPENAI_MODEL)ifapiKey||modelName{returnnil,errors.New(必须设置 OPENAI_API_KEY 和 OPENAI_MODEL)}cm,err:openai.NewChatModel(ctx,openai.ChatModelConfig{APIKey:apiKey,BaseURL:os.Getenv(OPENAI_BASE_URL),// 留空则使用默认地址Model:modelName,Timeout:30*time.Second,})iferr!nil{returnnil,fmt.Errorf(创建 ChatModel 失败: %w,err)}returncm,nil}说明几个设计取舍错误里带上上下文但不带密钥。%w保留了错误链方便上层判断是否为超时或认证失败同时没有把apiKey拼进错误信息。在初始化阶段就校验配置。少了环境变量程序应立刻明确报错而不是等到第一次调用才失败。Timeout交给适配器。适配器会据此设置底层 HTTP 客户端。你也可以传入自己的HTTPClient此时Timeout不再生效。五、完成第一次调用下面是一个完整的可运行程序放在cmd/assistant/main.go。为了能独立运行并方便验证它把模型初始化也包含在内packagemainimport(contexterrorsfmtiologosstringstimegithub.com/cloudwego/eino-ext/components/model/openaigithub.com/cloudwego/eino/schema)funcmain(){iferr:run();err!nil{log.Fatal(err)}}funcrun()error{// 为整次请求设置超时避免调用悬挂。ctx,cancel:context.WithTimeout(context.Background(),60*time.Second)defercancel()cm,err:newModel(ctx)iferr!nil{returnerr}ticket:用户反馈三楼办公室的打印机无法联网重启后仍显示离线影响当日多份合同打印。// 换成 streamSummarize 即可改为流式输出。returnsummarize(ctx,cm,ticket)}funcnewModel(ctx context.Context)(*openai.ChatModel,error){apiKey:os.Getenv(OPENAI_API_KEY)modelName:os.Getenv(OPENAI_MODEL)ifapiKey||modelName{returnnil,errors.New(必须设置 OPENAI_API_KEY 和 OPENAI_MODEL)}cm,err:openai.NewChatModel(ctx,openai.ChatModelConfig{APIKey:apiKey,BaseURL:os.Getenv(OPENAI_BASE_URL),Model:modelName,Timeout:30*time.Second,})iferr!nil{returnnil,fmt.Errorf(创建 ChatModel 失败: %w,err)}returncm,nil}// summarize 以非流式方式生成工单摘要。funcsummarize(ctx context.Context,cm*openai.ChatModel,ticketstring)error{msg,err:cm.Generate(ctx,buildMessages(ticket))iferr!nil{returnfmt.Errorf(生成摘要失败: %w,err)}ifmsgnil||strings.TrimSpace(msg.Content){returnerrors.New(模型返回了空内容)}fmt.Println(摘要,strings.TrimSpace(msg.Content))returnnil}// streamSummarize 以流式方式生成工单摘要。funcstreamSummarize(ctx context.Context,cm*openai.ChatModel,ticketstring)error{sr,err:cm.Stream(ctx,buildMessages(ticket))iferr!nil{returnfmt.Errorf(发起流式调用失败: %w,err)}defersr.Close()fmt.Print(摘要)for{chunk,err:sr.Recv()iferrors.Is(err,io.EOF){break}iferr!nil{returnfmt.Errorf(读取流式响应失败: %w,err)}fmt.Print(chunk.Content)}fmt.Println()returnnil}funcbuildMessages(ticketstring)[]*schema.Message{return[]*schema.Message{schema.SystemMessage(你是工单助手。请把工单描述整理成不超过三行的摘要保留设备、地点和影响范围不要推测根因或处理结果。,),schema.UserMessage(ticket),}}运行go run ./cmd/assistant如果配置正确终端会打印一行不超过三行的工单摘要。这里的几个关键点context贯穿调用。取消和超时会沿调用链传递下去前端断开连接时后端也能及时停止。Generate返回完整消息Stream返回读取器。两者输入相同输出方式不同。流式读取器必须关闭。defer sr.Close()不是可选项否则可能泄漏资源。别忘了检查空内容。模型有时会返回空文本业务侧需要明确处理而不是把空串当成有效摘要。六、验证方式我无法替你调用真实模型但可以在本地完成两类检查。第一类是与模型服务无关的静态检查任何环境都能跑go vet ./... go build ./...上文的完整程序已在Go 1.25 Eino v0.9.21 eino-ext/components/model/openai v0.1.13下通过编译与go vet。第二类是与真实服务联调的冒烟测试需要你的密钥和网络go run ./cmd/assistant建议再用两个反例确认错误路径确实生效# 缺少配置时应立刻报错而不是发起调用OPENAI_API_KEYOPENAI_MODELgo run ./cmd/assistant# 用错误密钥确认认证失败能被识别OPENAI_API_KEYinvalidOPENAI_MODELgpt-4.1-mini go run ./cmd/assistant能跑通一次不等于接好了。至少确认配置缺失时明确报错、认证失败时错误可读、超时能被触发并按预期结束。七、项目结构建议起步阶段把关注点分开即可位置放什么不该放什么cmd/组装依赖、解析参数、启动业务规则internal/model模型初始化与适配提示词内容的最终归属可单列internal/assistant业务逻辑与提示词密钥、硬编码地址不要一上来就套用复杂的 DDD 分层。等检索、工具、Agent 出现后目录自然会告诉你该往哪里拆。八、常见问题排查报错包含401、Unauthorized、invalid api key密钥缺失、过期或不适用于当前地址。先确认OPENAI_API_KEY是否为占位值再确认密钥与服务地址是否匹配。报错包含base URL或返回404BaseURL填成了非聊天补全地址或自建网关路径不对。先确认地址本身在命令行下可用再回来看代码。报错包含context deadline exceeded、timeout请求超过了Timeout或外层context的限制。先区分是网络慢、模型慢还是外层超时设得过短。报错包含context canceled通常是调用方主动取消例如用户断开连接。这不是框架故障应从应用层确认取消来源。版本冲突、找不到符号先执行go mod tidy再用go list -m all | grep cloudwego核对实际版本。示例与你本地版本不一致时以固定版本为准而不是反过来改示例。响应很慢或首字节迟迟不来这类现象通常和网络链路、模型负载有关。排查时把耗时拆开看连接建立、请求发送、首字节、完整响应而不是只看总时长。九、小结用go mod init建项目明确module名称。用go get ...版本号固定依赖提交go.mod与go.sum。配置从环境变量注入密钥永不进源码和日志。封装模型初始化统一处理超时与错误。Generate与Stream是两种输出方式流式读取器务必关闭。编译和go vet只能证明代码自洽能否真正调用成功需要真实服务验证。下一篇《ChatModel 实战消息、参数与多轮对话》我们会深入消息结构、生成参数和多轮历史的组织方式弄清楚“上下文”到底是怎么传给模型的。参考资料与版本说明资料核对日期为2026 年 10 月 9 日Eino 快速开始ChatWithEinoChatModel 使用说明ChatModel 组件集成Eino 主仓库、eino-ext 仓库本文示例按Eino v0.9.21、eino-ext/components/model/openai v0.1.13编写已通过编译与静态检查文中未执行需要密钥的真实模型调用。
阅读完成 · 觉得有帮助?