要让 LingBot-VA 2.0 接入后每次调用可回溯、每次下发可刹车,需要把「推理日志」和「动作下发」当成两条独立链路来管:推理侧保证一次调用产生一条带唯一序号的记录,动作侧在真正下发前插入限流与超时两层保护。日志字段缺项,事后回放就只能靠猜;限流缺位,一个错误的动作可能被连续发出去,等到发现时链路状态已经跑偏。
判断:日志与限流是两件事,不能靠「多打点日志」一起解决。建议先固定一次推理的最小字段集,用调用序号做主键落盘,再在动作下发前加限流、超时、默认动作三层保护,且保护触发时同样写日志。边界:字段取舍与限流阈值必须结合你的控制周期、模型响应时间和可接受延迟确认,本文给出的是可替换骨架,具体数值要在自己的环境里用一次超时、一次超频测试验证。
列出一次推理要记哪些字段
字段的目标只有一个:事后不看代码、只读日志,就能还原这次调用发生了什么。建议按下面这几类固定下来,先落全再考虑精简,字段缺失比冗余更难补救。
- 输入摘要:不存原始大对象,存能定位输入的东西,例如输入模态类型、帧数或步数、文本长度、以及输入内容的哈希值(如 sha256 前 16 位)。
- 输出形状:动作维度、batch 或单条、dtype、张量 shape,以及是否经过后处理裁剪。形状对不上,往往是版本或配置串了。
- 耗时:至少拆成排队等待、模型推理、后处理三段,只记总耗时会掩盖瓶颈在哪一段。
- 调用序号:一次会话内自增的 seq,配合 trace_id 使用,是后面回放检索的主键。
- 错误码与异常信息:区分超时、输入校验失败、模型返回空、下发失败等,异常信息截断保留前若干字符,避免日志被堆栈撑爆。
- 下发结果:这条推理产生的动作是被下发、被限流丢弃、还是落到默认动作,需要与序号一一对应。
这些字段建议在推理包装层统一写入,而不是散落在业务代码里临时拼,否则不同调用点写法不一致,回放时对不上。
把日志写成可按序号回放的格式
格式上推荐一行一条的 JSONL,字段名固定,方便用 grep 粗筛再用 jq 精查。示例(字段名可替换,结构保持扁平即可):
{"ts":"2026-01-01T00:00:00Z","seq":10231,"trace_id":"sess-7f3a","model":"LingBot-VA","model_ver":"2.0","in_digest":"9c1f...ab","in_shape":"rgb:16x224x224","out_shape":"act:7","latency_ms":{"queue":3,"infer":88,"post":5},"err_code":0,"err_msg":"","dispatch":"sent"}按序号定位某一次调用,可以先用 seq 收窄范围,再取完整记录:
grep '"seq":10231' /var/log/lingbot/infer.jsonl | jq .
grep '"seq":10231' /var/log/lingbot/infer.jsonl | jq -r '.in_digest, .out_shape'输入摘要里的敏感数据不要原样落盘:文本类先做哈希或只保留长度与前若干字符,图像与点云只存形状和哈希,需要复现时用外部留存的那份原始输入按哈希对齐。日志目录建议按天切割并保留有限天数,避免无限增长;具体保留策略按你的合规和排障需求确认。
在动作下发前加一层限流与超时
限流和超时要放在「模型已算出动作」与「动作真正写进执行端」之间,这一段是唯一的拦截点。通用配置骨架如下,键名可按你的框架替换:
dispatch:
max_rate_hz: 10 # 最大下发频率,超过即拒绝
burst: 1 # 允许的突发额度
action_ttl_ms: 150 # 单次动作有效时间,过期不再下发
on_timeout: "hold_last" # 超时后的默认动作:hold_last / zero / drop
on_rate_limited: "drop" # 超频时的处理:drop 丢弃并回执,或合并为最新一帧
log_reject: true # 被拒绝/超时是否写日志
fail_safe_timeout_ms: 500 # 连续无有效动作多久后进入安全态取舍点:on_timeout 选 hold_last 适合机械臂这类保持位姿更安全的场景,选 zero 适合需要立即停止的场景,两者语义不同,不能默认套用。限流阈值要小于或等于执行端能稳定接收的节奏,定得太高等于没有限流。超时后的默认动作必须是执行端本来就认识的指令,否则保护逻辑反而制造异常。
触发一次超时与一次限流,核对日志表现
保护逻辑写完不验证等于没写。可以人为制造两种场景:
- 制造超时:在推理包装层临时插入一段固定延时,让单次推理耗时超过
action_ttl_ms;或在测试桩里返回一个延迟响应。 - 制造超频:用循环以高于
max_rate_hz的间隔连续调用下发接口,不依赖模型,只压下发这一层。
日志中应能看到对应记录,例如被限流的一侧出现 dispatch:"rate_limited"、err_code 为限流码;超时一侧出现 dispatch:"timeout" 或 dispatch:"fallback",并带有实际耗时字段。返回给上层的结果应当是可判断的:限流时返回丢弃回执或最新一帧合并结果,超时时返回默认动作标识或安全态通知,而不是静默成功。如果两种场景下日志里什么都没有,说明拦截点放错了位置,通常是被放在了模型调用之前,或者日志开关没生效。
用回放日志复查一次异常片段
回放的目的是把一次异常转成具体的配置改动,而不是只看一眼报错。
- 先按时间范围加 seq 圈出异常片段,取到异常发生前后的若干条:
grep 'sess-7f3a' infer.jsonl | jq 'select(.seq>=10220 and .seq<=10240)'。 - 逐条对照
latency_ms三段耗时、in_shape/out_shape、err_code、dispatch。常见结论有三类:推理本身变慢导致超时、输入形状与预期不符导致输出异常、下发节奏超限被丢弃。 - 把结论落到配置项上:属第一类就调
action_ttl_ms或排查推理侧资源;属第二类就回到输入校验;属第三类就调max_rate_hz或改用合并下发。 - 改完复验:重复上一步的同类场景,用同样的 grep 命令检查对应字段是否按预期变化,并确认执行端没有收到连续异常动作。
回放日志的价值在于它把「当时到底发生了什么」变成可检索的事实。字段留全、限流有日志,这两条同时做到,出问题时才有得查。