traceplay:AI Agent 的 HTTP 流量录制回放测试工具
2026/9/21 3:51:40 网站建设 项目流程

1. 这不是又一个 Mock 工具,而是一次对 AI Agent 测试范式的重写

我做 traceplay 的起因特别朴素:上周五下午三点,我盯着 CI 流水线里第 7 次失败的test_agent_routing_logic用例发呆。它在本地跑得飞快、结果完美,一上 GitLab CI 就随机超时——不是模型响应慢,是 OpenAI API 的 rate limit 突然抖动,是 Anthropic 的 streaming response 偶尔多吐一个换行,是本地 mock server 没覆盖到某个 header 的大小写敏感逻辑。更讽刺的是,我们花三周写的那个“智能路由 Agent”,核心逻辑其实只用了 200 行 TypeScript,但测试代码写了 1200 行,其中 800 行在跟网络抖动、token 计费、服务端非确定性行为死磕。

traceplay 就是在这个崩溃时刻诞生的。它不拦截请求,不伪造响应,不做任何 runtime patch;它只做一件事:把真实世界里 AI Agent 和外部服务(LLM API、RAG 向量库、工具调用 Webhook)之间那条 HTTP 通信链路,像胶片一样完整录下来——包括每一个 request headers 的空格、每一个 streaming chunk 的 timing、每一个 retry 重试的间隔、甚至 LLM 返回的content字段里那个被前端 trim 掉的末尾空格。录完之后,你得到的不是一个 JSON mock 文件,而是一个带时间戳、带二进制 payload、带完整 TCP 层语义的 trace 文件。回放时,它不走网络,不消耗 token,不触发任何计费,连 DNS 查询都跳过——它直接把当年那个“真实世界”的快照,在你的本地内存里原样复现。

关键词traceplayAPI测试HTTP回放CITypeScript不是堆砌的标签,而是它的 DNA:它用 TypeScript 写成,天然兼容 Vite/React/Vue 项目;它的 trace 文件是纯 JSON + base64,Git 友好,可 diff,可 review;它设计之初就为gitlab cigithub ci而生——CI 里跑回归?直接npx traceplay replay --trace=prod-trace-20240520.json,0 token,0 网络,300ms 跑完 12 个复杂对话链路。这不是“模拟”,是“时光机”。你测的不是代码在理想环境下的表现,而是它在上周三凌晨 2:17 那个真实生产流量下的行为。这才是 AI Agent 测试该有的样子。

2. 为什么传统方案在 AI Agent 场景下集体失效?

2.1 Mock 的幻觉:当“假装”比“真实”更难维护

绝大多数团队第一反应是 mock。用jest.mock('axios')msw拦截请求,返回预设 JSON。这在 CRUD 应用里很稳,但在 AI Agent 世界里,它迅速崩塌:

  • 响应结构非确定性:同一个 prompt,GPT-4 Turbo 可能返回{choices:[{message:{content:"A"}}]},也可能返回{choices:[{message:{content:"A\n"}}]}(注意末尾换行)。mock 数据写死content: "A",Agent 里一句response.trim()就让测试通过;但线上真实流量里那个\n会触发下游 parser 的边界错误。你 mock 的不是 API,是运气。

  • Streaming 的时间维度丢失:AI Agent 很少等完整响应才处理。它监听text/event-stream,逐 chunk 解析data: {"delta":{"content":"h"}}。mock 工具只能返回完整 JSON,或者用setTimeout模拟延迟——但真实 streaming 的 chunk 大小、间隔、buffering 行为,mock 根本无法建模。我们曾为修复一个 streaming 下游解析 bug,写了 17 个不同 delay 组合的 mock 场景,最后发现真正问题是 Cloudflare 在特定 region 对 small chunk 的 TCP ACK 合并策略。

  • Header 和 Metadata 的隐形战场:OpenAI 的x-ratelimit-limit-tokens、Anthropic 的x-amzn-bedrock-invocation-id、自研 RAG 服务的x-trace-id……这些 header 不参与业务逻辑,但决定重试策略、日志追踪、甚至 billing 分账。mock 通常只 mock body,header 全部忽略。结果就是本地测试绿灯,CI 里因为 missingx-api-key被网关 401,排查两小时才发现 mock config 里漏了一行。

提示:Mock 不是错,错在把它当成“替代品”。在 AI Agent 测试里,mock 的唯一合理角色,是隔离那些你明确不想测的依赖(比如第三方支付回调),而不是替代核心 LLM 交互链路。

2.2 录制回放(Record & Replay)的老路为何走不通?

你可能立刻想到nockpollyjs——它们确实是 HTTP 录制回放的先驱。但 traceplay 必须和它们划清界限,因为 AI Agent 带来了三个新维度:

维度传统录制工具(如 nock)traceplay 的应对
Payload 类型默认 text/plain, application/json;binary(如 multipart/form-data)支持弱,base64 编码混乱原始二进制录制:Buffer.from(payload)直接序列化为 base64,保留所有字节,包括\0、BOM、UTF-8 surrogate pairs。回放时new Uint8Array(atob(...))精确还原。
Timing 语义只记录 request/response 时间戳,不保存中间状态记录每个 TCP segment 的到达时间(基于 Node.jsnet.Socket'data'事件 timestamp),回放时用setImmediate+performance.now()精确控制 chunk 发送节奏,模拟真实网络 jitter。
Stateful 协议假设每次 request 是独立原子操作显式建模 connection lifecycle:connectrequest sentfirst chunklast chunkclose。回放时重建 socket state,让 Agent 里的socket.on('close', ...)逻辑真实触发。

举个真实例子:我们有个 Agent 需要调用 AWS Bedrock 的invokeModelWithResponseStream,它返回的 stream 包含event: content-block-deltaevent: message-stop两种 event type。nock 只能 mock 一个静态 JSON,而 traceplay 录下的 trace 文件里,你会看到:

{ "events": [ {"type": "chunk", "data": "event: content-block-delta\ndata: {\"delta\":{\"text\":\"Hello\"}}\n\n", "timestamp": 1716234567890}, {"type": "chunk", "data": "event: content-block-delta\ndata: {\"delta\":{\"text\":\" world\"}}\n\n", "timestamp": 1716234567923}, {"type": "chunk", "data": "event: message-stop\ndata: {\"stopReason\":\"end_turn\"}\n\n", "timestamp": 1716234567956} ] }

回放时,fetch()的 ReadableStream 会按毫秒级精度 emit 这三个 chunk——Agent 里那个const reader = stream.getReader(); while (true) { const {done, value} = await reader.read(); }循环,行为和生产环境 100% 一致。

2.3 CI 友好性:为什么 “录一次,跑百次” 是硬需求?

GitLab CI 和 GitHub CI 的本质是“无状态容器”。每次 job 启动,都是一个干净的 Ubuntu 镜像。这意味着:

  • 不能依赖本地 mock servermsw需要启动一个 Express server,CI 容器里没端口权限,且 server 生命周期难管理。
  • 不能动态生成 mock 数据jest.fn()在 CI 里无法访问生产环境的实时数据,mock 数据陈旧。
  • token 计费是真金白银:在 CI 里每跑一次 test,就调用一次 OpenAI API,按 $0.01/1k tokens 算,一个中等规模的 Agent 测试套件,每天 CI 成本轻松破百美元。

traceplay 的解法极其暴力:录制阶段(Record)必须在真实生产或预发环境进行。我们约定每周四晚 8 点,运维同学执行npx traceplay record --target=https://api.openai.com --output=weekly-prod-trace.json,抓取当晚 1 小时的真实流量(带 sampling rate=0.1 防爆内存)。这个 JSON 文件提交到 Git 仓库的/traces/目录下,受 code review 约束——就像 review 任何一行业务代码。CI job 里,npm test脚本第一行就是npx traceplay replay --trace=traces/weekly-prod-trace.json,整个测试过程离线、零网络、零 token。你不是在测“代码能不能跑”,而是在测“代码在上周四晚 8:15 那个真实流量下,会不会出错”。

3. traceplay 的核心实现:从录制到回放的全链路拆解

3.1 录制引擎:如何在不侵入业务代码的前提下捕获原始流量?

traceplay 不要求你改一行业务代码。它的录制基于 Node.js 的http/https模块底层 hook,原理如下:

  1. 模块加载劫持(Module Load Hook)
    traceplay 提供一个register()函数,它在 Node.js 启动早期,通过require.extensions['.js']process.env.NODE_OPTIONS=--require ./traceplay-hook.js注入。这个 hook 会监测后续所有require('http')require('https')require('node-fetch')require('axios')的加载,并动态 patch 它们的request()方法。

  2. 原始 socket 层捕获
    关键不是 patchrequest(),而是 patchhttp.ClientRequest.prototype.writenet.Socket.prototype.on('data')。当 Agent 调用fetch('https://api.openai.com/v1/chat/completions', {...})时:

    • ClientRequest.write()被拦截,将原始 request headers + body(作为 Buffer)存入内存 trace buffer;
    • Socket.on('data')被拦截,每次收到 TCP packet,都记录Buffer+performance.now()时间戳;
    • Socket.on('close')触发,将本次 connection 的所有 events(request + chunks + close)打包为一个TraceSpan
  3. 智能采样与过滤
    生产环境流量巨大,全量录制不现实。traceplay 支持:

    • --sampling-rate=0.01:1% 请求概率录制;
    • --include-path="/v1/chat/completions":只录匹配路径的请求;
    • --include-header="x-agent-id: my-cool-agent":只录带特定 header 的请求;
    • --max-body-size=100kb:body 超过则截断并标记"body_truncated": true

注意:录制时 traceplay 会自动添加x-traceplay-id: tp_abc123到每个 request header,用于在 production logs 中关联 trace ID,方便事后审计。这个 header 在回放时会被自动 strip,避免污染测试环境。

3.2 Trace 文件格式:为什么是 JSON + base64,而不是 Protocol Buffer?

我们反复讨论过是否用 Protobuf 或 MsgPack 来压缩 trace 文件。最终选择纯 JSON + base64,理由非常务实:

  • Git 友好:JSON 是文本,可 diff。当你git diff traces/prod-trace-20240520.json,你能清晰看到:

    - "data": "event: content-block-delta\ndata: {\"delta\":{\"text\":\"Hello\"}}\n\n" + "data": "event: content-block-delta\ndata: {\"delta\":{\"text\":\"Hello!\"}}\n\n"

    而 Protobuf binary diff 是一团乱码,Code Review 无法进行。

  • 人类可读可编辑:测试失败时,开发者可以直接打开.json文件,用 VS Code 搜索"error",定位到那个失败的 response chunk,手动修改status: 500status: 200,再回放验证修复逻辑——这是 Protobuf 永远做不到的。

  • VS Code 插件生态:已有成熟的 JSON Schema 插件、Prettify 插件、JSONPath 查询插件。我们为 traceplay 定义了 官方 JSON Schema ,VS Code 安装 Schema 插件后,.json文件自动获得字段提示、格式校验、错误高亮。

一个典型的 trace 文件结构(精简版):

{ "version": "1.2", "recorded_at": "2024-05-20T20:15:33.123Z", "environment": "production", "spans": [ { "id": "span_001", "protocol": "https", "method": "POST", "url": "https://api.openai.com/v1/chat/completions", "request": { "headers": { "authorization": "Bearer sk-...", "content-type": "application/json" }, "body": "ewogICAicG9tIjogImFzc2lzdGFudCIsCiAgInVzZXIiOiAiYm9iIgp9" // base64 of '{"model":"assistant","user":"bob"}' }, "response": { "status": 200, "headers": { "content-type": "text/event-stream", "x-ratelimit-remaining-tokens": "9999" }, "events": [ { "type": "chunk", "data": "ZGF0YTogeyJkZWx0YSI6eyJ0ZXh0OiJIZWxsbyJ9fQoK", "timestamp": 1716234567890 }, { "type": "chunk", "data": "ZGF0YTogeyJkZWx0YSI6eyJ0ZXh0OiIgd29ybGQifX0K", "timestamp": 1716234567923 } ] } } ] }

3.3 回放引擎:如何让 fetch() “相信”它正在连接真实服务器?

回放的核心挑战是:如何让业务代码里的fetch()axios.post()等调用,不经过真实网络,却得到和录制时完全一致的响应流?traceplay 采用“双层拦截”策略:

  • Layer 1: Global Fetch/Request Override
    在回放模式下,traceplay 动态 patchglobalThis.fetchrequire('http').request。当fetch('https://api.openai.com/...')被调用时,它不发起网络请求,而是:

    1. 解析 URL,匹配 trace 文件中spans[].url
    2. 找到对应 span,检查spans[].request.body是否与当前 request body 一致(base64 decode 后 deepEqual);
    3. 如果匹配,进入 Layer 2。
  • Layer 2: Stream 仿真
    这是最精妙的部分。traceplay 不返回一个静态 Response,而是返回一个ReadableStream实例,其内部逻辑是:

    const stream = new ReadableStream({ start(controller) { const events = span.response.events; let index = 0; const sendNextChunk = () => { if (index >= events.length) { controller.close(); return; } const event = events[index]; // 计算从上一个 chunk 到现在的 delay(毫秒) const delay = index === 0 ? 0 : event.timestamp - events[index-1].timestamp; setTimeout(() => { controller.enqueue(new TextEncoder().encode(event.data)); index++; sendNextChunk(); }, delay); }; sendNextChunk(); } });

    这个 stream 的getReader()返回的 reader,其read()方法的行为,和真实 streaming response 的 reader 完全一致——包括done: falsevalueUint8Arrayabort()可中断等。Agent 代码无需任何修改,const reader = response.body.getReader();照常工作。

3.4 TypeScript 类型系统深度集成:不只是语法糖,而是安全网

traceplay 的 TypeScript 支持不是“有就行”,而是贯穿开发、测试、CI 全流程的安全增强:

  • 录制时类型推导
    当你在src/agent.ts里写:

    const response = await fetch('https://api.openai.com/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'gpt-4', messages: [...] }) });

    traceplay 的录制 hook 会自动提取这个 call site 的RequestInit类型,并在生成的 trace 文件 schema 中,为spans[].request添加@ts-type注释:

    { "request": { "@ts-type": "RequestInit", "headers": { "...": "..." } } }
  • 回放时编译期校验
    test/agent.test.ts中,你写:

    it('should handle streaming response', async () => { await traceplay.replay('traces/prod-trace.json'); const result = await runAgent(); // calls fetch(...) expect(result).toBe('Hello world'); });

    traceplay 的replay()函数签名是:

    declare function replay(tracePath: string): Promise<void>;

    但更重要的是,它提供了一个@traceplay/types包,其中定义了:

    // node_modules/@traceplay/types/index.d.ts declare module 'traceplay' { export interface TraceSpan { id: string; url: string; request: { headers: Record<string, string>; body: string; // base64 }; response: { status: number; headers: Record<string, string>; events: Array<{ data: string; timestamp: number }>; }; } }

    这意味着,如果你在 test 里试图访问span.response.nonexistentField,TypeScript 编译器会直接报错,而不是等到 CI 运行时才发现。

  • VS Code 智能提示
    安装@traceplay/types后,当你在.jsontrace 文件里输入"respo,VS Code 会自动提示"response",并显示其完整接口定义。你甚至可以Ctrl+Click跳转到类型定义——这比任何文档都直观。

4. 实操指南:从零开始,5 分钟接入你的第一个 AI Agent 测试

4.1 环境准备:TypeScript 项目一键集成

假设你有一个基于 Vite + React + TypeScript 的 AI Agent 项目,目录结构如下:

my-agent/ ├── src/ │ ├── agent.ts # 核心 Agent 逻辑,含 fetch 调用 │ └── main.ts ├── tests/ │ └── agent.test.ts # 未来存放 traceplay 测试 ├── package.json └── tsconfig.json

Step 1: 安装 traceplay

# 开发依赖,只在测试时需要 npm install --save-dev traceplay @traceplay/types # 或 yarn yarn add -D traceplay @traceplay/types

Step 2: 配置 TypeScript(关键!)
tsconfig.jsoncompilerOptions中,确保包含:

{ "compilerOptions": { "types": ["node", "traceplay"], // 👈 加入这一行!让 TS 知道 @traceplay/types "lib": ["ES2020", "DOM", "DOM.Iterable", "ScriptHost"], "moduleResolution": "node", "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "strict": true, "noImplicitOverride": true, "noPropertyAccessFromIndexSignature": true, "allowSyntheticDefaultImports": true, "esModuleInterop": true, "resolveJsonModule": true, // 👈 必须开启,因为 trace 文件是 .json "isolatedModules": true, "jsx": "react-jsx" } }

Step 3: 创建第一个 trace 文件(录制)
在生产环境服务器上(或你的本地 dev 环境,确保能访问真实 API):

# 录制 10 个请求,保存到 traces/demo.json npx traceplay record \ --target=https://api.openai.com \ --output=traces/demo.json \ --count=10 \ --timeout=30000

实操心得:第一次录制,务必用--count=1--verbose参数。它会打印出每个 captured span 的摘要,确认你录到了想要的请求。我们曾因.envOPENAI_BASE_URL指向了 mock server,录了一堆无效 trace,浪费 2 小时。

4.2 编写第一个回放测试:告别 token,拥抱确定性

tests/agent.test.ts中:

// 引入 traceplay 的全局类型和函数 import { replay } from 'traceplay'; import { runAgent } from '../src/agent'; describe('Agent Streaming Logic', () => { // beforeAll 在所有 test 之前运行,一次性加载 trace beforeAll(async () => { // 注意:路径是相对于 process.cwd(),即项目根目录 await replay('traces/demo.json'); }); it('should parse streaming chunks correctly', async () => { const result = await runAgent(); // runAgent() 内部会调用 fetch(...),但此时已被 traceplay 拦截 expect(result).toBe('Hello world'); // 这个字符串来自 trace 文件里的 events }); it('should handle error status gracefully', async () => { // 我们可以在 trace 文件里手动修改一个 span 的 status 为 500 // 然后测试 Agent 的错误 fallback 逻辑 const result = await runAgent(); expect(result).toBe('Fallback response'); }); });

Step 4: 运行测试

# 使用 Vitest(推荐,对 streaming 支持最好) npm install -D vitest # 在 package.json scripts 中添加 "scripts": { "test": "vitest" } # 运行!全程离线,0 token,< 1s npm test

4.3 CI 集成:GitLab CI / GitHub CI 的标准配置

GitHub CI (.github/workflows/test.yml)

name: Test AI Agent on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' - name: Install dependencies run: npm ci - name: Run traceplay tests run: npm test # 👇 关键:确保 trace 文件在 git 中,且 CI 能读取 # traceplay replay 会自动查找 traces/ 目录下的最新文件

GitLab CI (.gitlab-ci.yml)

test:ai-agent: image: node:20 before_script: - npm ci script: - npm test artifacts: - coverage/**/* only: - main - develop

注意事项:trace 文件必须 commit 到 Git。我们禁止在 CI 中动态录制(npx traceplay record),因为 CI 环境没有生产 API 密钥,且录制行为本身不可控。所有 trace 文件都由 SRE 团队在受控环境下生成,经 QA review 后 merge。这是 traceplay 的铁律:trace 是代码,不是日志

4.4 高级技巧:如何用 traceplay 做真正的“回归测试”

录制一次,只是开始。真正的价值在于“对比”和“变异”。

  • Diff 模式:发现 API 行为漂移
    当 OpenAI 更新了/v1/chat/completions的 response schema(比如新增usage字段),你的 Agent 可能因response.usage.total_tokens报错。用 traceplay 的 diff 工具:

    npx traceplay diff \ --old=traces/openai-v1.0.json \ --new=traces/openai-v1.1.json \ --output=diff-report.md

    它会生成 Markdown 报告,高亮所有response.headersresponse.events[].data的变化,甚至指出哪个 chunk 的data字段多了 3 个字节。

  • Mutation 模式:注入故障,测试韧性
    你怀疑 Agent 在 LLM 返回status: 429时重试逻辑有 bug?不用等真实限流,用 traceplay mutation:

    npx traceplay mutate \ --input=traces/prod-trace.json \ --output=traces/prod-trace-429.json \ --path='spans[0].response.status' \ --value=429

    然后在 test 里await replay('traces/prod-trace-429.json'),直接验证重试逻辑。

  • Coverage 模式:量化测试完整性
    traceplay 可以分析你的测试用例覆盖了 trace 文件中多少个 spans:

    npx traceplay coverage \ --trace=traces/prod-trace.json \ --test-dir=tests/ \ --output=coverage.json

    输出 JSON 包含total_spans: 120,covered_spans: 87,coverage_percent: 72.5。这比任何 Istanbul 覆盖率都真实——因为它覆盖的是你真正面对的生产流量。

5. 常见问题与避坑指南:那些只有踩过才懂的细节

5.1 “为什么我的 fetch 调用没被 traceplay 拦截?”

这是新手最高频问题。根本原因只有一个:traceplay 的 hook 没在 fetch 调用之前生效

典型场景和解决方案:

  • Scenario A:Vite 开发服务器热更新导致 hook 失效
    Vite HMR 会 reload 模块,但require.extensionshook 是全局的,不会被 HMR 清除。问题在于,你可能在main.tsimport { runAgent } from './agent',而agent.tsimport 'traceplay/register'—— 但 Vite 的 import order 不保证traceplay/register一定在fetch之前执行。
    ✅ 正确做法:在vite.config.tsoptimizeDeps.include中加入['traceplay/register'],强制它最先加载;或在index.html<script>中提前import 'traceplay/register'

  • Scenario B:使用了非标准 fetch 实现(如 undici)
    traceplay 默认只 patchnode:httpnode:https。如果你用undicicross-fetch,需要显式启用:

    npx traceplay record --target=https://api.openai.com --use-undici
  • Scenario C:fetch 调用在 Worker 或 iframe 中
    traceplay 的 hook 只作用于主 Node.js 进程。Web Worker 有自己的 global scope,需要在 worker script 开头import 'traceplay/register'

实操心得:永远先运行npx traceplay debug --list-hooks。它会列出当前进程所有被 patch 的模块(http,https,node-fetch,axios),如果列表为空,说明 hook 根本没加载。

5.2 “回放时 fetch 报错:TypeError: Cannot read properties of undefined (reading 'getReader')”**

这是 streaming response 的经典陷阱。错误原因是:你的 Agent 代码里写了response.body.getReader(),但 traceplay 回放返回的Response对象,其body是一个ReadableStream,而某些旧版node-fetchwhatwg-fetchpolyfill 会把body强制转成nullundefined

✅ 解决方案分三步:

  1. 升级 fetch 实现:确保用node-fetch@3.x或原生globalThis.fetch(Node.js 18+)。node-fetch@2.x不支持 streaming body。
  2. 在 test 中显式设置全局 fetch
    // tests/setup.ts import { fetch as nodeFetch } from 'node-fetch'; globalThis.fetch = nodeFetch as any;
  3. 检查 Agent 代码的健壮性
    // ❌ 危险写法 const reader = response.body.getReader(); // ✅ 安全写法(traceplay 也推荐) if (!response.body) { throw new Error('No response body'); } const reader = response.body.getReader();

5.3 “trace 文件太大,Git 提交失败”**

一个 1 小时的全量 trace 可能达 500MB。这不是设计缺陷,而是你需要主动管理的数据资产。

✅ 三个层次的瘦身策略:

  • Level 1: 录制时过滤
    --include-path="/v1/chat/completions" --exclude-header="x-debug",去掉无关请求和 debug header。
  • Level 2: 录制后压缩
    traceplay 提供npx traceplay compress --input=big.json --output=small.json --max-chunk-size=8192,它会把大 chunk 拆分成 8KB 小块,base64 编码后更利于 Git delta compression。
  • Level 3: Git LFS
    对于 >10MB 的 trace 文件,用 Git LFS:
    git lfs track "traces/*.json" git add .gitattributes git commit -m "track traces with lfs"

注意:不要用gzip压缩 trace 文件!因为.json.gz不可 diff,违背了 traceplay 的 Git 友好原则。压缩必须在 JSON 结构内完成(如 chunk 拆分、base64 优化)。

5.4 “CI 里 replay 报错:Error: ENOENT: no such file or directory, open 'traces/prod-trace.json'”**

这通常不是文件不存在,而是路径问题。

✅ 排查清单:

  • ls -la traces/确认文件存在,且权限为644(CI 容器默认无写权限);
  • pwd确认当前工作目录是项目根目录(不是tests/子目录);
  • cat traces/prod-trace.json | head -n 5确认文件内容可读;
  • 最关键:检查.gitignore—— 是否误加了traces/?trace 文件必须被 Git track!

实操心得:我们在 CI 的 first step 总是加一行echo "Current dir: $(pwd) && Files in traces/: $(ls -la traces/)",日志里一眼看清路径真相。

5.5 “如何测试多个 Agent 并行调用同一个 LLM API?”**

traceplay 天然支持并发。录制时,它为每个 TCP connection 分配唯一span.id;回放时,replay()会并发处理所有 spans。

✅ 但要注意:你的 Agent 代码必须是“纯函数式”的,不能依赖全局 mutable state。例如:

// ❌ 危险:共享 counter let requestCount = 0; async function runAgent() { requestCount++; // 这个值在回放时会错乱! return fetch(...); } // ✅ 安全:stateless async function runAgent() { return fetch(...); }

traceplay 的回放是 deterministic 的,但前提是你的业务逻辑也是 deterministic 的。这是 AI Agent 测试的终极哲学:你无法控制 LLM,但你能控制自己的代码

6. 从 traceplay 到 AI Engineering:一个工具引发的工程范式迁移

我最初写 traceplay,只是想让 CI 不再因网络抖动失败。但上线三个月后,它意外地重塑了我们整个 AI Engineering 流程。

首先是测试文化的转变。以前 PR review 的焦点是“这段代码逻辑对不对”,现在变成了“这个 trace 文件覆盖了哪些真实流量场景?有没有 edge case 漏掉?”。SRE 同学开始主动给每个新上线的 Agent 提供trace-sample.json,里面包含 5 个典型对话流(成功、streaming timeout、429 rate limit、503 service unavailable、malformed response)。这比任何 swagger doc 都直观。

其次是故障复盘的效率革命。上周生产环境出现一个偶发 bug:Agent 在处理长文本时,RAG 服务返回的 chunk 顺序错乱。过去,我们要翻查 3 小时的 CloudWatch logs,grep 出 200 行相关日志,再拼凑出时间线。现在,运维直接git checkout出那个时间点的 trace 文件,npx traceplay replay --trace=traces/20240519-2215.json,在本地 10 秒内复现,然后git bisect找出引入 bug 的 commit。整个过程 12 分钟,而不是过去的 3 小时。

最深远的影响,是对“确定性”的重新定义。在传统软件工程里,“确定性”意味着相同输入必

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询