1. 为什么你的 AI 测试套件总在半夜挂掉先说一个我踩过的坑去年做一个带工具调用的 Agent 项目本地跑得好好的一上 CI 就随机红。排查半天发现是某个上游模型那周悄悄改了一个字段的默认值Mock 固件还是旧的测试全绿但真实链路已经对不上了。这种问题不是个例而是 AI 应用测试的常态。一个 2026 年的典型 Agent 请求在返回最终响应之前链路里可能串了六七个服务LLM 推理、MCP 工具服务器、向量数据库检索、重排器、Web 搜索、内容审核甚至还有通过 A2A 协议通信的子 Agent。大多数团队只 Mock 了其中一环通常是 LLM剩下的全是真实网络调用。结果就是token 在烧、延迟在抖、响应格式在变测试套件看起来在跑实际上变成了一纸空谈。AIMock 想解决的就是这个整栈 Mock 的问题。它把 LLMock、MCPMock、A2AMock、VectorMock 以及搜索/重排/审核这些大家总忘记 Mock 的 API收进一个配置文件、一个端口里跑起来。零依赖全部基于 Node.js 内置模块npm install copilotkit/aimock就能用。但这里有个现实问题Mock 服务本身要跑起来、要联调、要验证你总得有个稳定的模型通道来对照真实响应或者在没有真实 Key 的情况下也能把链路跑通。这就是 TaoToken 出场的地方——它提供一个统一的 Key 和 API 通道让你在本地 Mock 联调时既能接真实模型做对照也能在纯 Mock 模式下不碰任何外部网络。下面我把整套配置、启动参数和端到端验证命令完整走一遍。2. TaoToken 统一 Key 与 API 通道前置准备在动手配 AIMock 之前先把模型通道这块理清楚。AIMock 的定位是 Mock但你在做录制与回放或者漂移检测时需要一个真实的上游来做对照。如果每个提供商都单独申请 Key、单独配 Base URL光是环境变量就能把你绕晕。TaoToken 的价值就在于把这些收敛成一个统一的入口。你需要准备的东西很简单一个 TaoToken 的 API Key以及它的 API 地址。官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 基址是 https://taotoken.net/api 。注意 API 地址后面不加任何 UTM 参数保持干净。拿到 Key 之后先别急着写代码用一条 curl 确认通道是通的。这一步很关键因为后面 AIMock 的录制模式会把请求转发到上游如果通道本身有问题你会误以为是 Mock 配置错了。curl -s https://taotoken.net/api/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -d { model: gpt-4o-mini, messages: [{role: user, content: ping}], stream: false }如果返回里能看到choices[0].message.content说明通道正常。这里有个细节AIMock 的 LLMock 支持 11 个提供商包括 OpenAI、Claude、Gemini、Bedrock、Azure、Vertex AI、Ollama、Cohere、OpenRouter 等。当你用 TaoToken 作为统一上游时可以在 AIMock 配置里把 provider 的 base URL 指向 TaoToken这样录制下来的固件格式是统一的不用为每个提供商单独处理。关于 Key 的获取和更多接入方式可以看接入文档https://taotoken.net/doc 。如果你只是想先验证模型对话是否正常可以直接用模型对话页面试一下https://taotoken.net/models 。对于长期做编码和 Agent 联调的团队Coding Plan 会更划算入口在 https://taotoken.net/coding-plan 。这里要强调一点TaoToken 是合规的 API 通道不是那种灰色中转。你在配置里正常填 Base URL 和 Key 就行不需要任何额外网络工具。这一点在团队协作时尤其重要因为 CI 环境里不允许有乱七八糟的东西。3. 可复制配置aimock.json 与 LLMock/MCPMock 启动参数现在进入正题。AIMock 的核心是一个配置文件它把 LLM、MCP、A2A、Vector 以及各类服务都收在一个端口上。下面这份aimock.json是我实测下来比较稳的版本你可以直接复制改路径。{ llm: { fixtures: ./fixtures/llm, providers: [openai, claude, gemini], upstream: { baseUrl: https://taotoken.net/api, apiKeyEnv: TAOTOKEN_API_KEY } }, mcp: { tools: ./fixtures/mcp/tools.json }, a2a: { agents: ./fixtures/a2a/agents.json }, vector: { path: /vector, collections: [] }, services: { search: true, rerank: true, moderate: true } }几个关键点解释一下。llm.upstream这块是我加上去的用来告诉 AIMock 在录制模式下把未命中的请求转发到哪里。apiKeyEnv指向环境变量不要把 Key 硬编码进配置文件这是基本安全习惯。services里三个开关全开意味着搜索、重排、审核都有兜底 Mock不会偷偷漏到真实网络。启动命令用 CLI 最省事export TAOTOKEN_API_KEY你的Key npx aimock --config aimock.json --port 4010如果你要开录制模式加上--record和上游地址npx aimock --fixtures ./fixtures \ --record \ --provider-openai https://taotoken.net/api \ --port 4010注意这里--provider-openai后面跟的是 TaoToken 的 API 基址。AIMock 会把认证头转发给上游但绝不会把 Key 写进固件文件这点设计得很干净。接下来是 MCPMock 的挂载。MCP 是 Agent 调用工具的方式如果你的 Agent 用了 MCP测试里每次工具调用都在打真实服务器延迟不可控、返回不可控。MCPMock 提供一个本地 JSON-RPC 2.0 服务器你的 Agent 连接它的方式和连真实 MCP 完全一样。import { LLMock, MCPMock } from copilotkit/aimock; const llm new LLMock({ port: 5555 }); const mcp new MCPMock(); mcp.addTool({ name: calc, description: Calculator, inputSchema: { type: object, properties: { expr: { type: string } } } }); mcp.onToolCall(calc, (args) { const { expr } args; return result: ${expr}; }); llm.mount(/mcp, mcp); await llm.start(); // MCP 现在挂在 http://127.0.0.1:5555/mcp这里有个容易踩的坑llm.mount(/mcp, mcp)让 LLMock 和 MCPMock 共享一个端口这样你的 Agent 只需要配一个 Base URL。如果你分开跑两个端口Agent 配置里要写两个地址联调时容易搞混。共享端口是更省心的做法。如果你用的是 Claude Code 或者 Cline 这类工具做联调配置里要写全三件套Base URL、Key、Model ID。以 Claude Code 的 settings 为例{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: 你的Key, ANTHROPIC_MODEL: claude-3-5-sonnet-20241022 } }Cline 的 MCP 配置类似在cline_mcp_settings.json里把 server 的 url 指向http://127.0.0.1:5555/mcp就能让 Cline 走本地 Mock 的 MCP 工具。Codex 的auth.json则是把OPENAI_BASE_URL指向 TaoToken 的 API 地址。这三件套缺一不可少一个就会出现 401 或者 model not found。4. 端到端验证一次请求的完整命令与预期返回配置写完了得验证它真的能跑通。我习惯从最简单的一条请求开始逐步加复杂度。先确认 LLMock 的非流式响应。curl -s http://127.0.0.1:4010/v1/chat/completions \ -H Content-Type: application/json \ -d { model: gpt-4, messages: [{role: user, content: hello}], stream: false }预期返回里应该有choices[0].message.contentobject字段是chat.completionid以chatcmpl-开头。如果你在固件里注册了hello对应的响应返回的就是你写死的内容如果没注册且开了录制模式它会转发到 TaoToken 上游并把响应存成固件。接着验证 MCP 工具调用。用一个最小的 MCP 客户端请求curl -s http://127.0.0.1:4010/mcp \ -H Content-Type: application/json \ -H Mcp-Session-Id: test-session-001 \ -d { jsonrpc: 2.0, id: 1, method: tools/call, params: { name: calc, arguments: { expr: 6*7 } } }预期返回里result.content[0].text应该是result: 6*7。注意Mcp-Session-Id这个头MCPMock 支持完整的会话生命周期符合 Streamable HTTP 规范。如果你不带这个头某些客户端会报会话错误。再验证一下搜索服务的兜底。因为配置里services.search开了请求/search端点应该返回 Mock 结果而不是真实网络调用curl -s http://127.0.0.1:4010/search \ -H Content-Type: application/json \ -d {query: weather}如果你在 LLMock 上用mock.onSearch(weather, [...])注册了固件返回的就是你写的那几条没注册的话onSearch(/.*/, [])这种 catch-all 会返回空数组保证不会漏到真实 API。最后跑一次完整的 Agent 链路验证。假设你的 Agent 先调 LLM 决定用工具再通过 MCP 调工具最后把结果回传给 LLM。用 vitest 写一个集成测试import { LLMock, MCPMock } from copilotkit/aimock; import { describe, it, expect, beforeAll, afterAll } from vitest; let llm, mcp; beforeAll(async () { llm new LLMock({ port: 5555 }); mcp new MCPMock(); mcp.addTool({ name: search, description: Search }); mcp.onToolCall(search, (args) Found 3 results for ${args.query}); llm.mount(/mcp, mcp); await llm.start(); }); afterAll(async () { await llm.stop(); }); it(agent uses tool and returns final answer, async () { llm.on( { userMessage: search for AI news }, { content: Here are the latest AI news... } ); const res await fetch(${llm.url}/v1/chat/completions, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ model: gpt-4, messages: [{ role: user, content: search for AI news }], stream: false }) }); const body await res.json(); expect(body.choices[0].message.content).toContain(AI news); });这个测试跑起来应该是秒级返回不碰任何真实网络。如果你把llm.url换成 TaoToken 的地址同样的测试就会走真实通道用来做对照验证。5. 本篇常见错排查401、local proxy failed 与 reading choices联调过程中最容易撞的几个报错我按出现频率排一下。401 Unauthorized。这个九成是 Key 没配对。检查三件事环境变量TAOTOKEN_API_KEY是否在当前 shell 里 export 了配置文件里apiKeyEnv的名字是否和实际环境变量一致请求头里Authorization: Bearer后面有没有多余空格。如果你用的是 Claude Code检查ANTHROPIC_API_KEY和ANTHROPIC_BASE_URL是否都指向了 TaoToken。三件套里少一个都会 401。local proxy failed / ECONNREFUSED。这个通常是 AIMock 没起来或者端口被占。先curl http://127.0.0.1:4010/health看服务是否活着。如果端口冲突换一个--port 4011。还有一种情况是你在 Docker 里跑 AIMock但请求从宿主机发这时候要用host.docker.internal而不是127.0.0.1。官方 Docker 镜像在ghcr.io/copilotkit/aimockCI 里用很方便。reading choices 报错 / Cannot read property choices of undefined。这个说明返回体不是预期的 chat completion 格式。常见原因有两个一是请求打到了错误的端点比如把/v1/chat/completions写成了/chat/completions二是上游返回了错误 JSON但你的代码直接去读choices。排查方法是先把原始响应打出来curl -v http://127.0.0.1:4010/v1/chat/completions \ -H Content-Type: application/json \ -d {model:gpt-4,messages:[{role:user,content:hi}]}看 HTTP 状态码和响应体。如果是 404说明固件没匹配上且没开录制如果是 503说明开了--strict模式且未命中固件这是预期行为补固件就行。OAuth / token refresh 相关报错。如果你用的是需要 OAuth 的提供商AIMock 的录制模式会转发认证头但不会保存。检查你的上游配置是否正确传递了 refresh token。用 TaoToken 的话一般不会有这个问题因为它是统一的 Key 通道不需要每个提供商单独 OAuth。漂移检测报 critical。这个不是错误是 AIMock 在提醒你 Mock 和真实 API 不一致了。看报告里的Path字段比如choices[].message.refusal在 SDK 和真实 API 里都有但 Mock 里缺失补上对应字段的固件即可。严重级别会直接让测试失败警告级别只记录。6. 把 Mock 链路接进你的日常开发流配置跑通之后接下来是把它变成习惯。我的做法是在项目根目录放一个aimock.json固件目录fixtures/提交进 GitCI 里用 Docker 镜像起服务测试全部指向本地端口。这样任何人 clone 下来npm test就能跑不依赖任何外部 Key。录制模式适合在开发新功能时用。你正常写代码、正常调 TaoToken 的真实模型AIMock 在后台把请求和响应录成固件。等固件攒够了切到回放模式测试就完全离线了。这里有个技巧录完之后跑一次--strict把所有未命中的请求都暴露出来逐个补固件直到测试全绿。这样你的测试套件就真正做到了确定性。混沌测试是另一个值得早点接入的东西。在固件里加chaos字段或者在请求头里加x-aimock-chaos-disconnect: 1.0就能强制注入故障。我一般会在 CI 里跑一轮带混沌的测试验证应用在 500、格式错误 JSON、连接中断这三种情况下的降级逻辑。这些故障在真实环境里迟早会遇到与其等用户报 bug不如在测试里先撞一遍。最后说下漂移检测。它每天在 CI 里跑三方对比SDK 类型、真实 API 响应、Mock 输出。任何一方不一致24 小时内就能知道。这个功能需要真实上游所以 TaoToken 的统一通道在这里就体现出价值了——你只需要配一个 Base URL 和一个 Key就能对多个提供商做漂移检测不用维护一堆环境变量。如果你还没开始用建议先从 LLMock 单模块入手把一条请求跑通再逐步加 MCPMock 和 VectorMock。不要一上来就配全套那样出问题不好定位。等单模块稳了再往整栈扩。API Key 在 https://taotoken.net/api-keys 拿接入文档在 https://taotoken.net/doc 模型对话验证在 https://taotoken.net/models 。长期做 Agent 联调的话Coding Plan 的入口在 https://taotoken.net/coding-plan 按需选就行。
阅读完成 · 觉得有帮助?