feat(flow): toolCalls 覆盖率追到全部编排回合(L3)—— 图带权威回合标识

- 新增 StartRoundWithTurnID(ctx, owner, text):从 ctx 取权威回合标识(与 chat_messages.turn_id
  同值);Run 的唯一生产建图入口切到它;原 StartRound 语义逐字不变(1 行包装)
- 归属桶(并行按路分图)保持不变;落盘查询键 lane_turn_id 对单路也有值 ⇒ 消息面按等值查得到图
- 无权威标识时**保持缺席**(不拿桶里的路标识顶上:不许臆造)
- 不需要迁移 0024(0023 的列+索引已在位;老单路行标识根本没存过,回填=编造)

形状变化(已登记契约):graph.id 对单路也追加 #{回合标识}(web-7 → web-7#turn-…),
顺带修掉「重启后单路轮次 upsert 掉旧行」的隐患。3 个变异自检(退回老入口/无视 ctx/臆造回退)。
This commit is contained in:
AskaEth committed 2026-10-06 14:12:25 +08:00
1 parent 6475eb875d
commit 4a937bd269
6 files changed
+353 -21

No files matched your search

+8 -2
View File
@@ -164,6 +164,11 @@ func sourcedLog(l *slog.Logger) *slog.Logger {
// 图 id(`trace.FlowGraph.ID`,也是本表的冲突键)。落盘时两个都写,使「按消息的那一路
// 查图」(`GraphsByTurnID`)是一次等值索引查询,而不是拿图 id 去反解。二次收尾同样
// 更新它:同一条路的图又被收一次时,标识必须跟着走到最新的那一份。
//
// ⚠️ `lane_turn_id` 的**列**口径(迁移 0023 的注释)写的是「单路回合的图不带该键 ⇒
// 落盘为空」,那是当时写侧的实情;写侧现已改为**每一路都取权威回合标识**(见
// `loop.FlowPublisher.StartRoundWithTurnID`)。列的形状与语义一字未改,只是**新的
// 单路行不再必然为空** —— 老行仍是空串(那时图里根本没存这个标识,任何回填都是编造)。
const upsertRoundSQL = `INSERT INTO flow_graphs
(session_id, turn_id, lane_turn_id, graph_json, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?)
@@ -179,8 +184,9 @@ ON CONFLICT(session_id, turn_id) DO UPDATE SET
// 它同时盖上这一路的**回合标识**(`g.TurnID` → `lane_turn_id`,迁移 0023),使消息面上
// 「这条回复当时调了什么工具」能按**等值**查到这一轮的图(见 `GraphsByTurnID`)。
// 标识为空时如实写空串:那一行因此按路查不到,读取侧让 `toolCalls` 整体缺席 ——
// 这正是契约的语义,绝不用时间窗或正文去猜(`g.TurnID` 为空的两类情形见迁移 0023
// 的注释:v0.77 之前的旧行与**当前**的单路回合)。
// 这正是契约的语义,绝不用时间窗或正文去猜。`g.TurnID` 为空只剩一类情形:
// **没有 ctx 通道**的调用方(未接编排器的写入路径、旧后端落下的历史行)——
// 编排器驱动的每一路(含普通单路回合与队首续跑路)都有权威回合标识。
//
// 失败**上抛**给调用方(发布器记 WARN 后继续),绝不 panic、绝不改变图 —— 图是可视化
// 资产,落盘失败不该影响会话本身。