pydantic-ai-slim 1.0.8__py3-none-any.whl → 1.0.10__py3-none-any.whl
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.
- pydantic_ai/_agent_graph.py +67 -55
- pydantic_ai/_cli.py +1 -1
- pydantic_ai/_otel_messages.py +2 -0
- pydantic_ai/_parts_manager.py +82 -12
- pydantic_ai/_run_context.py +8 -1
- pydantic_ai/_tool_manager.py +1 -0
- pydantic_ai/ag_ui.py +86 -33
- pydantic_ai/agent/__init__.py +2 -1
- pydantic_ai/builtin_tools.py +12 -0
- pydantic_ai/durable_exec/temporal/_model.py +14 -6
- pydantic_ai/durable_exec/temporal/_run_context.py +2 -1
- pydantic_ai/format_prompt.py +109 -17
- pydantic_ai/messages.py +65 -30
- pydantic_ai/models/anthropic.py +119 -45
- pydantic_ai/models/function.py +17 -8
- pydantic_ai/models/google.py +132 -33
- pydantic_ai/models/groq.py +68 -17
- pydantic_ai/models/openai.py +262 -41
- pydantic_ai/providers/__init__.py +1 -1
- pydantic_ai/result.py +21 -3
- pydantic_ai/toolsets/function.py +8 -2
- {pydantic_ai_slim-1.0.8.dist-info → pydantic_ai_slim-1.0.10.dist-info}/METADATA +5 -5
- {pydantic_ai_slim-1.0.8.dist-info → pydantic_ai_slim-1.0.10.dist-info}/RECORD +26 -26
- {pydantic_ai_slim-1.0.8.dist-info → pydantic_ai_slim-1.0.10.dist-info}/WHEEL +0 -0
- {pydantic_ai_slim-1.0.8.dist-info → pydantic_ai_slim-1.0.10.dist-info}/entry_points.txt +0 -0
- {pydantic_ai_slim-1.0.8.dist-info → pydantic_ai_slim-1.0.10.dist-info}/licenses/LICENSE +0 -0
|
@@ -4,7 +4,7 @@ from collections.abc import AsyncIterator, Callable
|
|
|
4
4
|
from contextlib import asynccontextmanager
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from datetime import datetime
|
|
7
|
-
from typing import Any
|
|
7
|
+
from typing import Any, cast
|
|
8
8
|
|
|
9
9
|
from pydantic import ConfigDict, with_config
|
|
10
10
|
from temporalio import activity, workflow
|
|
@@ -30,7 +30,8 @@ from ._run_context import TemporalRunContext
|
|
|
30
30
|
@with_config(ConfigDict(arbitrary_types_allowed=True))
|
|
31
31
|
class _RequestParams:
|
|
32
32
|
messages: list[ModelMessage]
|
|
33
|
-
model_settings
|
|
33
|
+
# `model_settings` can't be a `ModelSettings` because Temporal would end up dropping fields only defined on its subclasses.
|
|
34
|
+
model_settings: dict[str, Any] | None
|
|
34
35
|
model_request_parameters: ModelRequestParameters
|
|
35
36
|
serialized_run_context: Any
|
|
36
37
|
|
|
@@ -82,7 +83,11 @@ class TemporalModel(WrapperModel):
|
|
|
82
83
|
|
|
83
84
|
@activity.defn(name=f'{activity_name_prefix}__model_request')
|
|
84
85
|
async def request_activity(params: _RequestParams) -> ModelResponse:
|
|
85
|
-
return await self.wrapped.request(
|
|
86
|
+
return await self.wrapped.request(
|
|
87
|
+
params.messages,
|
|
88
|
+
cast(ModelSettings | None, params.model_settings),
|
|
89
|
+
params.model_request_parameters,
|
|
90
|
+
)
|
|
86
91
|
|
|
87
92
|
self.request_activity = request_activity
|
|
88
93
|
|
|
@@ -92,7 +97,10 @@ class TemporalModel(WrapperModel):
|
|
|
92
97
|
|
|
93
98
|
run_context = self.run_context_type.deserialize_run_context(params.serialized_run_context, deps=deps)
|
|
94
99
|
async with self.wrapped.request_stream(
|
|
95
|
-
params.messages,
|
|
100
|
+
params.messages,
|
|
101
|
+
cast(ModelSettings | None, params.model_settings),
|
|
102
|
+
params.model_request_parameters,
|
|
103
|
+
run_context,
|
|
96
104
|
) as streamed_response:
|
|
97
105
|
await self.event_stream_handler(run_context, streamed_response)
|
|
98
106
|
|
|
@@ -124,7 +132,7 @@ class TemporalModel(WrapperModel):
|
|
|
124
132
|
activity=self.request_activity,
|
|
125
133
|
arg=_RequestParams(
|
|
126
134
|
messages=messages,
|
|
127
|
-
model_settings=model_settings,
|
|
135
|
+
model_settings=cast(dict[str, Any] | None, model_settings),
|
|
128
136
|
model_request_parameters=model_request_parameters,
|
|
129
137
|
serialized_run_context=None,
|
|
130
138
|
),
|
|
@@ -161,7 +169,7 @@ class TemporalModel(WrapperModel):
|
|
|
161
169
|
args=[
|
|
162
170
|
_RequestParams(
|
|
163
171
|
messages=messages,
|
|
164
|
-
model_settings=model_settings,
|
|
172
|
+
model_settings=cast(dict[str, Any] | None, model_settings),
|
|
165
173
|
model_request_parameters=model_request_parameters,
|
|
166
174
|
serialized_run_context=serialized_run_context,
|
|
167
175
|
),
|
|
@@ -9,7 +9,7 @@ from pydantic_ai.tools import AgentDepsT, RunContext
|
|
|
9
9
|
class TemporalRunContext(RunContext[AgentDepsT]):
|
|
10
10
|
"""The [`RunContext`][pydantic_ai.tools.RunContext] subclass to use to serialize and deserialize the run context for use inside a Temporal activity.
|
|
11
11
|
|
|
12
|
-
By default, only the `deps`, `retries`, `tool_call_id`, `tool_name`, `tool_call_approved`, `retry` and `run_step` attributes will be available.
|
|
12
|
+
By default, only the `deps`, `retries`, `tool_call_id`, `tool_name`, `tool_call_approved`, `retry`, `max_retries` and `run_step` attributes will be available.
|
|
13
13
|
To make another attribute available, create a `TemporalRunContext` subclass with a custom `serialize_run_context` class method that returns a dictionary that includes the attribute and pass it to [`TemporalAgent`][pydantic_ai.durable_exec.temporal.TemporalAgent].
|
|
14
14
|
"""
|
|
15
15
|
|
|
@@ -42,6 +42,7 @@ class TemporalRunContext(RunContext[AgentDepsT]):
|
|
|
42
42
|
'tool_name': ctx.tool_name,
|
|
43
43
|
'tool_call_approved': ctx.tool_call_approved,
|
|
44
44
|
'retry': ctx.retry,
|
|
45
|
+
'max_retries': ctx.max_retries,
|
|
45
46
|
'run_step': ctx.run_step,
|
|
46
47
|
}
|
|
47
48
|
|
pydantic_ai/format_prompt.py
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
from __future__ import annotations as _annotations
|
|
2
2
|
|
|
3
3
|
from collections.abc import Iterable, Iterator, Mapping
|
|
4
|
-
from dataclasses import asdict, dataclass, is_dataclass
|
|
4
|
+
from dataclasses import asdict, dataclass, field, fields, is_dataclass
|
|
5
5
|
from datetime import date
|
|
6
|
-
from typing import Any
|
|
6
|
+
from typing import Any, Literal
|
|
7
7
|
from xml.etree import ElementTree
|
|
8
8
|
|
|
9
9
|
from pydantic import BaseModel
|
|
10
10
|
|
|
11
11
|
__all__ = ('format_as_xml',)
|
|
12
12
|
|
|
13
|
+
from pydantic.fields import ComputedFieldInfo, FieldInfo
|
|
14
|
+
|
|
13
15
|
|
|
14
16
|
def format_as_xml(
|
|
15
17
|
obj: Any,
|
|
@@ -17,6 +19,7 @@ def format_as_xml(
|
|
|
17
19
|
item_tag: str = 'item',
|
|
18
20
|
none_str: str = 'null',
|
|
19
21
|
indent: str | None = ' ',
|
|
22
|
+
include_field_info: Literal['once'] | bool = False,
|
|
20
23
|
) -> str:
|
|
21
24
|
"""Format a Python object as XML.
|
|
22
25
|
|
|
@@ -33,6 +36,10 @@ def format_as_xml(
|
|
|
33
36
|
for dataclasses and Pydantic models.
|
|
34
37
|
none_str: String to use for `None` values.
|
|
35
38
|
indent: Indentation string to use for pretty printing.
|
|
39
|
+
include_field_info: Whether to include attributes like Pydantic `Field` attributes and dataclasses `field()`
|
|
40
|
+
`metadata` as XML attributes. In both cases the allowed `Field` attributes and `field()` metadata keys are
|
|
41
|
+
`title` and `description`. If a field is repeated in the data (e.g. in a list) by setting `once`
|
|
42
|
+
the attributes are included only in the first occurrence of an XML element relative to the same field.
|
|
36
43
|
|
|
37
44
|
Returns:
|
|
38
45
|
XML representation of the object.
|
|
@@ -51,7 +58,12 @@ def format_as_xml(
|
|
|
51
58
|
'''
|
|
52
59
|
```
|
|
53
60
|
"""
|
|
54
|
-
el = _ToXml(
|
|
61
|
+
el = _ToXml(
|
|
62
|
+
data=obj,
|
|
63
|
+
item_tag=item_tag,
|
|
64
|
+
none_str=none_str,
|
|
65
|
+
include_field_info=include_field_info,
|
|
66
|
+
).to_xml(root_tag)
|
|
55
67
|
if root_tag is None and el.text is None:
|
|
56
68
|
join = '' if indent is None else '\n'
|
|
57
69
|
return join.join(_rootless_xml_elements(el, indent))
|
|
@@ -63,11 +75,26 @@ def format_as_xml(
|
|
|
63
75
|
|
|
64
76
|
@dataclass
|
|
65
77
|
class _ToXml:
|
|
78
|
+
data: Any
|
|
66
79
|
item_tag: str
|
|
67
80
|
none_str: str
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
81
|
+
include_field_info: Literal['once'] | bool
|
|
82
|
+
# a map of Pydantic and dataclasses Field paths to their metadata:
|
|
83
|
+
# a field unique string representation and its class
|
|
84
|
+
_fields_info: dict[str, tuple[str, FieldInfo | ComputedFieldInfo]] = field(default_factory=dict)
|
|
85
|
+
# keep track of fields we have extracted attributes from
|
|
86
|
+
_included_fields: set[str] = field(default_factory=set)
|
|
87
|
+
# keep track of class names for dataclasses and Pydantic models, that occur in lists
|
|
88
|
+
_element_names: dict[str, str] = field(default_factory=dict)
|
|
89
|
+
# flag for parsing dataclasses and Pydantic models once
|
|
90
|
+
_is_info_extracted: bool = False
|
|
91
|
+
_FIELD_ATTRIBUTES = ('title', 'description')
|
|
92
|
+
|
|
93
|
+
def to_xml(self, tag: str | None = None) -> ElementTree.Element:
|
|
94
|
+
return self._to_xml(value=self.data, path='', tag=tag)
|
|
95
|
+
|
|
96
|
+
def _to_xml(self, value: Any, path: str, tag: str | None = None) -> ElementTree.Element:
|
|
97
|
+
element = self._create_element(self.item_tag if tag is None else tag, path)
|
|
71
98
|
if value is None:
|
|
72
99
|
element.text = self.none_str
|
|
73
100
|
elif isinstance(value, str):
|
|
@@ -79,31 +106,96 @@ class _ToXml:
|
|
|
79
106
|
elif isinstance(value, date):
|
|
80
107
|
element.text = value.isoformat()
|
|
81
108
|
elif isinstance(value, Mapping):
|
|
82
|
-
self.
|
|
109
|
+
if tag is None and path in self._element_names:
|
|
110
|
+
element.tag = self._element_names[path]
|
|
111
|
+
self._mapping_to_xml(element, value, path) # pyright: ignore[reportUnknownArgumentType]
|
|
83
112
|
elif is_dataclass(value) and not isinstance(value, type):
|
|
113
|
+
self._init_structure_info()
|
|
84
114
|
if tag is None:
|
|
85
|
-
element =
|
|
86
|
-
|
|
87
|
-
self._mapping_to_xml(element, dc_dict)
|
|
115
|
+
element.tag = value.__class__.__name__
|
|
116
|
+
self._mapping_to_xml(element, asdict(value), path)
|
|
88
117
|
elif isinstance(value, BaseModel):
|
|
118
|
+
self._init_structure_info()
|
|
89
119
|
if tag is None:
|
|
90
|
-
element =
|
|
91
|
-
|
|
120
|
+
element.tag = value.__class__.__name__
|
|
121
|
+
# by dumping the model we loose all metadata in nested data structures,
|
|
122
|
+
# but we have collected it when called _init_structure_info
|
|
123
|
+
self._mapping_to_xml(element, value.model_dump(), path)
|
|
92
124
|
elif isinstance(value, Iterable):
|
|
93
|
-
for item in value: # pyright: ignore[reportUnknownVariableType]
|
|
94
|
-
|
|
95
|
-
element.append(item_el)
|
|
125
|
+
for n, item in enumerate(value): # pyright: ignore[reportUnknownVariableType,reportUnknownArgumentType]
|
|
126
|
+
element.append(self._to_xml(value=item, path=f'{path}.[{n}]' if path else f'[{n}]'))
|
|
96
127
|
else:
|
|
97
128
|
raise TypeError(f'Unsupported type for XML formatting: {type(value)}')
|
|
98
129
|
return element
|
|
99
130
|
|
|
100
|
-
def
|
|
131
|
+
def _create_element(self, tag: str, path: str) -> ElementTree.Element:
|
|
132
|
+
element = ElementTree.Element(tag)
|
|
133
|
+
if path in self._fields_info:
|
|
134
|
+
field_repr, field_info = self._fields_info[path]
|
|
135
|
+
if self.include_field_info and self.include_field_info != 'once' or field_repr not in self._included_fields:
|
|
136
|
+
field_attributes = self._extract_attributes(field_info)
|
|
137
|
+
for k, v in field_attributes.items():
|
|
138
|
+
element.set(k, v)
|
|
139
|
+
self._included_fields.add(field_repr)
|
|
140
|
+
return element
|
|
141
|
+
|
|
142
|
+
def _init_structure_info(self):
|
|
143
|
+
"""Create maps with all data information (fields info and class names), if not already created."""
|
|
144
|
+
if not self._is_info_extracted:
|
|
145
|
+
self._parse_data_structures(self.data)
|
|
146
|
+
self._is_info_extracted = True
|
|
147
|
+
|
|
148
|
+
def _mapping_to_xml(
|
|
149
|
+
self,
|
|
150
|
+
element: ElementTree.Element,
|
|
151
|
+
mapping: Mapping[Any, Any],
|
|
152
|
+
path: str = '',
|
|
153
|
+
) -> None:
|
|
101
154
|
for key, value in mapping.items():
|
|
102
155
|
if isinstance(key, int):
|
|
103
156
|
key = str(key)
|
|
104
157
|
elif not isinstance(key, str):
|
|
105
158
|
raise TypeError(f'Unsupported key type for XML formatting: {type(key)}, only str and int are allowed')
|
|
106
|
-
element.append(self.
|
|
159
|
+
element.append(self._to_xml(value=value, path=f'{path}.{key}' if path else key, tag=key))
|
|
160
|
+
|
|
161
|
+
def _parse_data_structures(
|
|
162
|
+
self,
|
|
163
|
+
value: Any,
|
|
164
|
+
path: str = '',
|
|
165
|
+
):
|
|
166
|
+
"""Parse data structures as dataclasses or Pydantic models to extract element names and attributes."""
|
|
167
|
+
if value is None or isinstance(value, (str | int | float | date | bytearray | bytes | bool)):
|
|
168
|
+
return
|
|
169
|
+
elif isinstance(value, Mapping):
|
|
170
|
+
for k, v in value.items(): # pyright: ignore[reportUnknownVariableType]
|
|
171
|
+
self._parse_data_structures(v, f'{path}.{k}' if path else f'{k}')
|
|
172
|
+
elif is_dataclass(value) and not isinstance(value, type):
|
|
173
|
+
self._element_names[path] = value.__class__.__name__
|
|
174
|
+
for field in fields(value):
|
|
175
|
+
new_path = f'{path}.{field.name}' if path else field.name
|
|
176
|
+
if self.include_field_info and field.metadata:
|
|
177
|
+
attributes = {k: v for k, v in field.metadata.items() if k in self._FIELD_ATTRIBUTES}
|
|
178
|
+
if attributes:
|
|
179
|
+
field_repr = f'{value.__class__.__name__}.{field.name}'
|
|
180
|
+
self._fields_info[new_path] = (field_repr, FieldInfo(**attributes))
|
|
181
|
+
self._parse_data_structures(getattr(value, field.name), new_path)
|
|
182
|
+
elif isinstance(value, BaseModel):
|
|
183
|
+
self._element_names[path] = value.__class__.__name__
|
|
184
|
+
for model_fields in (value.__class__.model_fields, value.__class__.model_computed_fields):
|
|
185
|
+
for field, info in model_fields.items():
|
|
186
|
+
new_path = f'{path}.{field}' if path else field
|
|
187
|
+
if self.include_field_info and (isinstance(info, ComputedFieldInfo) or not info.exclude):
|
|
188
|
+
field_repr = f'{value.__class__.__name__}.{field}'
|
|
189
|
+
self._fields_info[new_path] = (field_repr, info)
|
|
190
|
+
self._parse_data_structures(getattr(value, field), new_path)
|
|
191
|
+
elif isinstance(value, Iterable):
|
|
192
|
+
for n, item in enumerate(value): # pyright: ignore[reportUnknownVariableType,reportUnknownArgumentType]
|
|
193
|
+
new_path = f'{path}.[{n}]' if path else f'[{n}]'
|
|
194
|
+
self._parse_data_structures(item, new_path)
|
|
195
|
+
|
|
196
|
+
@classmethod
|
|
197
|
+
def _extract_attributes(cls, info: FieldInfo | ComputedFieldInfo) -> dict[str, str]:
|
|
198
|
+
return {attr: str(value) for attr in cls._FIELD_ATTRIBUTES if (value := getattr(info, attr, None)) is not None}
|
|
107
199
|
|
|
108
200
|
|
|
109
201
|
def _rootless_xml_elements(root: ElementTree.Element, indent: str | None) -> Iterator[str]:
|
pydantic_ai/messages.py
CHANGED
|
@@ -668,8 +668,11 @@ class BaseToolReturnPart:
|
|
|
668
668
|
content: Any
|
|
669
669
|
"""The return value."""
|
|
670
670
|
|
|
671
|
-
tool_call_id: str
|
|
672
|
-
"""The tool call identifier, this is used by some models including OpenAI.
|
|
671
|
+
tool_call_id: str = field(default_factory=_generate_tool_call_id)
|
|
672
|
+
"""The tool call identifier, this is used by some models including OpenAI.
|
|
673
|
+
|
|
674
|
+
In case the tool call id is not provided by the model, Pydantic AI will generate a random one.
|
|
675
|
+
"""
|
|
673
676
|
|
|
674
677
|
_: KW_ONLY
|
|
675
678
|
|
|
@@ -708,14 +711,16 @@ class BaseToolReturnPart:
|
|
|
708
711
|
def otel_message_parts(self, settings: InstrumentationSettings) -> list[_otel_messages.MessagePart]:
|
|
709
712
|
from .models.instrumented import InstrumentedModel
|
|
710
713
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
714
|
+
part = _otel_messages.ToolCallResponsePart(
|
|
715
|
+
type='tool_call_response',
|
|
716
|
+
id=self.tool_call_id,
|
|
717
|
+
name=self.tool_name,
|
|
718
|
+
)
|
|
719
|
+
|
|
720
|
+
if settings.include_content and self.content is not None:
|
|
721
|
+
part['result'] = InstrumentedModel.serialize_any(self.content)
|
|
722
|
+
|
|
723
|
+
return [part]
|
|
719
724
|
|
|
720
725
|
def has_content(self) -> bool:
|
|
721
726
|
"""Return `True` if the tool return has content."""
|
|
@@ -820,14 +825,16 @@ class RetryPromptPart:
|
|
|
820
825
|
if self.tool_name is None:
|
|
821
826
|
return [_otel_messages.TextPart(type='text', content=self.model_response())]
|
|
822
827
|
else:
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
828
|
+
part = _otel_messages.ToolCallResponsePart(
|
|
829
|
+
type='tool_call_response',
|
|
830
|
+
id=self.tool_call_id,
|
|
831
|
+
name=self.tool_name,
|
|
832
|
+
)
|
|
833
|
+
|
|
834
|
+
if settings.include_content:
|
|
835
|
+
part['result'] = self.model_response()
|
|
836
|
+
|
|
837
|
+
return [part]
|
|
831
838
|
|
|
832
839
|
__repr__ = _utils.dataclasses_no_defaults_repr
|
|
833
840
|
|
|
@@ -1131,8 +1138,10 @@ class ModelResponse:
|
|
|
1131
1138
|
**({'content': part.content} if settings.include_content else {}),
|
|
1132
1139
|
)
|
|
1133
1140
|
)
|
|
1134
|
-
elif isinstance(part,
|
|
1141
|
+
elif isinstance(part, BaseToolCallPart):
|
|
1135
1142
|
call_part = _otel_messages.ToolCallPart(type='tool_call', id=part.tool_call_id, name=part.tool_name)
|
|
1143
|
+
if isinstance(part, BuiltinToolCallPart):
|
|
1144
|
+
call_part['builtin'] = True
|
|
1136
1145
|
if settings.include_content and part.args is not None:
|
|
1137
1146
|
from .models.instrumented import InstrumentedModel
|
|
1138
1147
|
|
|
@@ -1142,6 +1151,19 @@ class ModelResponse:
|
|
|
1142
1151
|
call_part['arguments'] = {k: InstrumentedModel.serialize_any(v) for k, v in part.args.items()}
|
|
1143
1152
|
|
|
1144
1153
|
parts.append(call_part)
|
|
1154
|
+
elif isinstance(part, BuiltinToolReturnPart):
|
|
1155
|
+
return_part = _otel_messages.ToolCallResponsePart(
|
|
1156
|
+
type='tool_call_response',
|
|
1157
|
+
id=part.tool_call_id,
|
|
1158
|
+
name=part.tool_name,
|
|
1159
|
+
builtin=True,
|
|
1160
|
+
)
|
|
1161
|
+
if settings.include_content and part.content is not None: # pragma: no branch
|
|
1162
|
+
from .models.instrumented import InstrumentedModel
|
|
1163
|
+
|
|
1164
|
+
return_part['result'] = InstrumentedModel.serialize_any(part.content)
|
|
1165
|
+
|
|
1166
|
+
parts.append(return_part)
|
|
1145
1167
|
return parts
|
|
1146
1168
|
|
|
1147
1169
|
@property
|
|
@@ -1299,35 +1321,39 @@ class ToolCallPartDelta:
|
|
|
1299
1321
|
return ToolCallPart(self.tool_name_delta, self.args_delta, self.tool_call_id or _generate_tool_call_id())
|
|
1300
1322
|
|
|
1301
1323
|
@overload
|
|
1302
|
-
def apply(self, part: ModelResponsePart) -> ToolCallPart: ...
|
|
1324
|
+
def apply(self, part: ModelResponsePart) -> ToolCallPart | BuiltinToolCallPart: ...
|
|
1303
1325
|
|
|
1304
1326
|
@overload
|
|
1305
|
-
def apply(
|
|
1327
|
+
def apply(
|
|
1328
|
+
self, part: ModelResponsePart | ToolCallPartDelta
|
|
1329
|
+
) -> ToolCallPart | BuiltinToolCallPart | ToolCallPartDelta: ...
|
|
1306
1330
|
|
|
1307
|
-
def apply(
|
|
1331
|
+
def apply(
|
|
1332
|
+
self, part: ModelResponsePart | ToolCallPartDelta
|
|
1333
|
+
) -> ToolCallPart | BuiltinToolCallPart | ToolCallPartDelta:
|
|
1308
1334
|
"""Apply this delta to a part or delta, returning a new part or delta with the changes applied.
|
|
1309
1335
|
|
|
1310
1336
|
Args:
|
|
1311
1337
|
part: The existing model response part or delta to update.
|
|
1312
1338
|
|
|
1313
1339
|
Returns:
|
|
1314
|
-
Either a new `ToolCallPart` or an updated `ToolCallPartDelta`.
|
|
1340
|
+
Either a new `ToolCallPart` or `BuiltinToolCallPart`, or an updated `ToolCallPartDelta`.
|
|
1315
1341
|
|
|
1316
1342
|
Raises:
|
|
1317
|
-
ValueError: If `part` is neither a `ToolCallPart` nor a `ToolCallPartDelta`.
|
|
1343
|
+
ValueError: If `part` is neither a `ToolCallPart`, `BuiltinToolCallPart`, nor a `ToolCallPartDelta`.
|
|
1318
1344
|
UnexpectedModelBehavior: If applying JSON deltas to dict arguments or vice versa.
|
|
1319
1345
|
"""
|
|
1320
|
-
if isinstance(part, ToolCallPart):
|
|
1346
|
+
if isinstance(part, ToolCallPart | BuiltinToolCallPart):
|
|
1321
1347
|
return self._apply_to_part(part)
|
|
1322
1348
|
|
|
1323
1349
|
if isinstance(part, ToolCallPartDelta):
|
|
1324
1350
|
return self._apply_to_delta(part)
|
|
1325
1351
|
|
|
1326
1352
|
raise ValueError( # pragma: no cover
|
|
1327
|
-
f'Can only apply ToolCallPartDeltas to ToolCallParts or ToolCallPartDeltas, not {part}'
|
|
1353
|
+
f'Can only apply ToolCallPartDeltas to ToolCallParts, BuiltinToolCallParts, or ToolCallPartDeltas, not {part}'
|
|
1328
1354
|
)
|
|
1329
1355
|
|
|
1330
|
-
def _apply_to_delta(self, delta: ToolCallPartDelta) -> ToolCallPart | ToolCallPartDelta:
|
|
1356
|
+
def _apply_to_delta(self, delta: ToolCallPartDelta) -> ToolCallPart | BuiltinToolCallPart | ToolCallPartDelta:
|
|
1331
1357
|
"""Internal helper to apply this delta to another delta."""
|
|
1332
1358
|
if self.tool_name_delta:
|
|
1333
1359
|
# Append incremental text to the existing tool_name_delta
|
|
@@ -1358,8 +1384,8 @@ class ToolCallPartDelta:
|
|
|
1358
1384
|
|
|
1359
1385
|
return delta
|
|
1360
1386
|
|
|
1361
|
-
def _apply_to_part(self, part: ToolCallPart) -> ToolCallPart:
|
|
1362
|
-
"""Internal helper to apply this delta directly to a `ToolCallPart`."""
|
|
1387
|
+
def _apply_to_part(self, part: ToolCallPart | BuiltinToolCallPart) -> ToolCallPart | BuiltinToolCallPart:
|
|
1388
|
+
"""Internal helper to apply this delta directly to a `ToolCallPart` or `BuiltinToolCallPart`."""
|
|
1363
1389
|
if self.tool_name_delta:
|
|
1364
1390
|
# Append incremental text to the existing tool_name
|
|
1365
1391
|
tool_name = part.tool_name + self.tool_name_delta
|
|
@@ -1491,6 +1517,9 @@ class FunctionToolResultEvent:
|
|
|
1491
1517
|
__repr__ = _utils.dataclasses_no_defaults_repr
|
|
1492
1518
|
|
|
1493
1519
|
|
|
1520
|
+
@deprecated(
|
|
1521
|
+
'`BuiltinToolCallEvent` is deprecated, look for `PartStartEvent` and `PartDeltaEvent` with `BuiltinToolCallPart` instead.'
|
|
1522
|
+
)
|
|
1494
1523
|
@dataclass(repr=False)
|
|
1495
1524
|
class BuiltinToolCallEvent:
|
|
1496
1525
|
"""An event indicating the start to a call to a built-in tool."""
|
|
@@ -1504,6 +1533,9 @@ class BuiltinToolCallEvent:
|
|
|
1504
1533
|
"""Event type identifier, used as a discriminator."""
|
|
1505
1534
|
|
|
1506
1535
|
|
|
1536
|
+
@deprecated(
|
|
1537
|
+
'`BuiltinToolResultEvent` is deprecated, look for `PartStartEvent` and `PartDeltaEvent` with `BuiltinToolReturnPart` instead.'
|
|
1538
|
+
)
|
|
1507
1539
|
@dataclass(repr=False)
|
|
1508
1540
|
class BuiltinToolResultEvent:
|
|
1509
1541
|
"""An event indicating the result of a built-in tool call."""
|
|
@@ -1518,7 +1550,10 @@ class BuiltinToolResultEvent:
|
|
|
1518
1550
|
|
|
1519
1551
|
|
|
1520
1552
|
HandleResponseEvent = Annotated[
|
|
1521
|
-
FunctionToolCallEvent
|
|
1553
|
+
FunctionToolCallEvent
|
|
1554
|
+
| FunctionToolResultEvent
|
|
1555
|
+
| BuiltinToolCallEvent # pyright: ignore[reportDeprecated]
|
|
1556
|
+
| BuiltinToolResultEvent, # pyright: ignore[reportDeprecated]
|
|
1522
1557
|
pydantic.Discriminator('event_kind'),
|
|
1523
1558
|
]
|
|
1524
1559
|
"""An event yielded when handling a model response, indicating tool calls and results."""
|