diff --git a/docs/api/messages.md b/docs/api/messages.md index 3cc144e3d0..9986b8504e 100644 --- a/docs/api/messages.md +++ b/docs/api/messages.md @@ -1,3 +1,17 @@ # `pydantic_ai.messages` ::: pydantic_ai.messages + options: + members: + - Message + - SystemPrompt + - UserPrompt + - ToolReturn + - RetryPrompt + - ModelAnyResponse + - ModelTextResponse + - ModelStructuredResponse + - ToolCall + - ArgsJson + - ArgsObject + - MessagesTypeAdapter diff --git a/docs/concepts/dependencies.md b/docs/concepts/dependencies.md index e69de29bb2..843ddcfeca 100644 --- a/docs/concepts/dependencies.md +++ b/docs/concepts/dependencies.md @@ -0,0 +1,339 @@ +# 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). + +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. + +## Defining Dependencies + +Dependencies can be any python type. While in simple cases you might be able to pass a single object +as a dependency (e.g. an HTTP connection), [dataclasses][] are generally a convenient container when your dependencies included multiple objects. + +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" +from dataclasses import dataclass + +import httpx + +from pydantic_ai import Agent + + +@dataclass +class MyDeps: # (1)! + api_key: str + http_client: httpx.AsyncClient + + +agent = Agent( + 'openai:gpt-4o', + deps_type=MyDeps, # (2)! +) + + +async def main(): + async with httpx.AsyncClient() as client: + deps = MyDeps('foobar', client) + result = await agent.run( + 'Tell me a joke.', + deps=deps, # (3)! + ) + print(result.data) +``` + +1. Define a dataclass to hold dependencies. +2. Pass the dataclass type to the `deps_type` argument of the [`Agent` constructor][pydantic_ai.Agent.__init__]. **Note**: we're passing the type here, NOT an instance, this parameter is not actually used at runtime, it's here so we can get full type checking of the agent. +3. When running the agent, pass an instance of the dataclass to the `deps` parameter. + +_(This example is complete, it can be run "as is" inside an async context)_ + +## Accessing Dependencies + +Dependencies are accessed through the [`CallContext`][pydantic_ai.dependencies.CallContext] type, this should be the first parameter of system prompt functions etc. + + +```python title="system_prompt_dependencies.py" hl_lines="20-27" +from dataclasses import dataclass + +import httpx + +from pydantic_ai import Agent, CallContext + + +@dataclass +class MyDeps: + api_key: str + http_client: httpx.AsyncClient + + +agent = Agent( + 'openai:gpt-4o', + deps_type=MyDeps, +) + + +@agent.system_prompt # (1)! +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)! + ) + response.raise_for_status() + return f'Prompt: {response.text}' + + +async def main(): + async with httpx.AsyncClient() as client: + deps = MyDeps('foobar', client) + result = await agent.run('Tell me a joke.', deps=deps) + print(result.data) +``` + +1. [`CallContext`][pydantic_ai.dependencies.CallContext] may optionally be passed to a [`system_prompt`][pydantic_ai.Agent.system_prompt] function as the only argument. +2. [`CallContext`][pydantic_ai.dependencies.CallContext] is parameterized with the type of the dependencies, if this type is incorrect, static type checkers will raise an error. +3. Access dependencies through the [`.deps`][pydantic_ai.dependencies.CallContext.deps] attribute. +4. Access dependencies through the [`.deps`][pydantic_ai.dependencies.CallContext.deps] attribute. + +_(This example is complete, it can be run "as is" inside an async context)_ + +### Asynchronous vs. Synchronous dependencies + +System prompt functions, retriever functions and result validator are all run in the async context of an agent run. + +If these functions are not coroutines (e.g. `async def`) they are called with +[`run_in_executor`][asyncio.loop.run_in_executor] in a thread pool, it's therefore marginally preferable +to use `async` methods where dependencies perform IO, although synchronous dependencies should work fine too. + +!!! note "`run` vs. `run_sync` and Asynchronous vs. Synchronous dependencies" + Whether you use synchronous or asynchronous dependencies, is completely independent of whether you use `run` or `run_sync` — `run_sync` is just a wrapper around `run` and agents are always run in an async context. + +Here's the same example as above, but with a synchronous dependency: + +```python title="sync_dependencies.py" +from dataclasses import dataclass + +import httpx + +from pydantic_ai import Agent, CallContext + + +@dataclass +class MyDeps: + api_key: str + http_client: httpx.Client # (1)! + + +agent = Agent( + 'openai:gpt-4o', + deps_type=MyDeps, +) + + +@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}'} + ) + response.raise_for_status() + return f'Prompt: {response.text}' + + +async def main(): + deps = MyDeps('foobar', httpx.Client()) + result = await agent.run( + 'Tell me a joke.', + deps=deps, + ) + print(result.data) +``` + +1. Here we use a synchronous `httpx.Client` instead of an asynchronous `httpx.AsyncClient`. +2. To match the synchronous dependency, the system prompt function is now a plain function, not a coroutine. + +_(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). + +```python title="full_example.py" hl_lines="27-35 38-48" +from dataclasses import dataclass + +import httpx + +from pydantic_ai import Agent, CallContext, ModelRetry + + +@dataclass +class MyDeps: + api_key: str + http_client: httpx.AsyncClient + + +agent = Agent( + 'openai:gpt-4o', + deps_type=MyDeps, +) + + +@agent.system_prompt +async def get_system_prompt(ctx: CallContext[MyDeps]) -> str: + response = await ctx.deps.http_client.get('https://example.com') + response.raise_for_status() + return f'Prompt: {response.text}' + + +@agent.retriever_context # (1)! +async def get_joke_material(ctx: CallContext[MyDeps], subject: str) -> str: + response = await ctx.deps.http_client.get( + 'https://example.com#jokes', + params={'subject': subject}, + headers={'Authorization': f'Bearer {ctx.deps.api_key}'}, + ) + response.raise_for_status() + return response.text + + +@agent.result_validator # (2)! +async def validate_result(ctx: CallContext[MyDeps], final_response: str) -> str: + response = await ctx.deps.http_client.post( + 'https://example.com#validate', + headers={'Authorization': f'Bearer {ctx.deps.api_key}'}, + params={'query': final_response}, + ) + if response.status_code == 400: + raise ModelRetry(f'invalid response: {response.text}') + response.raise_for_status() + return final_response + + +async def main(): + async with httpx.AsyncClient() as client: + deps = MyDeps('foobar', client) + result = await agent.run('Tell me a joke.', deps=deps) + print(result.data) +``` + +1. To pass `CallContext` and to a retriever, us the [`retriever_context`][pydantic_ai.Agent.retriever_context] decorator. +2. `CallContext` may optionally be passed to a [`result_validator`][pydantic_ai.Agent.result_validator] function as the first argument. + +## Overriding Dependencies + +When testing agents, it's useful to be able to customise dependencies. + +While this can sometimes be done by calling the agent directly within unit tests, we can also override dependencies +while calling application code which in turn calls the agent. + +This is done via the [`override_deps`][pydantic_ai.Agent.override_deps] method on the agent. + +```py title="joke_app.py" +from dataclasses import dataclass + +import httpx + +from pydantic_ai import Agent, CallContext + + +@dataclass +class MyDeps: + api_key: str + http_client: httpx.AsyncClient + + async def system_prompt_factory(self) -> str: # (1)! + response = await self.http_client.get('https://example.com') + response.raise_for_status() + return f'Prompt: {response.text}' + + +joke_agent = Agent('openai:gpt-4o', deps_type=MyDeps) + + +@joke_agent.system_prompt +async def get_system_prompt(ctx: CallContext[MyDeps]) -> str: + return await ctx.deps.system_prompt_factory() # (2)! + + +async def application_code(prompt: str) -> str: # (3)! + ... + ... + # now deep within application code we call our agent + async with httpx.AsyncClient() as client: + 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. +2. Call the system prompt factory from within the system prompt function. +3. Application code that calls the agent, in a real application this might be an API endpoint. +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 + + +class TestMyDeps(MyDeps): # (1)! + async def system_prompt_factory(self) -> str: + return 'test prompt' + + +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' +``` + +1. Define a subclass of `MyDeps` in tests to customise the system prompt factory. +2. Create an instance of the test dependency, we don't need to pass an `http_client` here as it's not used. +3. Override the dependencies of the agent for the duration of the `with` block, `test_deps` will be used when the agent is run. +4. Now we can safely call our application code, the agent will use the overridden dependencies. + +## Agents as dependencies of other Agents + +Since dependencies can be any python type, and agents are just python objects, agents can be dependencies of other agents. + +```py title="agents_as_dependencies.py" +from dataclasses import dataclass + +from pydantic_ai import Agent, CallContext + + +@dataclass +class MyDeps: + factory_agent: Agent[None, list[str]] + + +joke_agent = Agent( + 'openai:gpt-4o', + deps_type=MyDeps, + 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]) + + +@joke_agent.retriever_context +async def joke_factory(ctx: CallContext[MyDeps], count: int) -> str: + r = await ctx.deps.factory_agent.run(f'Please generate {count} jokes.') + return '\n'.join(r.data) + + +result = joke_agent.run_sync('Tell me a joke.', deps=MyDeps(factory_agent)) +print(result.data) +``` + +## Examples + +The following examples demonstrate how to use dependencies in PydanticAI: + +- [Weather Agent](../examples/weather-agent.md) +- [SQL Generation](../examples/sql-gen.md) +- [RAG](../examples/rag.md) diff --git a/docs/concepts/message-history.md b/docs/concepts/message-history.md index e69de29bb2..8fff79a8c9 100644 --- a/docs/concepts/message-history.md +++ b/docs/concepts/message-history.md @@ -0,0 +1,129 @@ +from pydantic_ai_examples.pydantic_model import model + +# Messages and chat history + +PydanticAI provides access to messages exchanged during an agent run. These messages can be used both to continue a coherent conversation, and to understand how an agent performed. + +## Messages types + +[API documentation for `messages`][pydantic_ai.messages] contains details of the message types and their meaning. + +### Accessing Messages from Results + +After running an agent, you can access the messages exchanged during that run from the `result` object. + +Both [`RunResult`][pydantic_ai.result.RunResult] +(returned by [`Agent.run`][pydantic_ai.Agent.run], [`Agent.run_sync`][pydantic_ai.Agent.run_sync]) +and [`StreamedRunResult`][pydantic_ai.result.StreamedRunResult] (returned by [`Agent.run_stream`][pydantic_ai.Agent.run_stream]) have the following methods: + +* [`all_messages()`][pydantic_ai.result.RunResult.all_messages]: returns all messages, including messages from prior runs and system prompts. There's also a variant that returns JSON bytes, [`all_messages_json()`][pydantic_ai.result.RunResult.all_messages_json]. +* [`new_messages()`][pydantic_ai.result.RunResult.new_messages]: returns only the messages from the current run, excluding system prompts, this is generally the data you want when you want to use the messages in further runs to continue the conversation. There's also a variant that returns JSON bytes, [`new_messages_json()`][pydantic_ai.result.RunResult.new_messages_json]. + +!!! info "StreamedRunResult and complete messages" + On [`StreamedRunResult`][pydantic_ai.result.StreamedRunResult], the messages returned from these methods will only include the final response message once the stream has finished. + + E.g. you've awaited one of the following coroutines: + + * [`StreamedRunResult.stream()`][pydantic_ai.result.StreamedRunResult.stream] + * [`StreamedRunResult.stream_text()`][pydantic_ai.result.StreamedRunResult.stream_text] + * [`StreamedRunResult.stream_structured()`][pydantic_ai.result.StreamedRunResult.stream_structured] + * [`StreamedRunResult.get_data()`][pydantic_ai.result.StreamedRunResult.get_data] + +Example of accessing methods on a [`RunResult`][pydantic_ai.result.RunResult] : + +```python title="Accessing messages from a RunResult" hl_lines="9 12" +from pydantic_ai import Agent + +agent = Agent('openai:gpt-4o', system_prompt='Be a helpful assistant.') + +result = agent.run_sync('Tell me a joke.') +print(result.data) + +# all messages from the run +print(result.all_messages()) + +# messages excluding system prompts +print(result.new_messages()) +``` +_(This example is complete, it can be run "as is")_ + +Example of accessing methods on a [`StreamedRunResult`][pydantic_ai.result.StreamedRunResult] : + +```python title="Accessing messages from a StreamedRunResult" hl_lines="7 13" +from pydantic_ai import Agent + +agent = Agent('openai:gpt-4o', system_prompt='Be a helpful assistant.') + +async with agent.run_stream('Tell me a joke.') as result: + # incomplete messages before the stream finishes + print(result.all_messages()) + + async for text in result.stream(): + print(text) + + # complete messages once the stream finishes + print(result.all_messages()) +``` +_(This example is complete, it can be run "as is" inside an async context)_ + +### Using Messages as Input for Further Agent Runs + +The primary use of message histories in PydanticAI is to maintain context across multiple agent runs. + +To use existing messages in a run, pass them to the `message_history` parameter of +[`Agent.run`][pydantic_ai.Agent.run], [`Agent.run_sync`][pydantic_ai.Agent.run_sync] or +[`Agent.run_stream`][pydantic_ai.Agent.run_stream]. + +!!! info "`all_messages()` vs. `new_messages()`" + PydanticAI will inspect any messages it receives for system prompts. + + If any system prompts are found in `message_history`, new system prompts are not generated, + otherwise new system prompts are generated and inserted before `message_history` in the list of messages + used in the run. + + Thus you can decide whether you want to use system prompts from a previous run or generate them again by using + [`all_messages()`][pydantic_ai.result.RunResult.all_messages] or [`new_messages()`][pydantic_ai.result.RunResult.new_messages]. + + +```py title="Reusing messages in a conversation" hl_lines="8 11" +from pydantic_ai import Agent + +agent = Agent('openai:gpt-4o', system_prompt='Be a helpful assistant.') + +result1 = agent.run_sync('Tell me a joke.') +print(result1.data) + +result2 = agent.run_sync('Explain?', message_history=result1.new_messages()) +print(result2.data) + +print(result2.all_messages()) +``` +_(This example is complete, it can be run "as is")_ + +## Other ways of using messages + +Since messages are defined by simple dataclasses, you can manually create and manipulate, e.g. for testing. + +The message format is independent of the model used, so you can use messages in different agents, or the same agent with different models. + +```python +from pydantic_ai import Agent + +agent = Agent('openai:gpt-4o', system_prompt='Be a helpful assistant.') + +result1 = agent.run_sync('Tell me a joke.') +print(result1.data) + +result2 = agent.run_sync('Explain?', model='gemini-1.5-pro', message_history=result1.new_messages()) +print(result2.data) + +print(result2.all_messages()) +``` + +## Last Run Messages + +TODO: document [`last_run_messages`][pydantic_ai.Agent.last_run_messages]. + +## Examples + +For a more complete example of using messages in conversations, see the [chat app](../examples/chat-app.md) example. diff --git a/docs/examples/chat-app.md b/docs/examples/chat-app.md index 3af5719ac3..a64859f8a3 100644 --- a/docs/examples/chat-app.md +++ b/docs/examples/chat-app.md @@ -1,12 +1,8 @@ ---- -hide: [toc] ---- - Simple chat app example build with FastAPI. Demonstrates: -* reusing chat history +* [reusing chat history](../concepts/message-history.md) * serializing messages * streaming responses diff --git a/docs/examples/pydantic-model.md b/docs/examples/pydantic-model.md index b2f2cc14c7..4460e5e80e 100644 --- a/docs/examples/pydantic-model.md +++ b/docs/examples/pydantic-model.md @@ -1,7 +1,3 @@ ---- -hide: [toc] ---- - Simple example of using Pydantic AI to construct a Pydantic model from a text input. Demonstrates: diff --git a/docs/examples/rag.md b/docs/examples/rag.md index 20e77b526a..3db3f891a8 100644 --- a/docs/examples/rag.md +++ b/docs/examples/rag.md @@ -1,7 +1,3 @@ ---- -hide: [toc] ---- - # RAG RAG search example. This demo allows you to ask question of the [logfire](https://pydantic.dev/logfire) documentation. @@ -9,7 +5,7 @@ RAG search example. This demo allows you to ask question of the [logfire](https: Demonstrates: * retrievers -* agent dependencies +* [agent dependencies](../concepts/dependencies.md) * RAG search This is done by creating a database containing each section of the markdown documentation, then registering diff --git a/docs/examples/sql-gen.md b/docs/examples/sql-gen.md index badf8778ab..1b43abedf1 100644 --- a/docs/examples/sql-gen.md +++ b/docs/examples/sql-gen.md @@ -1,7 +1,3 @@ ---- -hide: [toc] ---- - # SQL Generation Example demonstrating how to use Pydantic AI to generate SQL queries based on user input. @@ -11,7 +7,7 @@ Demonstrates: * custom `result_type` * dynamic system prompt * result validation -* agent dependencies +* [agent dependencies](../concepts/dependencies.md) ## Running the Example diff --git a/docs/examples/stream-markdown.md b/docs/examples/stream-markdown.md index 2b81c44b94..20b1899490 100644 --- a/docs/examples/stream-markdown.md +++ b/docs/examples/stream-markdown.md @@ -1,7 +1,3 @@ ---- -hide: [toc] ---- - This example shows how to stream markdown from an agent, using the [`rich`](https://github.com/Textualize/rich) library to highlight the output in the terminal. It'll run the example with both OpenAI and Google Gemini models if the required environment variables are set. diff --git a/docs/examples/stream-whales.md b/docs/examples/stream-whales.md index d9a435f947..e537b4f9a3 100644 --- a/docs/examples/stream-whales.md +++ b/docs/examples/stream-whales.md @@ -1,7 +1,3 @@ ---- -hide: [toc] ---- - Information about whales — an example of streamed structured response validation. Demonstrates: diff --git a/docs/examples/weather-agent.md b/docs/examples/weather-agent.md index 4c96527435..fb2f774d1d 100644 --- a/docs/examples/weather-agent.md +++ b/docs/examples/weather-agent.md @@ -1,14 +1,10 @@ ---- -hide: [toc] ---- - Example of Pydantic AI with multiple tools which the LLM needs to call in turn to answer a question. Demonstrates: * retrievers * multiple retrievers -* agent dependencies +* [agent dependencies](../concepts/dependencies.md) In this case the idea is a "weather" agent — the user can ask for the weather in multiple locations, the agent will use the `get_lat_lng` tool to get the latitude and longitude of the locations, then use diff --git a/docs/img/pydantic-ai-dark.svg b/docs/img/pydantic-ai-dark.svg index de3a32b951..f24d822d94 100644 --- a/docs/img/pydantic-ai-dark.svg +++ b/docs/img/pydantic-ai-dark.svg @@ -1,4 +1,4 @@ - + diff --git a/docs/img/pydantic-ai-light.svg b/docs/img/pydantic-ai-light.svg index 86e06d3298..e94b9f5068 100644 --- a/docs/img/pydantic-ai-light.svg +++ b/docs/img/pydantic-ai-light.svg @@ -1,4 +1,4 @@ - + diff --git a/docs/index.md b/docs/index.md index 76d9a30a66..5b93a77ce1 100644 --- a/docs/index.md +++ b/docs/index.md @@ -89,7 +89,7 @@ async def main(): 7. Multiple retrievers can be registered with the same agent, the LLM can choose which (if any) retrievers to call in order to respond to a user. 8. Run the agent asynchronously, conducting a conversation with the LLM until a final response is reached. You can also run agents synchronously with `run_sync`. Internally agents are all async, so `run_sync` is a helper using `asyncio.run` to call `run()`. 9. The response from the LLM, in this case a `str`, Agents are generic in both the type of `deps` and `result_type`, so calls are typed end-to-end. -10. `result.all_messages()` includes details of messages exchanged, this is useful both to understand the conversation that took place and useful if you want to continue the conversation later — messages can be passed back to later `run/sync_run` calls. +10. `result.all_messages()` includes details of messages exchanged, this is useful both to understand the conversation that took place and useful if you want to continue the conversation later — messages can be passed back to later `run/run_sync` calls. !!! tip "Complete `weather_agent.py` example" This example is incomplete for the sake of brevity; you can find a complete `weather_agent.py` example [here](examples/weather-agent.md). diff --git a/docs/install.md b/docs/install.md index 9fd8cf2ece..b4bb26d552 100644 --- a/docs/install.md +++ b/docs/install.md @@ -20,7 +20,7 @@ It requires Python 3.9+. PydanticAI has an excellent (but completely optional) integration with [Pydantic Logfire](https://pydantic.dev/logfire) to help you view and understand agent runs. -To use Logfire, install PydanticAI with the `logfire` optional group: +To use Logfire with PydanticAI, install PydanticAI with the `logfire` optional group: === "pip" diff --git a/pydantic_ai/agent.py b/pydantic_ai/agent.py index b5792ba258..fb4211b06b 100644 --- a/pydantic_ai/agent.py +++ b/pydantic_ai/agent.py @@ -29,6 +29,8 @@ 'openai:gpt-4o-mini', 'openai:gpt-4-turbo', 'openai:gpt-4', + 'openai:o1-preview', + 'openai:o1-mini', 'openai:gpt-3.5-turbo', 'gemini-1.5-flash', 'gemini-1.5-pro', diff --git a/pydantic_ai/messages.py b/pydantic_ai/messages.py index 0db923c386..4da93aa197 100644 --- a/pydantic_ai/messages.py +++ b/pydantic_ai/messages.py @@ -14,7 +14,10 @@ @dataclass class SystemPrompt: - """A system prompt, generally written by the application developer.""" + """A system prompt, generally written by the application developer. + + This gives the model context and guidance on how to respond. + """ content: str """The content of the prompt.""" @@ -24,7 +27,11 @@ class SystemPrompt: @dataclass class UserPrompt: - """A user prompt, generally written by the end user.""" + """A user prompt, generally written by the end user. + + Content comes from the `user_prompt` parameter of [`Agent.run`][pydantic_ai.Agent.run], + [`Agent.run_sync`][pydantic_ai.Agent.run_sync], and [`Agent.run_stream`][pydantic_ai.Agent.run_stream]. + """ content: str """The content of the prompt.""" @@ -68,12 +75,24 @@ def model_response_object(self) -> dict[str, Any]: @dataclass class RetryPrompt: - """A message sent when running a retriever failed, result validation failed, or no tool could be found to call.""" + """A message back to a model asking it to try again. + + This can be sent for a number of reasons: + + * Pydantic validation of retriever arguments failed, here content is derived from a Pydantic + [`ValidationError`][pydantic_core.ValidationError] + * a retriever raised a [`ModelRetry`][pydantic_ai.exceptions.ModelRetry] exception + * no retriever was found for the tool name + * the model returned plain text when a structured response was expected + * Pydantic validation of a structured response failed, here content is derived from a Pydantic + [`ValidationError`][pydantic_core.ValidationError] + * a result validator raised a [`ModelRetry`][pydantic_ai.exceptions.ModelRetry] exception + """ content: list[pydantic_core.ErrorDetails] | str """Details of why and how the model should retry. - If the retry was triggered by a [ValidationError][pydantic_core.ValidationError], this will be a list of + If the retry was triggered by a [`ValidationError`][pydantic_core.ValidationError], this will be a list of error details. """ tool_name: str | None = None @@ -122,7 +141,7 @@ class ArgsObject: @dataclass class ToolCall: - """Either a retriever/tool call from the agent.""" + """Either a tool call from the agent.""" tool_name: str """The name of the tool to call.""" @@ -151,7 +170,10 @@ def has_content(self) -> bool: @dataclass class ModelStructuredResponse: - """A structured response from a model.""" + """A structured response from a model. + + This is used either to call a retriever or to return a structured response from an agent run. + """ calls: list[ToolCall] """The tool calls being made.""" @@ -166,7 +188,8 @@ class ModelStructuredResponse: ModelAnyResponse = Union[ModelTextResponse, ModelStructuredResponse] """Any response from a model.""" -Message = Union[SystemPrompt, UserPrompt, ToolReturn, RetryPrompt, ModelAnyResponse] +Message = Union[SystemPrompt, UserPrompt, ToolReturn, RetryPrompt, ModelTextResponse, ModelStructuredResponse] """Any message send to or returned by a model.""" MessagesTypeAdapter = _pydantic.LazyTypeAdapter(list[Annotated[Message, pydantic.Field(discriminator='role')]]) +"""Pydantic [`TypeAdapter`][pydantic.type_adapter.TypeAdapter] for (de)serializing messages.""" diff --git a/pydantic_ai/models/__init__.py b/pydantic_ai/models/__init__.py index 32de16a344..0378ab4c16 100644 --- a/pydantic_ai/models/__init__.py +++ b/pydantic_ai/models/__init__.py @@ -151,7 +151,7 @@ def timestamp(self) -> datetime: EitherStreamedResponse = Union[StreamTextResponse, StreamStructuredResponse] -ALLOW_MODEL_REQUESTS = False +ALLOW_MODEL_REQUESTS = True """Whether to allow requests to models. This global setting allows you to disable request to most models, e.g. to make sure you don't accidentally