deterministic-replay-from-logs · ZH · 2026-10-25

从请求日志重放一次线上调用:在一把 key 上还原提示词、参数与后端

本文演示如何从 LLM API 聚合站的请求日志中,完整还原一次线上调用的提示词、采样参数与实际命中的后端模型,并用同一把 API key 忠实重放请求以复现问题。内容涵盖日志字段解读、参数对齐、后端漂移排查与复现清单,强调可复现性优先于猜测。

为什么需要重放而不是猜

线上出现一次异常输出时,最容易犯的错是凭印象重写一版提示词去试。模型对措辞、系统提示词、temperature、max_tokens 顺序都敏感,重写的请求已经不是你线上跑的那次调用了,结果无论好坏都不说明问题。

聚合站的价值在这里很直接:它记录了每次请求的入参与实际路由到的后端,你可以把一次调用原样搬回来。前提是你会读日志。

日志里通常有哪些字段

一条请求日志一般包含两部分:客户端可见的部分,以及路由层附加的部分。

  • 请求标识:请求 ID、时间戳、所属 API key(确认是不是同一把 key 发出的)
  • 模型标识:你请求时写的模型名,以及实际路由到的后端
  • 消息体:system / user / assistant 各条消息的原文与顺序
  • 采样参数:temperature、topp、maxtokens、stop、seed(若支持)等
  • 返回:状态码、耗时、token 用量、错误信息(若有)

关键点在于「你请求的模型名」和「实际后端」可能是两个字段。聚合站会把一个模型别名分发到具体后端,所以复现时要把两者都记下来。

第一步:锁定这条日志的身份

先确认三件事再动手:

  1. 这条请求是用哪把 key 发出的。聚合站支持一把 key 调多个模型,不同 key 可能有不同的可见范围或路由策略,用错 key 重放可能落到别的后端。
  2. 时间戳是否落在后端配置变更的窗口内。如果异常正好出现在某个时间点附近,先怀疑路由变化再怀疑提示词。
  3. 请求 ID 是否可检索。有 ID 就能把日志、返回和你本地记录对齐。

第二步:逐字段还原请求体

按日志顺序重建 JSON,不要凭记忆补全:

  • 消息数组:system 提示词单独一条,注意前后空白与换行。日志里如果做了截断,需要回到完整记录取原文。
  • 参数:把 temperature、topp、maxtokens、stop 等照抄。没有记录的参数不要自己加默认值,保持请求体最小化。
  • 工具/函数定义:如果原请求带了 tools,一并还原,否则模型行为可能不同。

一个实用的判断:如果你的重放请求比原请求「更完整」,就已经不是重放了。宁可少写,也不要多写。

第三步:对齐后端

确认日志里的实际后端,然后决定怎么发:

  • 如果聚合站允许显式指定后端或使用与当时一致的模型别名,就直接用当时生效的名字发。
  • 如果想验证「是不是后端换了导致异常」,可以做两组重放:一组用当前别名(走现在的路由),一组显式指向日志记录的原后端。两组输出的差异就是路由层的贡献。

这一步能区分两类问题:提示词/参数造成的,和后端切换造成的。很多人把后者误判成前者,于是反复改提示词。

第四步:控制复现的变量

重放时尽量只变一个东西:

  • 用同一把 key,避免配额或权限差异
  • 同一模型别名/后端
  • 同一请求体,包括参数顺序(多数实现不敏感,但保持一致没有成本)
  • 连续重放多次,观察输出是否稳定。采样参数意味着单次结果不能下结论,看多次的分布。

如果多次重放都稳定复现异常,问题大概率在提示词或参数;如果时有时无,先看采样参数,再看后端是否在多个实例间分流。

第五步:记录复现结论

复现完成后,把结论写回工单或笔记,至少包含:请求 ID、使用的 key(脱敏)、请求模型名、实际后端、完整请求体、重放次数与结果分布。这样下次别人遇到同类现象,不用从零重来。

常见坑

  • 只复制了 user 消息,漏掉 system 提示词,导致行为完全不同。
  • 日志显示的是截断后的内容,拿去重放等于换了输入。
  • 用了另一把 key,落到不同后端,得出「无法复现」的错误结论。
  • 把单次输出当作稳定结论,没有考虑采样随机性。
  • 忘记记录实际后端,后续排查缺少对照。

小结

重放的核心是把一次调用当作可审计的对象:身份(key + 请求 ID)、输入(消息 + 参数)、路由(实际后端)三样对齐,再控制变量重发。聚合站把这三样都留在日志里,剩下的工作是严谨地照抄,而不是重新创作。