pydantic-ai 的 pytest VCR 测试工作流录制、回放与调试 HTTP Cassette 全指南【免费下载链接】pydantic-aiHow Python does AI. Agents, realtime voice, image generation, embeddings. Every model, every interface, typed end to end.项目地址: https://gitcode.com/GitHub_Trending/py/pydantic-ai本篇技术指南以 pydantic-ai 仓库内.claude/skills/testing-skill/SKILL.md为核心系统讲解该 AI 框架如何围绕 VCRHTTP 交互录制构建可离线、可复现的测试体系从--record-moderewrite录制、无参回放验证到parse_cassette.py解析与 inline-snapshot 审查的完整闭环。读完你将掌握在 pydantic-ai及其姊妹库 pydantic_graph、pydantic_evals中录制/重录 HTTP cassette、排查回放失败、检查请求响应体的完整实操方法并理解仓库底层自定义 matcher 与数据清洗机制的原理。为什么 pydantic-ai 的测试离不开 VCR Cassettepydantic-ai 是一个如何用 Python 做 AI的框架覆盖 Agent、实时语音、图像生成、Embeddings 与多种模型 ProviderOpenAI、Anthropic、Google、Bedrock 等。这类测试天然依赖外部 HTTP API直接发真实请求会带来三方面问题成本与速率限制每次跑测试都调用真实模型 API 既花钱又容易被限流结果不可复现LLM 输出具有不确定性测试断言无法稳定CI 环境无密钥持续集成环境往往没有 API Key。仓库的解决方案是VCRVideo Cassette Recorder模式首次运行时把真实 HTTP 请求/响应录制成 YAML cassette 文件后续测试离线回放这些录制内容保证测试确定性。证据直观可见——仓库tests/cassettes/下积累了test_embeddings/66 个 yaml、test_tool_search/30 个 yaml、test_thinking_wire_contract/23 个 yaml等大量录制文件tests/models/cassettes/更有上千个 yaml可见 cassette 是该仓库测试体系的基石。技能文件.claude/skills/testing-skill/SKILL.md将这套工作流总结为录制 → 验证回放 → 审查快照 → 解析检查四个阶段下面逐一展开。前置条件环境与密钥开始之前先确认.env文件存在。技能文档给出的检查命令test -f .env echo ok || echo missing输出ok环境文件就绪可以继续输出missing需要先补充环境变量文件。缺失 API Key 不会在录制前报错而是会在测试运行时以清晰的错误暴露出来技能文档原文Missing API keys will cause clear test errors at runtime。pydantic-ai 仓库中source .env是运行一切录制/回放命令的前置步骤它把密钥加载进当前 shell 供 pytest 与底层 Provider SDK 使用。必须掌握的关键 pytest 标志技能文档列出了五个核心标志理解它们才能正确组合命令标志作用--record-moderewrite录制模式对新建和已存在的 cassette 都会重新录制覆盖旧内容--lf只运行上次失败的测试last failed适合迭代调试-vv详细输出回放验证时用于观察每个测试的细节--tbline缩短 traceback 为单行让失败信息更聚焦-k按子串表达式筛选要运行的测试如-k tool只跑名称含 tool 的用例--record-moderewrite是录制阶段的核心它同时适用于新建 cassette与重录已有 cassette两种场景是日常更新录制内容的默认选择。仓库依赖层面pyproject.toml中声明了pytest9.0.3、pytest-recording0.13.2提供--record-mode选项、vcrpy8.2.1底层 VCR 实现、inline-snapshot0.32.5快照断言等这些依赖共同支撑起本工作流。阶段一录制 Cassette录制单个测试的完整命令source .env uv run pytest path/to/test.py::test_function_name -v --tbline --record-moderewrite要点拆解source .env先加载密钥环境uv run pytest通过 uv 管理的虚拟环境运行 pytest仓库使用 uv 作为包管理器path/to/test.py::test_function_name精确指定要录制的测试节点-v --tbline录制时观察执行进度并保持失败信息简洁--record-moderewrite开启录制。多个测试可以一次录制依次罗列节点即可source .env uv run pytest path/to/test.py::test_one path/to/test.py::test_two -v --tbline --record-moderewrite执行后VCR 会在约定目录通常与测试同名的cassettes/子目录下生成 YAML 文件。录制成功本身通常意味着该测试的 HTTP 调用链路是通的。阶段二验证回放不传录制参数重跑录制完成后必须做的一步不带--record-mode再跑一次同样的测试确认 cassette 能被正确回放source .env uv run pytest path/to/test.py::test_function_name -vv --tbline去掉--record-mode后pytest-recording 进入默认的none回放模式VCR 从磁盘加载 cassette 并按 matcher 匹配请求-vv用于观察详细的回放过程若回放成功说明测试已离线化后续 CI 无需 API Key 也能运行。仓库在 tests/conftest.py 中还加了额外的回放质量保障fail_partially_used_vcr_cassettesfixtureautouse回放结束后检查 cassette 的每个 interaction 是否都被播放过若有未使用的索引会直接pytest.fail防止录制了多余请求却没人消费--strict-vcr-cassette-usage命令行选项pyproject.toml 同款注册于 conftest开启后连零播放的 cassette 也会判失败进一步收紧 cassette 的使用完整性。阶段三审查 inline-snapshot 快照如果测试用到了snapshot()需要注意阶段二回放的那次运行会自动填充 snapshot 内容——inline-snapshot 在断言失败时会自动把实际值写入代码快照捕获的是测试实际产生的输出是对显式断言的补充而非人工设定的预期审查原则你只需要人工审查生成的快照是否符合预期输出不要手动编写快照内容You only review - dont manually write snapshot contents。之所以要审查是因为 LLM API 返回的文本中常混入智能引号、特殊 Unicode 字符它们会被录进 cassette、进而进入快照并触发 lint 报错。仓库的自定义序列化器 tests/json_body_serializer.py 专门处理了这一点通过SMART_CHAR_MAP把弯引号 、短破折号、省略号等映射为 ASCII 等价字符再叠加 NFKC 归一化确保 cassette 文件与快照可移植、稳定。阶段四解析 Cassetteparse_cassette.pyraw YAML 体积大、嵌套深直接读不方便。技能目录提供了专用解析工具 .claude/skills/testing-skill/parse_cassette.py把 cassette 转成易读的结构化输出。用法uv run python .claude/skills/testing-skill/parse_cassette.py cassette_path [--interaction N]实际示例# 解析 cassette 中的所有 interaction uv run python .claude/skills/testing-skill/parse_cassette.py tests/models/cassettes/test_foo/test_bar.yaml # 只解析第 1 个 interaction0 起始索引 uv run python .claude/skills/testing-skill/parse_cassette.py tests/models/cassettes/test_foo/test_bar.yaml --interaction 1输出内容对每个 interaction工具打印RequestHTTP method、URI、解析后的请求体超长 base64 会被截断Response状态码code message、解析后的响应体同样截断 base64。源码级实现细节从 parse_cassette.py 的实现可以看到三个关键设计body 提取顺序_extract_body优先取parsed_body字段pydantic-ai 的 cassette 序列化器写入的结构化体否则退回标准 VCR 的body.string并尝试用json.loads解析失败则原样返回字符串base64 截断策略truncate_base64递归遍历嵌套结构凡超过 100 字符且形如 base64^[A-Za-z0-9/]$的字符串截成前50字符...[truncated N chars]...后20字符data:前缀的># 1. 录制 cassette重写模式 source .env uv run pytest tests/models/test_openai.py::test_chat_completion -v --tbline --record-moderewrite # 2. 验证回放并自动填充快照 source .env uv run pytest tests/models/test_openai.py::test_chat_completion -vv --tbline # 3. 审查测试代码改动排除 cassettes git diff tests/ -- :!**/cassettes/** # 4. 仅列出新增/变更的 cassette 文件名内容用 parse_cassette.py 查看 git diff --name-only tests/ -- **/cassettes/** # 5. 需要时检查 cassette 内容 uv run python .claude/skills/testing-skill/parse_cassette.py tests/models/cassettes/test_openai/test_chat_completion.yaml流程逻辑录制 → 离线验证 → 只审代码 diffcassette 不算代码→ 确认哪些录制文件变了 → 按需深查内容。步骤 3 的:!**/cassettes/**路径排除语法保证 review 聚焦测试逻辑本身而非动辄几百行的 YAML。仓库底层自定义 Matcher 与请求清洗机制技能文档是操作层指引而它背后由 tests/conftest.py 的pytest_recording_configure支撑理解这些机制有助于排查回放不匹配类问题自定义序列化器注册json_body_serializer即 tests/json_body_serializer.py覆盖默认 yaml 序列化写入parsed_body字段并清洗特殊字符method / path matcher默认只匹配方法与路径path 匹配前会先把 AWS ARN 中的 12 位账号 ID 替换为123456789012并把 Vertex AI 路径中的locations/{region}、projects/{project}归一化为REGION、PROJECT消除录制环境差异host matcher将 Bedrock 不同区域的域名如bedrock-runtime.us-east-2.amazonaws.com归一化为bedrock-runtime.REGION.amazonaws.comVertex AI 主机去区域前缀实现跨区域回放请求清洗scrub_request直接丢弃oauth2.googleapis.com/token与auth.openai.com/oauth/token的令牌交换请求并统一清洗 URI 中的 AWS 账号 ID——这解释了为什么 cassette 中看不到敏感凭据全局 vcr_configmodule 级 fixtureignore_localhost: True放行本地服务、filter_headers: [authorization, x-api-key, cookie]过滤认证头、decode_compressed_response: True解压响应。真实 Cassette 长什么样以仓库现有录制为例 tests/cassettes/test_tavily/test_basic_search.yaml顶层是interactions列表每条含request与responseinteractions: - request: headers: content-type: - application/json host: - api.tavily.com method: POST parsed_body: query: What is Pydantic AI? search_depth: basic topic: general uri: https://api.tavily.com/search response: headers: content-type: - application/json parsed_body: query: What is Pydantic AI? request_id: 3b2f7385-b01d-479e-9825-1ce0f8c59b93 response_time: 0.89 results: ...注意这里的parsed_body正是parse_cassette.py优先读取的字段请求体的明文可见性而非 base64 编码的原始body.string就是自定义序列化器的功劳。录制内容只保留结构化数据认证类 header 已被过滤可直接安全地纳入版本库。适用场景与边界这套工作流适用于 pydantic-ai 仓库中以 HTTP 交互为主的测试模型 Provider、工具搜索、Embeddings、Realtime 等仓库中tests/cassettes/、tests/models/cassettes/、tests/providers/cassettes/等目录均遵循同一约定。使用时需注意录制阶段必须有真实 API Keysource .env回放阶段无需 Key但要求 cassette 与代码中请求构造一致body 默认不被匹配RequestCapture机制可辅助抓取真实出站请求体进行断言见 tests/conftest.py 中的RequestCapture实现涉及 Bedrock/Vertex AI 的测试依赖 conftest 中的归一化 matcher本地新录 cassette 时建议保持默认配置。掌握录制 → 回放 → 快照审查 → 解析排查这条链路你就能在 pydantic-ai 及其生态仓库中高效地维护 HTTP 级测试既保证离线确定性又能随时按需刷新真实 API 行为。【免费下载链接】pydantic-aiHow Python does AI. Agents, realtime voice, image generation, embeddings. Every model, every interface, typed end to end.项目地址: https://gitcode.com/GitHub_Trending/py/pydantic-ai创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考