feat(gateway): 面 1 补齐 —— 消息读取面 toolCalls[](工具回看)
前提由 FV2 提供(图自 v0.77 起带每路 turnId、每路各落一行)。本片:
- 迁移 0023:flow_graphs 加「按路查询键」(写入时回填图的 TurnID),老行按缺席处理
- GET /api/v1/sessions/{id}/messages 加**可选** toolCalls[],形状严格照 Docs/api-client.md
v0.76 ① 已登记的:{name, status(ok|error|unknown), durationMs, argsSummary, resultSummary}
- 🔴 图取不到 ⇒ **字段整体缺席**(绝不回空数组冒充「没调过工具」)
- 一条批量 SQL(按页内 turnId 集合),禁 N+1;单条 ≤20 个工具、resultSummary ≤500 字符(常量+依据)
- 只放图里已有的、已脱敏内容,不放宽脱敏通道
This commit is contained in:
1 parent
99a644d545
commit
c907a34510
10 files changed
+1429
-16
No files matched your search
@@ -3,8 +3,9 @@
|
||||
// 每次重载之后页面就没数据了」—— 在此之前图只活在 `loop.FlowPublisher` 的**内存**里,
|
||||
// 后端一重启、或该会话还没跑新轮次,就什么都查不到。
|
||||
//
|
||||
// 只做一件事:把**每一轮的收尾图**(`trace.FlowGraph`)连会话与轮次标识落进
|
||||
// `flow_graphs`(迁移 0022),并支持按轮次读回、按保留期清扫。
|
||||
// 只做两件事:把**每一路的收尾图**(`trace.FlowGraph`)连会话与轮次标识落进
|
||||
// `flow_graphs`(迁移 0022 / 0023),并支持按图 id 单轮读回、按**回合标识**批量读回
|
||||
// (`GraphsByTurnID`,消息面回看工具调用的读侧入口)、按保留期清扫。
|
||||
//
|
||||
// 三条职责边界(别在这里越界):
|
||||
//
|
||||
@@ -153,20 +154,34 @@ func sourcedLog(l *slog.Logger) *slog.Logger {
|
||||
return logger.WithSource(l, flowgraphLogSource)
|
||||
}
|
||||
|
||||
// upsertRoundSQL 是落盘的唯一语句:列按迁移 0022 显式列出、值全部参数化。冲突键是
|
||||
// `(session_id, turn_id)`(迁移里的唯一索引承载)—— 同一会话同一轮的二次收尾是同轮
|
||||
// 覆盖(更新图与 updated_at,**不动 created_at**),而不是插出两行「同一轮」。
|
||||
// upsertRoundSQL 是落盘的唯一语句:列按迁移 0022 / 0023 显式列出、值全部参数化。
|
||||
// 冲突键是 `(session_id, turn_id)`(迁移里的唯一索引承载)—— 同一会话同一轮的二次
|
||||
// 收尾是同轮覆盖(更新图与 updated_at,**不动 created_at**),而不是插出两行
|
||||
// 「同一轮」。
|
||||
//
|
||||
// `lane_turn_id`(迁移 0023)与 `turn_id` **不是同一个东西**:前者是这一**路**的权威
|
||||
// 回合标识(`trace.FlowGraph.TurnID`,与 `chat_messages.turn_id` 同值同源),后者是
|
||||
// 图 id(`trace.FlowGraph.ID`,也是本表的冲突键)。落盘时两个都写,使「按消息的那一路
|
||||
// 查图」(`GraphsByTurnID`)是一次等值索引查询,而不是拿图 id 去反解。二次收尾同样
|
||||
// 更新它:同一条路的图又被收一次时,标识必须跟着走到最新的那一份。
|
||||
const upsertRoundSQL = `INSERT INTO flow_graphs
|
||||
(session_id, turn_id, graph_json, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
(session_id, turn_id, lane_turn_id, graph_json, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(session_id, turn_id) DO UPDATE SET
|
||||
graph_json = excluded.graph_json,
|
||||
lane_turn_id = excluded.lane_turn_id,
|
||||
updated_at = excluded.updated_at`
|
||||
|
||||
// RecordRound 把一条**已收尾轮次**的图落盘(每轮一行)。它是发布器 `EndRound` 的落盘
|
||||
// 出口,因此签名与 `loop.RoundRecorder` 一致:**不接收 ctx、自己用一个有界的后台
|
||||
// context**(会话收尾路径不该被库拖住)。
|
||||
//
|
||||
// 它同时盖上这一路的**回合标识**(`g.TurnID` → `lane_turn_id`,迁移 0023),使消息面上
|
||||
// 「这条回复当时调了什么工具」能按**等值**查到这一轮的图(见 `GraphsByTurnID`)。
|
||||
// 标识为空时如实写空串:那一行因此按路查不到,读取侧让 `toolCalls` 整体缺席 ——
|
||||
// 这正是契约的语义,绝不用时间窗或正文去猜(`g.TurnID` 为空的两类情形见迁移 0023
|
||||
// 的注释:v0.77 之前的旧行与**当前**的单路回合)。
|
||||
//
|
||||
// 失败**上抛**给调用方(发布器记 WARN 后继续),绝不 panic、绝不改变图 —— 图是可视化
|
||||
// 资产,落盘失败不该影响会话本身。
|
||||
func (s *SQLStore) RecordRound(g *trace.FlowGraph) error {
|
||||
@@ -184,7 +199,7 @@ func (s *SQLStore) RecordRound(g *trace.FlowGraph) error {
|
||||
defer cancel()
|
||||
stamp := sqliteTimeStamp(s.now())
|
||||
if _, err := s.db.ExecContext(ctx, upsertRoundSQL,
|
||||
g.SessionID, g.ID, string(payload), stamp, stamp); err != nil {
|
||||
g.SessionID, g.ID, g.TurnID, string(payload), stamp, stamp); err != nil {
|
||||
return fmt.Errorf("flowgraph: 落轮次图(%s/%s): %w", g.SessionID, g.ID, err)
|
||||
}
|
||||
return nil
|
||||
|
||||
Reference in new issue
Block a user