Agent Skills › pydantic/pydantic-ai › adding-a-provider-api-feature

adding-a-provider-api-feature

GitHub

指导在 Pydantic AI 中新增跨提供商 API 功能(如缓存、工具调用等)。核心规则是先复用现有抽象而非新建,确保接口一致性。

.agents/skills/adding-a-provider-api-feature/SKILL.md pydantic/pydantic-ai

Trigger Scenarios

需要添加新的提供商 API 能力 设计 Provider 特性接入逻辑

Install

npx skills add pydantic/pydantic-ai --skill adding-a-provider-api-feature -g -y
More Options

Non-standard path

npx skills add https://github.com/pydantic/pydantic-ai/tree/main/.agents/skills/adding-a-provider-api-feature -g -y

Use without installing

npx skills use pydantic/pydantic-ai@adding-a-provider-api-feature

指定 Agent (Claude Code)

npx skills add pydantic/pydantic-ai --skill adding-a-provider-api-feature -a claude-code -g -y

安装 repo 全部 skill

npx skills add pydantic/pydantic-ai --all -g -y

预览 repo 内 skill

npx skills add pydantic/pydantic-ai --list

SKILL.md

Frontmatter
{
    "name": "adding-a-provider-api-feature",
    "description": "Add a new provider API capability (prompt caching, strict\/structured tool calling, thinking\/reasoning effort, service tier, safety settings, logprobs, etc.) to Pydantic AI. Use when wiring a provider feature through the library — it enforces reasoning from the existing cross-provider abstraction before designing anything, and picking default-on vs opt-in deliberately. Not for adding a new model id (that's a different flow) or a bug fix.",
    "allowed-tools": "Bash(git:*), Bash(gh:*), Bash(rg:*), Bash(ls:*), Bash(uv:*), Read, Write, Edit, Glob, Grep, WebFetch, AskUserQuestion, Agent",
    "user-invocable": true
}

Adding a provider API feature

Use this when exposing a new provider API capability through Pydantic AI — prompt caching, strict/structured tool calling, thinking/reasoning effort, service tier, safety settings, logprobs, cache breakpoints, and the like. The output is a change that is consistent with how sibling providers already expose the same concept, defaults deliberately, and gates support with a capability flag.

Not for: adding a new model id (that's add-new-model), a bug fix, or a refactor.

The one rule that prevents the most rework

Before designing anything, find the existing cross-provider abstraction that governs this capability and let its shape decide the API. Most "how should I expose this?" questions are already answered by an abstraction the codebase has — reaching for a new provider-specific knob when one exists is the single most common thing maintainers reject. When a feature routes through an existing abstraction, the abstraction's shape pre-decides the API surface, the opt-out, and often the default.

The tell that you skipped this: you find yourself listing 2-3 "options" for how a user controls the feature. If one of those options duplicates an existing cross-provider control, it isn't a real option — the existing abstraction wins.

Step 0 — Enumerate sibling precedent

For the capability you're adding, list how every provider that already has an analog exposes it, and name the governing existing abstraction. It is one of:

  • a per-tool flag — ToolDefinition.strict: bool | None (tools.py), resolved in models/__init__.py::_customize_tool_def;
  • a shared ModelSettings field — thinking, service_tier (settings.py), each with per-model resolvers mapping to native concepts;
  • a provider-prefixed {Provider}ModelSettings field — anthropic_cache, openai_prompt_cache_key, groq_reasoning_effort;
  • a message-stream marker — CachePoint in UserPromptPart.content (messages.py);
  • a ModelProfile capability flag — openai_supports_strict_tool_definition, bedrock_supports_prompt_caching.

Read the actual sibling implementations and any review threads on the PRs that added them (gh pr view <n> --comments). Contributors who skipped this got redirected: raw google_tool_config → use strict (#5366); string-prefix model detection → use a profile flag (#4604). Only open a design fork if no existing abstraction covers the capability.

Step 1 — Pick the API shape

  1. An existing cross-provider abstraction covers it → reuse it. Add a provider mapping (a _translate_* resolver, a JsonSchemaTransformer subclass, a CachePoint translation). Do not add a provider-prefixed knob for something the shared abstraction already expresses.
  2. No shared abstraction, but ≥3 providers now have the concept → promote to a shared ModelSettings field with a deliberately narrow common vocabulary and per-model resolvers, keeping per-provider fields underneath as precedence-winning escape hatches (the service_tier promotion, #4926). Don't delete provider knobs; deprecate only genuinely-misnamed ones with a TODO(v3).
  3. The provider's native values can't be expressed by the shared enum (family-disjoint value sets, platform-only request shaping) → a {provider}_* knob is justified, and it coexists with and outranks the unified field (groq_reasoning_effort, #5797).
  4. Choose the locus by "can the user naturally point at it?": a boundary inside the message stream → a marker (CachePoint); a structural region (system prompt, tool defs, whole-request setting) → a setting (#3363).
  5. Wire it into every request path the provider has (chat and responses APIs); put a setting shared across a provider's APIs on the base settings class (#3678).
  6. Type it. Reuse the provider SDK's own types where they exist; type knobs as Literal, never extra_body or untyped **kwargs (models/AGENTS.md; "kwargs are a big no no… I'd rather be repetitive but type safe" — DouweM, #3457).

Step 2 — Default: default-on (silent) vs opt-in

Default the feature on only when enabling it cannot change observable behavior and cannot cost the user — a pure, backward-compatible improvement (caching only lowers cost; a validation mode that needs no schema rewrite and can't reject a previously-valid request). A backward-compatible, purely-better default is welcome and needs no opt-in.

Keep it opt-in when it:

  • changes observable output or wire behavior;
  • is a preview feature (provider may change semantics);
  • can raise cost — and choose the default value so a shared field never silently upgrades anyone to a pricier tier (service_tier 'auto' vs 'default', #4926);
  • can hit provider limits at scale (auto-promoting strict silently broke agents with >20 tools; reverted in #5580);
  • applies lossy schema rewrites (OpenAI/Anthropic strict transforms).

When the default isn't obvious, decide it with a live probe, not an opinion (#5897 flipped a default on 0/5 → 5/5 recovery). Beware "automatic" language — verify whether it means a Pydantic AI default or just provider-side management of an opt-in feature.

Step 3 — Capability gating

Detect support with a provider-prefixed ModelProfile flag. Where the feature is used, read only the flag or the adapter's own declaration — never an inline isinstance, model-name or base_url check (profiles/AGENTS.md). Gate at the layer(s) that actually vary:

  • model/family → a flag in the family's profile function (google_supports_strict_tool_definition in profiles/google.py);
  • one provider's API → a flag set in that provider's Provider.model_profile() (bedrock_supports_prompt_caching in providers/bedrock.py);
  • transport (the Bedrock, Vertex and Foundry client classes of one SDK, or a client's own mode such as genai.Client.vertexai) → a flag set in the model's _instance_profile_defaults(), or in its profile property when the client can't send the feature at all. Provider.model_profile() is static and sees only the model name (models/AGENTS.md, the client-class bullet);
  • per-schema → the JsonSchemaTransformer.is_strict_compatible signal (default conservative unless the mode needs no rewrites);
  • SDK version → probe the SDK shape and degrade with a UserWarning (#5580, botocore strict param);
  • unknowable client-side → defer to the runtime API error.

Unsupported → silently ignore the setting (best-effort so as many requests as possible succeed), documented in the docstring — never hard-error. Conflicting user settings → UserWarning, not UserError. Profiles are layered: developer-keyed base + thin provider overlay resolved per family, with the provider overlay layering last (#5934, #6231).

Step 3b — Where the wire mapping goes

Read agent_docs/provider-adapters.md before you edit an adapter's mapper. Place the feature by what it adds:

  • a new part type → one arm in each message kind's dispatch, calling the part's own method on the model class. When most APIs can't render the part, lower it in Model.prepare_messages instead; an adapter that doesn't render it raises _unprepared_part_error(part) in its one arm.
  • a new native tool kind → one entry in the adapter's kind-to-wire-name table. Add a branch only where the wire shape differs.
  • a new response wire item → one function, called from both _process_response and the stream.
  • a new flag or setting that changes how an existing part maps → read it inside that part's method. Where the adapter still maps that part inline in its loop, read it where the part is mapped today, and leave extracting the method to its own structural PR.

Step 4 — Tests

Pyright does not flag a Literal-keyed dict that misses a key; the lookup raises KeyError only at request time. Add every new mapping keyed by ThinkingEffort to test_every_effort_level_resolves in tests/test_thinking.py, which runs each level through each mapping. When you add a value to another shared Literal, write the same kind of test for it.

Live-recorded wire-contract cassette asserting the exact outbound body (mode/field is sent for supported models, absent/ignored for unsupported), plus a test exercising the new setting. Prefer case-based parametrized VCR over mocks; a unit test is still right for asserting an internal request shape a cassette matcher wouldn't catch. This is also where a review bot's "this will fail" claim gets refuted with a recorded cassette.

Step 5 — Docs & skills

The new public symbol's docstring lists which providers support it and how each interprets the value. Add/refresh the docs/**.md section and any sibling docstrings that now under-claim ("only OpenAI" → the full provider list). Describe the mechanism only as far as the provider documents it — don't assert a mechanism a provider's docs leave unstated. Update the relevant agent skill.

ModelSettings' Supported by: lists are enforced: tests/models/test_model_settings_support.py probes each model class's outgoing request and asserts every list names exactly the classes that send the field. Forwarding a new setting means editing its list, and a new Model class means adding a Case there. tool_choice and thinking are exempt via HAND_MAINTAINED and stay hand-maintained.

Recurring maintainer principles (quoted)

  1. Reuse the cross-provider abstraction over a provider knob — "this maps to what the Anthropic and OpenAI APIs call strict… already represented on ToolDefinition… a more complete, consistent, 'doesn't require the user to do something special' implementation would be to automatically use this mode." (#5366)
  2. Promote to a shared setting once several providers have the concept; keep per-provider overrides underneath. (#4926)
  3. Best-effort — silently ignore unsupported settings, don't error. "we typically do a 'best effort' so that as many requests as possible succeed." (#3438)
  4. Hide provider complexity — the feature should be useful to people who don't want to become experts in that provider's limitations.
  5. Capability facts belong on ModelProfile flags, layered base + overlay. (#5934)
  6. Type safety over repetition; no untyped kwargs. (#3457)
  7. Put shared fields on the base settings class; cover every API surface. (#3678)
  8. Verify against the real API; defer validation to runtime. "If they're allowed by the SDK types and we can try it out and it doesn't fail, I'm fine with it." (#3678)
  9. Name the eventual unification even when deferring it — a {provider}_* knob today can note the future Caching/Thinking-style capability it should fold into. (#4604)

Precedent map

Capability Reused abstraction Default Gating PR
service tier (cross-provider) promoted to ModelSettings.service_tier opt-in, never silent upgrade map-and-drop #4926
strict — OpenAI (origin) ToolDefinition.strict auto-promote per compatible schema per-schema is_strict_compatible #1304
strict — Anthropic reused strict conservative opt-in transformer + profile flag #3457
strict — Bedrock (+ fix) reused strict opt-in (auto-promote reverted) transformer + profile + SDK probe #4237, #5580
strict — Gemini VALIDATED reused strict; rejected raw google_tool_config default-on (mode needs no schema rewrite) profile flag; is_strict_compatible = True #6353
thinking (cross-provider) ModelSettings.thinking + per-provider maps opt-in, graceful degradation supports_thinking flags #4640
reasoning effort — Groq provider knob coexists with thinking, outranks it opt-in per-family profile flag #5797, #6231
prompt caching — Anthropic → Bedrock/OpenRouter CachePoint marker + settings; breakpoint limits through models/_prompt_cache.py opt-in {provider}_supports_prompt_caching #3363, #3438, #4604

Version History

  • 819c9e7 Current 2026-11-03 00:26

    新增 provider adapter 主题指南及能力门控规则;统一 thinking 努力等级为 max

  • 43f982f 2026-08-19 23:48

    修正ModelSettings的Supported by列表并用探针测试固定(#7443)

  • 20cdf45 2026-07-25 05:33

Same Skill Collection

.agents/skills/add-new-model/SKILL.md
.agents/skills/adopt-pr/SKILL.md
.agents/skills/branch-context/SKILL.md
.agents/skills/complete-partial-pr/SKILL.md
.agents/skills/docs-parity-reviewer/SKILL.md
.agents/skills/file-followup-issue/SKILL.md
.agents/skills/i-have-adhd/SKILL.md
.agents/skills/initialize-worktree/SKILL.md
.agents/skills/pre-push-review/SKILL.md
.agents/skills/pushing-commits-to-the-repo/SKILL.md
.agents/skills/refresh-issue-brief/SKILL.md
.claude/skills/address-feedback/SKILL.md
.claude/skills/branch-context/SKILL.md
.claude/skills/pre-push-review/SKILL.md
.claude/skills/testing-skill/SKILL.md
pydantic_ai_slim/pydantic_ai/.agents/skills/building-pydantic-ai-agents/SKILL.md
pydantic_ai_slim/pydantic_ai/.agents/skills/migrating-agno-to-pydantic-ai/SKILL.md
pydantic_ai_slim/pydantic_ai/.agents/skills/migrating-claude-agent-sdk-to-pydantic-ai/SKILL.md
pydantic_ai_slim/pydantic_ai/.agents/skills/migrating-google-adk-to-pydantic-ai/SKILL.md
pydantic_ai_slim/pydantic_ai/.agents/skills/migrating-langchain-to-pydantic-ai/SKILL.md
pydantic_ai_slim/pydantic_ai/.agents/skills/migrating-mastra-to-pydantic-ai/SKILL.md
pydantic_ai_slim/pydantic_ai/.agents/skills/migrating-openai-agents-sdk-to-pydantic-ai/SKILL.md
pydantic_ai_slim/pydantic_ai/.agents/skills/migrating-pi-to-pydantic-ai/SKILL.md
pydantic_ai_slim/pydantic_ai/.agents/skills/migrating-vercel-ai-sdk-and-eve-to-pydantic-ai/SKILL.md
src/pydantic_ai_harness/.agents/skills/adopt-pr/SKILL.md
src/pydantic_ai_harness/.agents/skills/branch-context/SKILL.md
src/pydantic_ai_harness/.agents/skills/pushing-commits-to-the-repo/SKILL.md
.agents/skills/poweruser-feature-audit/SKILL.md

Metadata

Files
0
Version
819c9e7
Hash
7d5b6565
Indexed
2026-07-25 05:33

Accueil - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-11-03 00:54
浙ICP备14020137号-1