[build-system] requires = ["hatchling", "uv-dynamic-versioning>=0.7.0"] build-backend = "hatchling.build" [tool.hatch.version] source = "uv-dynamic-versioning" [tool.uv-dynamic-versioning] vcs = "git" style = "pep440" bump = true [project] name = "pydantic-ai" dynamic = ["version", "dependencies", "optional-dependencies"] description = "AI Agent Framework, the Pydantic way" authors = [ { name = "Douwe Maan", email = "douwe@pydantic.dev" }, { name = "David Sanchez", email = "david.sanchez@pydantic.dev" }, { name = "Aditya Vardhan", email = "aditya@pydantic.dev" }, { name = "David Montague", email = "david@pydantic.dev" }, { name = "Marcelo Trylesinski", email = "marcelotryle@gmail.com" }, { name = "Alex Hall", email = "alex@pydantic.dev" }, { name = "Samuel Colvin", email = "samuel@pydantic.dev" }, ] license = "MIT" license-files = ["LICENSE"] readme = "README.md" classifiers = [ "Development Status :: 5 - Production/Stable", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Intended Audience :: Developers", "Intended Audience :: Information Technology", "Operating System :: OS Independent", "Topic :: Internet", "Topic :: Scientific/Engineering :: Artificial Intelligence", "Topic :: Software Development :: Libraries :: Python Modules", "Framework :: Pydantic", "Framework :: Pydantic :: 2", ] requires-python = ">=3.10" [tool.hatch.metadata.hooks.uv-dynamic-versioning] dependencies = [ "pydantic-ai-slim[openai,anthropic,google,cli,mcp,evals,web,retries,logfire]=={{ version }}", ] [tool.hatch.metadata.hooks.uv-dynamic-versioning.optional-dependencies] examples = ["pydantic-ai-examples=={{ version }}"] dbos = ["pydantic-ai-slim[dbos]=={{ version }}"] prefect = ["pydantic-ai-slim[prefect]=={{ version }}"] sentence-transformers = ["pydantic-ai-slim[sentence-transformers]=={{ version }}"] voyageai = ["pydantic-ai-slim[voyageai]=={{ version }}"] # Opt-in extras forwarded from `pydantic-ai-slim` (some previously included in the # default `pydantic-ai` install, others newly exposed here for convenience). # Exposed at the root so users (and CI's `uv run --all-extras` matrix entry) can # pull them through `pydantic-ai[]`. cohere = ["pydantic-ai-slim[cohere]=={{ version }}"] mistral = ["pydantic-ai-slim[mistral]=={{ version }}"] bedrock = ["pydantic-ai-slim[bedrock]=={{ version }}"] xai = ["pydantic-ai-slim[xai]=={{ version }}"] groq = ["pydantic-ai-slim[groq]=={{ version }}"] openrouter = ["pydantic-ai-slim[openrouter]=={{ version }}"] huggingface = ["pydantic-ai-slim[huggingface]=={{ version }}"] ag-ui = ["pydantic-ai-slim[ag-ui]=={{ version }}"] ui = ["pydantic-ai-slim[ui]=={{ version }}"] duckduckgo = ["pydantic-ai-slim[duckduckgo]=={{ version }}"] tavily = ["pydantic-ai-slim[tavily]=={{ version }}"] exa = ["pydantic-ai-slim[exa]=={{ version }}"] web-fetch = ["pydantic-ai-slim[web-fetch]=={{ version }}"] temporal = ["pydantic-ai-slim[temporal]=={{ version }}"] spec = ["pydantic-ai-slim[spec]=={{ version }}"] [project.urls] Homepage = "https://ai.pydantic.dev" Source = "https://github.com/pydantic/pydantic-ai" Documentation = "https://ai.pydantic.dev" Changelog = "https://github.com/pydantic/pydantic-ai/releases" [project.scripts] pai = "pydantic_ai._cli:cli_exit" # TODO remove this when clai has been out for a while [tool.uv.sources] pydantic-ai = { workspace = true } pydantic-ai-slim = { workspace = true } pydantic-evals = { workspace = true } pydantic-graph = { workspace = true } pydantic-ai-examples = { workspace = true } pydantic-docs = { git = "https://github.com/pydantic/pydantic-docs" } [tool.uv.workspace] members = [ "pydantic_ai_slim", "pydantic_evals", "pydantic_graph", "clai", "examples", ] [tool.uv] default-groups = ["dev", "lint", "docs"] constraint-dependencies = [ "ray>=2.55.0", "authlib>=1.6.7", # Security-driven floors for purely transitive deps. Each line lifts the # minimum to the first patched release flagged by Dependabot so we don't # have to wait for upstream packages to bump. "urllib3>=2.7.0", "idna>=3.15", "cryptography>=48.0.1", "lxml>=6.1.0", "mako>=1.3.12", "pillow>=12.2.0", "pyarrow>=23.0.1", "pymdown-extensions>=10.21.3", "python-dotenv>=1.2.2", "pygments>=2.20.0", "xgrammar>=0.1.32", "langchain-core>=1.3.3", "langchain-text-splitters>=1.1.2", "langsmith>=0.8.18", "joserfc>=1.6.7", "pydantic-settings>=2.14.2", "vcrpy>=8.2.1", "starlette>=1.3.1", "aiohttp>=3.14.1", "python-multipart>=0.0.31", "pyjwt>=2.13.0", "torch>=2.12.1", "transformers>=5.3.0", ] [dependency-groups] dev = [ "anyio>=4.5.0", "asgi-lifespan>=2.1.0", "devtools>=0.12.2", "coverage[toml]>=7.10.7", "dirty-equals>=0.9.0", "duckduckgo-search>=7.0.0", "pydocket>=0.20.2", "exa-py>=2.0.0", "tavily-python>=0.5.0", "markdownify>=1.2", "inline-snapshot>=0.32.5", "pytest>=9.0.3", "pytest-examples>=0.0.18", "pytest-mock>=3.14.0", "pytest-pretty>=1.3.0", "pytest-recording>=0.13.2", "diff-cover>=9.2.0", "boto3-stubs[bedrock-runtime]>=1.42.63", "strict-no-cover @ git+https://github.com/pydantic/strict-no-cover.git@7fc59da2c4dff919db2095a0f0e47101b657131d", "pytest-xdist>=3.6.1", # Needed for PyCharm users "pip>=26.1", "genai-prices>=0.0.69", "brotli>=1.2.0", # Tests use `FastMCP(...)` in-process servers via fastmcp's server module — the user-facing # `[mcp]` extra ships `fastmcp-slim[client]` which lacks server support, so devs need the full # `fastmcp` package alongside. "fastmcp>=3.3.0", ] lint = ["mypy>=1.11.2", "pyright>=1.1.408", "ruff>=0.14.14"] docs = [ "black>=24.10.0", "mkdocs>=1.6.1", "mkdocs-glightbox>=0.4.0", "mkdocs-llmstxt>=0.2.0", "mkdocs-redirects>=1.2.2", "mkdocs-material[imaging]>=9.7.0", "mkdocstrings-python>=2.0.0", "griffe-warnings-deprecated>=1.1.0", "pydantic-docs", ] docs-upload = ["algoliasearch>=4.12.0", "pydantic>=2.10.1"] [tool.hatch.build.targets.wheel] bypass-selection = true exclude = ["/pydantic_ai_slim/pydantic_ai/.agents"] [tool.hatch.build.targets.sdist] include = ["/README.md", "/Makefile"] # We are excluding the tests because we have 100MB of test data in this repository. # Obviously that's a problem, but for now, we'll just exclude them. exclude = ["/tests"] [tool.ruff] line-length = 120 target-version = "py310" include = [ "pydantic_ai_slim/**/*.py", "pydantic_evals/**/*.py", "pydantic_graph/**/*.py", "examples/**/*.py", "clai/**/*.py", "tests/**/*.py", "docs/**/*.py", ".github/scripts/ci_duration.py", ".github/scripts/test_ci_duration.py", ".github/scripts/pydantic_ai_gh_aw_shim/**/*.py", ".github/scripts/test_pydantic_ai_runner.py", ] [tool.ruff.lint] preview = true # For `PLW1514` explicit-preview-rules = true select = ["E4", "E7", "E9", "F"] extend-select = [ "Q", "RUF100", "RUF018", # https://docs.astral.sh/ruff/rules/assignment-in-assert/ "RUF043", # https://docs.astral.sh/ruff/rules/pytest-raises-ambiguous-pattern/ "C90", "UP", "I", "D", "TID251", "PLW1514", # https://docs.astral.sh/ruff/rules/unspecified-encoding/ ] flake8-quotes = { inline-quotes = "single", multiline-quotes = "double" } mccabe = { max-complexity = 15 } ignore = [ "D100", # ignore missing docstring in module "D102", # ignore missing docstring in public method "D104", # ignore missing docstring in public package "D105", # ignore missing docstring in magic methods "D107", # ignore missing docstring in __init__ methods ] [tool.ruff.lint.isort] combine-as-imports = true known-first-party = ["pydantic_ai", "pydantic_evals", "pydantic_graph"] [tool.ruff.lint.pydocstyle] convention = "google" [tool.ruff.lint.flake8-tidy-imports.banned-api] "typing.TypedDict".msg = "Use typing_extensions.TypedDict instead." "typing.assert_never".msg = "Use typing_extensions.assert_never instead." "asyncio.Lock".msg = "Use anyio.Lock instead." [tool.ruff.format] # don't format python in docstrings, pytest-examples takes care of it docstring-code-format = false quote-style = "single" [tool.ruff.lint.per-file-ignores] "examples/**/*.py" = ["D101", "D103"] "tests/**/*.py" = ["D"] "docs/**/*.py" = ["D"] ".github/scripts/ci_duration.py" = ["D"] ".github/scripts/test_ci_duration.py" = ["D"] ".github/scripts/test_pydantic_ai_runner.py" = ["D"] [tool.pyright] pythonVersion = "3.10" typeCheckingMode = "strict" reportMissingTypeStubs = false reportUnnecessaryIsInstance = false reportUnnecessaryTypeIgnoreComment = true reportMissingModuleSource = false include = [ "pydantic_ai_slim", "pydantic_evals", "pydantic_graph", "tests", "examples", "clai", ".github/scripts/ci_duration.py", ".github/scripts/test_ci_duration.py", ".github/scripts/pydantic_ai_gh_aw_shim", ".github/scripts/test_pydantic_ai_runner.py", ] venvPath = '.' venv = ".venv" # see https://github.com/microsoft/pyright/issues/7771 - we don't want to error on decorated functions in tests # which are not otherwise used executionEnvironments = [ { root = "tests", reportUnusedFunction = false, reportPrivateImportUsage = false }, # `extraPaths` lets pyright resolve the shim package (which sits next to # the test file, not under the project root) the same way the runtime # `sys.path.insert(...)` at the top of the test does. { root = ".github/scripts/test_ci_duration.py", extraPaths = [".github/scripts"], reportUnusedFunction = false, reportPrivateImportUsage = false }, { root = ".github/scripts/test_pydantic_ai_runner.py", extraPaths = [".github/scripts"], reportUnusedFunction = false, reportPrivateImportUsage = false }, ] exclude = [ "examples/pydantic_ai_examples/weather_agent_gradio.py", "pydantic_ai_slim/pydantic_ai/embeddings/voyageai.py", # voyageai package has no type stubs ] [tool.mypy] files = "tests/typed_agent.py" strict = true [tool.pytest.ini_options] testpaths = ["tests", "docs/.hooks", ".github/scripts/test_ci_duration.py"] xfail_strict = true filterwarnings = [ "error", # boto3 "ignore::DeprecationWarning:botocore.*", "ignore::RuntimeWarning:pydantic_ai.mcp", # ag-ui-protocol >= 0.1.15 deprecated BinaryInputContent "ignore:BinaryInputContent is deprecated:DeprecationWarning", # uvicorn (mcp server) "ignore:websockets.legacy is deprecated.*:DeprecationWarning:websockets.legacy", "ignore:websockets.server.WebSocketServerProtocol is deprecated:DeprecationWarning", # random resource warnings; I suspect these are coming from vendor SDKs when running examples.. "ignore:unclosed = 1.0 deprecated (and ignores) # that arg and warns about it. The arg is a no-op and the call site isn't ours to change. "ignore:The `local_dir_use_symlinks` argument is deprecated and ignored:UserWarning", # fastmcp v3 does standard OTel distributed tracing (inject on client, extract on server). # logfire's default config wraps the global propagator to warn on extract via warnings.warn(), # which crashes tool execution when filterwarnings=["error"]. The proper upstream fix is for # logfire to use logging.warning() instead of warnings.warn() for this diagnostic — it's # informational, not a deprecation, and should never be able to crash execution. "ignore:Found propagated trace context:RuntimeWarning", ] # https://coverage.readthedocs.io/en/latest/config.html#run [tool.coverage.run] patch = ["subprocess"] concurrency = ["multiprocessing", "thread"] # We use a subdirectory for coverage data to avoid noisy coverage data files. data_file = ".coverage/.coverage" # required to avoid warnings about files created by create_module fixture include = [ "pydantic_ai_slim/**/*.py", "pydantic_evals/**/*.py", "pydantic_graph/**/*.py", "tests/**/*.py", ] omit = [ "tests/example_modules/*.py", "pydantic_ai_slim/pydantic_ai/common_tools/exa.py", # exa-py integration with external API calls "tests/models/xai_proto_cassettes.py", # dev-only helpers for recording/replaying xAI gRPC protobuf cassettes "tests/_inline_snapshot.py", # test utility wrapper; branches depend on CLI flags "tests/providers/test_gateway_catalog.py", # live gateway catalog smoke test; execution depends on opt-in flag and credentials ] branch = true # Disable include-ignored warnings as --source is enabled automatically causing a self conflict as per: # https://github.com/pytest-dev/pytest-cov/issues/532 # https://github.com/pytest-dev/pytest-cov/issues/369 # This prevents coverage being generated by pytest-cov which has direct editor support in VS Code, # making it super useful to check coverage while writing tests. disable_warnings = ["include-ignored"] [tool.coverage.paths] # Allow CI run assets to be downloaded an replicated locally. source = [ ".", "/home/runner/work/pydantic-ai/pydantic-ai", "/System/Volumes/Data/home/runner/work/pydantic-ai/pydantic-ai", ] # https://coverage.readthedocs.io/en/latest/config.html#report [tool.coverage.report] fail_under = 100 skip_covered = true show_missing = true ignore_errors = true precision = 2 exclude_lines = [ # `# pragma: no cover` is standard marker for code that's not covered, this will error if code is covered 'pragma: no cover', # use `# pragma: lax no cover` if you want to ignore cases where (some of) the code is covered 'pragma: lax no cover', 'raise NotImplementedError', 'if TYPE_CHECKING:', 'if typing.TYPE_CHECKING:', '@overload', '@deprecated', '@typing.overload', '@abstractmethod', '\(Protocol\):$', 'typing.assert_never', '$\s*assert_never\(', 'if __name__ == .__main__.:', 'except ImportError as _import_error:', '$\s*pass$', 'assert False', '@pytest\.mark\.skip', '@pytest\.mark\.xfail', ] [tool.logfire] ignore_no_config = true [tool.inline-snapshot] format-command = "ruff format --stdin-filename {filename}" [tool.inline-snapshot.shortcuts] snap-fix = ["create", "fix"] snap = ["create"] [tool.codespell] # Ref: https://github.com/codespell-project/codespell#using-a-config-file skip = '.git*,*.svg,*.lock,*.css,*.yaml' check-hidden = true # Ignore "formatting" like **L**anguage ignore-regex = '\*\*[A-Z]\*\*[a-z]+\b' ignore-words-list = 'asend,fpr,aci'