Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 15 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,27 @@ jobs:
enable-cache: true

- name: Install dependencies
run: uv sync --python 3.12 --frozen --all-extras
run: uv sync --python 3.12 --frozen --all-extras --no-dev --group lint

- uses: pre-commit/action@v3.0.0
with:
extra_args: --all-files --verbose
env:
SKIP: no-commit-to-branch

# mypy and lint are a bit slower than other jobs, so we run them separately
mypy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: astral-sh/setup-uv@v3
with:
enable-cache: true

- name: Install dependencies
run: uv sync --python 3.12 --frozen --no-dev --group lint

- run: make typecheck-mypy

docs:
Expand Down Expand Up @@ -138,7 +151,7 @@ jobs:
# https://github.com/marketplace/actions/alls-green#why used for branch protection checks
check:
if: always()
needs: [lint, docs, test-live, test, coverage]
needs: [lint, mypy, docs, test-live, test, coverage]
runs-on: ubuntu-latest

steps:
Expand Down
15 changes: 9 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

.PHONY: install # Install the package, dependencies, and pre-commit for local development
install: .uv .pre-commit
uv sync --frozen --all-extras --group docs
uv sync --frozen --all-extras --group lint --group docs
pre-commit install --install-hooks

.PHONY: format # Format the code
Expand Down Expand Up @@ -67,12 +67,15 @@ docs:
docs-serve:
uv run mkdocs serve --no-strict

# install insiders packages for docs (to avoid running this on every build, `touch .docs-insiders-install`)
.PHONY: .docs-insiders-install # install insiders packages for docs if necessary
.docs-insiders-install:
@echo 'installing insiders packages'
@uv pip install -U \
--extra-index-url https://pydantic:${PPPR_TOKEN}@pppr.pydantic.dev/simple/ \
mkdocs-material mkdocstrings-python
ifeq ($(shell uv pip show mkdocs-material | grep -q insiders && echo 'installed'), installed)
@echo 'insiders packages already installed'
else
@echo 'installing insiders packages...'
@uv pip install -U mkdocs-material mkdocstrings-python \
--extra-index-url https://pydantic:${PPPR_TOKEN}@pppr.pydantic.dev/simple/
endif

.PHONY: docs-insiders # Build the documentation using insiders packages
docs-insiders: .docs-insiders-install
Expand Down
5 changes: 0 additions & 5 deletions docs/api/agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,3 @@
- retriever_plain
- retriever_context
- result_validator

::: pydantic_ai.agent
options:
members:
- KnownModelName
1 change: 1 addition & 0 deletions docs/api/models/base.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
::: pydantic_ai.models
options:
members:
- KnownModelName
- Model
- AgentModel
- AbstractToolDefinition
Expand Down
File renamed without changes.
25 changes: 14 additions & 11 deletions docs/concepts/dependencies.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Dependencies

PydanticAI uses a dependency injection system to provide data and services to your agent's [system prompts](system-prompt.md), [retrievers](retrievers.md) and [result validators](result-validation.md#TODO).
PydanticAI uses a dependency injection system to provide data and services to your agent's [system prompts](system-prompt.md), [retrievers](retrievers.md) and [result validators](results.md#TODO).

Matching PydanticAI's design philosophy, our dependency system tries to use existing best practice in Python development rather than inventing esoteric "magic", this should make dependencies type-safe, understandable easier to test and ultimately easier to deploy in production.

Expand All @@ -13,7 +13,7 @@ Here's an example of defining an agent that requires dependencies.

(**Note:** dependencies aren't actually used in this example, see [Accessing Dependencies](#accessing-dependencies) below)

```python title="unused_dependencies.py"
```py title="unused_dependencies.py"
from dataclasses import dataclass

import httpx
Expand Down Expand Up @@ -41,6 +41,7 @@ async def main():
deps=deps, # (3)!
)
print(result.data)
#> Did you hear about the toothpaste scandal? They called it Colgate.
```

1. Define a dataclass to hold dependencies.
Expand Down Expand Up @@ -78,7 +79,7 @@ agent = Agent(
async def get_system_prompt(ctx: CallContext[MyDeps]) -> str: # (2)!
response = await ctx.deps.http_client.get( # (3)!
'https://example.com',
headers={'Authorization': f'Bearer {ctx.deps.api_key}'} # (4)!
headers={'Authorization': f'Bearer {ctx.deps.api_key}'}, # (4)!
)
response.raise_for_status()
return f'Prompt: {response.text}'
Expand All @@ -89,6 +90,7 @@ async def main():
deps = MyDeps('foobar', client)
result = await agent.run('Tell me a joke.', deps=deps)
print(result.data)
#> Did you hear about the toothpaste scandal? They called it Colgate.
```

1. [`CallContext`][pydantic_ai.dependencies.CallContext] may optionally be passed to a [`system_prompt`][pydantic_ai.Agent.system_prompt] function as the only argument.
Expand Down Expand Up @@ -134,8 +136,7 @@ agent = Agent(
@agent.system_prompt
def get_system_prompt(ctx: CallContext[MyDeps]) -> str: # (2)!
response = ctx.deps.http_client.get(
'https://example.com',
headers={'Authorization': f'Bearer {ctx.deps.api_key}'}
'https://example.com', headers={'Authorization': f'Bearer {ctx.deps.api_key}'}
)
response.raise_for_status()
return f'Prompt: {response.text}'
Expand All @@ -148,6 +149,7 @@ async def main():
deps=deps,
)
print(result.data)
#> Did you hear about the toothpaste scandal? They called it Colgate.
```

1. Here we use a synchronous `httpx.Client` instead of an asynchronous `httpx.AsyncClient`.
Expand All @@ -157,7 +159,7 @@ _(This example is complete, it can be run "as is")_

## Full Example

As well as system prompts, dependencies can be used in [retrievers](retrievers.md) and [result validators](result-validation.md#TODO).
As well as system prompts, dependencies can be used in [retrievers](retrievers.md) and [result validators](results.md#TODO).

```python title="full_example.py" hl_lines="27-35 38-48"
from dataclasses import dataclass
Expand Down Expand Up @@ -215,6 +217,7 @@ async def main():
deps = MyDeps('foobar', client)
result = await agent.run('Tell me a joke.', deps=deps)
print(result.data)
#> Did you hear about the toothpaste scandal? They called it Colgate.
```

1. To pass `CallContext` and to a retriever, us the [`retriever_context`][pydantic_ai.Agent.retriever_context] decorator.
Expand Down Expand Up @@ -264,7 +267,6 @@ async def application_code(prompt: str) -> str: # (3)!
app_deps = MyDeps('foobar', client)
result = await joke_agent.run(prompt, deps=app_deps) # (4)!
return result.data

```

1. Define a method on the dependency to make the system prompt easier to customise.
Expand All @@ -273,7 +275,7 @@ async def application_code(prompt: str) -> str: # (3)!
4. Call the agent from within the application code, in a real application this call might be deep within a call stack. Note `app_deps` here will NOT be used when deps are overridden.

```py title="test_joke_app.py" hl_lines="10-12"
from joke_app import application_code, joke_agent, MyDeps
from joke_app import MyDeps, application_code, joke_agent


class TestMyDeps(MyDeps): # (1)!
Expand All @@ -284,8 +286,8 @@ class TestMyDeps(MyDeps): # (1)!
async def test_application_code():
test_deps = TestMyDeps('test_key', None) # (2)!
with joke_agent.override_deps(test_deps): # (3)!
joke = application_code('Tell me a joke.') # (4)!
assert joke == 'funny'
joke = await application_code('Tell me a joke.') # (4)!
assert joke.startswith('Did you hear about the toothpaste scandal?')
```

1. Define a subclass of `MyDeps` in tests to customise the system prompt factory.
Expand Down Expand Up @@ -314,7 +316,7 @@ joke_agent = Agent(
system_prompt=(
'Use the "joke_factory" to generate some jokes, then choose the best. '
'You must return just a single joke.'
)
),
)

factory_agent = Agent('gemini-1.5-pro', result_type=list[str])
Expand All @@ -328,6 +330,7 @@ async def joke_factory(ctx: CallContext[MyDeps], count: int) -> str:

result = joke_agent.run_sync('Tell me a joke.', deps=MyDeps(factory_agent))
print(result.data)
#> Did you hear about the toothpaste scandal? They called it Colgate.
```

## Examples
Expand Down
Loading
Loading