From afb0190eb9bf6be76d85ebdd981c18a99d86ea22 Mon Sep 17 00:00:00 2001 From: Samuel Colvin Date: Tue, 26 Nov 2024 13:20:14 +0000 Subject: [PATCH 1/3] fix broken links and add type checking function --- docs/agents.md | 62 +++++++++++++++++++++++++-- docs/dependencies.md | 4 +- docs/examples/bank-support.md | 2 +- docs/examples/rag.md | 2 +- docs/examples/weather-agent.md | 2 +- docs/index.md | 6 +-- mkdocs.yml | 1 + pydantic_ai_slim/pydantic_ai/agent.py | 4 +- 8 files changed, 69 insertions(+), 14 deletions(-) diff --git a/docs/agents.md b/docs/agents.md index b77ddbf816..6604dd0684 100644 --- a/docs/agents.md +++ b/docs/agents.md @@ -8,12 +8,12 @@ but multiple agents can also interact to embody more complex workflows. The [`Agent`][pydantic_ai.Agent] class has full API documentation, but conceptually you can think of an agent as a container for: * A [system prompt](#system-prompts) — a set of instructions for the LLM written by the developer -* One or more [retrieval tool](#tools) — functions that the LLM may call to get information while generating a response +* One or more [retrieval tool](#function-tools) — functions that the LLM may call to get information while generating a response * An optional structured [result type](results.md) — the structured datatype the LLM must return at the end of a run * A [dependency](dependencies.md) type constraint — system prompt functions, tools and result validators may all use dependencies when they're run * Agents may optionally also have a default [LLM model](api/models/base.md) associated with them; the model to use can also be specified when running the agent -In typing terms, agents are generic in their dependency and result types, e.g., an agent which required dependencies of type `#!python Foobar` and returned results of type `#!python list[str]` would have type `#!python Agent[Foobar, list[str]]`. +In typing terms, agents are generic in their dependency and result types, e.g., an agent which required dependencies of type `#!python Foobar` and returned results of type `#!python list[str]` would have type `cAgent[Foobar, list[str]]`. In practice, you shouldn't need to care about this, it should just mean your IDE can tell you when you have the right type, and if you choose to use [static type checking](#static-type-checking) it should work well with PydanticAI. Here's a toy example of an agent that simulates a roulette wheel: @@ -394,9 +394,9 @@ If a tool has a single parameter that can be represented as an object in JSON sc ## Reflection and self-correction -Validation errors from both tool parameter validation and [structured result validation](results.md#structured-result-validation) can be passed back to the model with a request to retry. +Validation errors from both function tool parameter validation and [structured result validation](results.md#structured-result-validation) can be passed back to the model with a request to retry. -You can also raise [`ModelRetry`][pydantic_ai.exceptions.ModelRetry] from within a [tool](#tools) or [result validator function](results.md#result-validators-functions) to tell the model it should retry generating a response. +You can also raise [`ModelRetry`][pydantic_ai.exceptions.ModelRetry] from within a [tool](#function-tools) or [result validator function](results.md#result-validators-functions) to tell the model it should retry generating a response. - The default retry count is **1** but can be altered for the [entire agent][pydantic_ai.Agent.__init__], a [specific tool][pydantic_ai.Agent.tool], or a [result validator][pydantic_ai.Agent.__init__]. - You can access the current retry count from within a tool or result validator via [`ctx.retry`][pydantic_ai.dependencies.CallContext]. @@ -518,3 +518,57 @@ else: 1. Define a tool that will raise `ModelRetry` repeatedly in this case. _(This example is complete, it can be run "as is")_ + +## Static Type Checking + +PydanticAI is designed to work well with static type checkers, like [pyright](https://github.com/microsoft/pyright) +and [mypy](https://github.com/python/mypy). + +In particular, agents are generic in both the type of their dependencies and the type of results they return, so you can use the type hints to ensure you're using the right types. + +Consider the following script with type mistakes: + +```py title="type_mistakes.py" +from dataclasses import dataclass + +from pydantic_ai import Agent, CallContext + + +@dataclass +class User: + name: str + + +agent = Agent( + deps_type=User, # (1)! + result_type=bool, +) + + +@agent.system_prompt +def add_user_name(ctx: CallContext[str]) -> str: # (2)! + return f"The user's name is {ctx.deps}." + + +def foobar(x: bytes) -> None: + pass + + +result = agent.run_sync('Does their name start with "A"?', deps=User('Adam')) +foobar(result.data) # (3)! +``` + +1. The agent is defined as expecting an instance of `User` as `deps`. +2. But here `add_user_name` is defined as taking a `str` as the dependency, not a `User`. +3. Since the agent is defined as returning a `bool`, this will raise a type error since `foobar` expects `bytes`. + +Running `mypy` on this will give the following output: + +```bash +➤ uv run mypy type_mistakes.py +type_mistakes.py:17: error: Argument 1 to "system_prompt" of "Agent" has incompatible type "Callable[[CallContext[str]], str]"; expected "Callable[[CallContext[User]], str]" [arg-type] +type_mistakes.py:27: error: Argument 1 to "foobar" has incompatible type "bool"; expected "bytes" [arg-type] +Found 2 errors in 1 file (checked 1 source file) +``` + +Running `pyright` would identify the same issues. diff --git a/docs/dependencies.md b/docs/dependencies.md index 74ddc1a013..81c067fc59 100644 --- a/docs/dependencies.md +++ b/docs/dependencies.md @@ -1,6 +1,6 @@ # Dependencies -PydanticAI uses a dependency injection system to provide data and services to your agent's [system prompts](agents.md#system-prompts), [tools](agents.md#tools) and [result validators](results.md#result-validators-functions). +PydanticAI uses a dependency injection system to provide data and services to your agent's [system prompts](agents.md#system-prompts), [tools](agents.md#function-tools) and [result validators](results.md#result-validators-functions). 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. @@ -158,7 +158,7 @@ _(This example is complete, it can be run "as is")_ ## Full Example -As well as system prompts, dependencies can be used in [tools](agents.md#tools) and [result validators](results.md#result-validators-functions). +As well as system prompts, dependencies can be used in [tools](agents.md#function-tools) and [result validators](results.md#result-validators-functions). ```py title="full_example.py" hl_lines="27-35 38-48" from dataclasses import dataclass diff --git a/docs/examples/bank-support.md b/docs/examples/bank-support.md index 0abddc5d14..191f5e851c 100644 --- a/docs/examples/bank-support.md +++ b/docs/examples/bank-support.md @@ -4,7 +4,7 @@ Demonstrates: * [dynamic system prompt](../agents.md#system-prompts) * [structured `result_type`](../results.md#structured-result-validation) -* [tools](../agents.md#tools) +* [tools](../agents.md#function-tools) ## Running the Example diff --git a/docs/examples/rag.md b/docs/examples/rag.md index d7dee103d9..a3db240ee1 100644 --- a/docs/examples/rag.md +++ b/docs/examples/rag.md @@ -4,7 +4,7 @@ RAG search example. This demo allows you to ask question of the [logfire](https: Demonstrates: -* [tools](../agents.md#tools) +* [tools](../agents.md#function-tools) * [agent dependencies](../dependencies.md) * RAG search diff --git a/docs/examples/weather-agent.md b/docs/examples/weather-agent.md index c770431134..beebedb645 100644 --- a/docs/examples/weather-agent.md +++ b/docs/examples/weather-agent.md @@ -2,7 +2,7 @@ Example of PydanticAI with multiple tools which the LLM needs to call in turn to Demonstrates: -* [tools](../agents.md#tools) +* [tools](../agents.md#function-tools) * [agent dependencies](../dependencies.md) In this case the idea is a "weather" agent — the user can ask for the weather in multiple locations, diff --git a/docs/index.md b/docs/index.md index 938f372087..fe17f49230 100644 --- a/docs/index.md +++ b/docs/index.md @@ -124,11 +124,11 @@ async def main(): 1. This [agent](agents.md) will act as first-tier support in a bank. Agents are generic in the type of dependencies they accept and the type of result they return. In this case, the support agent has type `#!python Agent[SupportDependencies, SupportResult]`. 2. Here we configure the agent to use [OpenAI's GPT-4o model](api/models/openai.md), you can also set the model when running the agent. -3. The `SupportDependencies` dataclass is used to pass data, connections, and logic into the model that will be needed when running [system prompt](agents.md#system-prompts) and [tool](agents.md#tools) functions. PydanticAI's system of dependency injection provides a type-safe way to customise the behavior of your agents, and can be especially useful when running unit tests and evals. +3. The `SupportDependencies` dataclass is used to pass data, connections, and logic into the model that will be needed when running [system prompt](agents.md#system-prompts) and [tool](agents.md#function-tools) functions. PydanticAI's system of dependency injection provides a type-safe way to customise the behavior of your agents, and can be especially useful when running unit tests and evals. 4. Static [system prompts](agents.md#system-prompts) can be registered with the [`system_prompt` keyword argument][pydantic_ai.Agent.__init__] to the agent. 5. Dynamic [system prompts](agents.md#system-prompts) can be registered with the [`@agent.system_prompt`][pydantic_ai.Agent.system_prompt] decorator, and can make use of dependency injection. Dependencies are carried via the [`CallContext`][pydantic_ai.dependencies.CallContext] argument, which is parameterized with the `deps_type` from above. If the type annotation here is wrong, static type checkers will catch it. -6. [Tools](agents.md#tools) let you register "tools" which the LLM may call while responding to a user. Again, dependencies are carried via [`CallContext`][pydantic_ai.dependencies.CallContext], and any other arguments become the tool schema passed to the LLM. Pydantic is used to validate these arguments, and errors are passed back to the LLM so it can retry. -7. The docstring of a tool is also passed to the LLM as the description of the tool. Parameter descriptions are [extracted](agents.md#tools-tools-and-schema) from the docstring and added to the tool schema sent to the LLM. +6. [Tools](agents.md#function-tools) let you register "tools" which the LLM may call while responding to a user. Again, dependencies are carried via [`CallContext`][pydantic_ai.dependencies.CallContext], and any other arguments become the tool schema passed to the LLM. Pydantic is used to validate these arguments, and errors are passed back to the LLM so it can retry. +7. The docstring of a tool is also passed to the LLM as the description of the tool. Parameter descriptions are [extracted](agents.md#function-tools-and-schema) from the docstring and added to the tool schema sent to the LLM. 8. [Run the agent](agents.md#running-agents) asynchronously, conducting a conversation with the LLM until a final response is reached. Even in this fairly simple case, the agent will exchange multiple messages with the LLM as tools are called to retrieve a result. 9. The response from the agent will, be guaranteed to be a `SupportResult`, if validation fails [reflection](agents.md#reflection-and-self-correction) will mean the agent is prompted to try again. 10. The result will be validated with Pydantic to guarantee it is a `SupportResult`, since the agent is generic, it'll also be typed as a `SupportResult` to aid with static type checking. diff --git a/mkdocs.yml b/mkdocs.yml index 295d6e0638..c4ebc14add 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -93,6 +93,7 @@ validation: omitted_files: warn absolute_links: warn unrecognized_links: warn + anchors: warn extra_css: - 'extra/tweaks.css' diff --git a/pydantic_ai_slim/pydantic_ai/agent.py b/pydantic_ai_slim/pydantic_ai/agent.py index 5a40cd2cf5..fbb35bcfa6 100644 --- a/pydantic_ai_slim/pydantic_ai/agent.py +++ b/pydantic_ai_slim/pydantic_ai/agent.py @@ -448,7 +448,7 @@ def tool( Can decorate a sync or async functions. The docstring is inspected to extract both the tool description and description of each parameter, - [learn more](../agents.md#tools-tools-and-schema). + [learn more](../agents.md#function-tools-and-schema). We can't add overloads for every possible signature of tool, since the return type is a recursive union so the signature of functions decorated with `@agent.tool` is obscured. @@ -506,7 +506,7 @@ def tool_plain(self, func: ToolPlainFunc[ToolParams] | None = None, /, *, retrie Can decorate a sync or async functions. The docstring is inspected to extract both the tool description and description of each parameter, - [learn more](../agents.md#tools-tools-and-schema). + [learn more](../agents.md#function-tools-and-schema). We can't add overloads for every possible signature of tool, since the return type is a recursive union so the signature of functions decorated with `@agent.tool` is obscured. From d29a634e2a8439f9bb5f0527fabf25f77bf94700 Mon Sep 17 00:00:00 2001 From: Samuel Colvin Date: Tue, 26 Nov 2024 13:29:31 +0000 Subject: [PATCH 2/3] add note about pyright vs mypy --- docs/agents.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/agents.md b/docs/agents.md index 6604dd0684..36826a2706 100644 --- a/docs/agents.md +++ b/docs/agents.md @@ -521,14 +521,20 @@ _(This example is complete, it can be run "as is")_ ## Static Type Checking -PydanticAI is designed to work well with static type checkers, like [pyright](https://github.com/microsoft/pyright) -and [mypy](https://github.com/python/mypy). +PydanticAI is designed to work well with static type checkers, like mypy and pyright. + +!!! tip "mypy vs. pyright" + [mypy](https://github.com/python/mypy) and [pyright](https://github.com/microsoft/pyright) are both static type checkers for Python. + + Mypy was the first and is still generally considered the default, in part because it was developed parly by Guido van Rossum, the creator of Python. + + Pyright is generally faster and more sophisticated. It is develoepd by Eric Trout for use in VSCode, since that's its primary use case, it's terminal output is more verbose and harder to read than that of mypy. In particular, agents are generic in both the type of their dependencies and the type of results they return, so you can use the type hints to ensure you're using the right types. Consider the following script with type mistakes: -```py title="type_mistakes.py" +```py title="type_mistakes.py" hl_lines="17 27" from dataclasses import dataclass from pydantic_ai import Agent, CallContext From 6d3da9b159a79c9aa89dbb9bca2ab597b6071678 Mon Sep 17 00:00:00 2001 From: Samuel Colvin Date: Tue, 26 Nov 2024 14:10:33 +0000 Subject: [PATCH 3/3] fix tests --- docs/agents.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/agents.md b/docs/agents.md index 36826a2706..f92191b528 100644 --- a/docs/agents.md +++ b/docs/agents.md @@ -534,7 +534,7 @@ In particular, agents are generic in both the type of their dependencies and the Consider the following script with type mistakes: -```py title="type_mistakes.py" hl_lines="17 27" +```py title="type_mistakes.py" hl_lines="18 28" from dataclasses import dataclass from pydantic_ai import Agent, CallContext @@ -546,6 +546,7 @@ class User: agent = Agent( + 'test', deps_type=User, # (1)! result_type=bool, ) @@ -572,8 +573,8 @@ Running `mypy` on this will give the following output: ```bash ➤ uv run mypy type_mistakes.py -type_mistakes.py:17: error: Argument 1 to "system_prompt" of "Agent" has incompatible type "Callable[[CallContext[str]], str]"; expected "Callable[[CallContext[User]], str]" [arg-type] -type_mistakes.py:27: error: Argument 1 to "foobar" has incompatible type "bool"; expected "bytes" [arg-type] +type_mistakes.py:18: error: Argument 1 to "system_prompt" of "Agent" has incompatible type "Callable[[CallContext[str]], str]"; expected "Callable[[CallContext[User]], str]" [arg-type] +type_mistakes.py:28: error: Argument 1 to "foobar" has incompatible type "bool"; expected "bytes" [arg-type] Found 2 errors in 1 file (checked 1 source file) ```