流程可视化:把 Eino 编排图变成 Mermaid 图表

📅 2026/7/3 8:24:26
流程可视化:把 Eino 编排图变成 Mermaid 图表
系列「企业级 AI Agent 实现拆解」E26 篇。上一篇讲了 MCP 工具集成外部工具变 Eino Tool。这篇讲流程可视化——Graph、Chain、Workflow 编好之后怎么自动生成一张能看懂拓扑结构的 Mermaid 图。读完这篇你会知道GraphCompileCallback接口编译时回调拿到完整图结构GraphInfo里有什么节点、控制边、数据边、分支MermaidGenerator怎么把这些信息变成 Mermaid 语法Graph/Chain 和 Workflow 的边为什么要区别对待三种箭头的语义--、 control-only 、-.>一、问题编排代码越来越难读用 Eino 写复杂 Agent 的人都遇到过这个问题图构建代码超过 50 行之后光靠读代码已经很难快速理解节点之间的执行顺序。Graph 还好Chain 有了条件分支和并行节点之后Workflow 又引入了控制流和数据流分离——大脑要在代码顺序和执行顺序之间反复切换。解决方案直接编译时生成一张图。二、入口GraphCompileCallback 接口Eino 的compose包定义了一个编译回调接口// compose/introspect.gotypeGraphCompileCallbackinterface{OnFinish(ctx context.Context,info*GraphInfo)}OnFinish在每次图编译成功后被调用。GraphInfo包含编译时能拿到的一切typeGraphInfostruct{NamestringNodesmap[string]GraphNodeInfo// key → 节点信息组件类型、是否嵌套图等Edgesmap[string][]string// 控制边起点 → 终点列表DataEdgesmap[string][]string// 数据边起点 → 终点列表Branchesmap[string][]GraphBranch// 条件分支起点 → 分支列表// ...}关键区别Edges 是控制边决定执行顺序DataEdges 是数据边决定数据流向。Graph/Chain 里两者通常重合Workflow 里它们可以不同——这也是 Workflow 可视化复杂的原因。注册回调gen:visualize.NewMermaidGenerator(output/dir)runner,err:g.Compile(ctx,compose.WithGraphCompileCallbacks(gen),compose.WithGraphName(MyGraph),)Compile内部调用gen.OnFinish(ctx, info)图的完整信息就传进来了。三、MermaidGenerator 的核心逻辑devops/visualize/mermaid.go里的实现分四步收集所有节点Nodes Edges Branches 里出现的 ↓ 渲染节点普通节点 / Lambda / 嵌套子图 ↓ 渲染边控制边 数据边按 Workflow 模式决定是否加标签 ↓ 渲染分支菱形决策节点 出边节点形状规则节点类型Mermaid 形状示例普通节点方形[...]model[modelbr/(ChatModel)]Lambda 节点圆角(...)step(stepbr/(Lambda))嵌套 Graph/Chain/Workflowsubgraph递归渲染START / END椭圆([...])start_node([START])START 和 END 被重命名为start_node/end_node——因为end是 Mermaid 关键字直接用会破坏语法。四、三种边的语义Graph 和 Chain的边控制流和数据流几乎总是重合用最简单的箭头STARTprompt(ChatTemplate)model(ChatModel)ENDWorkflow专门把三种语义用箭头样式区分开// 控制 数据最常见start_node--controldata--b1// 只有控制AddDependency 产生节点要等前驱完成但不接收数据b1control-onlyannouncer// 只有数据控制流不走这条边但数据会从这里过来start_node-.data-only.-b2自动检测逻辑如果DataEdges和Edges完全一致说明是 Graph/Chain用简单箭头否则用带标签的 Workflow 样式。分支条件用菱形表示渲染错误:Mermaid 渲染失败: No diagram type detected matching given configuration for text: b1_branch_0{branch} b1 b1_branch_0 b1_branch_0 end_node b1_branch_0 b2五、三种编排的实际效果Graphcompose/graph/simpleg:compose.NewGraph[map[string]any,*schema.Message]()g.AddChatTemplateNode(prompt,pt)g.AddChatModelNode(model,cm)g.AddEdge(compose.START,prompt)g.AddEdge(prompt,model)g.AddEdge(model,compose.END)gen:visualize.NewMermaidGenerator(compose/graph/simple)g.Compile(ctx,compose.WithGraphCompileCallbacks(gen),compose.WithGraphName(SimpleGraph))生成START → prompt(ChatTemplate) → model(ChatModel) → ENDChaincompose/chainChain 把 Lambda、Branch、Passthrough、Parallel、嵌套 Graph 全串在一起生成图里会出现分支菱形节点b1和b2两个出口Parallel 展开为多个并行节点嵌套的rolePlayerChain展开为subgraphchain.Compile(ctx,compose.WithGraphCompileCallbacks(visualize.NewMermaidGenerator(compose/chain),),compose.WithGraphName(chain))Workflowcompose/workflow/4_control_only_branch这个例子故意展示控制流和数据流分离wf.AddLambdaNode(b1,...).AddInput(compose.START)// announcer 只依赖 b1 完成不接收 b1 的输出wf.AddLambdaNode(announcer,...).AddDependency(b1)// b2 的数据来自 START但控制流由 b1 的分支决定wf.AddLambdaNode(b2,...).AddInputWithOptions(compose.START,nil,compose.WithNoDirectDependency())gen:visualize.NewMermaidGenerator(compose/workflow/4_control_only_branch)wf.Compile(ctx,compose.WithGraphCompileCallbacks(gen),compose.WithGraphName(Workflow-Control-Only-Branch),)生成图里b1 → announcer是粗箭头 control-only START → b2是虚线箭头-.>六、输出文件和渲染NewMermaidGenerator(dir)创建后Compile触发时自动写两个文件文件内容GraphName.mdmermaid代码块可以直接粘贴到 GitHub/ObsidianGraphName.png渲染好的图片PNG 渲染优先找mmdc官方 CLI# 安装 mermaid CLInpminstall-gmermaid-js/mermaid-cli# 手动渲染mmdc-itopology.mmd-otopology.png没有mmdc就自动退化到 headless Chromechromedp用浏览器渲染 SVG 再截图。七、不想生成文件用 mermaid.live最快的方式把.md文件里的 mermaid 代码块内容粘贴到 https://mermaid.live实时渲染支持导出 PNG/SVG不需要装任何工具。开发调试阶段常用的工作流1. 编译时生成 .md 文件 2. 打开 mermaid.live 3. 粘贴 mermaid 代码块内容 4. 立刻看到拓扑图 5. 调整节点顺序 / 边关系重新 Compile 6. 刷新 mermaid.liveCI 里要生成可存档的图片才需要 mmdc。八、自己实现一个 GraphCompileCallbackMermaidGenerator能做的事其他工具也能做——只要实现GraphCompileCallbacktypemyLoggerstruct{}func(l*myLogger)OnFinish(_context.Context,info*compose.GraphInfo){fmt.Printf(图名: %s\n,info.Name)fmt.Printf(节点数: %d\n,len(info.Nodes))forstart,ends:rangeinfo.Edges{for_,end:rangeends{fmt.Printf( %s → %s\n,start,end)}}}// 接入g.Compile(ctx,compose.WithGraphCompileCallbacks(myLogger{}))想往 CI artifact 里写 JSON、或者把图结构上报到监控系统——都是这个接口。小结GraphCompileCallback是 Eino 编译阶段的唯一扩展点GraphInfo里包含节点、控制边、数据边、分支的完整结构。MermaidGenerator把它变成 Mermaid 语法普通节点方形Lambda 圆角嵌套图展开为 subgraphSTART/END 用椭圆并重命名避开关键字冲突。Graph/Chain 用简单箭头Workflow 自动区分三种边语义controldata、control-only、data-only。接入只需两行NewMermaidGenerator(dir)WithGraphCompileCallbacks(gen)。看图用 mermaid.live存档用 mmdc。开发期把图粘进 PR 描述其他人 review 代码时就能看到拓扑不用再脑补。下篇继续。代码来源cloudwego/eino-examples · cloudwego/eino