适用读者:要用 Go 把"多步模型调用"写成可控流程、而不是一串
if的人;被节点类型对齐折磨过的人。
前置:S21-B02(ReAct 与规划)、B03(工具设计)、J01(Harness 工程)。
覆盖旧文:A-3581(1. Chain 与 Graph 编排)、A-3584(2. Workflow 与字段映射)、A-3673(链 / LangChain 对照)→ 3 篇 / 6.4 万字符。
扩展点:E-S21-019(编排三步:定义→连接→编译)、E-S21-020(Graph 的类型对齐困境与三种错解)、E-S21-021(Workflow 的"控制流 / 数据流分离"判据)。
上一篇:S21-J05 · CLI 与 GUI Agent
下一篇:S21-J07 · ADK 多智能体编排与生产化特性。
1. 一句话结论
ReAct 让模型自由发挥,编排让你自己定顺序。 前者适合"下一步不确定",后者适合"流程已知但分支多"。Eino 的 compose 包把编排拆成定义 → 连接 → 编译三步;编译期做类型检查,错误不等到运行时才炸。
出处:
A-3581、A-3584(秀才,Eino 教程,2026-04)。
Chain ⊂ Graph ⊂ Workflow(表达力)
Chain 简单 → Graph 任意 DAG → Workflow 字段级映射
2. 三步模型(E-S21-019)
| 步 | 做什么 | 产物 |
|---|---|---|
| ① 定义节点 | 每个节点是一个组件:ChatModel / Prompt 模板 / 工具集 / 自定义函数(Lambda) | *compose.Node |
| ② 连接节点 | AddEdge(顺序)、AddBranch(条件)、并行扇出 |
DAG |
| ③ 编译 | Compile 做类型检查与连接校验 |
Runnable |
// 形状示意(API 以 Eino 当前版本为准)
chain := compose.NewChain[Input, Output]()
chain.AppendChatTemplate(tpl).AppendChatModel(model)
runnable, err := chain.Compile(ctx) // ← 类型/连接在这里报错
out, err := runnable.Invoke(ctx, in)
判据:"定义与执行分离"是它的核心收益——启动时编译一次,运行时零额外开销;相对的,很多动态解释型框架把错误留到运行时。
3. Chain:线性流水线
chain := compose.NewChain[map[string]any, *schema.Message]()
chain.
AppendChatTemplate(prompt.FromMessages(schema.FString, sysTpl, userTpl)).
AppendLambda(compose.InvokableLambda(func(ctx context.Context, in map[string]any) (map[string]any, error) {
// 自定义逻辑:清洗、拼装、取上下文
return in, nil
})).
AppendChatModel(model)
| 方法 | 追加什么 |
|---|---|
AppendChatModel |
模型节点 |
AppendChatTemplate |
Prompt 模板节点 |
AppendLambda |
任意函数(最常用) |
AppendBranch |
条件分支 |
要点:AppendLambda 是"逃生口"。任何框架没覆盖的逻辑,都包成 Lambda 塞进去,不必改框架。
4. Graph:任意 DAG
g := compose.NewGraph[In, Out]()
g.AddChatModelNode("model", model)
g.AddLambdaNode("score", compose.InvokableLambda(scoreFn))
g.AddEdge(compose.START, "model")
g.AddEdge("model", "score")
g.AddEdge("score", compose.END)
r, err := g.Compile(ctx)
| 能力 | API | 说明 |
|---|---|---|
| 顺序 | AddEdge(a, b) |
数据从 a 流到 b |
| 条件路由 | AddBranch(node, branch, map[string]bool{...}) |
按条件选边 |
| 并行 | 从同一节点扇出多条边 | 下游并行执行,再汇合 |
Invoke → 一次性拿完整结果
Stream → 边算边吐(首 token 更快,见 J05)
判据:要"流式输出"就用 Stream;要"拿完整对象再处理"就用 Invoke。 混用会让下游拿到半截数据。
5. Graph 的类型对齐困境(E-S21-020)
问题:AddEdge 同时决定"执行顺序"和"数据传递"——A 的输出必须能赋值给 B 的输入。
A 输出 []*schema.Message
B 输入 ScoreInput{Text string, Dimension string}
→ 类型对不上
三种常见错解:
| 错解 | 后果 |
|---|---|
| 万能结构体 | 每个节点背一堆无关字段,业务语义糊掉 |
全用 map[string]any |
丢掉编译期类型检查,运行时断言炸 |
| 到处插转换 Lambda | 几十个节点时,胶水比业务还多 |
判据:类型不匹配的根因是"控制流与数据流被绑在一起"。 解法不是加胶水,是把两者拆开。
6. Workflow:字段级映射(E-S21-021)
wf := compose.NewWorkflow[In, Out]()
wf.AddLambdaNode("count", ...) // 只要 text
wf.AddLambdaNode("keyword", ...) // 只要 text + keyword
wf.AddLambdaNode("merge", ...) // 要两个结果
// 关键:用 AddInput 声明"这个节点的每个字段来自哪里"
wf.AddInput("count", compose.MapFields("text", "Text"))
wf.AddInput("merge", compose.MapFields("countResult", "Count", "keywordResult", "Hits"))
wf.AddInput("merge", compose.SetStaticValue("Unit", "char"))
| 机制 | 作用 |
|---|---|
MapFields |
把上游输出字段映射到本节点字段 |
MapFieldPaths |
深层路径映射(嵌套结构) |
SetStaticValue |
注入常量(单位、阈值) |
AddDependency |
只控顺序、不传数据 |
判据:控制依赖与数据依赖分开声明。 需要"先跑完 A 再跑 B,但 B 不用 A 的数据"时,用 AddDependency,别硬塞一个没用的字段。
6.1 三者怎么选
| 场景 | 选 |
|---|---|
| 线性 2–5 步 | Chain |
| 有分支/并行、节点类型天然一致 | Graph |
| 节点各自有业务结构、字段来源多样 | Workflow |
| 需要动态调整拓扑(运行时改图) | 以上都不合适 → 自己写调度 |
7. 反例与排障
| 现象 | 原因 | 处理 |
|---|---|---|
Compile 报类型不匹配 |
边两端类型不一致 | 改 Workflow + MapFields |
运行时 nil 字段 |
映射的字段名拼错 | 用小图先跑通映射 |
| 并行节点结果顺序乱 | 汇合处没定顺序 | 用 map 收集后按 key 取 |
Stream 下游拿不到完整值 |
用了只支持 Invoke 的 Lambda | 换 StreamableLambda 或改 Invoke |
| 分支永远走一条 | AddBranch 的条件函数恒真/恒假 |
打日志确认分支函数的输入 |
8. 常见误区
| 误区 | 纠正 |
|---|---|
| "Chain 就是简化版 Graph,功能一样" | Chain 无分支/并行表达力 |
| "类型对不上就加 Lambda" | 胶水会淹没业务,应换 Workflow |
| "编译过了就一定跑得对" | 编译只管类型与连接,不管语义 |
| "并行一定更快" | 受下游汇合与限流约束 |
| "Workflow 比 Graph 高级" | 同一层,差别只在数据映射方式 |
9. 自测
- Eino 编排的三步是什么?哪一步做类型检查?
- Graph 里
AddEdge同时决定了哪两件事?这带来什么问题? - 节点 B 只要 A 输出的
Score字段,Graph 与 Workflow 各怎么写? - 只想控制顺序、不传数据,用哪个 API?
答案
- 定义节点 → 连接节点 → Compile(编译期类型检查)。
- 执行顺序 + 数据传递;导致相邻节点类型必须完全匹配。
- Graph:插转换 Lambda 或改类型;Workflow:
AddInput("B", MapFields("A", "Score", ...))。 AddDependency。
10. 下一篇
S21-J07 · ADK 多智能体编排与生产化特性 —— 四种 Agent 类型(ChatModel / Sequential / Parallel / Loop)如何嵌套,以及回调、中断恢复、Session Values 与中间件怎么把 Demo 推成生产。
本篇属于系列《S21 · Agent 工程与 MCP》。旧文来源:A-3581/3584;扩展点 E-S21-019/020/021。Eino API 名称以官方文档当前版本为准。