资讯动态

AI SDK 测试夹具捕获实战:为 Provider 响应解析测试录制真实 API 响应

发布时间:2026/9/12 3:49:44 来源:尧图企业网站定制
AI SDK 测试夹具捕获实战为 Provider 响应解析测试录制真实 API 响应【免费下载链接】aiThe AI Toolkit for TypeScript. From the creators of Next.js, the AI SDK is a free open-source library for building AI-powered applications and agents项目地址: https://gitcode.com/GitHub_Trending/ai/ai本文是 AI SDKThe AI Toolkit for TypeScript仓库内开发者技能文档 skills/capture-api-response-test-fixture/SKILL.md 的完整展开。它面向为 AI SDK 贡献 Provider 解析代码的开发者讲解如何把模型提供方如 OpenAI返回的真实响应固化为测试夹具test fixture并用仓库自带的examples/ai-functions示例工程一键生成这些夹具。读完本文你将掌握generateTextdoGenerate与streamTextdoStream两类响应的录制流程、夹具存放与命名规范、底层录制工具的实现原理以及如何让新录制的夹具无缝接入现有单元测试。为什么需要“真实响应”测试夹具Provider 包的核心职责是把各厂商五花八门的 API 响应JSON 对象、SSE 事件流、错误结构统一解析成 AI SDK 的语言模型接口。解析逻辑的正确性高度依赖响应结构的“每一个字段”。如果测试里只用开发者手写、伪造的响应样本往往会出现两种问题结构与真实响应脱节手写样本遗漏了真实响应中的可选字段、嵌套结构或时序细节导致解析代码在线上遇到真实响应时崩溃难以回归模型提供方调整响应格式后旧的伪造样本无法察觉差异测试无法起到守护作用。因此AI SDK 的 Provider 响应解析测试遵循一条原则优先使用提供方的真实响应作为测试夹具。只有响应体过大时才允许做“不改变语义”的裁剪。这样测试断言的不再是想象出来的响应而是线上真实返回的数据。这条原则体现在仓库各 Provider 包的测试中。以 packages/openai/src/responses/openai-responses-language-model.test.ts 为例其内部通过fs.readFileSync直接读取夹具文件后交给解析逻辑断言fs.readFileSync(src/responses/__fixtures__/${filename}.json, utf8)以及.readFileSync(src/responses/__fixtures__/${filename}.chunks.txt, utf8)也就是说测试运行时完全不依赖网络与 API Key只需读取仓库内的静态文件。真实响应被捕获一次、固化进仓库之后任何开发者都能在无网络环境下复跑测试确保解析行为稳定可回归。夹具的存放位置与命名规范真实响应夹具统一存放在各 Provider 源码目录下的__fixtures__子文件夹中。例如 OpenAI Responses API 的夹具位于 packages/openai/src/responses/fixtures其中包含大量成对出现的文件openai-web-search-tool.1.json与openai-web-search-tool.1.chunks.txtopenai-shell-tool.1.json与openai-shell-tool.1.chunks.txtopenai-mcp-tool-approval.1.json与openai-mcp-tool-approval.1.chunks.txtopenai-error.1.json纯错误响应无对应 chunksreasoning-model-temperature-error.json等命名规范可以归纳为功能描述.序号.后缀其中功能描述用连字符分隔的短横线命名直接点明该响应对应的场景如web-search-tool、file-search-tool、code-interpreter-tool、compaction、parallel-tool-call-wrapper序号同一场景多次请求时递增的.1、.2、.3对应一次测试中多轮请求的不同响应后缀.json表示一次非流式响应体generateText场景.chunks.txt表示流式响应逐行存放的原始 chunkstreamText场景。以 openai-web-search-tool.1.chunks.txt 为例它是逐行存放的 JSON每行对应一个 SSE 事件例如response.created、response.in_progress、response.output_item.added、response.web_search_call.searching等完整还原了 OpenAI Responses API 的流式时序{type:response.created,sequence_number:0,response:{id:resp_0cc96a...,status:in_progress,...}} {type:response.in_progress,sequence_number:1,response:{...}} {type:response.output_item.added,sequence_number:2,output_index:0,item:{id:rs_...,type:reasoning,summary:[]}}而 openai-error.1.json 则是一个典型的错误响应样本用于测试错误解析分支{ error: { message: You exceeded your current quota, please check your plan and billing details. ..., type: insufficient_quota, param: null, code: insufficient_quota } }新贡献者在添加夹具前应先在 packages/openai/src/responses/fixtures里浏览既有文件名沿用这套命名风格保证仓库一致性。生成夹具的总入口examples/ai-functions 示例工程捕获真实响应的推荐途径是复用仓库自带的示例工程 examples/ai-functions它包含两套与夹具强相关的目录examples/ai-functions/src/generate-text/openaigenerateText场景示例脚本examples/ai-functions/src/stream-text/openaistreamText场景示例脚本。这些脚本以run(...)包裹异步函数并在成功后自动把结果写入夹具文件。核心机制来自 examples/ai-functions/src/lib/run.tsimport dotenv/config; import { APICallError } from ai; import { print } from ./print; import { isRecordableResult, recordFixture } from ./record-fixture; export function run(fn: () Promiseunknown) { fn() .then(result { if (isRecordableResult(result)) { return recordFixture(result); } }) .catch(error { console.error(error); if (APICallError.isInstance(error)) { console.log(); print(Request body:, error.requestBodyValues); print(Response body:, error.responseBody); } if (process.env.FAIL_ON_ERROR 1) { process.exit(1); } }); }值得注意的细节run顶部先import dotenv/config即脚本启动时自动加载.envProvider API Key 通过环境变量注入若调用成功且结果可录制见下文isRecordableResult会自动调用recordFixture落盘若抛出APICallError会把请求体与响应体一并打印到控制台方便调试失败场景设置环境变量FAIL_ON_ERROR1时任何错误都会以非零码退出便于 CI 中严格校验。recordFixture的实现位于 examples/ai-functions/src/lib/record-fixture.ts其判断逻辑为export function isRecordableResult(value: unknown): value is RecordableResult { return ( value ! null typeof value object (fullStream in value || steps in value) ); }即带有fullStream属性streamText结果或steps属性generateText结果的对象才会被录制。文件命名取自被运行脚本的文件名去掉扩展名并按请求序号生成.1、.2…function fixtureBaseName() { return path.basename(process.argv[1]).replace(/\.[jt]s$/, ); }场景一generateTextdoGenerate 测试的夹具捕获对于generateText这类非流式调用目标是拿到单次完整响应体。SKILL 文档给出了两种落地方式。方式 A脚本内 console.log 原始响应把脚本放在src/generate-text/provider/目录下以 OpenAI 为例即 examples/ai-functions/src/generate-text/openai调用generateText后把result.response.body序列化打印到控制台再将输出复制为新的夹具文件import { openai } from ai-sdk/openai; import { generateText } from ai; import { run } from ../../lib/run; run(async () { const result await generateText({ model: openai(gpt-5-nano), prompt: Invent a new holiday and describe its traditions., }); console.log(JSON.stringify(result.response.body, null, 2)); });脚本内run的导入路径../../lib/run是相对脚本位置的其实际文件为 examples/ai-functions/src/lib/run.ts。运行后控制台输出即为该次请求的原始响应体可整体粘贴进__fixtures__下的新.json文件。方式 B依赖 recordFixture 自动落盘实际上由于run内置了录制能力generateText的结果含steps会被自动录制recordFixture遍历result.steps将每一步的step.response.body写成脚本名.序号.json写入 examples/ai-functions 的output目录该目录被 gitignore不会污染仓库} else { result.steps.forEach((step, i) fs.writeFileSync( path.join(OUTPUT_DIR, ${name}.${i 1}.json), JSON.stringify(step.response.body, null, 2), ), ); }这意味着运行一次多步generateText示例会按步骤产出多个.json夹具天然覆盖“多轮请求”场景。场景二streamTextdoStream 测试的夹具捕获流式场景的目标是逐 chunk 还原 SSE 原始事件流因为解析代码需要验证每个事件如文本增量、工具调用增量、完成事件都能被正确消费。SKILL 文档明确了两点要求调用时开启原始 chunk 收集includeRawChunks: true使用专用的saveRawChunks辅助函数落盘。标准示例脚本如下import { openai } from ai-sdk/openai; import { streamText } from ai; import { run } from ../../lib/run; import { saveRawChunks } from ../../lib/save-raw-chunks; run(async () { const result streamText({ model: openai(gpt-5-nano), prompt: Invent a new holiday and describe its traditions., includeRawChunks: true, }); await saveRawChunks({ result, filename: openai-gpt-5-nano }); });saveRawChunks的实现位于 examples/ai-functions/src/lib/save-raw-chunks.tsexport async function saveRawChunks({ result, filename, }: { result: StreamTextResultany, any, any; filename: string; }) { const rawChunks: unknown[] []; for await (const chunk of result.stream) { if (chunk.type raw) { rawChunks.push(chunk.rawValue); } } fs.writeFileSync( output/${filename}.chunks.txt, rawChunks.map(chunk JSON.stringify(chunk)).join(\n), ); }它遍历result.stream仅收集type raw的 chunk把每个rawValue序列化后按行拼接写入output/filename.chunks.txt。由于每个原始 chunk 独占一行之后可整体复制为夹具如openai-web-search-tool.1.chunks.txt的形态并保持“一行一个 SSE 事件”的格式约定。与 recordFixture 自动录制的区别从源码看recordFixture对streamText结果同样具备自动录制能力它按start-step分组、把rawchunk 聚合成多步文件output/脚本名.序号.chunks.txt。两种方式可以任选自动方式脚本直接返回streamText结果不手动调用saveRawChunksrun内recordFixture自动按步骤录制适合多步、带工具调用的复杂流手动方式用saveRawChunks自定义文件名适合需要精确控制夹具文件名的简单单步流。真实仓库示例仓库中 examples/ai-functions/src/stream-text/openai/responses-raw-chunks.ts 给出了一个完整参考它显式开启include: { rawChunks: true }随后遍历result.stream对type raw的 chunk 打印原始值同时对text-delta、reasoning-delta计数最终统计文本块、推理块与原始块数量。该文件对应的夹具即为__fixtures__中成对的.chunks.txt文件。运行方式与前置条件录制脚本需要在 examples/ai-functions 目录下执行命令模板SKILL 文档明确给出pnpm tsx src/stream-text/provider/script-name.ts例如录制某个 OpenAI 流式场景pnpm tsx src/stream-text/openai/responses-raw-chunks.ts前置条件与注意事项安装依赖先在该目录monorepo 中为 examples/ai-functions/package.json执行依赖安装并确保tsx可用配置 API Keyrun内部加载dotenv因此需要在 examples/ai-functions 目录准备.env写入对应 Provider 的密钥如OPENAI_API_KEY...这是调用真实 API 的必要前提输出位置无论自动录制还是saveRawChunks产物都落在 examples/ai-functions 的output目录生成后将其复制到对应 Provider 的__fixtures__文件夹并按命名规范重命名如openai-gpt-5-nano.chunks.txt→openai-xxx.1.chunks.txt超大响应裁剪若真实响应过大允许做不改变语义的裁剪但务必保留测试断言所依赖的结构与字段。让夹具接入测试以 OpenAI Responses 为例夹具录制完成后测试侧通过fs.readFileSync加载并交给解析函数例如 packages/openai/src/responses/openai-responses-language-model.test.ts 中既有.json夹具的加载也有.chunks.txt夹具的加载见该文件第 248 行与第 256 行附近。测试采用createTestServer与TestResponseController模拟 HTTP 层把夹具内容作为服务端响应返回从而在完全离线的情况下驱动完整解析链路并验证generateText/streamText的最终结果、工具调用解析、并行工具调用封装、推理内容、错误映射等行为。也就是说整个工作流是闭环的在 examples/ai-functions 中编写/复用示例脚本调用真实 API通过runrecordFixture或saveRawChunks把真实响应落入output复制到 packages/openai/src/responses/fixtures并按功能.序号.后缀规范重命名在 openai-responses-language-model.test.ts 中引用新夹具名运行 Vitest 验证解析正确性。这套“真实响应 → 夹具 → 离线测试”的流程同样适用于其他 Provider 包如 Anthropic、Google 等其测试目录结构类似也是 AI SDK 保证 Provider 解析质量的关键基础设施。【免费下载链接】aiThe AI Toolkit for TypeScript. From the creators of Next.js, the AI SDK is a free open-source library for building AI-powered applications and agents项目地址: https://gitcode.com/GitHub_Trending/ai/ai创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价