设计原则(DAG 怎么画 —— 全部规则)
1. 连线:每个节点连它的父,先往下、再往右
唯一的连线规则:每个节点连它的【父】,从父先往下(父正下方画竖线到子的行)、再往右(横到子)。所有连线都这样,没有例外。
- 父 = caller 优先(谁调用我),没有 caller 才用 predecessor。
- ROOT→问题:从 ROOT 下面竖 + 右到问题。问题必须连到 ROOT 上。
- 问题→A回复、A回复→工具:子调用从父先下后右,逐层缩进(函数调用逻辑)。
- 同一列的两个节点(问题 / B回复了):从上面那个正下方一条竖线直连下来。
- 不存在"一根贯穿整列的主干竖线";竖线只是"父正下方到子那一段"。节点之间不互相乱连,全靠"连父"自然成树。
2. 节点形状 = 角色
◇ ROOT(菱形,白填充) · ○ user(真人用户消息,圆) · △ llm(AI / agent 消息,三角) · □ code(工具 / 函数调用,方块) · ◉ merge(合并汇聚点,实心带孔圆)。
agent 之间发来的消息是 △(三角),不是 ○——圆只给真人用户。
3. 颜色 = 分支身份(不表示线的类型)
每条 lane(分支)一个颜色。任何线都用它所属/指向分支的 lane 颜色。绝不用颜色区分线的类型——那会和"颜色=分支"冲突。
4. 线型 = 连线类型(颜色之外的另一维,正交)
- ━ 实线:同分支调用(父→子,正常对话 / 子调用)
- - - 虚线 5 4:retry 分叉(用户手动重试 / 改写)
- -·- 点划线 4 2 1 2:大模型主动建分支(spawn_branch)
- ··· 点线 1 5:分支间通信(send_to_branch)
- ═ 粗实线 2.4:合并汇入(merge)
- ┄ 长虚线 4 4:attach 回流(子分支结果嵌回)
5. 列(lane / tier)
- tier(缩进):按角色固定。ROOT=0,user=1,llm=2,工具=3,子调用逐层 +1。子节点在父的右下角。
- lane(分支列):有几条分支排几列,按出现顺序 0,1,2,不跳号。
- fork 占两列:一条 fork 分支 = 它自己的竖线列 + 节点列。新分支整条排在它分叉出的那条分支【全部内容】的右边,不重叠。
- 正方形网格:横向格距 = 纵向格距,子节点严格在父的右下角(45°)。
- 紧凑:空行上移、空列左移,收起节点后腾出的行列要补上,不留空。
6. 行(depth)= 时间序
按全局 created_at(seq)排,早发生的在上。所以一条已存在的分支(如被通信的 B)排在引用它的操作(send)之前,不会被画成"通信时才新建"。retry 的兄弟对齐到同一行(同一时刻的平行可能)。
7. 分支间通信(agent ↔ agent)—— 异步
不是同步阻塞(那会让 A 干等 B、两条并行分支变串行甚至死锁)。是异步,像"派后台 agent 干活 + 还能继续聊天":
- A 的 △ 调 send_to_branch → A 这边出一个 □ 工具节点;工具瞬间返回,A 不阻塞,继续自己的事。
- 消息跨到 B:B 那边相当于另一个 agent 发来消息 → 一个 △「B收到」,B 再产出 △「B应答」。B收到/B应答 属于 B 分支(B 的颜色、串在 B 上),不在 A 那边。
- B 答完,结果异步送回 A 末尾 → 一个 △「B回复了」,它是外部推回 A 的新一轮输入,和「问题」同级(user 级,挂 A 主干)。
- A 再对它回应 → △「A回应」。
- 两条通信线(··· 点线):A→B 发、B→A 回。两条分支各自的线都不停、不阻塞。
8. 分支合并 / 子分支服务
- 合并:N 条分支汇聚到一个 ◉ merge 节点(多父)。merge 落在 base 分支的 lane;其它分支用 ═ 粗汇入线汇进来。
- 子分支服务:A 调 spawn_branch 派子分支 B(-·- 点划线,B 的首节点和 spawn 节点同一行起)→ B 独立跑 → 结果 ┄ attach 回流嵌回 A。
9. 软连接线:默认隐藏,hover 端点才显示 + 动画
调用线(同分支 / retry / spawn —— 结构骨架)常驻显示。
软连接线(通信 send→收到、返回 应答→回复了、merge 汇入、attach 回流 —— 凡是跨分支的"谁连了谁")默认全部隐藏:只有鼠标放到这条线连接的两个端点节点中的任意一个上时,这条线才显示出来,并带流动动画(虚线流动,表示方向)。移开就隐藏。
理由:这类线会非常多,常驻会把图糊成一团;按需 hover 显示,既不挡视线又能查"这个节点连去了哪"。
10. 本图是什么
不是写死坐标的示意图。这里内置了和后端 graph_layout 一致的布局算法(lane/tier/depth + 占列规则),喂节点数据 → 算法自己算出每个节点的位置 → 画出来。上方切换场景看不同数据跑出来的真实布局。