数据截至 (上游 commit fefb05ff27be)
第 2 章 · WorkflowSchema 怎样编译成 eino 数据流图
本章讲什么: 这是本库的核心。
compose.NewWorkflow把WorkflowSchema编译成一张 cloudwego/eino 的compose.Workflow数据流图。重点是它怎样把画布上「节点 B 的某字段取自节点 A 的某字段」这种映射,翻译成 eino 的字段级输入边。
2.1 先建直觉:为什么不是简 单的 DAG
最朴素的做法是:连线 = 依赖,A→B 的线表示「A 跑完才跑 B,把 A 的整个输出给 B」。但工作流需要更细的粒度:
- B 的
query字段取自 A 的user.name,B 的topK字段是个写死的常量5,B 的history取自更早的节点 C——一个节点的输入可能来自多个上游的不同字段,还混着静态值。 - 有时「数据来自 A 的字段」但画布上 A 和 B 之间并没有连线(用户只在字段里引用了,没拉线)——这叫间接依赖:要拿 A 的数据,但不要求紧挨在 A 后面执行。
所以编译器要把每个节点的输入拆成几类,分别用 eino 的不同 API 表达。
2.2 一个节点 = 一个 Lambda + 一组输入边
eino 的 compose.Workflow 模型是:每个节点是一个 WorkflowNode,通过 AddInput(fromNode, fieldMappings...) 声明「我的某些字段来自某个上游节点的某些字段」。Coze 的编译就是把 schema 翻译成这些调用。
核心入口 NewWorkflow(compose/workflow.go:83):
// compose/workflow.go:87 —— 基于 eino 的 compose.NewWorkflow 建图,泛型参数是 map[string]any
wf := &Workflow{
workflow: compose.NewWorkflow[map[string]any, map[string]any](compose.WithGenLocalState(GenState())),
...
}
注意泛型:所有节点的输入输出统一是 map[string]any。这是 Coze 的一个关键设计——节点间传的是「字段名→值」的 map,字段映射就是在两个 map 之间搬字段。GenState() 是共享状态(见 04 章)。
建图分两步(workflow.go:117-141):先加所有复合节点(带内层子工作流),再加其余普通节点;遇到 Exit 节点记下它的「终止策略」(返回变量 / 返回文本)。最后 Compile 出一个 Runner。