pydantic-ai 模型适配器开发规范:从 API 设计到 Compaction 与工具可见性的源码级指南 pydantic-ai 模型适配器开发规范从 API 设计到 Compaction 与工具可见性的源码级指南【免费下载链接】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 通过统一的Model抽象接入 OpenAI、Anthropic、Google、Bedrock 等数十家模型提供方。本文以仓库中pydantic_ai_slim/pydantic_ai/models/目录的适配器开发准则CLAUDE.md为核心骨架结合 模型抽象基类、Anthropic 适配器、消息模型 与 设置模型 的实现系统讲解模型适配器models/{provider}.py在 API 设计、错误处理、类型系统、工具延迟/添加模式、Compaction 与网关兼容上的工程规范。读者读完将掌握如何为 pydantic-ai 新增一个行为正确、可流式、可缓存、可被网关路由的模型适配器以及这些规范背后的源码依据与失效案例。说明本文所述规范与行为以当前仓库源码为准。原文档CLAUDE.md是维护者从 PR 评审模式中提炼的 braindump每条规则带有!-- rule:NNN --溯源注释适合作为适配器开发与评审的检查清单。一、模型适配器在 pydantic-ai 中的位置pydantic-ai 把所有模型适配器集中在 pydantic_ai_slim/pydantic_ai/models/ 下每个提供方一个文件openai.py、anthropic.py、google.py、bedrock.py、mistral.py、cohere.py、xai.py等 30 余个模块并列存在另有fallback.py回退模型、function.py函数模型、test.py测试模型、wrapper.py等特殊适配器。所有适配器共享两层身份抽象pydantic_ai_slim/pydantic_ai/models/_abstract.py 中的AbstractModel定义所有模型含实时语音模型RealtimeModel共有的model_name、systemprovider 名用于gen_ai.systemOpenTelemetry 语义约定属性、base_url、model_idprovider:model_name格式与label等身份信息pydantic_ai_slim/pydantic_ai/models/init.py#L389-L539 中的Model抽象类请求-响应模型的基类声明request()/request_stream()抽象方法、工具可见性相关属性与 Compaction 裁剪辅助方法。规范的第一条架构原则rule:9就是provider 特有代码必须放在models/{provider}.py而不是共享模块中即使某些 provider 的实现很简单也要为所有 provider 保持一致地添加函数。这样共享兼容层不会堆积 provider 特例逻辑职责边界清晰。二、API 设计让模型间可移植性与流式一致性成为默认2.1 静默忽略不支持的通用调优设置rule:912同一份 Agent 代码可能在不同 provider 之间切换而各家 API 对采样参数的支持各不相同。规范要求对不支持的通用调优设置temperature、采样参数、penalties 等在运行时静默忽略no-op并在 docstring 中说明。一个对不支持旋钮直接 no-op 的模型能让客户端代码跨模型保持可移植而失败时大声报错则会破坏这种可移植性。这里有一个明确的边界provider 命名空间的设置google_*、openai_*等不归这条规则管由 2.3 节单独约束。通用设置与命名空间设置的语义差异在 settings.py 的ModelSettings类型中被显式建模——通用字段如temperature、max_tokens、top_p统一声明每个字段通过Supported by:列表标注支持它的模型类详见第六节。2.2 流式与非流式必须共享同一套响应处理rule:81如果request()调用了_process_response()那么request_stream()必须把同样的处理应用到每个 chunk 上。这保证流式与非流式两条代码路径支持完全相同的消息类型ToolCallPart、NativeToolCallPart、TextPart等行为一致避免某个特性在一种模式下能用、另一种模式下失效的经典 bug。源码佐证Anthropic 适配器 中request()与request_stream()并列实现request_stream()逐 chunk 应用_process_response()该方法定义在同文件 L1596 附近而非流式路径则对完整响应调用同一方法。2.3 不要用客户端守卫预判 provider 能力rule:26针对google_*、openai_*这类 provider 命名空间设置不要添加预防性的客户端守卫基于想当然的能力上限去拒绝它们。正确做法是把用户选择传入的设置直接转发让 provider API 自己暴露真正的不兼容性。API 才是当前支持能力的权威来源客户端守卫只会基于过时假设悄悄降级功能。2.4 通过provider_details暴露 provider 特有数据rule:598各 provider 有各自的 logprobs、安全过滤器、内容过滤、用量指标等数据。规范要求通过ModelResponse.provider_details或TextPart.provider_details暴露而不是为每个 provider 往核心响应类型上加字段——这既防止 API 膨胀又保持核心响应接口干净同时维持 provider 集成的模式一致性。源码佐证messages.py 中ModelResponse.provider_detailsL2803 附近兼容vendor_details别名与TextPart.provider_detailsL2138均已实现流式场景还有对应的TextPartDelta.provider_details增量合并逻辑以及CompactionPart.provider_details中存放加密内容与压缩溯源戳详见第四节。2.5 Token 计数必须镜像真实请求rule:478Token 计数estimate必须镜像实际的请求参数tools、system_prompt、configs并使用完全相同的消息格式化。否则估算值与真实 API 用量不符导致账单意外与配额错误。2.6 注入位置按消息身份锚定而不是按历史长度rule:912 补充请求内容的注入或修改消息块、工具定义、指令、缓存断点必须落在由消息身份message identity决定的位置集合上——例如每一条 user message——绝不能锚定在最后一条消息这种由长度定义的尾部位置上。因为尾部每轮都会移动导致可缓存前缀漂移provider 会静默地重新处理尾部而不是命中缓存造成不报错的成本/延迟回归。规范进一步强调稳定只是必要不充分条件。只钉住第一条 user message 也是稳定的但当线上请求需要更靠后的注入时仍然是错的——这正是container_upload块无法送达新容器的真实事故对应上游 issue 7775。正确做法是覆盖 API 实际作用且愿意接受注入的所有位置然后逐一检查每个位置是否按身份锚定。这两个集合并不相同Anthropic 会在仅含tool_result块的 user message中处理container_upload并拒绝此类请求因此该位置是刻意排除的。三、错误处理显式报错优于静默降级3.1 不支持的模型特性必须显式抛错rule:562对于给定模型无法构造的特性function tools、JSON/native 输出模式等必须抛出显式错误绝不静默跳过或降级。这使能力边界在运行时即可发现discoverable。注意与 2.1 的分工不支持的是设置走静默忽略规则而无法表达的部件/消息类型走 3.2 规则。3.2 消息部件类型使用穷尽模式匹配rule:65在模型适配器中对消息部件/内容类型要使用穷尽式模式匹配exhaustive pattern matching对不支持的部件类型例如FileContent抛出显式错误而不是过滤或断言。这防止消息映射过程中的静默数据丢失并在模型 API 不支持某些内容类型时给出清晰的反馈让集成失败可调试而非神秘。3.3 可恢复失败返回带元数据的空响应rule:433对于可恢复的 API 失败内容过滤器触发、空内容返回ModelResponse其中parts[]但元数据完整填充finish_reason、timestamp、provider_response_id。这让系统优雅降级而不是级联报错保留响应元数据用于可观测性同时以无可用内容作为信号避免在模型适配器中产生不必要的异常传播。四、类型系统让配置与响应都可静态检查4.1 类型化设置类替代裸字典rule:73provider 特有配置必须使用带 provider 前缀字段的类型化设置类如OpenAISettings、AnthropicSettings而不是extra_body或 dict 字面量。这为 provider 特有配置提供类型检查与自动补全防止拼写错误或非法值造成的运行时错误。例如 anthropic.py 中的AnthropicModelSettings继承ModelSettings并扩展 provider 特有字段。4.2 用 Pydantic 模型校验 API 响应rule:972解析外部 API 数据时定义 Pydantic 模型做响应校验避免.get()的脆弱性并在 schema 变化时及早发现。这防止缺失/畸形字段造成的运行时错误为外部数据解析提供类型安全。五、工具可见性与 Compaction两个容易翻车的深水区5.1 通过self.tool_deferral_mode/self.tool_addition_mode读取揭示模式模型读取工具揭示模式reveal modes时必须通过self.tool_deferral_mode和self.tool_addition_mode绝不直接读取 profile 中对应的键。原因是二者语义不同profile模型档案声明该模型家族声称支持什么模式adapter适配器类声明该适配器的渲染器实际实现了什么模式通过类变量supported_tool_deferral_modes与supported_tool_addition_modes表达继承得到的空集合是对没有渲染器的适配器的安全默认。实际生效值是两者的交集Model.tool_deferral_mode的实现是mode self.profile.get(tool_deferral_mode)然后return mode if mode in self.supported_tool_deferral_modes else None。这意味着即使透传型厂商 profile 声称支持某种模式只要适配器类没有声明就永远不会把工具解析成该适配器无法渲染的线上形态。源码佐证AnthropicModel声明 supported_tool_deferral_modes frozenset({standalone})、supported_tool_addition_modes frozenset({by_reference})并在 L936 附近覆写tool_addition_mode属性。5.2 Compaction声明 API 事实由唯一助手转成裁剪行为在线上 honorCompactionPart的适配器需要声明两个类变量并在自己的消息预处理步骤中调用self._trim_before_compaction()绝不直接调用_trim_messages_before_compaction也绝不重述声明所隐含的含义compaction_requires_encrypted_content本适配器的 API 是否只 honor 携带加密内容的CompactionPart。若为真一个没有加密内容的压缩部件就不是线边界——适配器会省略它若让它隐藏更早的历史则不会有任何内容顶替上去compaction_retains_standing_prompt本适配器的压缩条目是否继续服务它替换窗口的头部系统条目。若为真边界之后重发 standing prompt 会造成重复若为假默认standing prompt 经由按请求重建的通道传递裁剪时必须重新插入它否则会在后续每个请求中被静默丢弃。两个声明各自只陈述 API 做了什么是否需要加密 blob 才能 honor 条目条目是否继续服务窗口的系统条目把声明转成裁剪行为只属于_trim_before_compaction这一个助手。两者相互独立——当前两个适配器恰好给出相同答案但新增第三个时不能互相推断。关键设计点它们属于适配器adapter不属于 profile。因为八个 provider 把各自的 profile 路由到OpenAIResponsesModel若放在 profile 上恰恰在线格式最确定的场景下反而缺失该键。至于裁剪发生在请求构建的哪一步属于适配器机制细节OpenAI Responses 从未裁剪的历史解析服务端状态因此它保留一个独立的裁剪视图。源码佐证Model._trim_before_compaction()models/init.py#L502-L526读取两个声明后委托给模块级_trim_messages_before_compactionL2133 附近AnthropicModel声明 compaction_requires_encrypted_content False、compaction_retains_standing_prompt False并在消息预处理步骤L1272、L2017调用self._trim_before_compaction(messages)。5.3 第三方模型回退与工具可见性自定义Model子类如果继续读取tool_defs则优雅降级所有工具被完整声明可用性增量availability delta退化为系统文本通告且该模型上不扣留withhold延迟加载。而读取declared_tool_defs和visibility_of()则让适配器进入扣留withholding模式。源码佐证ModelRequestParameters.visibility_of(tool_name)返回解析后的ToolVisibilityvisible/withheld/via_history等declared_tool_defs只包含进入 provider 普通tools集合的定义output tools 无条件包含function tools 按可见性过滤AnthropicModel在tool_addition_mode by_reference时按declared_tool_defs与visibility_of()决定哪些工具延迟声明。六、设置转发与网关兼容两条收尾规则6.1 转发ModelSettings字段必须双处登记当模型转发一个通用ModelSettings字段时必须把它加进 pydantic_ai/settings.py 中该字段的Supported by:列表同时给新的Model类在 tests/models/test_model_settings_support.py 中增加一个用例。该测试会探查每个类发出的请求当列表与线上实际行为不一致时测试失败——这是防止文档说支持、线上没发漂移的自动化闸门。6.2 按客户端类裁剪能力而不是按base_url网关gateway提供的模型必须与其 canonical API 行为完全一致Pydantic AI Gateway 与普通企业代理一样通过带代理 base_url 的 provider 官方 SDK 客户端到达该 API。因此能力裁剪只能按客户端类client class进行绝不能按客户端的base_url。真正独立的传输AsyncAnthropicBedrock、AsyncAnthropicVertex、AsyncAnthropicFoundry等是不同的客户端类各自拥有自己的能力闸门——这正是isinstance画出的边界。规范还给出了两条实操建议用探测方式验证网关路径Model(id, providergateway)而不是靠推理网关确实不服务的模型应列入UNSUPPORTED_GATEWAY_MODEL_NAMES而不是搞特殊豁免让该 ID 继续被广告却处于降级状态。七、其他架构约定Anthropic 专属辅助工具有长期背景的 Anthropic 专属助手放在_anthropic_*.py兄弟文件中如_anthropic_containers.py、_anthropic_bedrock_count_tokens.py让阅读anthropic.py的读者不必被迫通读它们。文件组织原则所有 provider 的功能函数要跨所有 provider 保持一致地添加即使某些 provider 的实现很简单防止共享兼容层积累 provider 特例逻辑。文档溯源机制CLAUDE.md中的每条规则都带!-- rule:NNN --注释是评审模式提取的溯源 ID便于回溯 PR 上下文新增适配器时应延续这一机制。结语pydantic-ai 的模型适配层之所以能同时支撑 30 余个 provider 而保持接口统一靠的正是这一组软规范通用设置静默兼容以保可移植性、流式/非流式共享响应处理以防功能漂移、provider_details收口 provider 特有数据、显式报错代替静默降级、类型化设置与 Pydantic 校验提升可诊断性以及按消息身份锚定注入、adapter 声明 唯一助手裁剪的 Compaction 机制。对希望深入理解或扩展该框架的开发者而言模型抽象基类、Anthropic 适配器、消息模型、设置模型 与 测试 共同构成了完整的学习闭环。【免费下载链接】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),仅供参考