首页 > AI教程 >给AI Agent装黑匣子:记录每次LLM调用,崩溃可确定性回放

给AI Agent装黑匣子:记录每次LLM调用,崩溃可确定性回放

来源:互联网 2026-07-14 06:25:05

针对AIAgent中LLM调用非确定、不可复现的痛点,设计FlightBox黑匣子工具。通过录制每次LLM调用的完整请求与响应,实现确定性回放、差异对比及导出测试用例,从而精准定位并固化Agent运行中的异常行为。

当你在AI Agent开发中越陷越深,迟早会遇到这样一个夜晚:线上系统运行正常,但用户反馈“它今天给我的回答很奇怪”。你打开日志,将同样的输入重新喂给系统,试图复现问题——结果一切正常。再试一次,依然正常。那一次真正出错的运行,仿佛从未发生过,无论怎样都无法复现。

给AI Agent装黑匣子:记录每次LLM调用,崩溃可确定性回放

长期稳定更新的攒劲资源: >>>点此立即查看<<<

这种bug之所以折磨人,是因为它和我们熟悉的传统软件bug完全不同:

传统软件bugAgent bug
能否复现有堆栈、有步骤,照着走就出现一次性的,发生后消失
手中有什么证据完整日志经常只剩最后一条输出
如何定位根因单步调试连那次的调用序列都抓不回来

为什么Agent的bug如此难以捕捉?根因有三条,每一条都致命:

  • LLM本身非确定性:同样的输入,下一次的输出无法保证一致。
  • 调用序列无法重放:Agent那一轮跑了多少步、每一步模型返回了什么、触发了哪些工具,全都过去了。
  • 中间状态未记录:日志中大概率只有最后那条输出,中间每一步模型返回了什么、工具收到了什么参数,根本没有记录。

一个反直觉的事实:唯独LLM这一层,我们没做回放

后来想明白一件事:整个技术栈几乎每一层都是可回放的,唯独LLM调用这一层,我们把它留成了不可回放的。

  • 数据库挂了,有快照,能恢复到出事那一刻。
  • HTTP请求出问题,那条curl原样再发一次就能复现。
  • 分布式系统这种最难缠的,业界都在用确定性模拟去重放同一串事件。

我们对“可复现”是有执念的,也有一整套工具。可偏偏到了Agent这里,那个最不确定、最容易出妖蛾子的环节,我们却任由它非确定、不留痕、过完就忘。

飞机为什么要装黑匣子?恰恰因为空难罕见、严重、而且几乎不可能事后复现。你没法让飞机再炸一次给你看,所以得在它正常飞的时候就把一切录下来,出事之后靠录像倒推。

Agent的bug是同一种形状的问题。所以有了FlightBox,给AI Agent装一个这样的黑匣子。

怎么用:录、放、对、固,四步

第一步,录。一个with把Agent代码包起来,块里的调用一个都不漏,业务代码一行不动:

import flightbox
from openai import OpenAI
client = OpenAI()
with flightbox.record("debug-session") as rec:
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": "..."}],
    )
    print(f"录制 ID: {rec.run_id}")

这一块里每一次chat.completions.create(),连同完整的请求、响应、延迟、Token用量、工具调用,都写进一个本地SQLite(默认在.flightbox/recordings.db)。纯本地,不走云,不上报。

第二步,放。拿到一个run_id,用replay把同样的代码再包一次:

with flightbox.replay("abc123def4"):
    response = client.chat.completions.create(...)
    # 拿回来的就是当初录下的那个响应,一模一样

回放时,被接管的方法不再真正调用API,而是把当初录下的那条响应原样还给你。你的Agent在本地就变成了完全确定性的:可以在那次“奇怪的回答”上反复打断点、加日志、改逻辑,每次都精确重演同一条轨迹,直到揪出根因。这跟“再跑几遍碰碰运气”是完全不同的世界。

运行起来像这样,同一段代码在replay下逐步命中录制,输出和首次一模一样:

$ python debug.py
[flightbox] mode=replay run=abc123def4
step 1/3 chat.completions.create  replayed (cache hit) 12ms
step 2/3 chat.completions.create  replayed (cache hit) 9ms
step 3/3 chat.completions.create  replayed (cache hit) 11ms
[flightbox] 3/3 calls replayed deterministically · 0 live API hits
最终回答:(与首次录制逐字一致)

第三步,对。改了Prompt或Agent逻辑,想知道“这次跟上次哪儿不一样”:

flightbox diff  

它精确告诉你第几步、哪个字段开始对不上,不用你拿两份日志肉眼比较:

$ flightbox diff abc123def4 9f7e21aa01
step 1 request.messages  identical
step 2 request.system  changed
- You are a helpful assistant.
+ You are a concise assistant. Prefer tool calls over prose.
step 2 response.tool_calls  0 → 1 (search_docs)
step 3  identical
1 step diverged · first divergence at step 2 (request.system)

第四步,固(自己最偏爱的用法)。把一次真实的、尤其是出过问题的运行,直接固化成pytest回放测试:

flightbox export  -f pytest -o test_replay.py
$ flightbox export abc123def4 -f pytest -o test_replay.py
wrote test_replay.py · 1 replay test · 3 recorded calls pinned
$ pytest test_replay.py -q
.
[100%] 1 passed in 0.18s

这一步的意义在于,你抓到的那个bug从此变成了一条回归测试,以后改代码再也别想让它悄悄复活。再往大里想一层:你线上每一次有价值的真实流量都能沉淀成测试用例,回归测试集是从真实世界里长出来的,而不是你拍脑袋编的。也可以-f jsonl导成评测数据集。

为什么是Patch SDK这一层,而不是HTTP或框架

这部分最值得说。FlightBox去Monkey-patch的是OpenAI和Anthropic两个官方SDK的方法(chat.completions.createmessages.create),而不是拦更底层的HTTP,也不是耦合某个框架的内部。挑这一层有三个理由:

  • 它是最窄、最稳定的那道缝。上层框架绕来绕去,最后都会收敛到openai.chat.completions.create这一下。
  • 补一道缝接住所有框架。LangChain、CrewAI、Pydantic AI都从这里过,你不用理解任何一个框架的内部。
  • 不会随框架升级而碎。拦的是稳定的SDK公开方法,不是某框架的私有实现。

一句话:选对要拦的那一层,比拦本身更重要。

边界要说清楚

免得你期待错位。FlightBox录的是“经过OpenAI/Anthropic SDK的调用”这一层的真相:

  • 录得到:每次LLM调用的完整请求、响应、延迟、Token、工具调用。
  • 录不到:你Agent内部那些非LLM的随机性,比如自己代码里的random、当前时间、某个外部接口的返回。

它解决的是“LLM调用不可复现”这个最大的不确定性来源,不负责消灭你代码里所有的不确定。把这条记住,它就是个非常趁手的东西。

和AgentProbe配合

如果你也在做Agent的测试,它能和另一个项目AgentProbe(一个给Agent行为做断言的pytest插件)串起来用:

  • FlightBox负责把真实运行录下来、变成确定性的回放。
  • AgentProbe负责在这条确定的轨迹上断言:它该调几次工具、该不该走某条分支。

一个管录下真相,一个管在真相上立规矩。

侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述

热游推荐

更多
湘ICP备14008430号-1 湘公网安备 43070302000280号
All Rights Reserved
本站为非盈利网站,不接受任何广告。本站所有软件,都由网友
上传,如有侵犯你的版权,请发邮件给xiayx666@163.com
抵制不良色情、反动、暴力游戏。注意自我保护,谨防受骗上当。
适度游戏益脑,沉迷游戏伤身。合理安排时间,享受健康生活。