Skip to content

Commit 6b9feb6

Browse files
committed
Merge main
2 parents 3ce4de1 + 2f2c757 commit 6b9feb6

File tree

220 files changed

+23260
-6034
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

220 files changed

+23260
-6034
lines changed

.claude/settings.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Bash(rg:*)",
5+
"Bash(make:*)",
6+
"Bash(uv run pytest:*)",
7+
"Bash(uv run:*)",
8+
"Bash(git push:*)",
9+
"Bash(git checkout:*)",
10+
"Bash(gh pr create:*)",
11+
"Bash(gh pr view:*)",
12+
"Bash(git add:*)",
13+
"Bash(git commit:*)",
14+
"Bash(gh pr comment:*)",
15+
"Bash(gh pr checks:*)",
16+
"Bash(gh run view:*)",
17+
"Bash(gh pr list:*)",
18+
"Bash(gh api:*)",
19+
"Bash(pre-commit run:*)",
20+
"Bash(grep:*)",
21+
"Bash(find:*)",
22+
"Bash(coverage:*)"
23+
],
24+
"deny": []
25+
}
26+
}

.github/ISSUE_TEMPLATE/config.yaml renamed to .github/ISSUE_TEMPLATE/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
blank_issues_enabled: true
1+
blank_issues_enabled: false
22
contact_links:
33
- name: 💬 Join Slack
44
url: 'https://logfire.pydantic.dev/docs/help/#slack'

.github/workflows/ci.yml

Lines changed: 77 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ jobs:
107107
with:
108108
model: qwen2:0.5b
109109

110+
- run: uv sync --only-dev
110111
- run: >
111112
uv run
112113
--package pydantic-ai-slim
@@ -130,13 +131,20 @@ jobs:
130131
CO_API_KEY: ${{ secrets.COHERE_API_KEY }}
131132
132133
test:
133-
name: test on ${{ matrix.python-version }}
134+
name: test on ${{ matrix.python-version }} (${{ matrix.install.name }})
134135
runs-on: ubuntu-latest
135136
timeout-minutes: 10
136137
strategy:
137138
fail-fast: false
138139
matrix:
139140
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
141+
install:
142+
- name: pydantic-ai-slim
143+
command: "--package pydantic-ai-slim"
144+
- name: standard
145+
command: ""
146+
- name: all-extras
147+
command: "--all-extras"
140148
env:
141149
UV_PYTHON: ${{ matrix.python-version }}
142150
CI: true
@@ -154,40 +162,86 @@ jobs:
154162

155163
- run: mkdir .coverage
156164

157-
# run tests with just `pydantic-ai-slim` dependencies
158-
- run: uv run --package pydantic-ai-slim coverage run -m pytest -n auto --dist=loadgroup
165+
- run: uv sync --only-dev
166+
- run: uv run ${{ matrix.install.command }} coverage run -m pytest --durations=100 -n auto --dist=loadgroup
159167
env:
160-
COVERAGE_FILE: .coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }}-slim
168+
COVERAGE_FILE: .coverage/.coverage.${{ matrix.python-version }}-${{ matrix.install.name }}
161169

162-
- run: uv run coverage run -m pytest -n auto --dist=loadgroup
163-
env:
164-
COVERAGE_FILE: .coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }}-standard
170+
- name: store coverage files
171+
uses: actions/upload-artifact@v4
172+
if: matrix.python-version != '3.9'
173+
with:
174+
name: coverage-${{ matrix.python-version }}-${{ matrix.install.name }}
175+
path: .coverage
176+
include-hidden-files: true
165177

166-
- run: uv run --all-extras coverage run -m pytest -n auto --dist=loadgroup
167-
env:
168-
COVERAGE_FILE: .coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }}-all-extras
178+
test-lowest-versions:
179+
name: test on ${{ matrix.python-version }} (lowest-versions)
180+
runs-on: ubuntu-latest
181+
timeout-minutes: 10
182+
strategy:
183+
fail-fast: false
184+
matrix:
185+
python-version: ["3.10", "3.11", "3.12", "3.13"]
186+
env:
187+
UV_PYTHON: ${{ matrix.python-version }}
188+
CI: true
189+
COVERAGE_PROCESS_START: ./pyproject.toml
190+
steps:
191+
- uses: actions/checkout@v4
169192

170-
- run: uv run --all-extras python tests/import_examples.py
193+
- uses: astral-sh/setup-uv@v5
194+
with:
195+
enable-cache: true
171196

172-
# this must run last as it modifies the environment!
173-
- name: test lowest versions
174-
if: matrix.python-version != '3.9'
175-
run: |
176-
unset UV_FROZEN
177-
uv run --all-extras --resolution lowest-direct coverage run -m pytest -n auto --dist=loadgroup
197+
- uses: denoland/setup-deno@v2
198+
with:
199+
deno-version: v2.x
200+
201+
- run: mkdir .coverage
202+
203+
- run: uv sync --group dev
204+
205+
- run: unset UV_FROZEN
206+
207+
- run: uv run --all-extras --resolution lowest-direct coverage run -m pytest --durations=100 -n auto --dist=loadgroup
178208
env:
179-
COVERAGE_FILE: .coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }}-lowest-versions
209+
COVERAGE_FILE: .coverage/.coverage.${{matrix.python-version}}-lowest-versions
180210

181211
- name: store coverage files
182212
uses: actions/upload-artifact@v4
183213
with:
184-
name: coverage-${{ matrix.python-version }}
214+
name: coverage-${{ matrix.python-version }}-lowest-versions
185215
path: .coverage
186216
include-hidden-files: true
187217

218+
test-examples:
219+
name: test examples on ${{ matrix.python-version }}
220+
runs-on: ubuntu-latest
221+
timeout-minutes: 10
222+
strategy:
223+
fail-fast: false
224+
matrix:
225+
python-version: ["3.11", "3.12", "3.13"]
226+
env:
227+
UV_PYTHON: ${{ matrix.python-version }}
228+
CI: true
229+
steps:
230+
- uses: actions/checkout@v4
231+
232+
- uses: astral-sh/setup-uv@v5
233+
with:
234+
enable-cache: true
235+
236+
- uses: denoland/setup-deno@v2
237+
with:
238+
deno-version: v2.x
239+
240+
- run: uv run --all-extras python tests/import_examples.py
241+
188242
coverage:
189243
runs-on: ubuntu-latest
190-
needs: [test]
244+
needs: [test, test-lowest-versions]
191245
steps:
192246
- uses: actions/checkout@v4
193247
with:
@@ -204,31 +258,9 @@ jobs:
204258
with:
205259
enable-cache: true
206260

207-
- run: uv sync --package pydantic-ai-slim --only-dev
208-
- run: rm .coverage/.coverage.*-py3.9-* # Exclude 3.9 coverage as it gets the wrong line numbers, causing invalid failures.
261+
- run: uv sync --group dev
209262
- run: uv run coverage combine
210-
211-
- run: uv run coverage html --show-contexts --title "Pydantic AI coverage for ${{ github.sha }}"
212-
213-
- name: Store coverage html
214-
uses: actions/upload-artifact@v4
215-
with:
216-
name: coverage-html
217-
path: htmlcov
218-
include-hidden-files: true
219-
220-
- run: uv run coverage xml
221-
222-
- run: uv run diff-cover coverage.xml --html-report index.html
223-
224-
- name: Store diff coverage html
225-
uses: actions/upload-artifact@v4
226-
with:
227-
name: diff-coverage-html
228-
path: index.html
229-
230-
- run: uv run coverage report --fail-under 100
231-
- run: uv run diff-cover coverage.xml --fail-under 100
263+
- run: uv run coverage report
232264

233265
- run: uv run strict-no-cover
234266
env:
@@ -260,7 +292,7 @@ jobs:
260292
# https://github.com/marketplace/actions/alls-green#why used for branch protection checks
261293
check:
262294
if: always()
263-
needs: [lint, mypy, docs, test-live, test, coverage, test-mcp-run-python]
295+
needs: [lint, mypy, docs, test-live, test, test-lowest-versions, test-examples, coverage, test-mcp-run-python]
264296
runs-on: ubuntu-latest
265297

266298
steps:

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ node_modules/
1919
**.idea/
2020
.coverage*
2121
/test_tmp/
22+
.mcp.json

.hyperlint/config.yaml

Lines changed: 0 additions & 3 deletions
This file was deleted.

CLAUDE.md

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,23 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
55
## Development Commands
66

77
### Core Development Tasks
8+
89
- **Install dependencies**: `make install` (requires uv, pre-commit, and deno)
9-
- **Run all checks**: `make` (format, lint, typecheck, test with coverage)
10-
- **Format code**: `make format`
11-
- **Lint code**: `make lint`
12-
- **Type checking**: `make typecheck` (uses pyright) or `make typecheck-both` (pyright + mypy)
13-
- **Run tests**: `make test` (with coverage)
10+
- **Run all checks**: `pre-commit run --all-files`
11+
- **Run tests**: `make test`
1412
- **Build docs**: `make docs` or `make docs-serve` (local development)
1513

1614
### Single Test Commands
15+
1716
- **Run specific test**: `uv run pytest tests/test_agent.py::test_function_name -v`
1817
- **Run test file**: `uv run pytest tests/test_agent.py -v`
1918
- **Run with debug**: `uv run pytest tests/test_agent.py -v -s`
2019

21-
### Multi-Python Testing
22-
- **Install all Python versions**: `make install-all-python`
23-
- **Test all Python versions**: `make test-all-python`
24-
2520
## Project Architecture
2621

2722
### Core Components
2823

29-
**Agent System (`pydantic_ai_slim/pydantic_ai/agent.py`)**
24+
**Agent System (`pydantic_ai_slim/pydantic_ai/agent/`)**
3025
- `Agent[AgentDepsT, OutputDataT]`: Main orchestrator class with generic types for dependency injection and output validation
3126
- Entry points: `run()`, `run_sync()`, `run_stream()` methods
3227
- Handles tool management, system prompts, and model interaction
@@ -125,3 +120,43 @@ This is a uv workspace with multiple packages:
125120
- **Lock file**: `uv.lock` (commit this file)
126121
- **Sync command**: `make sync` to update dependencies
127122
- **Optional extras**: Define groups in `pyproject.toml` optional-dependencies
123+
124+
## Best Practices
125+
126+
This is the list of best practices for working with the codebase.
127+
128+
### Rename a class
129+
130+
When asked to rename a class, you need to rename the class in the code and add a deprecation warning to the old class.
131+
132+
```python
133+
from typing_extensions import deprecated
134+
135+
class NewClass: ... # This class was renamed from OldClass.
136+
137+
@deprecated("Use `NewClass` instead.")
138+
class OldClass(NewClass): ...
139+
```
140+
141+
In the test suite, you MUST use the `NewClass` instead of the `OldClass`, and create a new test to verify the
142+
deprecation warning:
143+
144+
```python
145+
def test_old_class_is_deprecated():
146+
with pytest.warns(DeprecationWarning, match="Use `NewClass` instead."):
147+
OldClass()
148+
```
149+
150+
In the documentation, you should not have references to the old class, only the new class.
151+
152+
### Writing documentation
153+
154+
Always reference Python objects with the "`" (backticks) around them, and link to the API reference, for example:
155+
156+
```markdown
157+
The [`Agent`][pydantic_ai.agent.Agent] class is the main entry point for creating and running agents.
158+
```
159+
160+
### Coverage
161+
162+
Every pull request MUST have 100% coverage. You can check the coverage by running `make test`.

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ typecheck-both: typecheck-pyright typecheck-mypy
6060

6161
.PHONY: test
6262
test: ## Run tests and collect coverage data
63-
COVERAGE_PROCESS_START=./pyproject.toml uv run coverage run -m pytest -n auto --dist=loadgroup
63+
uv run coverage run -m pytest -n auto --dist=loadgroup
6464
@uv run coverage combine
6565
@uv run coverage report
6666

clai/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ Either way, running `clai` will start an interactive session where you can chat
4949
- `/exit`: Exit the session
5050
- `/markdown`: Show the last response in markdown format
5151
- `/multiline`: Toggle multiline input mode (use Ctrl+D to submit)
52+
- `/cp`: Copy the last response to clipboard
5253

5354
## Help
5455

@@ -61,6 +62,7 @@ Special prompts:
6162
* `/exit` - exit the interactive mode (ctrl-c and ctrl-d also work)
6263
* `/markdown` - show the last markdown output of the last question
6364
* `/multiline` - toggle multiline mode
65+
* `/cp` - copy the last response to clipboard
6466
6567
positional arguments:
6668
prompt AI Prompt, if omitted fall into interactive mode

docs/ag-ui.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ There are three ways to run a Pydantic AI agent based on AG-UI run input with st
3535

3636
1. [`run_ag_ui()`][pydantic_ai.ag_ui.run_ag_ui] takes an agent and an AG-UI [`RunAgentInput`](https://docs.ag-ui.com/sdk/python/core/types#runagentinput) object, and returns a stream of AG-UI events encoded as strings. It also takes optional [`Agent.iter()`][pydantic_ai.Agent.iter] arguments including `deps`. Use this if you're using a web framework not based on Starlette (e.g. Django or Flask) or want to modify the input or output some way.
3737
2. [`handle_ag_ui_request()`][pydantic_ai.ag_ui.handle_ag_ui_request] takes an agent and a Starlette request (e.g. from FastAPI) coming from an AG-UI frontend, and returns a streaming Starlette response of AG-UI events that you can return directly from your endpoint. It also takes optional [`Agent.iter()`][pydantic_ai.Agent.iter] arguments including `deps`, that you can vary for each request (e.g. based on the authenticated user).
38-
3. [`Agent.to_ag_ui()`][pydantic_ai.Agent.to_ag_ui] returns an ASGI application that handles every AG-UI request by running the agent. It also takes optional [`Agent.iter()`][pydantic_ai.Agent.iter] arguments including `deps`, but these will be the same for each request, with the exception of the AG-UI state that's injected as described under [state management](#state-management). This ASGI app can be [mounted](https://fastapi.tiangolo.com/advanced/sub-applications/) at a given path in an existing FastAPI app.
38+
3. [`Agent.to_ag_ui()`][pydantic_ai.agent.AbstractAgent.to_ag_ui] returns an ASGI application that handles every AG-UI request by running the agent. It also takes optional [`Agent.iter()`][pydantic_ai.Agent.iter] arguments including `deps`, but these will be the same for each request, with the exception of the AG-UI state that's injected as described under [state management](#state-management). This ASGI app can be [mounted](https://fastapi.tiangolo.com/advanced/sub-applications/) at a given path in an existing FastAPI app.
3939

4040
### Handle run input and output directly
4141

@@ -117,7 +117,7 @@ This will expose the agent as an AG-UI server, and your frontend can start sendi
117117

118118
### Stand-alone ASGI app
119119

120-
This example uses [`Agent.to_ag_ui()`][pydantic_ai.Agent.to_ag_ui] to turn the agent into a stand-alone ASGI application:
120+
This example uses [`Agent.to_ag_ui()`][pydantic_ai.agent.AbstractAgent.to_ag_ui] to turn the agent into a stand-alone ASGI application:
121121

122122
```py {title="agent_to_ag_ui.py" py="3.10" hl_lines="4"}
123123
from pydantic_ai import Agent
@@ -265,7 +265,7 @@ uvicorn ag_ui_tool_events:app --host 0.0.0.0 --port 9000
265265

266266
## Examples
267267

268-
For more examples of how to use [`to_ag_ui()`][pydantic_ai.Agent.to_ag_ui] see
268+
For more examples of how to use [`to_ag_ui()`][pydantic_ai.agent.AbstractAgent.to_ag_ui] see
269269
[`pydantic_ai_examples.ag_ui`](https://github.com/pydantic/pydantic-ai/tree/main/examples/pydantic_ai_examples/ag_ui),
270270
which includes a server for use with the
271271
[AG-UI Dojo](https://docs.ag-ui.com/tutorials/debugging#the-ag-ui-dojo).

0 commit comments

Comments
 (0)