字节跳动 2026 年开源的 Eino 是 Go 语言 AI 应用框架里的异类——不抄 Python API,按 Go 的接口/结构体习惯从零设计。本文从单智能体写到 DeepAgent 多智能体编排,带可运行代码和选型对比。
2026 年 3 月,字节跳动把内部用了两年的 LLM 应用框架 Eino(读作 "I know")正式开源。跟 LangChainGo 不同,Eino 不是 Python API 的翻译——它从第一天起就用 Go 的接口、结构体、goroutine 来设计组件抽象和编排模型。我在一个 12 人后端团队的 AI 功能迭代里拿它替换了自研的 OpenAI 调用层:模型供应商切换从改 5 个文件变成改 1 行配置,工具调用链路从手动 JSON 解析变成框架自动处理。这篇文章把核心概念、可运行的代码、以及踩过的坑串一遍。
Python 仍然是 AI 研究和原型的主力语言,这点没变。但生产环境有三个硬伤在规模化之后会直接暴露:
Go 的方案很直接:goroutine 处理几千并发连接几乎没有心智负担,编译成一个 15-30MB 的静态二进制丢进容器,冷启动 < 200ms。字节用 Go 构建 Eino、Google 推 ADK-Go、社区维护 LangChainGo——2026 年的 Go AI 开发生态已经不需要自己拼 HTTP Client + 手写 JSON 解析了。关于 Go 在企业级 AI 项目中的完整选型考量,可参考我们之前的 Go 后端开发外包指南。
理解 Eino 最快的方式是看它的分层,而不是从 API 开始背:
| 层级 | 概念 | 做什么 |
|---|---|---|
| Component(组件) | 可复用的能力单元 | 对话模型组件调大模型、Tool 执行外部动作、Retriever 取相关信息、ChatTemplate 拼 prompt |
| Composition(编排) | 组件之间的连接方式 | Chain(线性的有向链)、有向图模式(支持分支和条件路由) |
| ADK(智能体开发) | 自主决策 + 工具调用 | 单智能体(ReAct 循环)、DeepAgent(多智能体协作 + 任务分解) |
组件抽象都用 Go interface 定义,对话模型的核心接口只暴露三个方法:Generate 做单次调用、Stream 做流式输出、BindTools 注册可用工具。具体实现放在 eino-ext 仓库——OpenAI、Claude、Gemini、字节自家的 Ark、Ollama 都有官方实现。切换模型供应商就是换一个构造函数,编排层代码完全不用动。
最简单的形态就是大模型 + ReAct 循环:模型决定要不要调工具、调哪个、拿到结果后要不要再调。Eino 把这一套内建好了:
import (
"github.com/cloudwego/eino/adk"
openai "github.com/cloudwego/eino/components/model/openai"
)
m, _ := openai.NewChatModel(ctx, &openai.ChatModelConfig{
Model: "gpt-4o", APIKey: os.Getenv("OPENAI_API_KEY"),
})
a, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{Model: m})
r := adk.NewRunner(ctx, adk.RunnerConfig{Agent: a})
// 流式查询
for iter := r.Query(ctx, "深圳今天天气?"); ; {
ev, ok := iter.Next()
if !ok { break }
fmt.Print(ev.Message.Content)
}
不加工具的时候这就是个带流式输出的聊天封装。接上工具之后,框架会自动走 ReAct 循环——调工具、读结果、决定要不要再调。Tool 接口是标准 Go interface:实现 Info() 返回工具描述和参数 schema,实现 InvokableRun() 写实际逻辑。框架自动把模型的 function call 响应映射到对应 Tool 执行,开发者不再需要手写 switch tool_name { case "weather": ... } 这种胶水代码。
Chain(线性链)适合「模板渲染 → 调模型 → 解析输出」这种单向流水线。一旦出现条件分支——比如「模型返回 tool_call 就走工具节点,返回普通文本就结束」——就需要更灵活的编排方式。Eino 提供了有向图 + Branch(N 选 1 的条件路由)。
建图流程很直接:用 compose.NewGraph 创建图,通过 AddChatTemplateNode、AddChatModelNode、AddToolsNode、AddLambdaNode 注册四种节点类型,然后用 AddEdge 连线、用 AddBranch 设条件路由。核心的条件分支逻辑长这样:
// 在模型节点 "llm" 后设置分支
g.AddBranch("llm", compose.NewGraphBranch(
func(_ context.Context, in *schema.Message) (string, error) {
if len(in.ToolCalls) > 0 {
return "tools", nil // 有工具调用请求 → 走工具节点
}
return "convert", nil // 无工具调用 → 走格式转换节点
},
map[string]bool{"tools": true, "convert": true},
))
最后 Compile() 编译,Invoke() 执行。一个实战场景:我们某个客户的内部知识库问答系统,需要根据用户问题类型走不同的检索策略——「查文档」走 Elasticsearch Retriever、「问数据」走 SQL Tool、「闲聊」直接走大模型。用 Branch 把三条路径画出来之后,整个流程变成一张可调试的有向图,新增第四种策略就是加一个节点 + 一条分支,不动现有逻辑。
编译好的有向图可以暴露为 Tool,让上层智能体按需调用。这意味着你可以把确定性的业务流水线(比如「数据校验 → 模型生成 → 格式转换」)编成图,再交给智能体决策什么时候执行。
单智能体能解决的问题有天花板——调用链超过 5 步之后 ReAct 循环容易跑偏,token 消耗也非线性增长。Eino 的 DeepAgent 把复杂任务拆成子任务,每个子任务交给专门的执行单元。你只需要在 deep.New() 时传入主模型、子智能体列表(如 researchAgent、codeAgent、reviewAgent)和可用工具集(shell、python、webSearch)。框架内部维护任务分解器,把「分析 report.csv 的销售数据,生成趋势图并给出结论」拆成三步,分别路由到对应执行单元,每个单元有自己的工具集和输入窗口,互不污染。
Eino 还支持 Interrupt/Resume——智能体或 Tool 在执行过程中可以主动暂停,等待人工输入后从检查点恢复。这个机制在审批类场景(比如「生成 SQL → 人工审核 → 执行」)里是刚需。框架层处理状态持久化和路由,业务代码只需要在 Tool 里 return interrupt("请确认 SQL: ...")。
Eino 对流的处理比较特别——不是让每个组件自己实现 Stream() 就完了。框架在编排层自动处理流的拼接、装箱、合并和复制:模型输出 StreamReader[*Message],下游节点可以是流式消费者也可以是批式消费者,Eino 负责中间的适配。不需要在每个节点里写 for chunk := range stream { ... } 然后手动拼 buffer。
Callbacks 机制则在组件执行的固定节点注入横切逻辑——日志、追踪、指标采集。用 NewHandlerBuilder() 注册三个钩子:OnStart(组件开始执行)、OnEnd(执行完成)、OnError(执行出错)。注入粒度支持全局(所有节点)、按组件类型(只对模型调用节点)、或按节点名精确指定。出问题时能精确到是哪个节点、哪次模型调用、耗时多少,排查效率远高于裸用 SDK。
| 维度 | Eino (CloudWeGo) | LangChainGo | MCP Go SDK (官方) |
|---|---|---|---|
| 定位 | 完整 AI 应用开发框架 | 通用 LLM 编排库 | 模型-工具通信协议 |
| Go 原生程度 | 从零按 Go 习惯设计 | Python API 的 Go 移植,部分模式生硬 | 协议层 SDK,Go 风格干净 |
| 编排能力 | Chain + 有向图 + 单/多智能体 | Chain + 基础智能体,有向图有限 | 无编排,只管通信 |
| 流式处理 | 框架自动拼接/装箱/合并 | 需要手动处理流转换 | Streamable HTTP 传输 |
| 模型支持 | OpenAI / Claude / Gemini / Ark / Ollama | 20+ 供应商 | 不绑定模型 |
| 可观测性 | 三级钩子注入,按节点/类型/全局 | 基础 callback | 无(依赖上层) |
| 中断/恢复 | 原生 Interrupt/Resume | 需自行实现 | 不涉及 |
| 适用场景 | 微服务团队生产级 AI 智能体 | 快速原型 + 多模型切换 | 暴露工具/数据源给 LLM |
实际选型不复杂:如果在 Go 微服务体系里构建 AI 智能体,Eino 的编排能力和 Go 原生设计是最佳匹配。如果需要灵活切换 20+ 模型供应商且不在意 Python 风格 API,LangChainGo 的生态广度有优势。MCP Go SDK 解决的是另一个问题——让 LLM 调用你的工具,它不负责编排逻辑,通常作为 Eino 或 LangChainGo 的 Tool 层来用。关于 MCP 协议在 Go 中的完整实践,见 Go 语言 MCP Server 实战以及 Go 与 Python MCP 方案对比。
能。对话模型是 interface,实现 Generate() 和 Stream() 就能接入任意模型。官方已经提供了 OpenAI 兼容 API 的实现,大部分私有化模型(vLLM、Ollama、LocalAI)都兼容这个接口。如果用的是字节的豆包/Ark,直接用 eino-ext 里的 Ark 实现,不需要走 OpenAI 兼容层。
工具 ≤ 5 个、任务步骤 ≤ 3 步、不需要子任务分解——直接用单智能体模式,资源开销最小。当系统需要协调多个专业子模块(比如一个做代码生成、一个做代码审查、一个跑测试),或者单次对话涉及多阶段推理(先查数据库、再分析、再画图),上 DeepAgent。注意 DeepAgent 每个子模块独立消耗 token,成本会比单智能体高 2-4 倍,不要为了架构好看无脑上多智能体。
不能。调用 Compile() 之后图结构就固定了。需要动态路由用 Branch——在编译前定义好所有可能的分支路径,运行时根据输出走不同分支。如果需求是「运行时动态添加节点」,Eino 目前不支持,需要在外层自己管理多个图实例。
Eino 在字节内部跑了两年才开源,豆包和抖音的部分 AI 功能背后就是它。开源版本和内部版本核心一致,区别是内部有一些字节基础设施(自研向量库、监控平台)的私有实现。开源社区目前处于快速增长期,GitHub issues 响应速度在 24-48 小时内。
OpenAI Go SDK 解决的是「怎么调 GPT-4」,Eino 解决的是「怎么把调 GPT-4、查数据库、发邮件、人工审批串成一个可维护的系统」。如果需求就是一个 Chat Completion 然后用结果,不需要换模型、不需要工具调用编排、不需要流式处理横切面——那确实不需要框架。一旦超过 3 个工具、或者未来可能换模型供应商,Eino 的抽象层就开始值回学习成本。