核心设计

LLM 调用日志可观测性 (LLM Invocation Observability) 的目标不是只保存一段原始响应,而是同时提供:

  • 原始 JSON:保证协议字段和异常数据可追溯。
  • 解析行:按 messages_before → events → messages_after 保留消息、Thinking、工具调用、工具结果、usage 和错误事件顺序。
  • 聚合摘要:为列表和详情快速提供 token、流速、成本、价格快照和上游调用信息。

原始数据与解析结果必须分离。解析失败或协议字段未知时保留 raw_fragment,不能为了 UI 方便破坏原始日志。

工具调用聚合规则

同一轮日志中,事件流和消息历史可能同时描述同一个工具调用。以 tool_call_id 作为聚合键,只在 tool_calls 摘要中保留一条记录:

recorded → running → completed

parsed_rows 仍保留每一个原始事件和消息行,方便审计与 CSV 分析。聚合时只补全缺失字段;结构化工具结果已经存在时,不被消息历史中的文本结果覆盖。这样可以同时满足“事件不丢失”和“详情卡片不重复”。

SQLite 幂等迁移

SQLite 的 CREATE TABLE IF NOT EXISTS 不会为已有表增加新列。新增字段时,启动流程应:

  1. 通过 PRAGMA table_info(<table>) 检查列是否存在。
  2. 缺列时执行 ALTER TABLE ... ADD COLUMN ...。
  3. 迁移函数可重复执行;多进程同时通过检查时,重复列错误应视为竞争中的成功结果,其他错误继续返回。

价格快照 (Price Snapshot) 字段应允许旧记录为 NULL。旧日志没有历史价格时不能用当前 Profile 价格反推,否则修改配置后历史金额会失去可解释性。新调用在开始时写入输入价、缓存输入价和输出价,结束时用调用记录中的快照计费。

接口一致性

日志列表、统计和批量导出必须复用同一组筛选条件,例如时间、模型、Profile、分组、协议、调用类型、用户和 Session。否则页面指标与下载文件会出现范围不一致。

详情接口同时返回原始内容、解析结果和同一轮的上游调用明细;前端默认展示摘要,原始 JSON 放入独立 tab,避免首屏被大字段淹没。

验证方法

  • 启动两次旧数据库,确认迁移不会报 duplicate column。
  • 用旧 schema 的日志确认三个价格快照返回 null。
  • 用事件流和 messages_after 同时包含同一 tool_call_id 的样例,确认聚合数量为 1、结构化结果优先。
  • 检查 CSV 的 UTF-8 BOM、标准引号转义和稀疏列结构。
  • 对列表、统计和批量导出使用同一筛选条件,确认条数与范围一致。

关联项目


本文归档于个人知识库 · 领域:CS & AI · 模块:04 Project_CS_AI/AI apps