LINGSHU · 报错定位方案 · v2 已拍板 · 2026-10-26

报错能看见,
钥匙放进错误体

入口定在账单详情(账单与请求 1:1),错误体带上请求 ID;不解请求原文,只存「完整增量消息 + 其他参数」。 本文只定落库字段、写入点、权限与工作量,不写代码。
1 个入口:账单详情(会话复用同一视图)
2 列新增落库:req_params / token_id
7.5人日,P0 两天能上线
PRODUCT SPEC · v2状态:已拍板 · 待开任务书变更:入口 / 错误体 / 存储策略
现状实测 · AS IS

记录八成都在,缺的是钱与路

AUDIT02/10

call_logs 已有的

0002 / 0011 / 0012
  • 01
    req_messages 请求消息增量(role / content / tool_calls)
  • 02
    decision 决策轨迹:候选渠道、跳过原因、协议转换与 lossy、成本
  • 03
    resp_body 上游返回体(错误体截断 4KB,落库前已清洗凭据)
  • 04
    upstream_usage 上游原始用量 + 归一化三段 token
  • 05
    error_message / duration_ms / first_token_ms 失败原因与耗时
索引 idx_call_logs_request 已就绪,按请求 ID 查不用加表。

缺的三样(本轮修)

拍板范围
  • !
    错误体没 ID:只有 message/type/code,request_id 藏在响应头,SDK 不回显
  • !
    其他参数没落库:temperature / max_tokens / top_p / tools / stream / response_format / 端点 / 协议 / 上游 URL 全无
  • !
    令牌标识没落库:只在 billing_records 有,失败无账单场景查不到是谁调的
另有权限漏点:开发者端能看到渠道密钥名 / 内部模型 / 成本。
2现有入口:账单详情 / 会话详情
1 : N一次请求多条日志(随候选数)
0 天现有保留期(永不清理)
0 个控制台断点适配
结论:不重建体系,只补三样——错误体给钥匙、参数补齐、权限收口。
诊断
灵枢 · 报错定位方案 v2
02/10
断点 → 修法 · FIX MAP

四个断点,各有对应动作

FIX03/10
STEP 1
网关返回报错
加 ID 错误体带 request_id
→
STEP 2
用户拿到钥匙
已定 复制粘贴即可
→
STEP 3
账单列表搜 ID
新增 搜索框 + 列
→
STEP 4
账单详情看全貌
复用 一套详情视图
→
STEP 5
补齐参数
P1 req_params
断点现状修法批次
手里没 ID错误体 {"error":{message,type,code}}顶层加 request_id,响应头保留P0
搜不到记录只有按账单 ID / 会话 ID 查账单列表加「请求 ID」关键字搜索 + 列P0
一次请求多行每候选失败各写一条,账单出现多行同 request_id 归并主行 + "另有 N 次尝试"角标P0
参数不全只落 messages 增量新增 req_params:messages 之外全部参数 + 端点/协议/上游 URLP1
身份不明令牌 ID 只在账单里call_logs 补 token_id,界面只给掩码P1
越权可见开发者端能看到渠道/成本后端按角色裁剪 + 前端开关默认关P1
顺序:先给钥匙(P0),再补货(P1),最后适配手机(P2)。
批次
灵枢 · 报错定位方案 v2
03/10
入口定案 · ENTRY = BILLING DETAIL

不新开页面:账单详情就是报错详情

DECIDED04/10
入口
账单列表
搜索:请求 ID / 账单 ID / 会话 ID
→
主视图
账单详情
结论 + 请求 + 决策 + 上游 + 用量
→
复用
会话详情
同一详情组件,按序展开

为什么账单详情够用

1:1
每个账单 = 一次请求,`billing_id` 与 call_logs 一一对应;失败请求同样落一条失败账单,所以"报错"在账单列表里本来就有一条。

界面上只做三件事:加请求 ID 列、加搜索、把详情补全。不新增菜单、不新增页面。

一个必须说清的现实

处理
多候选失败时,每个候选会各写一条日志/账单,于是同一个请求在列表里有多行。

处理:按 request_id 归并成主行,角标「另有 N 次尝试」,点开看整条尝试链。不归并的话,用户会点进错误的那一行。
省下的:不做独立日志页与列表接口,约 1.5 人日。
代价:跨会话排查只能走会话详情;无账单的拒绝记录(令牌无效等)看不到。
兜底:详情组件只写一份,将来要加日志页可直接复用。
一句话:入口不新增,只把"账单详情"补成"能定责的详情"。
定案
灵枢 · 报错定位方案 v2
04/10
详情页设计 · SIX ZONES

六区:先结论,再消息,再参数,再轨迹

LAYOUT05/10
01失败 · 上游 400 · 1.8s · 请求 ID ⧉
02a完整增量消息 JSON(气泡渲染 + 原始文本折叠)
02b其他参数:model · temperature · max_tokens · top_p · stream · tools(3) · 端点 · 协议 · 上游 URL
03决策轨迹:候选① 400 ▸ 候选② 跳过·限流 ▸ 候选③ 转换 lossy 2 项
04上游返回:400 · param=model · 原文折叠
05用量:上游原始 usage / 三段归一化 / 计费三步
06关联:同请求其余尝试 / 所属会话 / 关联账单

消息与参数分开看

核心
消息是"用户发了什么",参数是"我们用这些开关发了什么"。上游 400 多半在参数区,日志缺失也多在参数区。

决策轨迹用时间线

关键
一候选一行:渠道 / 密钥 / 内部模型 / 跳过原因(中文)/ 耗时 / 上游状态码 / 是否转换 + lossy 项。

首屏只说一件事

必做
哪一步失败、上游原话、请求 ID 一键复制。其余折叠,手机上一屏读完。

缺失要有话

体验
没收到上游响应 / 已过保留期 / 无权查看,三种文案分开写,别统一"暂无数据"。
层级原则:结论 → 我发了什么(消息+参数)→ 平台做了什么(轨迹)→ 上游回了什么。
IA
灵枢 · 报错定位方案 v2
05/10
落库设计 · SCHEMA & WRITE POINT

加两列,不动表结构,不存请求原文

SCHEMA06/10
列类型内容约束
req_messages收紧JSONB完整增量消息数组:role / content / tool_calls / tool_call_id / name不裁剪、不二次摘要
req_params新增JSONBmessages 之外全部:model / temperature / max_tokens / top_p / n / stop / stream / response_format / tools_summary{count,names,bytes,truncated} / endpoint / inbound_protocol / upstream_protocol / upstream_url≤8KB,超限截断并打标
token_id新增BIGINT调用令牌(失败无账单时也能定位调用方)展示仅掩码
schema_version建议INT字段演进标记,默认 1便于后续解析兼容

写入点只有一个

handler 组包处
请求体解析完、算出增量消息的那一处,顺手组装 req_params;
调用日志写入函数(calllog)加两个参数,INSERT 与列常量同步;
失败路径复用同一组装函数,避免"成功有、失败没有"。
解析请求 → 算增量消息 + 组装 req_params → 候选循环 → 写日志(统一入口)

明确不存(控成本)

红线
  • ×
    请求体原文、headers 值、query 串
  • ×
    tools 全文(只存数量 + 名称 + 字节数 + 截断标记)
  • ×
    上游 URL 的 query(只 host+path,防 api_key 泄漏)
  • ×
    凭据明文(只给名称 + 指纹首末 4 位)
体积口径:单行新增 ≤8KB,异步批量写入,不阻塞热路径;读取走已有 billing_id / request_id 索引。
成本
灵枢 · 报错定位方案 v2
06/10
权限 · ACCESS CONTROL

开发者看自己的请求,管理员看全链路

RBAC07/10
信息管理员开发者
增量消息 / 其他参数 / 上游返回可看仅本人记录
跳过原因 / 耗时 / 上游状态码可看可看
渠道名 / 渠道密钥 / 内部模型可看隐藏(现状泄漏)
成本 / 时段系数可看隐藏(现状泄漏)
令牌标识可看仅本人令牌,掩码
导出 / 分享后续版本不可

P1 泄漏:必须同批修

漏洞
详情组件的"显示内部字段"开关默认打开,账单详情页没关;开发者端因此能看到渠道名、渠道密钥名、内部模型、成本,与接口注释"不含内部模型/渠道名"矛盾。

修法:后端权威 + 前端兜底

双保险
  • 01
    后端按角色裁剪:开发者拿到的轨迹不含渠道/内部模型/成本
  • 02
    前端开关显式传参,默认关闭而非默认打开
  • 03
    越权统一返回"不存在",沿用现有主人校验口径
原则:自己请求的全貌可看,平台内部结构不可看;渠道与成本属于平台资产。
权限
灵枢 · 报错定位方案 v2
07/10
交互与移动端 · MOBILE FIRST

手机上先给结论,别让人横着拖

UX08/10

五条硬规则

对齐报告铁律
  • 01
    一屏一结论:首屏只有失败原因 + 请求 ID + 复制按钮
  • 02
    搜索置顶:请求 ID / 账单 ID / 会话 ID 自动识别,回车即查
  • 03
    表格转卡片:决策轨迹窄屏变时间线,不横向滚动
  • 04
    字号底线:正文 ≥14px、标题 ≥16px、表格 ≥13px
  • 05
    断点 640px:侧栏改抽屉、内容单列、卡片圆角加大
手机首屏 · 375px · 账单详情
400 · 上游拒绝
请求 ID req-8f2a… ⧉ 复制
模型 gpt-4o / 端点 /v1/chat/completions
参数 temperature 1.2 · max_tokens 8000 · tools 3
候选① 400 | 候选② 跳过·限流
增量消息 …(点击展开)
用量与计费 …(点击展开)
长文本:等宽 12–13px、独立滚动区、右上角复制,默认只展开 20 行。
空态三分:无记录 / 已过保留期 / 无权查看,文案分开。
跳转:详情底部给"所属会话""其余尝试",别让人回列表重找。
现状反差:控制台零断点适配,侧栏固定 160px,5 列决策表在手机上必然溢出。
现状
灵枢 · 报错定位方案 v2
08/10
数据边界 · RETENTION & MASKING

正文 30 天,元数据长期;凭据永不见明文

POLICY09/10

保留策略

建议 30 天
  • 01
    30 天内:消息 / 参数 / 上游返回全文可查
  • 02
    超过 30 天:正文清空,保留模型、状态、耗时、用量、错误摘要
  • 03
    清理任务:每日一次、分批更新,可回滚(先软清空再物理删)

脱敏(落库前 + 展示前各一遍)

红线
  • 01
    sk-* / Bearer * / api_key= 一律掩码
  • 02
    渠道密钥只给名称 + 指纹首末 4 位
  • 03
    上游 URL 只 host+path,query 丢弃

存储成本对照

增量 vs 全文
增量消息 + 其他参数(本方案)
完整请求体全文(不采用)
长上下文请求里 tools / 历史消息重复率高,存全文的体积与重复成本不成比例。
一句话:够定位、够省、够安全——30 天正文 + 长期元数据 + 全程无明文凭据。
边界
灵枢 · 报错定位方案 v2
09/10
工作量 · EFFORT & NEXT

三期 7.5 人日,P0 两天就能搜到

PLAN10/10

分期

后端 / 前端
P0 钥匙与入口
2 人日
错误体带请求 ID · 账单列表搜索与列 · 同请求归并 · 详情接统一视图
P1 字段与权限
3 人日
req_params / token_id 扩列与写入点 · 截断策略 · 开发者端裁剪(修泄漏)· 参数展示区
P2 移动与治理
2.5 人日
断点适配(列表 + 详情)· 保留期清理任务 · 导出(可选)
TOTAL
7.5
人日(不含 e2e 1–2)

真正成本在两处

注意
后端口径扩列要动组包处并回归热路径;前端要把两套详情合并成一套。

验收口径

可判定
上游报错后,凭错误体里的请求 ID,在控制台 3 次点击内看到:完整增量消息 + 其他参数 + 走了哪些候选 + 上游原文;开发者端看不到渠道与成本。

开工前确认 3 条

不阻塞
① 保留期 30 天?② req_params 单行 8KB 上限?③ 上游 URL 只留 host+path(丢 query)?
下一步:写进任务书 P0(含验收口径),闲时开工;详版在 notes/calllog-detail-方案.md。
NEXT
灵枢 · 报错定位方案 v2
10/10