测试实战:为 Agent 行为生成基线并零成本回放回归)
tarko MCP-Agent 快照Snapshot测试实战为 Agent 行为生成基线并零成本回放回归【免费下载链接】UI-TARS-desktopThe Open-Source Multimodal AI Agent Stack: Connecting Cutting-Edge AI Models and Agent Infra项目地址: https://gitcode.com/GitHub_Trending/ui/UI-TARS-desktop基于 tarko/agent 的 MCP-Agent如 GitHub Reviewer Agent是一类依赖真实大模型输出的长链路程序其行为存在很强的非确定性同样的输入不同模型、不同时间点的输出都可能不同。为了让这类 Agent 的回归测试可重复、可离线、可进 CImultimodal/tarko/mcp-agent/snapshot目录提供了一套基于tarko/agent-snapshot的「快照Snapshot」方案——先真实运行一次把完整事件流固化下来再以零外部依赖的方式反复回放并与基线比对。本文以snapshot/README.md为骨架结合 runner、vitest 测试与底层库源码完整讲解这套快照机制的目录约定、数据格式、生成/回放命令、校验维度与扩展新用例的方法。快照机制是什么让 Agent 回归测试可回放与普通函数不同Agent 的运行对外部世界高度敏感每次都要真实调用 LLM、真实执行工具打开浏览器、读写文件既消耗 API 配额又难以保证结果稳定。快照测试Snapshot Testing的核心思路是Generate生成用真实 LLM 完整运行一次 Agent把整个过程——用户输入、assistant 消息、每次 tool_call、tool_result、最终回复等——按事件流顺序录制下来作为基线baselineReplay回放此后测试不再请求真实 LLM而是用录制好的 Mock LLM 回复驱动 Agent 重新走一遍决策循环并把新产生的行为与基线逐项比对不一致即失败。这正是 tarko/agent-snapshotAgent snapshot based test framework for tarko/agent based Agents在 mcp-agent 示例包中的落地方式。它在仓库中的实际部署位置是 mcp-agent/snapshot 目录。目录结构速览快照相关文件分布在两处职责清晰multimodal/tarko/mcp-agent/ ├── snapshot/ │ ├── README.md # 使用说明本文讲解对象 │ ├── runner.ts # 声明全部用例 CLI 入口 │ ├── index.test.ts # vitest 快照断言 │ └── github-reviewer-agent/ │ ├── gpt-4o-2024-11-20/event-stream.jsonl │ ├── aws_sdk_claude37_sonnet/event-stream.jsonl │ ├── doubao-1.5-thinking-vision-pro/event-stream.jsonl │ └── doubao-seed-1.6/event-stream.jsonl └── examples/ └── github-reviewer-agent/ ├── gpt-4o-2024-11-20.ts ├── aws_sdk_claude37_sonnet.ts ├── doubao-1.5-thinking-vision-pro.ts ├── doubao-seed-1.6.ts └── shared.ts可以清晰地看到一对映射关系examples 下每个.ts用例模块负责构建一个 Agent 实例与其运行参数snapshot 下同名目录存储该用例录制出的 JSONL 快照基线。此外底层库 agent-snapshot/src 提供运行时能力AgentSnapshot、Runner、Hook、SnapshotManager、Normalizer 等。快照数据长什么样event-stream.jsonl每个用例录制的结果是一个 event-stream.jsonl顶层是数组内含顺序排列的事件对象。从实际录制内容看它忠实还原了一次 Agent 运行的完整状态机agent_run_start记录本次运行的 input、provider、model 与 sessionIduser_message用户输入原文assistant_message模型的中间推理消息携带toolCallsfunction 名 arguments JSON与finishReasontool_call/tool_resultAgent 实际执行的每次工具调用及其返回结果例如browser_navigate、write_file并附带该工具的description与参数schema最终assistant_messagefinishReason: stop的最终回复例如 GitHub Reviewer 将评审报告落盘到filesystem/review__*.md后给出完成语。每条事件都有id、timestamp、type等字段。正是这份逐事件录制让后续回放能够在每轮收到哪条消息、触发哪个工具、得到什么结果上与基线严格对齐。如何声明用例runner.ts 与命名约定runner.ts 是快照用例的中央配置它基于tarko/agent-snapshot的AgentSnapshotRunner构建。关键点如下三类目录常量runner.ts L9-L13EXAMPLES_DIR指向../examples即用例源码目录FIXTURES_DIR指向../snapshot即 JSONL 基线目录SNAPSHOTS_DIR指向../__snapshots__即 vitest.snap断言文件输出目录。createCaseConfig(name)runner.ts L18-L28把category/subPath形式的名字拆解映射到三条路径用例模块examples/category/subPath.ts快照基线目录snapshot/category/subPathvitest 快照目录__snapshots__/category/subPath。内置用例runner.ts L31-L36覆盖github-reviewer-agent场景下的四个模型gpt-4o-2024-11-20aws_sdk_claude37_sonnetdoubao-1.5-thinking-vision-prodoubao-seed-1.6AgentSnapshotRunner的 CLI 解析逻辑在 agent-snapshot-runner.ts L53-L111argv[0]为子命令generate或replayargv[1]为用例名all代表全部并支持-u/--updateSnapshot开关L46-L48。若指定的用例名不在注册表中会打印Example ... not found.并以状态码 1 退出。生成快照把一次真实运行固化为基线生成快照需要真实 LLM 调用因此请确保 Agent 配置的 provider/model 对应的 API Key 等环境变量可用。README 给出的命令是# 生成指定用例的快照 npx tsx snapshot/runner.ts generate github-reviewer-agent/volcengine # 生成全部用例的快照 npx tsx snapshot/runner.ts generate all提示README 中的github-reviewer-agent/volcengine属于示例命名。当前 runner.ts 的examples数组中实际注册的是github-reviewer-agent/gpt-4o-2024-11-20、github-reviewer-agent/aws_sdk_claude37_sonnet、github-reviewer-agent/doubao-1.5-thinking-vision-pro、github-reviewer-agent/doubao-seed-1.6四个用例使用时请以runner.ts中声明的名称为准或直接使用all。目标名未注册时会收到 not found 错误提示。命令执行的实际动作在AgentSnapshotRunner.generateSnapshotagent-snapshot-runner.ts L147-L158加载该用例模块拿到agent与runOptions后以updateSnapshots: true构造AgentSnapshot并调用其generate()。底层AgentSnapshot.generateagent-snapshot.ts L188-L253的执行要点挂载AgentGenerateSnapshotHook实现在 agent-generate-snapshot-hook.ts对 Agent 运行过程做插桩调用真实 Agent 的run(runOptions)驱动 LLM 与工具执行完整跑一遍从agent.getEventStream().getEvents()取回全部事件由SnapshotManager序列化写入快照目录统计快照目录下loop-N目录的数量得到loopCount并返回AgentSnapshot通过countLoops()统计见 agent-snapshot.ts L423-L440其中loop对应 Agent 的一次思考—工具调用—观察迭代。generate 阶段返回的SnapshotGenerationResult包含snapshotPath、loopCount、最终response、全部events与metasnapshotName、executionTime类型定义见 types.ts L65-L93。回放快照零成本回归快照生成后回放不再需要任何真实 LLM 调用因此可随时离线运行。README 给出的命令npx tsx snapshot/runner.ts replay github-reviewer-agent/volcengine同样把示例名替换为 runner.ts 中实际注册的用例名例如github-reviewer-agent/gpt-4o-2024-11-20或用all回放全部用例。可选加-u/--updateSnapshot进入更新模式当新行为与基线不一致时不再报错而是直接把基线覆盖为新值。AgentSnapshotRunner.replaySnapshotagent-snapshot-runner.ts L163-L191加载用例后构造AgentSnapshot透传 update 标志并调用其replay()。底层 AgentSnapshot.replay 做的事情包括注入 Mock LLMAgentReplaySnapshotHookagent-replay-snapshot-hook.ts按录制好的逐轮 LLM 请求构造 Mock 客户端agent.setCustomLLMClient(mock)之后 Agent 的所有模型调用都走录制数据进入回放态调用agent._setIsReplay()避免 Agent 在实际执行中对某些副作用行为的处理与真实运行不一致逐轮驱动按loopCount期望迭代数跑完整决策循环同时做三类默认开启的校验见 types.ts L41-L59默认值均为trueverifyLLMRequests比对 Mock 消费的 LLM 请求是否与基线完全对应verifyEventStreams比对运行产生的状态流转与基线是否一致verifyToolCalls比对实际触发的工具调用是否与基线匹配强校验循环数如果 Agent 实际执行的循环数与基线的loop-N目录数不一致会直接抛错Loop count mismatch: Agent executed X loops, but fixture has Y loop directoriesagent-snapshot.ts L390-L394收尾清理通过SnapshotManager清理本次运行产生的 actual 文件确保每次回放互不污染。回放结果是SnapshotRunResult包含response、events与带loopCount的metatypes.ts L98-L117。用 vitest 跑快照断言除命令行回放外快照还能接入 vitest 做结构化断言入口是 index.test.ts。它把 vitest 的内联快照能力与 agent-snapshot 的回放能力结合起来import { AgentSnapshotNormalizer } from tarko/agent-snapshot; import { snapshotRunner } from ./runner; const normalizer new AgentSnapshotNormalizer({}); expect.addSnapshotSerializer(normalizer.createSnapshotSerializer()); describe.skip(AgentSnapshot tests, () { for (const example of snapshotRunner.examples) { test(should match snapshot for ${example.name}, async () { const response await snapshotRunner.replaySnapshot(example); expect(response.meta).matchSnapshot(); expect(response.events).matchSnapshot(); expect(response.response).matchSnapshot(); }); } });三个值得注意的实现细节遍历 runner 的 examples新增用例只需要改runner.tsvitest 会自动覆盖到无需为每个用例重复写断言AgentSnapshotNormalizer序列化器matchSnapshot前先通过 Normalizer 对事件做归一化处理剔除时间戳、随机 id 等运行时抖动字段避免仅因毫秒数不同导致的脆弱失败当前describe.skip默认跳过说明该 vitest 套件作为可随时开启的验证手段存在若需纳入 CI 常驻回归可按需移除skip。vitest 模式下生成的.snap断言文件会输出到 runner 中声明的__snapshots__/case目录。由于快照回放完全不访问外部 LLMvitest 模式非常适合在无网络、无 API 密钥的 CI 环境里运行。注意若改动会改变 Agent 行为如提示词、工具集、模型更新需要先重新 generate 或加-u回放再更新 vitest 快照否则两套基线都会报差异。源码级工作原理串联把上面几节串起来一次生成→回放完整闭环涉及的底层模块为agent-snapshot.ts门面类AgentSnapshot统一对外暴露与Agent.run签名对齐的run作为透明包装同时录制快照、generate真实执行并落盘与replayMock 驱动并校验构造时还通过原型链/属性代理技巧把宿主 Agent 的方法与属性透传到自身L68-L96agent-snapshot-runner.tsAgentSnapshotRunner负责 CLI 与多用例编排loadSnapshotCaseL123-L142要求用例模块导出agent实例与runOptions支持 default 导出否则抛出Invalid agent case moduleagent-generate-snapshot-hook.ts / agent-replay-snapshot-hook.ts分别负责录制期插桩与回放期 Mock 注入snapshot-manager.ts负责快照文件的写入、actual 文件清理与归一化配置管理snapshot-normalizer.ts提供 vitest 快照序列化所需的归一化能力。如何新增一个自己的用例以 GitHub Reviewer 为例examples 源码见 examples/github-reviewer-agent共享配置集中在shared.ts写用例模块在multimodal/tarko/mcp-agent/examples/category/下新建model.ts导出agent基于 tarko/agent 组装好的、绑定目标 provider/model 的 Agent 实例与runOptionsinput等运行参数。可参考同目录下已有的gpt-4o-2024-11-20.ts等文件注册到 runner在 runner.ts 的examples数组中新增一行createCaseConfig(category/model)生成基线从multimodal/tarko/mcp-agent包根目录执行npx tsx snapshot/runner.ts generate category/model真实调用 LLM需可用凭据成功后snapshot/category/model/event-stream.jsonl即生成回放验证npx tsx snapshot/runner.ts replay category/model应离线通过如需接入 vitest移除 index.test.ts 中的skip后运行npx vitest snapshot/index.test.ts。实践建议与常见注意事项生成阶段才需要外部依赖generate 依赖真实 LLM 与 API 凭据、网络replay 与 vitest 模式全程离线应作为日常回归与 CI 的主要通道行为变更要同步刷新两套基线提示词、工具集或模型版本变化导致输出变化时先以-u回放更新 JSONL 基线再更新 vitest.snap避免回放差异被误判为回归用例名必须与 runner 注册表一致CLI 对未注册名字直接报not found并以非零码退出README 中的github-reviewer-agent/volcengine是示意占位实际以 runner.ts examples 数组 为准快照文件不要手工编辑event-stream.jsonl 与.snap应视为程序生成的基线资产更新一律走工具命令否则容易引入隐蔽的不一致保持快照体积可控每次运行事件全量录制用例输入越聚焦、工具调用越少快照越稳定、越容易被审查。通过这套generate 固化 replay 回归的双通道机制多模态 MCP-Agent 的复杂行为可以被当作普通单元测试一样反复验证让依赖真实 LLM 的长链路 Agent 获得低成本、高确定性的质量保障。【免费下载链接】UI-TARS-desktopThe Open-Source Multimodal AI Agent Stack: Connecting Cutting-Edge AI Models and Agent Infra项目地址: https://gitcode.com/GitHub_Trending/ui/UI-TARS-desktop创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考