debug-bad-output-audit-trail · ZH · 2026-10-14

复现一次糟糕返回:为了排障,客户端该自己记下什么

本文介绍在 LLM API 聚合场景下,当补全结果异常时,客户端应记录哪些关键信息以复现问题。涵盖请求哈希、模型标识、参数、原始响应等字段,并提供日志结构与脱敏建议,帮助开发者不依赖网关日志进行排障。

为什么客户端需要自己留证据

使用 LLM API 聚合站时,一次补全请求会经过网关路由到实际模型。当返回结果异常(乱码、截断、不符合预期),你首先会想:“是模型的问题,还是网关转发错了?” 如果只能依赖网关日志,排查会受限于日志保留策略、访问权限,甚至网关方的记录粒度。客户端自己记录关键产物,就能独立重建调用现场,快速定位。

必须记录的请求侧信息

请求侧的信息用于唯一标识你发出的内容,以及影响模型行为的参数。

  • 请求哈希:对请求体(消息数组、模型、参数)计算 SHA-256 等哈希,作为该次调用的唯一 ID。即使内容很长,哈希也能快速比对。
  • 模型标识:你指定的模型名称(如 claude-3-5-sonnet 或聚合站的自定义别名)。注意记录你实际传给 API 的值,而非你以为的。
  • 生成参数:temperature、topp、maxtokens、stop 等。不同参数会导致同一提示产生不同输出,复现时必须一致。
  • 消息数组:完整的对话历史,包括 system、user、assistant 角色。注意脱敏敏感信息,但保留结构。
  • 时间戳:请求发出的精确时间(UTC),精确到毫秒。用于关联服务端事件。

必须记录的响应侧信息

响应侧信息用于还原模型实际返回了什么,以及是否被网关修改。

  • 原始响应体:尽可能保存未解析的原始字节或 JSON。解析后的文本可能丢失错误细节(如 finish_reason、usage)。
  • 响应头:部分聚合站会在响应头中携带路由信息、请求 ID 或限流状态,这些对排障很有用。
  • HTTP 状态码:区分成功、客户端错误、服务端错误。
  • 完成原因:finishreason(如 stop、length、contentfilter)能解释输出为何截断或为空。
  • Token 用量:usage 字段中的 prompt/completion tokens,可辅助验证是否被意外截断。

建议的日志结构

将上述信息组织成 JSON 行(JSONL)格式,便于追加和检索。例如:

{
  "request_id": "客户端生成的UUID",
  "request_hash": "sha256:...",
  "timestamp": "2025-01-01T00:00:00.000Z",
  "model": "claude-3-5-sonnet",
  "parameters": {
    "temperature": 0.7,
    "max_tokens": 1024
  },
  "messages": [...],
  "response": {
    "status": 200,
    "headers": {...},
    "body": {...}
  }
}

每条记录一行,写入本地文件或日志系统。注意控制日志轮转,避免磁盘写满。

脱敏与隐私

记录原始请求和响应可能包含敏感数据。务必:

  • 对用户输入中的 PII(个人身份信息)进行脱敏或哈希处理。
  • 若保存完整消息数组,确保存储位置加密且访问受限。
  • 明确日志保留期限,定期清理。

实际排障流程示例

假设用户反馈某次补全结果错误。你可以:

  1. 根据用户提供的时间范围,在本地日志中检索对应记录。
  2. 提取 request_hash 和 model,确认请求参数是否与预期一致。
  3. 检查 response.body 中的 finishreason 和 usage,判断是否因 maxtokens 过小导致截断。
  4. 如果参数无误、响应完整但内容异常,则可能是模型本身或网关路由问题。此时可将请求哈希和原始响应提供给聚合站支持,加速定位。

不要只依赖网关日志

网关日志通常用于计费、限流和路由,未必保留完整的请求体和响应体,也可能因隐私政策快速删除。客户端自记日志让你拥有第一手证据,无论你用的是哪家聚合站,都能自主排障。

小结

为每次 LLM 调用记录请求哈希、模型 ID、参数、原始响应和关键响应头,是复现异常的最低要求。结合合理的日志结构和脱敏措施,你就能在不依赖网关日志的情况下,重建调用现场,快速找到问题根源。