Skip to content

fix(gateway): turns table user_id always empty + Slack model missing + synthetic turn metadata incomplete #559

@aaronwong1989

Description

@aaronwong1989

Summary

turns 表存在三个数据质量问题,导致运营数据分析(用户成本归因、模型效率对比、crash 追踪)不可用。

数据库审计结果:816 turns(492 assistant),全部受影响。

数据证据

-- 所有 session 的 owner_id 为空,user_id 有值
SELECT owner_id, COUNT(*) FROM sessions GROUP BY owner_id;
-- result: '' → 308 sessions

-- turns.user_id 全部为空
SELECT user_id, COUNT(*) FROM turns WHERE role='assistant' GROUP BY user_id;
-- result: '' → 492 turns

-- Slack session 的 model 全部为空
SELECT s.platform, t.model, COUNT(*) 
FROM turns t JOIN sessions s ON t.session_id = s.id 
WHERE t.role='assistant' GROUP BY s.platform, t.model;
-- result: feishu/glm-5.1 → 220, feishu/'' → 1, slack/'' → 32

根因链

Bug 1: user_id 始终为空(全部 492 assistant turns)

  1. sessions.owner_id 列在 session 创建时从未被赋值(所有 308 条记录为空字符串)
  2. SQL 查询层 COALESCE(owner_id, user_id) 提供了读取时 fallback,但仅对 DB 查询生效
  3. bridge_forward.go:forwardEvents 初始化 forwardContext 时调用 b.sm.Get()
    • 活跃 session 从内存 map 读取(manager.go),内存中 SessionInfo.OwnerID 为创建时的原始值(空字符串)
    • fc.sessOwner = si.OwnerID → 始终为空
  4. captureAssistantTurn 使用 fc.sessOwner 设置 TurnWriteRequest.UserID → 写入空字符串

影响:无法按用户维度做成本归因和使用分析。

Bug 2: Slack session model 始终为空(32/32 turns)

  1. session_stats.go:mergePerTurnStatsdata.Stats["model_usage"](CC 格式)或 data.Stats["tokens"](OCS 格式)提取 model
  2. Slack Worker 的 done 事件不包含这两个 stats 字段
  3. acc.ModelName 保持为零值(空字符串)
  4. captureAssistantTurn 写入 Model: acc.ModelName → 空字符串

影响:无法按模型分析 Slack 渠道的成本效率。

Bug 3: captureSyntheticEvent 缺少 Platform/UserID/Model

bridge_forward.go:543-573 — crash/timeout/fresh_start 场景的 synthetic turn:

turn := &eventstore.TurnWriteRequest{
    SessionID:  sessionID,
    Generation: acc.Generation,
    TurnNum:    acc.TurnCount,
    // Platform, UserID, Model — 全部未设置
    ...
}

影响:crash/timeout turn 无法关联到用户和平台,降低了运维可观测性。

修复方案

Bug 1 修复(优先级 P2)

方案 A(推荐):在 forwardEvents 初始化 forwardContext 时增加 fallback:

// bridge_forward.go forwardEvents()
if si, err := b.sm.Get(context.Background(), sessionID); err == nil {
    fc.sessPlatform = si.Platform
    fc.sessOwner = si.OwnerID
    if fc.sessOwner == "" {
        fc.sessOwner = si.UserID // fallback to user_id
    }
}

方案 B(根治):确保 session 创建时填充 owner_id。追踪 StartSessionGetOrCreate → upsert SQL,在创建路径中 owner_id = user_id

Bug 2 修复(优先级 P3)

在 Slack Worker 适配器中确保 done 事件包含 model_usage 或在 mergePerTurnStats 中增加备用来源(如从 OCS converter 已有的 takeStats 路径获取)。

Bug 3 修复(优先级 P3)

captureSyntheticEvent 补充 PlatformUserIDModel 字段:

turn := &eventstore.TurnWriteRequest{
    ...
    Platform: fc.sessPlatform,  // 需传入 forwardContext
    UserID:   fc.sessOwner,     // 需传入 forwardContext
    Model:    acc.ModelName,
    ...
}

当前 captureSyntheticEvent 不接收 forwardContext,需扩展签名或在调用点补充。

Acceptance Criteria

  • turns 表所有 assistant turn 的 user_id 非空(来自 session 的 COALESCE(owner_id, user_id)
  • Slack session turns 的 model 字段非空
  • synthetic turn(crash/timeout/fresh_start)包含 platformuser_id
  • 现有数据无需修复(仅修前进路径)
  • make check 全量通过

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions