pydantic-ai-slim 0.0.14__tar.gz → 0.0.15__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pydantic-ai-slim might be problematic. Click here for more details.

Files changed (27) hide show
  1. {pydantic_ai_slim-0.0.14 → pydantic_ai_slim-0.0.15}/.gitignore +1 -1
  2. {pydantic_ai_slim-0.0.14 → pydantic_ai_slim-0.0.15}/PKG-INFO +1 -2
  3. {pydantic_ai_slim-0.0.14 → pydantic_ai_slim-0.0.15}/pydantic_ai/__init__.py +2 -1
  4. {pydantic_ai_slim-0.0.14 → pydantic_ai_slim-0.0.15}/pydantic_ai/_result.py +2 -2
  5. {pydantic_ai_slim-0.0.14 → pydantic_ai_slim-0.0.15}/pydantic_ai/agent.py +71 -17
  6. {pydantic_ai_slim-0.0.14 → pydantic_ai_slim-0.0.15}/pydantic_ai/models/mistral.py +10 -15
  7. {pydantic_ai_slim-0.0.14 → pydantic_ai_slim-0.0.15}/pydantic_ai/result.py +21 -4
  8. {pydantic_ai_slim-0.0.14 → pydantic_ai_slim-0.0.15}/pydantic_ai/settings.py +4 -0
  9. {pydantic_ai_slim-0.0.14 → pydantic_ai_slim-0.0.15}/pydantic_ai/tools.py +0 -16
  10. {pydantic_ai_slim-0.0.14 → pydantic_ai_slim-0.0.15}/pyproject.toml +2 -2
  11. {pydantic_ai_slim-0.0.14 → pydantic_ai_slim-0.0.15}/README.md +0 -0
  12. {pydantic_ai_slim-0.0.14 → pydantic_ai_slim-0.0.15}/pydantic_ai/_griffe.py +0 -0
  13. {pydantic_ai_slim-0.0.14 → pydantic_ai_slim-0.0.15}/pydantic_ai/_pydantic.py +0 -0
  14. {pydantic_ai_slim-0.0.14 → pydantic_ai_slim-0.0.15}/pydantic_ai/_system_prompt.py +0 -0
  15. {pydantic_ai_slim-0.0.14 → pydantic_ai_slim-0.0.15}/pydantic_ai/_utils.py +0 -0
  16. {pydantic_ai_slim-0.0.14 → pydantic_ai_slim-0.0.15}/pydantic_ai/exceptions.py +0 -0
  17. {pydantic_ai_slim-0.0.14 → pydantic_ai_slim-0.0.15}/pydantic_ai/messages.py +0 -0
  18. {pydantic_ai_slim-0.0.14 → pydantic_ai_slim-0.0.15}/pydantic_ai/models/__init__.py +0 -0
  19. {pydantic_ai_slim-0.0.14 → pydantic_ai_slim-0.0.15}/pydantic_ai/models/anthropic.py +0 -0
  20. {pydantic_ai_slim-0.0.14 → pydantic_ai_slim-0.0.15}/pydantic_ai/models/function.py +0 -0
  21. {pydantic_ai_slim-0.0.14 → pydantic_ai_slim-0.0.15}/pydantic_ai/models/gemini.py +0 -0
  22. {pydantic_ai_slim-0.0.14 → pydantic_ai_slim-0.0.15}/pydantic_ai/models/groq.py +0 -0
  23. {pydantic_ai_slim-0.0.14 → pydantic_ai_slim-0.0.15}/pydantic_ai/models/ollama.py +0 -0
  24. {pydantic_ai_slim-0.0.14 → pydantic_ai_slim-0.0.15}/pydantic_ai/models/openai.py +0 -0
  25. {pydantic_ai_slim-0.0.14 → pydantic_ai_slim-0.0.15}/pydantic_ai/models/test.py +0 -0
  26. {pydantic_ai_slim-0.0.14 → pydantic_ai_slim-0.0.15}/pydantic_ai/models/vertexai.py +0 -0
  27. {pydantic_ai_slim-0.0.14 → pydantic_ai_slim-0.0.15}/pydantic_ai/py.typed +0 -0
@@ -10,6 +10,6 @@ env*/
10
10
  /TODO.md
11
11
  /postgres-data/
12
12
  .DS_Store
13
- /pydantic_ai_examples/.chat_app_messages.sqlite
13
+ examples/pydantic_ai_examples/.chat_app_messages.sqlite
14
14
  .cache/
15
15
  .vscode/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pydantic-ai-slim
3
- Version: 0.0.14
3
+ Version: 0.0.15
4
4
  Summary: Agent Framework / shim to use Pydantic with LLMs, slim package
5
5
  Author-email: Samuel Colvin <samuel@pydantic.dev>
6
6
  License-Expression: MIT
@@ -36,7 +36,6 @@ Requires-Dist: groq>=0.12.0; extra == 'groq'
36
36
  Provides-Extra: logfire
37
37
  Requires-Dist: logfire>=2.3; extra == 'logfire'
38
38
  Provides-Extra: mistral
39
- Requires-Dist: json-repair>=0.30.3; extra == 'mistral'
40
39
  Requires-Dist: mistralai>=1.2.5; extra == 'mistral'
41
40
  Provides-Extra: openai
42
41
  Requires-Dist: openai>=1.54.3; extra == 'openai'
@@ -1,11 +1,12 @@
1
1
  from importlib.metadata import version
2
2
 
3
- from .agent import Agent
3
+ from .agent import Agent, capture_run_messages
4
4
  from .exceptions import AgentRunError, ModelRetry, UnexpectedModelBehavior, UsageLimitExceeded, UserError
5
5
  from .tools import RunContext, Tool
6
6
 
7
7
  __all__ = (
8
8
  'Agent',
9
+ 'capture_run_messages',
9
10
  'RunContext',
10
11
  'Tool',
11
12
  'AgentRunError',
@@ -12,8 +12,8 @@ from typing_extensions import Self, TypeAliasType, TypedDict
12
12
 
13
13
  from . import _utils, messages as _messages
14
14
  from .exceptions import ModelRetry
15
- from .result import ResultData
16
- from .tools import AgentDeps, ResultValidatorFunc, RunContext, ToolDefinition
15
+ from .result import ResultData, ResultValidatorFunc
16
+ from .tools import AgentDeps, RunContext, ToolDefinition
17
17
 
18
18
 
19
19
  @dataclass
@@ -5,12 +5,13 @@ import dataclasses
5
5
  import inspect
6
6
  from collections.abc import AsyncIterator, Awaitable, Iterator, Sequence
7
7
  from contextlib import asynccontextmanager, contextmanager
8
+ from contextvars import ContextVar
8
9
  from dataclasses import dataclass, field
9
10
  from types import FrameType
10
11
  from typing import Any, Callable, Generic, Literal, cast, final, overload
11
12
 
12
13
  import logfire_api
13
- from typing_extensions import assert_never
14
+ from typing_extensions import assert_never, deprecated
14
15
 
15
16
  from . import (
16
17
  _result,
@@ -35,7 +36,7 @@ from .tools import (
35
36
  ToolPrepareFunc,
36
37
  )
37
38
 
38
- __all__ = ('Agent',)
39
+ __all__ = 'Agent', 'capture_run_messages', 'EndStrategy'
39
40
 
40
41
  _logfire = logfire_api.Logfire(otel_scope='pydantic-ai')
41
42
 
@@ -89,12 +90,6 @@ class Agent(Generic[AgentDeps, ResultData]):
89
90
  be merged with this value, with the runtime argument taking priority.
90
91
  """
91
92
 
92
- last_run_messages: list[_messages.ModelMessage] | None
93
- """The messages from the last run, useful when a run raised an exception.
94
-
95
- Note: these are not used by the agent, e.g. in future runs, they are just stored for developers' convenience.
96
- """
97
-
98
93
  _result_schema: _result.ResultSchema[ResultData] | None = field(repr=False)
99
94
  _result_validators: list[_result.ResultValidator[AgentDeps, ResultData]] = field(repr=False)
100
95
  _allow_text_result: bool = field(repr=False)
@@ -161,7 +156,6 @@ class Agent(Generic[AgentDeps, ResultData]):
161
156
  self.end_strategy = end_strategy
162
157
  self.name = name
163
158
  self.model_settings = model_settings
164
- self.last_run_messages = None
165
159
  self._result_schema = _result.ResultSchema[result_type].build(
166
160
  result_type, result_tool_name, result_tool_description
167
161
  )
@@ -234,7 +228,7 @@ class Agent(Generic[AgentDeps, ResultData]):
234
228
  ) as run_span:
235
229
  run_context = RunContext(deps, 0, [], None, model_used)
236
230
  messages = await self._prepare_messages(user_prompt, message_history, run_context)
237
- self.last_run_messages = run_context.messages = messages
231
+ run_context.messages = messages
238
232
 
239
233
  for tool in self._function_tools.values():
240
234
  tool.current_retry = 0
@@ -393,7 +387,7 @@ class Agent(Generic[AgentDeps, ResultData]):
393
387
  ) as run_span:
394
388
  run_context = RunContext(deps, 0, [], None, model_used)
395
389
  messages = await self._prepare_messages(user_prompt, message_history, run_context)
396
- self.last_run_messages = run_context.messages = messages
390
+ run_context.messages = messages
397
391
 
398
392
  for tool in self._function_tools.values():
399
393
  tool.current_retry = 0
@@ -614,7 +608,7 @@ class Agent(Generic[AgentDeps, ResultData]):
614
608
  #> success (no tool calls)
615
609
  ```
616
610
  """
617
- self._result_validators.append(_result.ResultValidator(func))
611
+ self._result_validators.append(_result.ResultValidator[AgentDeps, Any](func))
618
612
  return func
619
613
 
620
614
  @overload
@@ -835,14 +829,25 @@ class Agent(Generic[AgentDeps, ResultData]):
835
829
  async def _prepare_messages(
836
830
  self, user_prompt: str, message_history: list[_messages.ModelMessage] | None, run_context: RunContext[AgentDeps]
837
831
  ) -> list[_messages.ModelMessage]:
832
+ try:
833
+ messages = _messages_ctx_var.get()
834
+ except LookupError:
835
+ messages = []
836
+ else:
837
+ if messages:
838
+ raise exceptions.UserError(
839
+ 'The capture_run_messages() context manager may only be used to wrap '
840
+ 'one call to run(), run_sync(), or run_stream().'
841
+ )
842
+
838
843
  if message_history:
839
844
  # shallow copy messages
840
- messages = message_history.copy()
845
+ messages.extend(message_history)
841
846
  messages.append(_messages.ModelRequest([_messages.UserPromptPart(user_prompt)]))
842
847
  else:
843
848
  parts = await self._sys_parts(run_context)
844
849
  parts.append(_messages.UserPromptPart(user_prompt))
845
- messages: list[_messages.ModelMessage] = [_messages.ModelRequest(parts)]
850
+ messages.append(_messages.ModelRequest(parts))
846
851
 
847
852
  return messages
848
853
 
@@ -864,11 +869,15 @@ class Agent(Generic[AgentDeps, ResultData]):
864
869
  else:
865
870
  tool_calls.append(part)
866
871
 
867
- if texts:
872
+ # At the moment, we prioritize at least executing tool calls if they are present.
873
+ # In the future, we'd consider making this configurable at the agent or run level.
874
+ # This accounts for cases like anthropic returns that might contain a text response
875
+ # and a tool call response, where the text response just indicates the tool call will happen.
876
+ if tool_calls:
877
+ return await self._handle_structured_response(tool_calls, run_context)
878
+ elif texts:
868
879
  text = '\n\n'.join(texts)
869
880
  return await self._handle_text_response(text, run_context)
870
- elif tool_calls:
871
- return await self._handle_structured_response(tool_calls, run_context)
872
881
  else:
873
882
  raise exceptions.UnexpectedModelBehavior('Received empty model response')
874
883
 
@@ -1115,6 +1124,51 @@ class Agent(Generic[AgentDeps, ResultData]):
1115
1124
  self.name = name
1116
1125
  return
1117
1126
 
1127
+ @property
1128
+ @deprecated(
1129
+ 'The `last_run_messages` attribute has been removed, use `capture_run_messages` instead.', category=None
1130
+ )
1131
+ def last_run_messages(self) -> list[_messages.ModelMessage]:
1132
+ raise AttributeError('The `last_run_messages` attribute has been removed, use `capture_run_messages` instead.')
1133
+
1134
+
1135
+ _messages_ctx_var: ContextVar[list[_messages.ModelMessage]] = ContextVar('var')
1136
+
1137
+
1138
+ @contextmanager
1139
+ def capture_run_messages() -> Iterator[list[_messages.ModelMessage]]:
1140
+ """Context manager to access the messages used in a [`run`][pydantic_ai.Agent.run], [`run_sync`][pydantic_ai.Agent.run_sync], or [`run_stream`][pydantic_ai.Agent.run_stream] call.
1141
+
1142
+ Useful when a run may raise an exception, see [model errors](../agents.md#model-errors) for more information.
1143
+
1144
+ Examples:
1145
+ ```python
1146
+ from pydantic_ai import Agent, capture_run_messages
1147
+
1148
+ agent = Agent('test')
1149
+
1150
+ with capture_run_messages() as messages:
1151
+ try:
1152
+ result = agent.run_sync('foobar')
1153
+ except Exception:
1154
+ print(messages)
1155
+ raise
1156
+ ```
1157
+
1158
+ !!! note
1159
+ You may not call `run`, `run_sync`, or `run_stream` more than once within a single `capture_run_messages` context.
1160
+ If you try to do so, a [`UserError`][pydantic_ai.exceptions.UserError] will be raised.
1161
+ """
1162
+ try:
1163
+ yield _messages_ctx_var.get()
1164
+ except LookupError:
1165
+ messages: list[_messages.ModelMessage] = []
1166
+ token = _messages_ctx_var.set(messages)
1167
+ try:
1168
+ yield messages
1169
+ finally:
1170
+ _messages_ctx_var.reset(token)
1171
+
1118
1172
 
1119
1173
  @dataclass
1120
1174
  class _MarkFinalResult(Generic[ResultData]):
@@ -8,6 +8,7 @@ from datetime import datetime, timezone
8
8
  from itertools import chain
9
9
  from typing import Any, Callable, Literal, Union
10
10
 
11
+ import pydantic_core
11
12
  from httpx import AsyncClient as AsyncHTTPClient, Timeout
12
13
  from typing_extensions import assert_never
13
14
 
@@ -39,7 +40,6 @@ from . import (
39
40
  )
40
41
 
41
42
  try:
42
- from json_repair import repair_json
43
43
  from mistralai import (
44
44
  UNSET,
45
45
  CompletionChunk as MistralCompletionChunk,
@@ -198,11 +198,10 @@ class MistralAgentModel(AgentModel):
198
198
  """Create a streaming completion request to the Mistral model."""
199
199
  response: MistralEventStreamAsync[MistralCompletionEvent] | None
200
200
  mistral_messages = list(chain(*(self._map_message(m) for m in messages)))
201
-
202
201
  model_settings = model_settings or {}
203
202
 
204
203
  if self.result_tools and self.function_tools or self.function_tools:
205
- # Function Calling Mode
204
+ # Function Calling
206
205
  response = await self.client.chat.stream_async(
207
206
  model=str(self.model_name),
208
207
  messages=mistral_messages,
@@ -218,9 +217,9 @@ class MistralAgentModel(AgentModel):
218
217
  elif self.result_tools:
219
218
  # Json Mode
220
219
  parameters_json_schemas = [tool.parameters_json_schema for tool in self.result_tools]
221
-
222
220
  user_output_format_message = self._generate_user_output_format(parameters_json_schemas)
223
221
  mistral_messages.append(user_output_format_message)
222
+
224
223
  response = await self.client.chat.stream_async(
225
224
  model=str(self.model_name),
226
225
  messages=mistral_messages,
@@ -270,12 +269,13 @@ class MistralAgentModel(AgentModel):
270
269
  @staticmethod
271
270
  def _process_response(response: MistralChatCompletionResponse) -> ModelResponse:
272
271
  """Process a non-streamed response, and prepare a message to return."""
272
+ assert response.choices, 'Unexpected empty response choice.'
273
+
273
274
  if response.created:
274
275
  timestamp = datetime.fromtimestamp(response.created, tz=timezone.utc)
275
276
  else:
276
277
  timestamp = _now_utc()
277
278
 
278
- assert response.choices, 'Unexpected empty response choice.'
279
279
  choice = response.choices[0]
280
280
  content = choice.message.content
281
281
  tool_calls = choice.message.tool_calls
@@ -546,20 +546,15 @@ class MistralStreamStructuredResponse(StreamStructuredResponse):
546
546
  calls.append(tool)
547
547
 
548
548
  elif self._delta_content and self._result_tools:
549
- # NOTE: Params set for the most efficient and fastest way.
550
- output_json = repair_json(self._delta_content, return_objects=True, skip_json_loads=True)
551
- assert isinstance(
552
- output_json, dict
553
- ), f'Expected repair_json as type dict, invalid type: {type(output_json)}'
549
+ output_json: dict[str, Any] | None = pydantic_core.from_json(
550
+ self._delta_content, allow_partial='trailing-strings'
551
+ )
554
552
 
555
553
  if output_json:
556
554
  for result_tool in self._result_tools.values():
557
- # NOTE: Additional verification to prevent JSON validation to crash in `result.py`
555
+ # NOTE: Additional verification to prevent JSON validation to crash in `_result.py`
558
556
  # Ensures required parameters in the JSON schema are respected, especially for stream-based return types.
559
- # For example, `return_type=list[str]` expects a 'response' key with value type array of str.
560
- # when `{"response":` then `repair_json` sets `{"response": ""}` (type not found default str)
561
- # when `{"response": {` then `repair_json` sets `{"response": {}}` (type found)
562
- # This ensures it's corrected to `{"response": {}}` and other required parameters and type.
557
+ # Example with BaseModel and required fields.
563
558
  if not self._validate_required_json_schema(output_json, result_tool.parameters_json_schema):
564
559
  continue
565
560
 
@@ -4,9 +4,10 @@ from abc import ABC, abstractmethod
4
4
  from collections.abc import AsyncIterator, Awaitable, Callable
5
5
  from dataclasses import dataclass, field
6
6
  from datetime import datetime
7
- from typing import Generic, TypeVar, cast
7
+ from typing import Generic, Union, cast
8
8
 
9
9
  import logfire_api
10
+ from typing_extensions import TypeVar
10
11
 
11
12
  from . import _result, _utils, exceptions, messages as _messages, models
12
13
  from .settings import UsageLimits
@@ -14,21 +15,37 @@ from .tools import AgentDeps, RunContext
14
15
 
15
16
  __all__ = (
16
17
  'ResultData',
18
+ 'ResultValidatorFunc',
17
19
  'Usage',
18
20
  'RunResult',
19
21
  'StreamedRunResult',
20
22
  )
21
23
 
22
24
 
23
- ResultData = TypeVar('ResultData')
25
+ ResultData = TypeVar('ResultData', default=str)
24
26
  """Type variable for the result data of a run."""
25
27
 
28
+ ResultValidatorFunc = Union[
29
+ Callable[[RunContext[AgentDeps], ResultData], ResultData],
30
+ Callable[[RunContext[AgentDeps], ResultData], Awaitable[ResultData]],
31
+ Callable[[ResultData], ResultData],
32
+ Callable[[ResultData], Awaitable[ResultData]],
33
+ ]
34
+ """
35
+ A function that always takes `ResultData` and returns `ResultData` and:
36
+
37
+ * may or may not take [`RunContext`][pydantic_ai.tools.RunContext] as a first argument
38
+ * may or may not be async
39
+
40
+ Usage `ResultValidatorFunc[AgentDeps, ResultData]`.
41
+ """
42
+
26
43
  _logfire = logfire_api.Logfire(otel_scope='pydantic-ai')
27
44
 
28
45
 
29
46
  @dataclass
30
47
  class Usage:
31
- """LLM usage associated to a request or run.
48
+ """LLM usage associated with a request or run.
32
49
 
33
50
  Responsibility for calculating usage is on the model; PydanticAI simply sums the usage information across requests.
34
51
 
@@ -36,7 +53,7 @@ class Usage:
36
53
  """
37
54
 
38
55
  requests: int = 0
39
- """Number of requests made."""
56
+ """Number of requests made to the LLM API."""
40
57
  request_tokens: int | None = None
41
58
  """Tokens used in processing requests."""
42
59
  response_tokens: int | None = None
@@ -22,6 +22,7 @@ class ModelSettings(TypedDict, total=False):
22
22
  """The maximum number of tokens to generate before stopping.
23
23
 
24
24
  Supported by:
25
+
25
26
  * Gemini
26
27
  * Anthropic
27
28
  * OpenAI
@@ -37,6 +38,7 @@ class ModelSettings(TypedDict, total=False):
37
38
  Note that even with `temperature` of `0.0`, the results will not be fully deterministic.
38
39
 
39
40
  Supported by:
41
+
40
42
  * Gemini
41
43
  * Anthropic
42
44
  * OpenAI
@@ -51,6 +53,7 @@ class ModelSettings(TypedDict, total=False):
51
53
  You should either alter `temperature` or `top_p`, but not both.
52
54
 
53
55
  Supported by:
56
+
54
57
  * Gemini
55
58
  * Anthropic
56
59
  * OpenAI
@@ -61,6 +64,7 @@ class ModelSettings(TypedDict, total=False):
61
64
  """Override the client-level default timeout for a request, in seconds.
62
65
 
63
66
  Supported by:
67
+
64
68
  * Gemini
65
69
  * Anthropic
66
70
  * OpenAI
@@ -16,7 +16,6 @@ from .exceptions import ModelRetry, UnexpectedModelBehavior
16
16
  __all__ = (
17
17
  'AgentDeps',
18
18
  'RunContext',
19
- 'ResultValidatorFunc',
20
19
  'SystemPromptFunc',
21
20
  'ToolFuncContext',
22
21
  'ToolFuncPlain',
@@ -73,21 +72,6 @@ SystemPromptFunc = Union[
73
72
  Usage `SystemPromptFunc[AgentDeps]`.
74
73
  """
75
74
 
76
- ResultData = TypeVar('ResultData')
77
-
78
- ResultValidatorFunc = Union[
79
- Callable[[RunContext[AgentDeps], ResultData], ResultData],
80
- Callable[[RunContext[AgentDeps], ResultData], Awaitable[ResultData]],
81
- Callable[[ResultData], ResultData],
82
- Callable[[ResultData], Awaitable[ResultData]],
83
- ]
84
- """
85
- A function that always takes `ResultData` and returns `ResultData`,
86
- but may or maybe not take `CallInfo` as a first argument, and may or may not be async.
87
-
88
- Usage `ResultValidator[AgentDeps, ResultData]`.
89
- """
90
-
91
75
  ToolFuncContext = Callable[Concatenate[RunContext[AgentDeps], ToolParams], Any]
92
76
  """A tool function that takes `RunContext` as the first argument.
93
77
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "pydantic-ai-slim"
7
- version = "0.0.14"
7
+ version = "0.0.15"
8
8
  description = "Agent Framework / shim to use Pydantic with LLMs, slim package"
9
9
  authors = [
10
10
  { name = "Samuel Colvin", email = "samuel@pydantic.dev" },
@@ -46,7 +46,7 @@ openai = ["openai>=1.54.3"]
46
46
  vertexai = ["google-auth>=2.36.0", "requests>=2.32.3"]
47
47
  anthropic = ["anthropic>=0.40.0"]
48
48
  groq = ["groq>=0.12.0"]
49
- mistral = ["mistralai>=1.2.5", "json-repair>=0.30.3"]
49
+ mistral = ["mistralai>=1.2.5"]
50
50
  logfire = ["logfire>=2.3"]
51
51
 
52
52
  [dependency-groups]