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

发布时间:2026/9/12 3:49:42

AI SDK 测试夹具捕获实战:为 Provider 响应解析测试录制真实 API 响应 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),仅供参考
延伸阅读

更多相关文章

2026/9/12 3:44:42

Q-learning实现水声通信自适应调制

1. 项目概述:为什么水声通信必须“自己学会调制”?水声通信,说白了就是让声音在水里当快递员——把数据打包成声波,从A点送到B点。但这个快递员特别难干:海水不是静止的墨水,它有温度梯度、盐度变化、洋流扰…

2026/9/12 4:34:47

Java StringEntry接口设计与实现最佳实践

1. StringEntry接口的设计背景与核心诉求在软件开发中,数据结构的抽象与封装一直是提升代码复用性和可维护性的关键手段。StringEntry这个接口概念的提出,本质上是为了解决字符串类型数据在多种场景下的统一操作问题。我最近在重构一个历史遗留系统时&am…

2026/9/12 4:34:47

海光DCU接入K8s:整卡、共享与vDCU调度实战解析

如果你接过“把一批海光 DCU 节点接进 Kubernetes,再让上层 AI 平台和 DeepSeek 推理服务跑起来”这种需求,第一反应大概率是:装个驱动、部署个 Device Plugin 不就行了?真动手之后才会发现,整卡、共享、vDCU 虚拟化是…

2026/9/12 4:34:47

2026年软考高级系统分析师论文备考指南与高分技巧

1. 2026年软考高级系统分析师论文备考指南作为国内IT领域最具含金量的职业资格认证之一,软考高级系统分析师(以下简称"系分")的论文环节一直是考生面临的"拦路虎"。不同于选择题和案例分析,论文写作不仅需要扎…

2026/9/12 4:34:47

Linux下TFTP服务器安装配置与优化指南

1. Linux下TFTP服务器的安装与配置指南在嵌入式开发和网络设备维护领域,TFTP(Trivial File Transfer Protocol)作为轻量级文件传输协议,因其实现简单、资源占用少的特点,成为固件更新、配置文件传输的标配工具。不同于…

2026/9/12 4:34:47

ThinkPHP在线客服系统实战:多坐席分配与部署调优全解析

简介:基于ThinkPHP框架打造的运营级在线客服系统源码,面向需要快速搭建网页客服、多坐席协作与智能客服平台的开发者和企业技术团队,可直接用于电商、官网、SaaS产品等场景的客户服务模块。系统包含实时聊天、坐席状态管理、访客分配、智能自…

2026/9/12 4:29:47

PyMC 采样与推断方法实战指南:MCMC、变分推断与诊断调优

PyMC 采样与推断方法实战指南:MCMC、变分推断与诊断调优 【免费下载链接】scientific-agent-skills Turn any AI agent into an AI Scientist. The #1 Agent Skills library for science, used by 190,000 scientists worldwide. 165 ready-to-use validated skills…

2026/9/12 2:05:33

超人会飞不算本事:系统稳定依赖清晰规则与边界设计

开头先不绕弯子。“#斯坦李吐槽dc 所以超人是无缘无故会飞的嘛哈哈哈哈哈哈哈锤哥真是技术人才啊!#雷神 #复联”这类调侃式短标题,第一波冲击力在于它把两个宇宙的角色塞进同一个吐槽箱里,但细想一下就能发现,它真正碰到的根本不是…

2026/9/12 3:55:12

超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

把“蜘蛛侠 vs 超人”放在 CSDN 上聊,可能很多人第一反应是走错片场了。但如果把这两个角色看成“两个持续运营了 80 多年的文化产品”,你会发现,这场比较本质上是两个不同 IP 策略的长期结果对比:超人赢在定义了整个超级英雄题材…

2026/9/9 16:31:09

基于CNN的调制信号识别:MATLAB实现时频图分类实战

简介:本资源是一套面向通信工程与信号处理方向学习者、研究者的深度学习实践方案,聚焦调制信号自动检测与识别这一典型无线通信任务,解决传统方法依赖人工特征、低信噪比下性能下降等痛点。压缩包共12个文件(10.73MB)&…

2026/9/12 0:04:17

MATLAB仿生优化框架:长鼻浣熊算法多策略融合实现

简介:本资源是一份面向智能优化算法研究者与MATLAB初学者的仿生智能算法实践代码包,聚焦于长鼻浣熊优化算法(COA)的多策略改进与性能验证。针对传统COA易陷局部最优、收敛精度不足等问题,作者融合Circle映射初始化提升…

2026/9/12 0:04:17

【JAVA毕设源码分享】基于 JavaWeb 的校园一卡通管理系统的设计与实现 基于 JavaWeb 的校园卡业务管理系统(程序+文档+代码讲解+一条龙定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/9/12 0:04:17

【JAVA毕设源码分享】基于 Java 的图书馆借阅管理平台的搭建与实现 基于 Java 的图书馆综合管理系统(程序+文档+代码讲解+一条龙定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/9/10 12:32:02

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/10 15:19:50

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/10 15:49:53

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码