开源 Python 库 Stepfork 记录智能体运行中的模型与工具交互,支持离线重放失败过程并生成 pytest 回归测试。文章提供真实 Gemini 调用案例,针对模型输出变化、外部数据变化和工具副作用造成的复现困难。
我开发了一个开源 Python 工具,用来记录 AI Agent 的运行过程、离线重放失败场景,并把它们转成 pytest 回归测试。下面是我用真实 Gemini 模型测试它时发生的事情。
GitHub:utsab345/stepfork
真实 LLM 案例研究:Stepfork 仓库中的 examples/real-llm-case-study
传统 Python 函数出错时,复现问题通常很直接。传入相同的输入,再运行一次函数,然后排查哪里出了问题。
但到了 AI Agent 这里,事情就复杂多了。
一个 Agent 可能会调用 LLM、检索文档、执行多个工具,再根据中间结果做出决策。
一旦出错,重新运行 Agent 可能会得到不同的结果。模型的回答可能不同,外部数据可能变化,工具也可能产生副作用。
我一直在想一个简单的问题:
能不能只记录一次 Agent 的失败,就把它变成一个普通的回归测试?
这就是我开始开发 Stepfork 的原因。它是一个用于记录和重放 AI Agent 执行过程的开源 Python 库。
记录一次 Agent 执行,包括经过插桩的 LLM 和工具交互。
把这些交互保存到可移植的 trace 中。
重放记录下来的交互,无须再次调用模型。
生成一个 pytest 回归测试。
修复应用,再用同一份 trace 验证行为。
不过,我想用真实模型来测试这套流程,而不只是用 mock 响应。
我开发了一个小型 IT 事件分级 Agent。
它接收事件报告,检查服务健康状态和近期部署,读取 runbook,然后判断事件的严重程度。
实验中,我使用了下面这个合成事件:
生产环境中的一个 API 对大多数客户返回 HTTP 500 错误。部署后,错误率急剧上升,支付结账端点也出现了故障。
Agent 使用了三类本地工具:
运维数据是合成的,但 LLM 请求是真实的。
我使用了 Gemini 2.5 Flash,通过 Google 的 OpenAI-compatible API 端点访问。
本地测试数据表明,结账 API 的错误率达到 72%,客户受到影响,而且近期发生过一次部署。
按照独立定义的事件严重程度策略,这是一起需要立即升级处理的 P0 事件。
Gemini 判断对了。
{
"severity": "P0"
}
然后,我的 Python 代码把它改成了 P1。
Agent 有一个确定性的后处理函数,用来分析事件与近期部署之间的关联。
我故意在这个函数里引入了一个 bug:
def _correlate_recent_deployment(decision, evidence):
recent = evidence["deployments"][INCIDENT["primary_service"]]["recent_deployment"]
if recent:
decision["severity"] = "P1"
decision["escalation_required"] = False
return decision
这段代码错误地认为,只要近期发生过部署,就可以降低事件的严重程度。
于是,最终输出变成了:
{
"severity": "P1",
"escalation_required": false
}
模型正确识别出了一起严重事件,但应用悄悄覆盖了它的决策。
没有抛出任何异常。Agent 成功执行完毕。
如果测试只检查 Agent 是否运行时没有崩溃,它就会通过。
失败的是业务结果,而不是执行状态。
我用 Stepfork 记录了 Agent 中经过插桩的依赖调用。
生成的 .sftrace 文件包包含以下内容:
trace 捕获了工具调用顺序、记录下来的输入和输出,以及 LLM 交互。
记录中的模型响应仍然是 P0,而应用返回的是 P1。
这个区别很重要,因为它能准确告诉我们,行为究竟在哪一步出了问题。
接下来,我使用了 Stepfork 的 frozen replay 模式。
Stepfork 不再执行经过插桩的工具,也不再向 Gemini 发送请求,而是用记录下来的响应替代这些调用。
应用逻辑仍然会执行,所以我可以修改这部分逻辑,并测试最终结果。
验证结果如下:
Instrumented tool bodies executed: 0
Live LLM attempts: 0
Recorded dependency calls: 6
Substituted dependency calls: 6
这意味着,我可以复现这段相关的执行过程,无须额外调用模型 API。
这也意味着,回归测试可以离线运行,不需要 API key。
这里有一个重要限制:Stepfork 只会替换经过插桩的依赖。它不会阻止任意未经插桩的代码执行,而且 replay 并不是 sandbox。
我独立定义了预期的业务结果:
{
"severity": "P0",
"escalation_required": true
}
然后用 Stepfork 导出回归测试:
stepfork export traces/incident-triage.sftrace \
--pytest \
--entrypoint agent:run_incident_agent \
--expect-output expected.json \
--output tests/test_incident_regression.py
在有 bug 的应用上运行生成的测试,结果是:
1 failed
AssertionError: behavior mismatch at 'escalation_required':
expected true, got false
这正是我想要的结果。
测试利用记录下来的模型交互,检测出了一个真实的应用层错误。
修复很简单:不再让部署关联逻辑覆盖事件严重程度策略。
修正后的函数变成了:
def _correlate_recent_deployment(decision, evidence):
return decision
我再次运行了同一个生成的 pytest 测试。
1 passed
我没有重新生成 trace。
我没有修改预期结果。
我没有再向 Gemini 发起请求。
唯一实质性的变化,就是应用逻辑。
有一个细节需要注意:通过独立 CLI 重放修复后的应用时,可能会报告输出与最初记录的错误结果存在差异。这是预期行为。生成的 pytest 测试会单独依据独立定义的预期结果,验证修正后的输出。
我还测试了 Agent 依赖调用轨迹发生变化的情况。
一次不影响行为的重构保留了工具调用、参数、顺序和模型 prompt,测试通过了。
但当我改变工具调用顺序时,frozen replay 拒绝了这次执行:
ReplayMismatchError:
call #1: expected tool 'lookup_service_health'
but the agent called 'get_recent_deployments'
把工具参数从 window_minutes=60 改为 window_minutes=120,也会触发不匹配。
这很有用,因为 Agent 的回归问题并不总是体现在最终答案上。有时,Agent 会开始调用不同的工具,或者传入不同的参数。
我一共向 Gemini 发起了三次真实请求:
根据报告的 token 用量和定价假设,估算的 API 成本约为 0.009 美元。
两次 hybrid 评估都返回了 P0。不过,每个 prompt 只有一个样本,这不足以证明哪个 prompt 更好。
关键结果是,记录下来的执行过程可以反复重放,无须再向模型发送请求。
完整案例研究收录在 Stepfork 主仓库中:
utsab345/stepfork 的 examples/real-llm-case-study 目录。
你不需要 Gemini API key,就能复现这些离线测试。
git clone https://github.com/utsab345/stepfork.git
cd stepfork/examples/real-llm-case-study
uv sync --locked --extra test
uv run python -m pytest -q
验证记录下来的 trace:
uv run stepfork validate \
traces/incident-triage.sftrace \
--verify-integrity
验证 frozen replay:
uv run python scripts/verify_frozen.py
案例研究包含原始 trace、合成测试数据、有 bug 和修复后的应用快照、生成的 pytest 回归测试,以及记录下来的验证证据。
Stepfork 仍然处于早期 alpha 阶段。
它不会自动知道 Agent 的答案是否正确。你仍然需要定义有意义的预期结果。
它也不会拦截所有可能的副作用。frozen replay 只适用于受支持且经过插桩的依赖。
当前的 OpenAI adapter 存在一些限制,包括不支持 streaming,以及部分较新的 API 接口。项目也支持 LangGraph 集成,并计划增加更多集成。
我正在努力实现一个简单的开发流程:
Agent 失败一次。你把它记录下来。你修复代码。然后,这次失败就变成了一个可以反复运行的测试。
我正在把 Stepfork 开发成一个开源项目,尤其希望听到使用 LLM Agent、LangGraph 和 Python 测试工具的开发者的反馈。
哪类 Agent 失败最难复现?
记录工具调用并离线重放,能帮助你的调试流程吗?
如果你想试用或参与贡献:
GitHub:utsab345/stepfork
文档:Stepfork Documentation
真实 Gemini 案例研究:Stepfork 仓库中的 examples/real-llm-case-study
Agent 失败了。把这次失败变成一个测试。
如需进一步采取措施,可以考虑屏蔽此人,或举报滥用行为。