Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
78e006c
Revert pyodide to a version that doesn't print to stdout when install…
samuelcolvin Jun 8, 2025
4f257bd
Add token usage metrics to `InstrumentedModel` (#1898)
alexmojaki Jun 9, 2025
75845d1
Update Documentation for Provider arguments in google.md (#1946)
Deriverx2 Jun 10, 2025
1ed1073
Add API reference to llmstxt (#1940)
Deriverx2 Jun 10, 2025
4734135
Handle include_pending=false for bank_support example (#1912)
amihalik Jun 10, 2025
fe03daf
Add `service_tier` to `OpenAIModelSettings` (#1923)
empezarcero Jun 11, 2025
a1259fe
Allow users to supply `extra_http_client_args` in `MCPServerHTTP` (#…
mpfaffenberger Jun 11, 2025
0690748
Don't send sampling settings like `temperature` and `top_p` to OpenAI…
DouweM Jun 11, 2025
4af2463
docs: format google python file (#1963)
Kludex Jun 12, 2025
2fce134
Support field `fileData` (direct file URL) for `GeminiModel` and `Goo…
vricciardulli Jun 12, 2025
b7a2870
Reuse last request from message history if no user prompt was provide…
DouweM Jun 12, 2025
8aa964b
Prevent Anthropic API errors from empty message content (#1934)
mike-luabase Jun 12, 2025
764e957
Add MCP Streamable HTTP implementation (#1965)
Kludex Jun 13, 2025
7c1e47e
feat(openai): expose Responses API id as vendor_id (#1949)
sarunas-zebra Jun 13, 2025
67c381e
Use `GoogleModel` instead of `GeminiModel` on inference (#1881)
Kludex Jun 13, 2025
352acff
Proper check if callable is async (#1972)
Kludex Jun 13, 2025
e3e435e
More flexible method infer_provider (#1945)
hovi Jun 13, 2025
477a590
Ignore dynamic instructions returning an empty string (#1961)
giacbrd Jun 13, 2025
ae0c3ce
Set Anthropic max_tokens to the highest allowed by the model by defau…
DouweM Jun 13, 2025
b65c8b6
uprev Pyodide to 0.27.6 (#1944)
samuelcolvin Jun 16, 2025
380fbf3
refactor: updated tools doc with function naming: roll_die → roll_dic…
yamanahlawat Jun 16, 2025
c33fe23
Set Anthropic `max_tokens` to 4096 by default (#1994)
Kludex Jun 16, 2025
6651510
feat: add `history_processors` parameter to `Agent` for message proce…
Kludex Jun 16, 2025
701ac1b
Yield events for unknown tool calls (#1960)
proever Jun 16, 2025
a953d34
Always set a parameters schema on a Gemini function declaration, even…
DouweM Jun 17, 2025
388ecc2
Handle `McpError` from MCP tool calls (#1999)
ppcantidio Jun 17, 2025
b9233de
Add process_tool_call hook to MCP servers to modify tool args, metada…
stevenh Jun 17, 2025
d006b41
Respect ModelSettings.timeout in GoogleModel (#2006)
DouweM Jun 17, 2025
7fdd745
feat: add RunContext support to history processors (#2002)
Wh1isper Jun 17, 2025
b487d60
Support Thinking part (#1142)
Kludex Jun 18, 2025
9c7480d
Update Google models (#2010)
tacoo Jun 18, 2025
79561a4
docs: Update client.md (#2013)
a-klos Jun 18, 2025
f2646de
fix: update `ThinkingPart` when delta contains `signature` (#2012)
Kludex Jun 18, 2025
be5cda6
Support MCP sampling (#1884)
samuelcolvin Jun 19, 2025
ddcd5af
codespell fix (#2029)
samuelcolvin Jun 19, 2025
473b2ce
uprev `pytest-pretty` (#2030)
samuelcolvin Jun 19, 2025
9512987
merge
Kludex Jun 20, 2025
800a71a
Pass tests
Kludex Jun 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ jobs:

- run: make lint-js

- run: uv run --package mcp-run-python pytest mcp-run-python -v
- run: uv run --package mcp-run-python pytest mcp-run-python -v --durations=100

- run: deno task dev warmup
working-directory: mcp-run-python
Expand Down
25 changes: 14 additions & 11 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,20 @@ repos:
rev: 0.6.8
hooks:
- id: fix-smartquotes
exclude: "cassettes/"
- id: fix-spaces
exclude: "cassettes/"
- id: fix-ligatures
exclude: "cassettes/"

- repo: https://github.com/codespell-project/codespell
# Configuration for codespell is in pyproject.toml
rev: v2.3.0
hooks:
- id: codespell
args: ["--skip", "tests/models/cassettes/*"]
additional_dependencies:
- tomli

- repo: local
hooks:
Expand All @@ -38,12 +50,12 @@ repos:
args: [lint-js]
language: system
types_or: [javascript, ts, json]
files: '^mcp-run-python/'
files: "^mcp-run-python/"
pass_filenames: false
- id: clai-help
name: clai help output
entry: uv
args: [run, pytest, 'clai/update_readme.py']
args: [run, pytest, "clai/update_readme.py"]
language: system
types_or: [python, markdown]
pass_filenames: false
Expand All @@ -54,12 +66,3 @@ repos:
language: system
types: [python]
pass_filenames: false

- repo: https://github.com/codespell-project/codespell
# Configuration for codespell is in pyproject.toml
rev: v2.3.0
hooks:
- id: codespell
args: ['--skip', 'tests/models/cassettes/*,docs/a2a/fasta2a.md,tests/models/test_groq.py']
additional_dependencies:
- tomli
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ test: ## Run tests and collect coverage data
@uv run coverage report

.PHONY: test-fast
test-fast: ## Same as test except no coverage. ~1/4th the time depending on hardware.
test-fast: ## Same as test except no coverage and 4x faster depending on hardware
uv run pytest -n auto --dist=loadgroup

.PHONY: test-all-python
Expand All @@ -78,12 +78,12 @@ test-all-python: ## Run tests on Python 3.9 to 3.13
@uv run coverage report

.PHONY: testcov
testcov: test ## Run tests and generate a coverage report
testcov: test ## Run tests and generate an HTML coverage report
@echo "building coverage html"
@uv run coverage html

.PHONY: test-mrp
test-mrp: ## Build and tests of mcp-run-python
test-mrp: ## Build and tests of mcp-run-python
cd mcp-run-python && deno task build
uv run --package mcp-run-python pytest mcp-run-python -v

Expand Down
2 changes: 2 additions & 0 deletions docs/agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,8 @@ _(This example is complete, it can be run "as is")_

You can also dynamically change the instructions for an agent by using the `@agent.instructions` decorator.

Note that returning an empty string will result in no instruction message added.

```python {title="dynamic_instructions.py"}
from datetime import date

Expand Down
12 changes: 8 additions & 4 deletions docs/api/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@

::: pydantic_ai.providers.cohere

::: pydantic_ai.providers.mistral
::: pydantic_ai.providers.mistral.MistralProvider

::: pydantic_ai.providers.fireworks
::: pydantic_ai.providers.fireworks.FireworksProvider

::: pydantic_ai.providers.grok
::: pydantic_ai.providers.grok.GrokProvider

::: pydantic_ai.providers.together
::: pydantic_ai.providers.together.TogetherProvider

::: pydantic_ai.providers.heroku.HerokuProvider

::: pydantic_ai.providers.openrouter.OpenRouterProvider
16 changes: 13 additions & 3 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ PydanticAI is still pre-version 1, so breaking changes will occur, however:
!!! note
Here's a filtered list of the breaking changes for each version to help you upgrade PydanticAI.

### v0.1.0 (2025-04-15)
### v0.3.0 (2025-06-18)

See [#1248](https://github.com/pydantic/pydantic-ai/pull/1248) — the attribute/parameter name `result` was renamed to `output` in many places. Hopefully all changes keep a deprecated attribute or parameter with the old name, so you should get many deprecation warnings.
See [#1142](https://github.com/pydantic/pydantic-ai/pull/1142) — Adds support for thinking parts.

See [#1484](https://github.com/pydantic/pydantic-ai/pull/1484) — `format_as_xml` was moved and made available to import from the package root, e.g. `from pydantic_ai import format_as_xml`.
We now convert the thinking blocks (`"<think>..."</think>"`) in provider specific text parts to
PydanticAI `ThinkingPart`s. Also, as part of this release, we made the choice to not send back the
`ThinkingPart`s to the provider - the idea is to save costs on behalf of the user. In the future, we
intend to add a setting to customize this behavior.

### v0.2.0 (2025-05-12)

Expand All @@ -25,6 +28,13 @@ See [#1647](https://github.com/pydantic/pydantic-ai/pull/1647) — usage makes s
* Adds `usage` to `ModelResponse` (field has a default factory of `Usage()` so it'll work to load data that doesn't have usage)
* changes the return type of `Model.request` to just `ModelResponse` instead of `tuple[ModelResponse, Usage]`


### v0.1.0 (2025-04-15)

See [#1248](https://github.com/pydantic/pydantic-ai/pull/1248) — the attribute/parameter name `result` was renamed to `output` in many places. Hopefully all changes keep a deprecated attribute or parameter with the old name, so you should get many deprecation warnings.

See [#1484](https://github.com/pydantic/pydantic-ai/pull/1484) — `format_as_xml` was moved and made available to import from the package root, e.g. `from pydantic_ai import format_as_xml`.

---

## Full Changelog
Expand Down
2 changes: 1 addition & 1 deletion docs/evals.md
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,7 @@ class SpanTracingEvaluator(Evaluator[str, str]):
has_errors = span_tree.any(error_query)

# Calculate a performance score (lower is better)
performance_score = 1.0 if total_processing_time < 0.5 else 0.5
performance_score = 1.0 if total_processing_time < 1.0 else 0.5

return {
'has_spans': True,
Expand Down
2 changes: 1 addition & 1 deletion docs/graph.md
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,7 @@ Instead of running the entire graph in a single process invocation, we run the g

from dataclasses import dataclass, field

from groq import BaseModel
from pydantic import BaseModel
from pydantic_graph import (
BaseNode,
End,
Expand Down
29 changes: 21 additions & 8 deletions docs/input.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

Some LLMs are now capable of understanding audio, video, image and document content.


## Image Input

!!! info
Expand Down Expand Up @@ -64,14 +65,6 @@ You can provide video input using either [`VideoUrl`][pydantic_ai.VideoUrl] or [
!!! info
Some models do not support document input. Please check the model's documentation to confirm whether it supports document input.

!!! warning
When using Gemini models, the document content will always be sent as binary data, regardless of whether you use `DocumentUrl` or `BinaryContent`. This is due to differences in how Vertex AI and Google AI handle document inputs.

For more details, see [this discussion](https://discuss.ai.google.dev/t/i-am-using-google-generative-ai-model-gemini-1-5-pro-for-image-analysis-but-getting-error/34866/4).

If you are unsatisfied with this behavior, please let us know by opening an issue on
[GitHub](https://github.com/pydantic/pydantic-ai/issues).

You can provide document input using either [`DocumentUrl`][pydantic_ai.DocumentUrl] or [`BinaryContent`][pydantic_ai.BinaryContent]. The process is similar to the examples above.

If you have a direct URL for the document, you can use [`DocumentUrl`][pydantic_ai.DocumentUrl]:
Expand Down Expand Up @@ -109,3 +102,23 @@ result = agent.run_sync(
print(result.output)
# > The document discusses...
```

## User-side download vs. direct file URL

As a general rule, when you provide a URL using any of `ImageUrl`, `AudioUrl`, `VideoUrl` or `DocumentUrl`, PydanticAI downloads the file content and then sends it as part of the API request.

The situation is different for certain models:

- [`AnthropicModel`][pydantic_ai.models.anthropic.AnthropicModel]: if you provide a PDF document via `DocumentUrl`, the URL is sent directly in the API request, so no download happens on the user side.

- [`GeminiModel`][pydantic_ai.models.gemini.GeminiModel] and [`GoogleModel`][pydantic_ai.models.google.GoogleModel] on Vertex AI: any URL provided using `ImageUrl`, `AudioUrl`, `VideoUrl`, or `DocumentUrl` is sent as-is in the API request and no data is downloaded beforehand.

See the [Gemini API docs for Vertex AI](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/inference#filedata) to learn more about supported URLs, formats and limitations:

- Cloud Storage bucket URIs (with protocol `gs://`)
- Public HTTP(S) URLs
- Public YouTube video URL (maximum one URL per request)

However, because of crawling restrictions, it may happen that Gemini can't access certain URLs. In that case, you can instruct PydanticAI to download the file content and send that instead of the URL by setting the boolean flag `force_download` to `True`. This attribute is available on all objects that inherit from [`FileUrl`][pydantic_ai.messages.FileUrl].

- [`GeminiModel`][pydantic_ai.models.gemini.GeminiModel] and [`GoogleModel`][pydantic_ai.models.google.GoogleModel] on GLA: YouTube video URLs are sent directly in the request to the model.
117 changes: 104 additions & 13 deletions docs/mcp/client.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,33 +18,35 @@ pip/uv-add "pydantic-ai-slim[mcp]"

PydanticAI comes with two ways to connect to MCP servers:

- [`MCPServerHTTP`][pydantic_ai.mcp.MCPServerHTTP] which connects to an MCP server using the [Streamable HTTP transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) transport
- [`MCPServerSSE`][pydantic_ai.mcp.MCPServerSSE] which connects to an MCP server using the [HTTP SSE](https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/transports/#http-with-sse) transport
- [`MCPServerStreamableHTTP`][pydantic_ai.mcp.MCPServerStreamableHTTP] which connects to an MCP server using the [Streamable HTTP](https://modelcontextprotocol.io/introduction#streamable-http) transport
- [`MCPServerStdio`][pydantic_ai.mcp.MCPServerStdio] which runs the server as a subprocess and connects to it using the [stdio](https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/transports/#stdio) transport

Examples of both are shown below; [mcp-run-python](run-python.md) is used as the MCP server in both examples.

### HTTP Client
### SSE Client

[`MCPServerHTTP`][pydantic_ai.mcp.MCPServerHTTP] connects over HTTP using the [Streamable HTTP transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) to a server.
[`MCPServerSSE`][pydantic_ai.mcp.MCPServerSSE] connects over HTTP using the [HTTP + Server Sent Events transport](https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/transports/#http-with-sse) to a server.

!!! note
[`MCPServerHTTP`][pydantic_ai.mcp.MCPServerHTTP] requires an MCP server to be running and accepting HTTP connections before calling [`agent.run_mcp_servers()`][pydantic_ai.Agent.run_mcp_servers]. Running the server is not managed by PydanticAI.
[`MCPServerSSE`][pydantic_ai.mcp.MCPServerSSE] requires an MCP server to be running and accepting HTTP connections before calling [`agent.run_mcp_servers()`][pydantic_ai.Agent.run_mcp_servers]. Running the server is not managed by PydanticAI.

The StreamableHTTP Transport is able to connect to both stateless HTTP and older Server Sent Events (SSE) servers.
The name "HTTP" is used since this implemented will be adapted in future to use the new
[Streamable HTTP](https://github.com/modelcontextprotocol/specification/pull/206) currently in development.

Before creating the HTTP client, we need to run the server (docs [here](run-python.md)):
Before creating the SSE client, we need to run the server (docs [here](run-python.md)):

```bash {title="terminal (run http server)"}
```bash {title="terminal (run sse server)"}
deno run \
-N -R=node_modules -W=node_modules --node-modules-dir=auto \
jsr:@pydantic/mcp-run-python sse
```

```python {title="mcp_sse_client.py" py="3.10"}
from pydantic_ai import Agent
from pydantic_ai.mcp import MCPServerHTTP
from pydantic_ai.mcp import MCPServerSSE

server = MCPServerHTTP(url='http://localhost:3001/sse') # (1)!
server = MCPServerSSE(url='http://localhost:3001/sse') # (1)!
agent = Agent('openai:gpt-4o', mcp_servers=[server]) # (2)!


Expand All @@ -55,7 +57,7 @@ async def main():
#> There are 9,208 days between January 1, 2000, and March 18, 2025.
```

1. Define the MCP server with the URL used to connect. This will typically end in `/mcp` for HTTP servers and `/sse` for SSE.
1. Define the MCP server with the URL used to connect.
2. Create an agent with the MCP server attached.
3. Create a client session to connect to the server.

Expand Down Expand Up @@ -83,6 +85,53 @@ Will display as follows:

![Logfire run python code](../img/logfire-run-python-code.png)

### Streamable HTTP Client

[`MCPServerStreamableHTTP`][pydantic_ai.mcp.MCPServerStreamableHTTP] connects over HTTP using the
[Streamable HTTP](https://modelcontextprotocol.io/introduction#streamable-http) transport to a server.

!!! note
[`MCPServerStreamableHTTP`][pydantic_ai.mcp.MCPServerStreamableHTTP] requires an MCP server to be
running and accepting HTTP connections before calling
[`agent.run_mcp_servers()`][pydantic_ai.Agent.run_mcp_servers]. Running the server is not
managed by PydanticAI.

Before creating the Streamable HTTP client, we need to run a server that supports the Streamable HTTP transport.

```python {title="streamable_http_server.py" py="3.10" test="skip"}
from mcp.server.fastmcp import FastMCP

app = FastMCP()

@app.tool()
def add(a: int, b: int) -> int:
return a + b

app.run(transport='streamable-http')
```

Then we can create the client:

```python {title="mcp_streamable_http_client.py" py="3.10"}
from pydantic_ai import Agent
from pydantic_ai.mcp import MCPServerStreamableHTTP

server = MCPServerStreamableHTTP('http://localhost:8000/mcp') # (1)!
agent = Agent('openai:gpt-4o', mcp_servers=[server]) # (2)!

async def main():
async with agent.run_mcp_servers(): # (3)!
result = await agent.run('How many days between 2000-01-01 and 2025-03-18?')
print(result.output)
#> There are 9,208 days between January 1, 2000, and March 18, 2025.
```

1. Define the MCP server with the URL used to connect.
2. Create an agent with the MCP server attached.
3. Create a client session to connect to the server.

_(This example is complete, it can be run "as is" with Python 3.10+ — you'll need to add `asyncio.run(main())` to run `main`)_

### MCP "stdio" Server

The other transport offered by MCP is the [stdio transport](https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/transports/#stdio) where the server is run as a subprocess and communicates with the client over `stdin` and `stdout`. In this case, you'd use the [`MCPServerStdio`][pydantic_ai.mcp.MCPServerStdio] class.
Expand Down Expand Up @@ -118,6 +167,48 @@ async def main():

1. See [MCP Run Python](run-python.md) for more information.

## Tool call customisation

The MCP servers provide the ability to set a `process_tool_call` which allows
the customisation of tool call requests and their responses.

A common use case for this is to inject metadata to the requests which the server
call needs.

```python {title="mcp_process_tool_call.py" py="3.10"}
from typing import Any

from pydantic_ai import Agent
from pydantic_ai.mcp import CallToolFunc, MCPServerStdio, ToolResult
from pydantic_ai.models.test import TestModel
from pydantic_ai.tools import RunContext


async def process_tool_call(
ctx: RunContext[int],
call_tool: CallToolFunc,
tool_name: str,
args: dict[str, Any],
) -> ToolResult:
"""A tool call processor that passes along the deps."""
return await call_tool(tool_name, args, metadata={'deps': ctx.deps})


server = MCPServerStdio('python', ['-m', 'tests.mcp_server'], process_tool_call=process_tool_call)
agent = Agent(
model=TestModel(call_tools=['echo_deps']),
deps_type=int,
mcp_servers=[server]
)


async def main():
async with agent.run_mcp_servers():
result = await agent.run('Echo with deps set to 42', deps=42)
print(result.output)
#> {"echo_deps":{"echo":"This is an echo message","deps":42}}
```

## Using Tool Prefixes to Avoid Naming Conflicts

When connecting to multiple MCP servers that might provide tools with the same name, you can use the `tool_prefix` parameter to avoid naming conflicts. This parameter adds a prefix to all tool names from a specific server.
Expand All @@ -134,15 +225,15 @@ This allows you to use multiple servers that might have overlapping tool names w

```python {title="mcp_tool_prefix_http_client.py" py="3.10"}
from pydantic_ai import Agent
from pydantic_ai.mcp import MCPServerHTTP
from pydantic_ai.mcp import MCPServerSSE

# Create two servers with different prefixes
weather_server = MCPServerHTTP(
weather_server = MCPServerSSE(
url='http://localhost:3001/sse',
tool_prefix='weather' # Tools will be prefixed with 'weather_'
)

calculator_server = MCPServerHTTP(
calculator_server = MCPServerSSE(
url='http://localhost:3002/sse',
tool_prefix='calc' # Tools will be prefixed with 'calc_'
)
Expand Down
Loading