解决 AI 应用开发中频繁调用真实 API 导致成本爆炸的问题,通过集中式 Mock 服务提升开发效率和降低开支。
Angular 支持 · 在 Angular 中构建智能体应用与生成式 UI · 已发布
当我们开始构建 AI 应用时,每次测试运行都会调用实时 LLM API、烧代币,并因为某个 LLM 提供商那周改了响应格式而随机失败。所以我们构建了 LLMock,开源了它,然后认为完成了。
但实际上,在 2026 年,一个单独的智能体请求返回响应之前,可能会接触六七个服务:LLM、MCP 工具服务器、向量数据库、重排序器、网络搜索 API、内容审核层、A2A 子智能体。
大多数团队只模拟其中一个。其他六个都是实时的、非确定性的,默默地让你的测试套件成为一个谎言。这就是我们构建 AIMock 的原因。
AIMock 从单个配置文件模拟你的整个智能体栈。它还做了其他模拟工具做不了的事情:记录真实 API 响应并将其作为测试数据重放、运行每日漂移检测以在用户发现之前捕捉提供商变化,以及注入故障来证明你的应用能优雅地处理这些情况。
零依赖,全部用 Node.js 内置模块构建。文档。
npm install @copilotkit/aimock
AG-UI 在生产环境中使用 AIMock。
AIMock 是开源且免费的。
一个现实的智能体请求看起来是这样的:
用户消息
→ LLM 决定使用工具
→ 通过 MCP 调用工具(文件系统、数据库、日历)
→ 从 Pinecone 或 Qdrant 检索 RAG
→ 通过 Tavily 网络搜索
→ Cohere 重排序器排序结果
→ 回到 LLM,带上完整上下文
每个都是测试环境中的实时网络调用。每个都可能失败、返回稍微不同的东西,或者让你消耗代币。
我们查看了市面上的每个工具。有些处理 LLM 模拟。有些处理一个协议。没有人覆盖全貌。你需要拼凑三四个库,每个都有自己的配置格式,而且还有漏洞。
这是 AIMock 与其他选项的对比。
它模拟你的 AI 应用与之通信的所有东西。这是里面包含的内容:
LLMock:11 个提供商、完整流式传输、工具调用、推理模型
MCPMock:本地 MCP 服务器,完整 JSON-RPC 2.0、会话管理、工具、资源、提示
A2AMock:智能体卡发现、消息路由、智能体间 SSE 流式传输
VectorMock:用于确定性 RAG 检索的模拟向量数据库
Services:搜索、重排序和内容审核。那些每个人都忘记模拟的 API。
除此之外,AIMock 做了其他模拟工具做不了的三件事:
Drift Detection:每天针对真实提供商 API 运行,在 24 小时内捕捉响应格式变化,比用户早发现
Record and Replay:代理真实 API 调用、将其保存为测试数据、在 CI 中永远重放它们,不再接触实时 API
Chaos Testing:注入 500 错误、格式错误的 JSON 和中途断开连接,证明你的应用能处理故障
在一个端口上运行所有这些,使用单个配置文件:
{
"llm": { "fixtures": "./fixtures/llm", "providers": ["openai", "claude", "gemini"] },
"mcp": { "tools": "./fixtures/mcp/tools.json" },
"a2a": { "agents": "./fixtures/a2a/agents.json" },
"vector": { "path": "/vector", "collections": [] }
}
用 AIMock CLI 快速开始:
npx aimock --config aimock.json --port 4010
让我们简要介绍每一个。
LLMock 在真实端口上运行真实 HTTP 服务器,不是进程内补丁。机器上的任何进程都可以访问它:你的 Next.js 应用、智能体工作者、LangGraph 进程,任何能说 HTTP 的东西。
它原生支持 11 个提供商:OpenAI、Claude、Gemini、Bedrock、Azure、Vertex AI、Ollama、Cohere、OpenRouter 和 Anthropic Azure。推理模型在所有提供商中都得到支持。
任何兼容 OpenAI 的端点,如 Mistral、Groq、Together AI、vLLM 也能开箱即用。完整流式传输、工具调用、结构化输出、扩展思考、多轮对话和 WebSocket API 全部内置。
AIMock 内部处理翻译,所以一个测试数据格式可以跨所有提供商工作。
使用 vitest 的程序化 API,注册一个测试数据并对响应进行断言。beforeAll 为测试套件启动一次模拟服务器,afterAll 将其关闭,mock.on() 注册一个映射用户消息到确定性响应的测试数据。
import { LLMock } from "@copilotkit/aimock";
import { describe, it, expect, beforeAll, afterAll } from "vitest";
let mock: LLMock;
beforeAll(async () => {
mock = new LLMock();
await mock.start();
});
afterAll(async () => {
await mock.stop();
});
it("non-streaming text response", async () => {
mock.on({ userMessage: "hello" }, { content: "Hello! How can I help?" });
const res = await fetch(`${mock.url}/v1/chat/completions`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model: "gpt-4",
messages: [{ role: "user", content: "hello" }],
stream: false,
}),
});
const body = await res.json();
expect(body.choices[0].message.content).toBe("Hello! How can I help?");
expect(body.object).toBe("chat.completion");
expect(body.id).toMatch(/^chatcmpl-/);
});
将你现有的 OpenAI 客户端指向模拟 URL。你的代码中没有其他东西改变。没有 API 密钥、没有网络调用、每次都是相同的响应。完整提供商文档在这里。
MCP 是智能体调用工具的方式。如果你的智能体使用 MCP,测试套件中的每次工具调用都会访问实时服务器,有真实延迟且无法控制返回的内容。
MCPMock 给你一个使用完整 MCP 协议通过 JSON-RPC 2.0 通信的本地服务器。你的智能体连接到它就像连接到真实 MCP 服务器一样。你控制返回的内容。
import { MCPMock } from "@copilotkit/aimock/mcp";
const mcp = new MCPMock();
mcp.addTool({
name: "search",
description: "Search the web",
inputSchema: { type: "object", properties: { query: { type: "string" } } },
});
mcp.onToolCall("search", (args) => {
const { query } = args as { query: string };
return `Found 3 results for "${query}"`;
});
const url = await mcp.start();
// point your MCP client at `url`
在真实的智能体测试中,你的 LLM 和 MCP 工具服务器一起运行。将 MCPMock 挂载到 LLMock 上,使它们共享一个端口:
import { LLMock, MCPMock } from "@copilotkit/aimock";
const llm = new LLMock({ port: 5555 });
const mcp = new MCPMock();
mcp.addTool({ name: "calc", description: "Calculator" });
mcp.onToolCall("calc", (args) => "42");
llm.mount("/mcp", mcp);
await llm.start();
// MCP available at http://127.0.0.1:5555/mcp
A2A(Agent2Agent)是智能体相互发现和通信的方式。它处理智能体卡、消息路由和智能体间的流式响应。
当每个智能体都是实时的时,测试多智能体系统很困难。一个智能体出现故障,你的整个测试套件就会断裂。
A2AMock 给你一个本地 A2A 服务器,具有完整的智能体卡发现、消息路由、任务管理和 SSE 流式传输。注册你的智能体、定义它们如何响应,以及端到端测试你的多智能体工作流,无需任何东西实际运行。
import { A2AMock } from "@copilotkit/aimock/a2a";
const a2a = new A2AMock();
a2a.registerAgent({
name: "translator",
description: "Translates text between languages",
skills: [{ id: "translate", name: "Translate" }],
});
a2a.onMessage("translator", "translate", [{ text: "Translated text" }]);
const url = await a2a.start();
// Agent card at: ${url}/.well-known/agent-card.json
// JSON-RPC at: ${url}/
这是具有增量更新的长期任务的模式:
a2a.onStreamingTask("agent", "long-task", [
{ type: "status", state: "TASK_STATE_WORKING" },
{ type: "artifact", parts: [{ text: "partial result" }], name: "output" },
{ type: "artifact", parts: [{ text: "final result" }], lastChunk: true, name: "output" },
], 50); // 50ms delay between events
关于任务管理、智能体卡和 JSON-RPC 方法的详细信息,见完整文档。
如果你的应用使用检索增强生成,那么测试结果就取决于向量数据库当前存储的内容。开发环境的索引混乱不堪,预发布环境的索引与生产环境不一致,而且每当有人插入或更新向量时,检索结果都会发生变化。
VectorMock 是一个模拟向量数据库服务器,支持 Pinecone、Qdrant 和 ChromaDB 的 API 格式,并提供集合管理、插入或更新、查询和删除操作。
import { VectorMock } from "@copilotkit/aimock/vector";
const vector = new VectorMock();
vector.addCollection("docs", { dimension: 1536 });
vector.onQuery("docs", [
{ id: "doc-1", score: 0.95, metadata: { title: "Getting Started" } },
{ id: "doc-2", score: 0.87, metadata: { title: "API Reference" } },
]);
const url = await vector.start();
// point your vector DB client at `url`
如果需要根据查询动态改变结果,可以改用动态处理器:
vector.onQuery("docs", (query) => {
const topK = query.topK ?? 10;
return Array.from({ length: topK }, (_, i) => ({
id: `result-${i}`,
score: 1 - i * 0.1,
}));
});
这里提供了与 Pinecone、Qdrant 和 ChromaDB API 兼容的端点。
一致的检索结果。完整文档见此处。
这些 API 最容易被人们忘记模拟,却又会悄无声息地让测试套件变得不确定。
AIMock 中的 Services 为网页搜索、重排序和内容审核提供了内置模拟功能。你可以在 LLMock 实例上注册固件匹配模式,请求会根据查询或输入文本进行匹配。不需要单独启动服务器。
Tavily 搜索——通过查询模式模拟 POST /search 的网页搜索结果
Cohere 重排序——模拟 POST /v2/rerank 返回的重排序文档列表
OpenAI 内容审核——模拟 POST /v1/moderations 的审核决策。默认情况下,未匹配的请求会返回未标记结果。
import { LLMock } from "@copilotkit/aimock";
const mock = new LLMock();
// String pattern — case-insensitive substring match
mock.onSearch("weather", [
{ title: "Weather Report", url: "https://example.com/weather", content: "Sunny today" },
]);
// RegExp pattern
mock.onSearch(/stock\s+price/i, [
{ title: "ACME Stock", url: "https://example.com/stocks", content: "$42.00", score: 0.95 },
]);
// Catch-all — empty results for unmatched queries
mock.onSearch(/.*/, []);
mock.onRerank("machine learning", [
{ index: 0, relevance_score: 0.99 },
{ index: 2, relevance_score: 0.85 },
]);
mock.onModerate("violent", {
flagged: true,
categories: { violence: true, hate: false },
category_scores: { violence: 0.95, hate: 0.01 },
});
如果只需要通过兜底响应阻止实时请求,请在配置中启用全部三项服务:
{
"services": {
"search": true,
"rerank": true,
"moderate": true
}
}
字符串模式使用不区分大小写的子字符串匹配。RegExp 模式会执行完整的正则表达式测试。第一个匹配项生效。所有服务请求都会记录在日志中,因此你可以准确检查调用了哪些内容。完整文档见此处。
这是其他任何模拟工具都没有提供的功能之一。
问题在于:模拟数据只是你编写固件时 API 行为的快照。OpenAI 增加了一个字段。Claude 修改了某个默认值。Gemini 调整了流式传输格式。你的模拟测试仍然能够通过,CI 仍然是绿色的,然后应用却在生产环境中崩溃了。
漂移检测每天都会在 CI 中执行三方比较:
SDK 类型——TypeScript 类型定义所声明的数据结构
真实 API 响应——实际发送给 OpenAI、Anthropic 和 Gemini 的实时请求
AIMock 输出——模拟器针对同一请求返回的内容
如果这三者之间存在任何分歧,你会在 24 小时内得知,而不是等到用户提交缺陷报告时才发现。
检测到漂移时,输出如下:
$ pnpm test:drift
[critical] AIMOCK DRIFT — field in SDK + real API but missing from mock
Path: choices[].message.refusal
SDK: null Real: null Mock: <absent>
[critical] TYPE MISMATCH — real API and mock disagree on type
Path: content[].input
SDK: object Real: object Mock: string
[warning] PROVIDER ADDED FIELD — in real API but not in SDK or mock
Path: choices[].message.annotations
SDK: <absent> Real: array Mock: <absent>
✓ 2 critical (test fails) · 1 warning (logged) · detected before any user reported it
严重:模拟与真实 API 不匹配,测试立即失败
警告:服务提供商新增了一个 SDK 和模拟器都尚未知晓的字段,将其记录为早期预警
正常:三个来源完全一致,无须处理
下面是三方比较的底层工作方式:
import { extractShape, triangulate, formatDriftReport, shouldFail } from "./schema";
// 1. Get the SDK shape (what TypeScript says)
const sdkShape = openaiChatCompletionShape();
// 2. Call the real API and the mock in parallel
const [realRes, mockRes] = await Promise.all([
openaiChatNonStreaming(config, [{ role: "user", content: "Say hello" }]),
httpPost(`${instance.url}/v1/chat/completions`, { /* ... */ }),
]);
// 3. Extract response shapes
const realShape = extractShape(realRes.body);
const mockShape = extractShape(JSON.parse(mockRes.body));
// 4. Three-way comparison
const diffs = triangulate(sdkShape, realShape, mockShape);
const report = formatDriftReport("OpenAI Chat (non-streaming text)", diffs);
// 5. Critical diffs fail the test
if (shouldFail(diffs)) {
expect.soft([], report).toEqual(
diffs.filter(d => d.severity === "critical")
);
}
你可以自行针对实时端点运行:
# Run drift checks against live endpoints
pnpm vitest --config vitest.config.drift.ts
MSW、VidaiMock 和 Mokksy 都不具备这项能力。你的模拟绝不应该在不知不觉中变得过时。完整文档见此处。
对于简单场景,手动编写固件没有问题。但当你需要处理包含工具调用、流式响应和分支逻辑的多轮智能体对话时,它很快就会变得令人痛苦。MSW、VidaiMock、mock-llm 和 piyook——这些 LLM 模拟工具都无法解决这个问题。
录制与重放的工作方式类似录像机。具体过程如下:
客户端向 AIMock 发送请求
AIMock 像往常一样尝试匹配固件
匹配失败时:将请求转发给已配置的上游服务提供商
立即将上游响应转发回客户端
如果是流式响应,则将其合并,并作为固件保存到磁盘和内存中
后续相同请求会匹配新录制的固件
使用 CLI 快速开始:
$ npx aimock --fixtures ./fixtures \
--record \
--provider-openai https://api.openai.com \
--provider-anthropic https://api.anthropic.com
录制的固件会自动保存,格式如下:
{
"fixtures": [
{
"match": { "userMessage": "What is the weather?" },
"response": { "content": "I don't have real-time weather data..." }
}
]
}
AIMock 可以自动处理六种格式的流式响应合并:OpenAI SSE、Anthropic SSE、Gemini SSE、Cohere SSE、Ollama NDJSON 和 Bedrock EventStream。身份验证标头会转发给上游服务提供商,但绝不会保存到固件中。
它还提供编程式 API,包括 enableRecording(),以及用于规范化时间戳等动态数据的 requestTransform。
在 CI 中加入 --strict,这样任何未匹配的请求都会返回 503 并立即导致测试失败,而不是悄无声息地漏过去。如果不使用该选项,缺少固件时会返回 404,而测试套件可能永远不会告诉你它曾尝试调用实时 API。
你的应用最终总会遇到 OpenAI 返回 500、工具返回格式错误的 JSON 响应,或者向量数据库在流式传输过程中断开连接。问题在于,你会在测试环境中发现这些问题,还是在生产环境中发现。
混沌测试允许你在三个级别按照可配置的概率注入故障:服务器级别、固件级别或单个请求级别。
drop——返回 HTTP 500 和 {"error":{"message":"Chaos: request dropped","code":"chaos_drop"}}
malformed——返回 HTTP 200 和无效 JSON
disconnect——立即断开 TCP 连接,不返回任何响应
服务器级混沌配置适用于所有请求:
import { LLMock } from "@copilotkit/aimock";
const mock = new LLMock();
mock.setChaos({
dropRate: 0.1, // 10% of requests return 500
malformedRate: 0.05, // 5% return broken JSON
disconnectRate: 0.02, // 2% drop the connection
});
// remove all chaos later
mock.clearChaos();
固件级混沌配置只针对特定响应:
{
"fixtures": [
{
"match": { "userMessage": "unstable" },
"response": { "content": "This might fail!" },
"chaos": {
"dropRate": 0.3,
"malformedRate": 0.2,
"disconnectRate": 0.1
}
}
]
}
单个请求的标头会覆盖其他所有配置——这适合在某项测试中强制触发特定故障:
// 在此特定请求上强制 100% 断开连接
await fetch(`${mock.url}/v1/chat/completions`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-aimock-chaos-disconnect": "1.0",
},
body: JSON.stringify({ model: "gpt-4", messages: [{ role: "user", content: "hello" }] }),
});
所有混沌事件都在日志中用 chaosAction 字段追踪,并在 Prometheus 指标中计数。完整文档见此处。
除了核心模块外,AIMock 还包含许多值得了解的功能。
嵌入向量 - 完全支持 POST /v1/embeddings。可通过 fixtures 返回显式向量,或让 AIMock 根据输入文本的哈希确定性地生成向量。相同输入总是产生相同向量,默认维度为 1536。
WebSocket APIs - 支持 OpenAI Realtime、OpenAI Responses over WebSocket 和 Gemini Live,均使用原始 RFC 6455 framing。如果你的应用使用语音或实时流式 AI 智能体,这些都有覆盖。
顺序响应 - 对同一提示的连续调用返回不同的响应。对于测试重试逻辑和多轮工作流很有用,其中相同消息应在不同时刻表现不同。
[
{ "match": { "userMessage": "retry", "sequenceIndex": 0 }, "response": { "content": "First attempt" } },
{ "match": { "userMessage": "retry", "sequenceIndex": 1 }, "response": { "content": "Second attempt" } },
{ "match": { "userMessage": "retry" }, "response": { "content": "Fallback" } }
]
流式物理特性 - 配置 ttft(首个 token 延迟)、tps(每秒 token 数)和抖动以模拟现实的时序特性。预构建的配置文件覆盖快速模型、推理模型和过载系统。
Prometheus 指标 - 请求计数、延迟直方图和当前 fixture 计数(位于 /metrics)。使用 --metrics 标志启用。
Docker 和 Helm - 官方 Docker 镜像位于 ghcr.io/copilotkit/aimock(GitHub Container Registry),可用于 CI/CD。它作为纯 HTTP 服务器运行,因此任何语言都可使用。测试运行器端无需 Node.js。
AG-UI 是连接 AI 智能体与前端应用的开放协议,已被 LangGraph、CrewAI、Mastra、Google ADK、AWS Bedrock AgentCore 等采用。
它在端到端测试套件中使用 AIMock,通过 fixture 驱动的响应跨 LLM 提供商验证 AI 智能体行为。AG-UI 是生产中使用的协议。如果你想看一个规模上真实的 AIMock 设置,那是一个很好的起点。
文档包括 MSW、VidaiMock、mock-llm、Python mocks 和 Mokksy 的分步迁移指南。你会找到并排比较和你所获得与保留内容的分解。
如果你在使用 MSW,不必替换所有内容:可以保留 MSW 用于通用 REST 和 GraphQL mocking,仅对 AI 端点使用 AIMock。
如果你已在使用 @copilotkit/llmock,升级是查找替换的问题:
pnpm remove @copilotkit/llmock
pnpm add @copilotkit/aimock
LLMock 类、所有 fixture 格式和编程 API 保持不变。现有测试将直接工作。
npm install @copilotkit/aimock
文档:aimock.copilotkit.dev
GitHub:github.com/CopilotKit/aimock
npm:@copilotkit/aimock
你的测试套件应该和你的技术栈一样完善。这就是 AIMock 的用途。
订阅我们的博客,在你的收件箱中获取关于 CopilotKit 的更新。