pydantic-ai 模型适配器开发规范:从 API 设计到 Compaction 与工具可见性的源码级指南
2026/9/14 6:54:44 网站建设 项目流程

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.pyanthropic.pygoogle.pybedrock.pymistral.pycohere.pyxai.py等 30 余个模块并列存在,另有fallback.py(回退模型)、function.py(函数模型)、test.py(测试模型)、wrapper.py等特殊适配器。

所有适配器共享两层身份抽象:

  • pydantic_ai_slim/pydantic_ai/models/_abstract.py 中的AbstractModel:定义所有模型(含实时语音模型RealtimeModel)共有的model_namesystem(provider 名,用于gen_ai.systemOpenTelemetry 语义约定属性)、base_urlmodel_id'provider: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类型中被显式建模——通用字段(如temperaturemax_tokenstop_p)统一声明,每个字段通过Supported by:列表标注支持它的模型类(详见第六节)。

2.2 流式与非流式必须共享同一套响应处理(rule:81)

如果request()调用了_process_response(),那么request_stream()必须把同样的处理应用到每个 chunk 上。这保证流式与非流式两条代码路径支持完全相同的消息类型(ToolCallPartNativeToolCallPartTextPart等),行为一致,避免"某个特性在一种模式下能用、另一种模式下失效"的经典 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_detailsTextPart.provider_details暴露,而不是为每个 provider 往核心响应类型上加字段——这既防止 API 膨胀,又保持核心响应接口干净,同时维持 provider 集成的模式一致性。

源码佐证:messages.py 中ModelResponse.provider_details(L2803 附近,兼容vendor_details别名)与TextPart.provider_details(L2138)均已实现;流式场景还有对应的TextPartDelta.provider_details增量合并逻辑,以及CompactionPart.provider_details中存放加密内容与压缩溯源戳(详见第四节)。

2.5 Token 计数必须镜像真实请求(rule:478)

Token 计数(estimate)必须镜像实际的请求参数(toolssystem_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_reasontimestampprovider_response_id)。这让系统优雅降级而不是级联报错:保留响应元数据用于可观测性,同时以"无可用内容"作为信号,避免在模型适配器中产生不必要的异常传播。

四、类型系统:让配置与响应都可静态检查

4.1 类型化设置类替代裸字典(rule:73)

provider 特有配置必须使用带 provider 前缀字段的类型化设置类(如OpenAISettingsAnthropicSettings),而不是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_modeself.tool_addition_mode,绝不直接读取 profile 中对应的键。原因是二者语义不同:

  • profile(模型档案)声明"该模型家族声称支持什么模式";
  • adapter(适配器类)声明"该适配器的渲染器实际实现了什么模式",通过类变量supported_tool_deferral_modessupported_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_compaction(L2133 附近);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_defsvisibility_of()则让适配器进入扣留(withholding)模式

源码佐证:ModelRequestParameters.visibility_of(tool_name)返回解析后的ToolVisibility'visible'/'withheld'/'via_history'等);declared_tool_defs只包含进入 provider 普通tools集合的定义(output tools 无条件包含,function tools 按可见性过滤);AnthropicModeltool_addition_mode == 'by_reference'时按declared_tool_defsvisibility_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。真正独立的传输(AsyncAnthropicBedrockAsyncAnthropicVertexAsyncAnthropicFoundry等)是不同的客户端类,各自拥有自己的能力闸门——这正是isinstance画出的边界。

规范还给出了两条实操建议:

  • 用探测方式验证网关路径(Model('<id>', provider='gateway')),而不是靠推理;
  • 网关确实不服务的模型应列入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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询