anthropic 0.76.0__py3-none-any.whl → 0.77.1__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.
- anthropic/_base_client.py +5 -2
- anthropic/_compat.py +3 -3
- anthropic/_utils/_json.py +35 -0
- anthropic/_version.py +1 -1
- anthropic/lib/_parse/_response.py +29 -1
- anthropic/lib/streaming/__init__.py +3 -0
- anthropic/lib/streaming/_messages.py +74 -40
- anthropic/lib/streaming/_types.py +42 -2
- anthropic/resources/beta/messages/messages.py +170 -59
- anthropic/resources/messages/messages.py +407 -5
- anthropic/types/__init__.py +7 -0
- anthropic/types/beta/beta_code_execution_tool_20250522_param.py +1 -0
- anthropic/types/beta/beta_code_execution_tool_20250825_param.py +1 -0
- anthropic/types/beta/beta_memory_tool_20250818_param.py +1 -0
- anthropic/types/beta/beta_output_config_param.py +15 -1
- anthropic/types/beta/beta_server_tool_use_block.py +4 -4
- anthropic/types/beta/beta_tool_bash_20241022_param.py +1 -0
- anthropic/types/beta/beta_tool_bash_20250124_param.py +1 -0
- anthropic/types/beta/beta_tool_computer_use_20241022_param.py +1 -0
- anthropic/types/beta/beta_tool_computer_use_20250124_param.py +1 -0
- anthropic/types/beta/beta_tool_computer_use_20251124_param.py +1 -0
- anthropic/types/beta/beta_tool_param.py +1 -0
- anthropic/types/beta/beta_tool_search_tool_bm25_20251119_param.py +1 -0
- anthropic/types/beta/beta_tool_search_tool_regex_20251119_param.py +1 -0
- anthropic/types/beta/beta_tool_text_editor_20241022_param.py +1 -0
- anthropic/types/beta/beta_tool_text_editor_20250124_param.py +1 -0
- anthropic/types/beta/beta_tool_text_editor_20250429_param.py +1 -0
- anthropic/types/beta/beta_tool_text_editor_20250728_param.py +1 -0
- anthropic/types/beta/beta_web_fetch_tool_20250910_param.py +1 -0
- anthropic/types/beta/beta_web_search_tool_20250305_param.py +1 -0
- anthropic/types/beta/beta_web_search_tool_result_error_code.py +1 -1
- anthropic/types/beta/message_count_tokens_params.py +9 -5
- anthropic/types/beta/message_create_params.py +9 -5
- anthropic/types/beta/messages/batch_create_params.py +2 -9
- anthropic/types/json_output_format_param.py +15 -0
- anthropic/types/message_count_tokens_params.py +4 -0
- anthropic/types/message_create_params.py +4 -0
- anthropic/types/output_config_param.py +19 -0
- anthropic/types/parsed_message.py +56 -0
- anthropic/types/tool_bash_20250124_param.py +3 -0
- anthropic/types/tool_param.py +3 -0
- anthropic/types/tool_text_editor_20250124_param.py +3 -0
- anthropic/types/tool_text_editor_20250429_param.py +3 -0
- anthropic/types/tool_text_editor_20250728_param.py +3 -0
- anthropic/types/web_search_tool_20250305_param.py +3 -0
- anthropic/types/web_search_tool_request_error_param.py +8 -1
- anthropic/types/web_search_tool_result_error.py +8 -1
- {anthropic-0.76.0.dist-info → anthropic-0.77.1.dist-info}/METADATA +1 -1
- {anthropic-0.76.0.dist-info → anthropic-0.77.1.dist-info}/RECORD +51 -47
- {anthropic-0.76.0.dist-info → anthropic-0.77.1.dist-info}/WHEEL +0 -0
- {anthropic-0.76.0.dist-info → anthropic-0.77.1.dist-info}/licenses/LICENSE +0 -0
anthropic/_base_client.py
CHANGED
|
@@ -89,6 +89,7 @@ from ._exceptions import (
|
|
|
89
89
|
APIConnectionError,
|
|
90
90
|
APIResponseValidationError,
|
|
91
91
|
)
|
|
92
|
+
from ._utils._json import openapi_dumps
|
|
92
93
|
from ._utils._httpx import get_environment_proxies
|
|
93
94
|
from ._legacy_response import LegacyAPIResponse
|
|
94
95
|
|
|
@@ -567,8 +568,10 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]):
|
|
|
567
568
|
kwargs["content"] = options.content
|
|
568
569
|
elif isinstance(json_data, bytes):
|
|
569
570
|
kwargs["content"] = json_data
|
|
570
|
-
|
|
571
|
-
|
|
571
|
+
elif not files:
|
|
572
|
+
# Don't set content when JSON is sent as multipart/form-data,
|
|
573
|
+
# since httpx's content param overrides other body arguments
|
|
574
|
+
kwargs["content"] = openapi_dumps(json_data) if is_given(json_data) and json_data is not None else None
|
|
572
575
|
kwargs["files"] = files
|
|
573
576
|
else:
|
|
574
577
|
headers.pop("Content-Type", None)
|
anthropic/_compat.py
CHANGED
|
@@ -145,6 +145,7 @@ def model_dump(
|
|
|
145
145
|
exclude_defaults: bool = False,
|
|
146
146
|
warnings: bool = True,
|
|
147
147
|
mode: Literal["json", "python"] = "python",
|
|
148
|
+
by_alias: bool | None = None,
|
|
148
149
|
) -> dict[str, Any]:
|
|
149
150
|
if (not PYDANTIC_V1) or hasattr(model, "model_dump"):
|
|
150
151
|
return model.model_dump(
|
|
@@ -154,13 +155,12 @@ def model_dump(
|
|
|
154
155
|
exclude_defaults=exclude_defaults,
|
|
155
156
|
# warnings are not supported in Pydantic v1
|
|
156
157
|
warnings=True if PYDANTIC_V1 else warnings,
|
|
158
|
+
by_alias=by_alias,
|
|
157
159
|
)
|
|
158
160
|
return cast(
|
|
159
161
|
"dict[str, Any]",
|
|
160
162
|
model.dict( # pyright: ignore[reportDeprecated, reportUnnecessaryCast]
|
|
161
|
-
exclude=exclude,
|
|
162
|
-
exclude_unset=exclude_unset,
|
|
163
|
-
exclude_defaults=exclude_defaults,
|
|
163
|
+
exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, by_alias=bool(by_alias)
|
|
164
164
|
),
|
|
165
165
|
)
|
|
166
166
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Any
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing_extensions import override
|
|
5
|
+
|
|
6
|
+
import pydantic
|
|
7
|
+
|
|
8
|
+
from .._compat import model_dump
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def openapi_dumps(obj: Any) -> bytes:
|
|
12
|
+
"""
|
|
13
|
+
Serialize an object to UTF-8 encoded JSON bytes.
|
|
14
|
+
|
|
15
|
+
Extends the standard json.dumps with support for additional types
|
|
16
|
+
commonly used in the SDK, such as `datetime`, `pydantic.BaseModel`, etc.
|
|
17
|
+
"""
|
|
18
|
+
return json.dumps(
|
|
19
|
+
obj,
|
|
20
|
+
cls=_CustomEncoder,
|
|
21
|
+
# Uses the same defaults as httpx's JSON serialization
|
|
22
|
+
ensure_ascii=False,
|
|
23
|
+
separators=(",", ":"),
|
|
24
|
+
allow_nan=False,
|
|
25
|
+
).encode()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class _CustomEncoder(json.JSONEncoder):
|
|
29
|
+
@override
|
|
30
|
+
def default(self, o: Any) -> Any:
|
|
31
|
+
if isinstance(o, datetime):
|
|
32
|
+
return o.isoformat()
|
|
33
|
+
if isinstance(o, pydantic.BaseModel):
|
|
34
|
+
return model_dump(o, exclude_unset=True, mode="json", by_alias=True)
|
|
35
|
+
return super().default(o)
|
anthropic/_version.py
CHANGED
|
@@ -5,6 +5,8 @@ from typing_extensions import TypeVar
|
|
|
5
5
|
from ..._types import NotGiven
|
|
6
6
|
from ..._models import TypeAdapter, construct_type_unchecked
|
|
7
7
|
from ..._utils._utils import is_given
|
|
8
|
+
from ...types.message import Message
|
|
9
|
+
from ...types.parsed_message import ParsedMessage, ParsedTextBlock, ParsedContentBlock
|
|
8
10
|
from ...types.beta.beta_message import BetaMessage
|
|
9
11
|
from ...types.beta.parsed_beta_message import ParsedBetaMessage, ParsedBetaTextBlock, ParsedBetaContentBlock
|
|
10
12
|
|
|
@@ -18,7 +20,7 @@ def parse_text(text: str, output_format: ResponseFormatT | NotGiven) -> Response
|
|
|
18
20
|
return None
|
|
19
21
|
|
|
20
22
|
|
|
21
|
-
def
|
|
23
|
+
def parse_beta_response(
|
|
22
24
|
*,
|
|
23
25
|
output_format: ResponseFormatT | NotGiven,
|
|
24
26
|
response: BetaMessage,
|
|
@@ -42,3 +44,29 @@ def parse_response(
|
|
|
42
44
|
"content": content_list,
|
|
43
45
|
},
|
|
44
46
|
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def parse_response(
|
|
50
|
+
*,
|
|
51
|
+
output_format: ResponseFormatT | NotGiven,
|
|
52
|
+
response: Message,
|
|
53
|
+
) -> ParsedMessage[ResponseFormatT]:
|
|
54
|
+
content_list: list[ParsedContentBlock[ResponseFormatT]] = []
|
|
55
|
+
for content in response.content:
|
|
56
|
+
if content.type == "text":
|
|
57
|
+
content_list.append(
|
|
58
|
+
construct_type_unchecked(
|
|
59
|
+
type_=ParsedTextBlock[ResponseFormatT],
|
|
60
|
+
value={**content.to_dict(), "parsed_output": parse_text(content.text, output_format)},
|
|
61
|
+
)
|
|
62
|
+
)
|
|
63
|
+
else:
|
|
64
|
+
content_list.append(content) # type: ignore
|
|
65
|
+
|
|
66
|
+
return construct_type_unchecked(
|
|
67
|
+
type_=ParsedMessage[ResponseFormatT],
|
|
68
|
+
value={
|
|
69
|
+
**response.to_dict(),
|
|
70
|
+
"content": content_list,
|
|
71
|
+
},
|
|
72
|
+
)
|
|
@@ -6,6 +6,9 @@ from ._types import (
|
|
|
6
6
|
MessageStopEvent as MessageStopEvent,
|
|
7
7
|
MessageStreamEvent as MessageStreamEvent,
|
|
8
8
|
ContentBlockStopEvent as ContentBlockStopEvent,
|
|
9
|
+
ParsedMessageStopEvent as ParsedMessageStopEvent,
|
|
10
|
+
ParsedMessageStreamEvent as ParsedMessageStreamEvent,
|
|
11
|
+
ParsedContentBlockStopEvent as ParsedContentBlockStopEvent,
|
|
9
12
|
)
|
|
10
13
|
from ._messages import (
|
|
11
14
|
MessageStream as MessageStream,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from types import TracebackType
|
|
4
|
-
from typing import TYPE_CHECKING, Any, Type, Callable, cast
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Type, Generic, Callable, cast
|
|
5
5
|
from typing_extensions import Self, Iterator, Awaitable, AsyncIterator, assert_never
|
|
6
6
|
|
|
7
7
|
import httpx
|
|
@@ -16,17 +16,21 @@ from ._types import (
|
|
|
16
16
|
ThinkingEvent,
|
|
17
17
|
InputJsonEvent,
|
|
18
18
|
SignatureEvent,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
ParsedMessageStopEvent,
|
|
20
|
+
ParsedMessageStreamEvent,
|
|
21
|
+
ParsedContentBlockStopEvent,
|
|
22
22
|
)
|
|
23
|
-
from ...types import
|
|
23
|
+
from ...types import RawMessageStreamEvent
|
|
24
|
+
from ..._types import NOT_GIVEN, NotGiven
|
|
24
25
|
from ..._utils import consume_sync_iterator, consume_async_iterator
|
|
25
26
|
from ..._models import build, construct_type, construct_type_unchecked
|
|
26
27
|
from ..._streaming import Stream, AsyncStream
|
|
28
|
+
from ..._utils._utils import is_given
|
|
29
|
+
from .._parse._response import ResponseFormatT, parse_text
|
|
30
|
+
from ...types.parsed_message import ParsedMessage, ParsedContentBlock
|
|
27
31
|
|
|
28
32
|
|
|
29
|
-
class MessageStream:
|
|
33
|
+
class MessageStream(Generic[ResponseFormatT]):
|
|
30
34
|
text_stream: Iterator[str]
|
|
31
35
|
"""Iterator over just the text deltas in the stream.
|
|
32
36
|
|
|
@@ -37,11 +41,16 @@ class MessageStream:
|
|
|
37
41
|
```
|
|
38
42
|
"""
|
|
39
43
|
|
|
40
|
-
def __init__(
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
raw_stream: Stream[RawMessageStreamEvent],
|
|
47
|
+
output_format: ResponseFormatT | NotGiven,
|
|
48
|
+
) -> None:
|
|
41
49
|
self._raw_stream = raw_stream
|
|
42
50
|
self.text_stream = self.__stream_text__()
|
|
43
51
|
self._iterator = self.__stream__()
|
|
44
|
-
self.__final_message_snapshot:
|
|
52
|
+
self.__final_message_snapshot: ParsedMessage[ResponseFormatT] | None = None
|
|
53
|
+
self.__output_format = output_format
|
|
45
54
|
|
|
46
55
|
@property
|
|
47
56
|
def response(self) -> httpx.Response:
|
|
@@ -51,10 +60,10 @@ class MessageStream:
|
|
|
51
60
|
def request_id(self) -> str | None:
|
|
52
61
|
return self.response.headers.get("request-id") # type: ignore[no-any-return]
|
|
53
62
|
|
|
54
|
-
def __next__(self) ->
|
|
63
|
+
def __next__(self) -> ParsedMessageStreamEvent[ResponseFormatT]:
|
|
55
64
|
return self._iterator.__next__()
|
|
56
65
|
|
|
57
|
-
def __iter__(self) -> Iterator[
|
|
66
|
+
def __iter__(self) -> Iterator[ParsedMessageStreamEvent[ResponseFormatT]]:
|
|
58
67
|
for item in self._iterator:
|
|
59
68
|
yield item
|
|
60
69
|
|
|
@@ -77,7 +86,7 @@ class MessageStream:
|
|
|
77
86
|
"""
|
|
78
87
|
self._raw_stream.close()
|
|
79
88
|
|
|
80
|
-
def get_final_message(self) ->
|
|
89
|
+
def get_final_message(self) -> ParsedMessage[ResponseFormatT]:
|
|
81
90
|
"""Waits until the stream has been read to completion and returns
|
|
82
91
|
the accumulated `Message` object.
|
|
83
92
|
"""
|
|
@@ -112,15 +121,16 @@ class MessageStream:
|
|
|
112
121
|
|
|
113
122
|
# properties
|
|
114
123
|
@property
|
|
115
|
-
def current_message_snapshot(self) ->
|
|
124
|
+
def current_message_snapshot(self) -> ParsedMessage[ResponseFormatT]:
|
|
116
125
|
assert self.__final_message_snapshot is not None
|
|
117
126
|
return self.__final_message_snapshot
|
|
118
127
|
|
|
119
|
-
def __stream__(self) -> Iterator[
|
|
128
|
+
def __stream__(self) -> Iterator[ParsedMessageStreamEvent[ResponseFormatT]]:
|
|
120
129
|
for sse_event in self._raw_stream:
|
|
121
130
|
self.__final_message_snapshot = accumulate_event(
|
|
122
131
|
event=sse_event,
|
|
123
132
|
current_snapshot=self.__final_message_snapshot,
|
|
133
|
+
output_format=self.__output_format,
|
|
124
134
|
)
|
|
125
135
|
|
|
126
136
|
events_to_fire = build_events(event=sse_event, message_snapshot=self.current_message_snapshot)
|
|
@@ -133,7 +143,7 @@ class MessageStream:
|
|
|
133
143
|
yield chunk.delta.text
|
|
134
144
|
|
|
135
145
|
|
|
136
|
-
class MessageStreamManager:
|
|
146
|
+
class MessageStreamManager(Generic[ResponseFormatT]):
|
|
137
147
|
"""Wrapper over MessageStream that is returned by `.stream()`.
|
|
138
148
|
|
|
139
149
|
```py
|
|
@@ -146,13 +156,16 @@ class MessageStreamManager:
|
|
|
146
156
|
def __init__(
|
|
147
157
|
self,
|
|
148
158
|
api_request: Callable[[], Stream[RawMessageStreamEvent]],
|
|
159
|
+
*,
|
|
160
|
+
output_format: ResponseFormatT | NotGiven,
|
|
149
161
|
) -> None:
|
|
150
|
-
self.__stream: MessageStream | None = None
|
|
162
|
+
self.__stream: MessageStream[ResponseFormatT] | None = None
|
|
151
163
|
self.__api_request = api_request
|
|
164
|
+
self.__output_format = output_format
|
|
152
165
|
|
|
153
|
-
def __enter__(self) -> MessageStream:
|
|
166
|
+
def __enter__(self) -> MessageStream[ResponseFormatT]:
|
|
154
167
|
raw_stream = self.__api_request()
|
|
155
|
-
self.__stream = MessageStream(raw_stream)
|
|
168
|
+
self.__stream = MessageStream(raw_stream, output_format=self.__output_format)
|
|
156
169
|
return self.__stream
|
|
157
170
|
|
|
158
171
|
def __exit__(
|
|
@@ -165,7 +178,7 @@ class MessageStreamManager:
|
|
|
165
178
|
self.__stream.close()
|
|
166
179
|
|
|
167
180
|
|
|
168
|
-
class AsyncMessageStream:
|
|
181
|
+
class AsyncMessageStream(Generic[ResponseFormatT]):
|
|
169
182
|
text_stream: AsyncIterator[str]
|
|
170
183
|
"""Async iterator over just the text deltas in the stream.
|
|
171
184
|
|
|
@@ -176,11 +189,16 @@ class AsyncMessageStream:
|
|
|
176
189
|
```
|
|
177
190
|
"""
|
|
178
191
|
|
|
179
|
-
def __init__(
|
|
192
|
+
def __init__(
|
|
193
|
+
self,
|
|
194
|
+
raw_stream: AsyncStream[RawMessageStreamEvent],
|
|
195
|
+
output_format: ResponseFormatT | NotGiven,
|
|
196
|
+
) -> None:
|
|
180
197
|
self._raw_stream = raw_stream
|
|
181
198
|
self.text_stream = self.__stream_text__()
|
|
182
199
|
self._iterator = self.__stream__()
|
|
183
|
-
self.__final_message_snapshot:
|
|
200
|
+
self.__final_message_snapshot: ParsedMessage[ResponseFormatT] | None = None
|
|
201
|
+
self.__output_format = output_format
|
|
184
202
|
|
|
185
203
|
@property
|
|
186
204
|
def response(self) -> httpx.Response:
|
|
@@ -190,10 +208,10 @@ class AsyncMessageStream:
|
|
|
190
208
|
def request_id(self) -> str | None:
|
|
191
209
|
return self.response.headers.get("request-id") # type: ignore[no-any-return]
|
|
192
210
|
|
|
193
|
-
async def __anext__(self) ->
|
|
211
|
+
async def __anext__(self) -> ParsedMessageStreamEvent[ResponseFormatT]:
|
|
194
212
|
return await self._iterator.__anext__()
|
|
195
213
|
|
|
196
|
-
async def __aiter__(self) -> AsyncIterator[
|
|
214
|
+
async def __aiter__(self) -> AsyncIterator[ParsedMessageStreamEvent[ResponseFormatT]]:
|
|
197
215
|
async for item in self._iterator:
|
|
198
216
|
yield item
|
|
199
217
|
|
|
@@ -216,7 +234,7 @@ class AsyncMessageStream:
|
|
|
216
234
|
"""
|
|
217
235
|
await self._raw_stream.close()
|
|
218
236
|
|
|
219
|
-
async def get_final_message(self) ->
|
|
237
|
+
async def get_final_message(self) -> ParsedMessage[ResponseFormatT]:
|
|
220
238
|
"""Waits until the stream has been read to completion and returns
|
|
221
239
|
the accumulated `Message` object.
|
|
222
240
|
"""
|
|
@@ -251,15 +269,16 @@ class AsyncMessageStream:
|
|
|
251
269
|
|
|
252
270
|
# properties
|
|
253
271
|
@property
|
|
254
|
-
def current_message_snapshot(self) ->
|
|
272
|
+
def current_message_snapshot(self) -> ParsedMessage[ResponseFormatT]:
|
|
255
273
|
assert self.__final_message_snapshot is not None
|
|
256
274
|
return self.__final_message_snapshot
|
|
257
275
|
|
|
258
|
-
async def __stream__(self) -> AsyncIterator[
|
|
276
|
+
async def __stream__(self) -> AsyncIterator[ParsedMessageStreamEvent[ResponseFormatT]]:
|
|
259
277
|
async for sse_event in self._raw_stream:
|
|
260
278
|
self.__final_message_snapshot = accumulate_event(
|
|
261
279
|
event=sse_event,
|
|
262
280
|
current_snapshot=self.__final_message_snapshot,
|
|
281
|
+
output_format=self.__output_format,
|
|
263
282
|
)
|
|
264
283
|
|
|
265
284
|
events_to_fire = build_events(event=sse_event, message_snapshot=self.current_message_snapshot)
|
|
@@ -272,7 +291,7 @@ class AsyncMessageStream:
|
|
|
272
291
|
yield chunk.delta.text
|
|
273
292
|
|
|
274
293
|
|
|
275
|
-
class AsyncMessageStreamManager:
|
|
294
|
+
class AsyncMessageStreamManager(Generic[ResponseFormatT]):
|
|
276
295
|
"""Wrapper over AsyncMessageStream that is returned by `.stream()`
|
|
277
296
|
so that an async context manager can be used without `await`ing the
|
|
278
297
|
original client call.
|
|
@@ -287,13 +306,16 @@ class AsyncMessageStreamManager:
|
|
|
287
306
|
def __init__(
|
|
288
307
|
self,
|
|
289
308
|
api_request: Awaitable[AsyncStream[RawMessageStreamEvent]],
|
|
309
|
+
*,
|
|
310
|
+
output_format: ResponseFormatT | NotGiven = NOT_GIVEN,
|
|
290
311
|
) -> None:
|
|
291
|
-
self.__stream: AsyncMessageStream | None = None
|
|
312
|
+
self.__stream: AsyncMessageStream[ResponseFormatT] | None = None
|
|
292
313
|
self.__api_request = api_request
|
|
314
|
+
self.__output_format = output_format
|
|
293
315
|
|
|
294
|
-
async def __aenter__(self) -> AsyncMessageStream:
|
|
316
|
+
async def __aenter__(self) -> AsyncMessageStream[ResponseFormatT]:
|
|
295
317
|
raw_stream = await self.__api_request
|
|
296
|
-
self.__stream = AsyncMessageStream(raw_stream)
|
|
318
|
+
self.__stream = AsyncMessageStream(raw_stream, output_format=self.__output_format)
|
|
297
319
|
return self.__stream
|
|
298
320
|
|
|
299
321
|
async def __aexit__(
|
|
@@ -309,16 +331,18 @@ class AsyncMessageStreamManager:
|
|
|
309
331
|
def build_events(
|
|
310
332
|
*,
|
|
311
333
|
event: RawMessageStreamEvent,
|
|
312
|
-
message_snapshot:
|
|
313
|
-
) -> list[
|
|
314
|
-
events_to_fire: list[
|
|
334
|
+
message_snapshot: ParsedMessage[ResponseFormatT],
|
|
335
|
+
) -> list[ParsedMessageStreamEvent[ResponseFormatT]]:
|
|
336
|
+
events_to_fire: list[ParsedMessageStreamEvent[ResponseFormatT]] = []
|
|
315
337
|
|
|
316
338
|
if event.type == "message_start":
|
|
317
339
|
events_to_fire.append(event)
|
|
318
340
|
elif event.type == "message_delta":
|
|
319
341
|
events_to_fire.append(event)
|
|
320
342
|
elif event.type == "message_stop":
|
|
321
|
-
events_to_fire.append(
|
|
343
|
+
events_to_fire.append(
|
|
344
|
+
build(ParsedMessageStopEvent[ResponseFormatT], type="message_stop", message=message_snapshot)
|
|
345
|
+
)
|
|
322
346
|
elif event.type == "content_block_start":
|
|
323
347
|
events_to_fire.append(event)
|
|
324
348
|
elif event.type == "content_block_delta":
|
|
@@ -382,9 +406,14 @@ def build_events(
|
|
|
382
406
|
elif event.type == "content_block_stop":
|
|
383
407
|
content_block = message_snapshot.content[event.index]
|
|
384
408
|
|
|
385
|
-
|
|
386
|
-
|
|
409
|
+
event_to_fire = build(
|
|
410
|
+
ParsedContentBlockStopEvent,
|
|
411
|
+
type="content_block_stop",
|
|
412
|
+
index=event.index,
|
|
413
|
+
content_block=content_block,
|
|
387
414
|
)
|
|
415
|
+
|
|
416
|
+
events_to_fire.append(event_to_fire)
|
|
388
417
|
else:
|
|
389
418
|
# we only want exhaustive checking for linters, not at runtime
|
|
390
419
|
if TYPE_CHECKING: # type: ignore[unreachable]
|
|
@@ -404,8 +433,9 @@ TRACKS_TOOL_INPUT = (
|
|
|
404
433
|
def accumulate_event(
|
|
405
434
|
*,
|
|
406
435
|
event: RawMessageStreamEvent,
|
|
407
|
-
current_snapshot:
|
|
408
|
-
|
|
436
|
+
current_snapshot: ParsedMessage[ResponseFormatT] | None,
|
|
437
|
+
output_format: ResponseFormatT | NotGiven = NOT_GIVEN,
|
|
438
|
+
) -> ParsedMessage[ResponseFormatT]:
|
|
409
439
|
if not isinstance(cast(Any, event), BaseModel):
|
|
410
440
|
event = cast( # pyright: ignore[reportUnnecessaryCast]
|
|
411
441
|
RawMessageStreamEvent,
|
|
@@ -419,7 +449,7 @@ def accumulate_event(
|
|
|
419
449
|
|
|
420
450
|
if current_snapshot is None:
|
|
421
451
|
if event.type == "message_start":
|
|
422
|
-
return
|
|
452
|
+
return cast(ParsedMessage[ResponseFormatT], ParsedMessage.construct(**cast(Any, event.message.to_dict())))
|
|
423
453
|
|
|
424
454
|
raise RuntimeError(f'Unexpected event order, got {event.type} before "message_start"')
|
|
425
455
|
|
|
@@ -427,8 +457,8 @@ def accumulate_event(
|
|
|
427
457
|
# TODO: check index
|
|
428
458
|
current_snapshot.content.append(
|
|
429
459
|
cast(
|
|
430
|
-
|
|
431
|
-
construct_type(type_=
|
|
460
|
+
Any, # Pydantic does not support generic unions at runtime
|
|
461
|
+
construct_type(type_=ParsedContentBlock, value=event.content_block.model_dump()),
|
|
432
462
|
),
|
|
433
463
|
)
|
|
434
464
|
elif event.type == "content_block_delta":
|
|
@@ -466,6 +496,10 @@ def accumulate_event(
|
|
|
466
496
|
# we only want exhaustive checking for linters, not at runtime
|
|
467
497
|
if TYPE_CHECKING: # type: ignore[unreachable]
|
|
468
498
|
assert_never(event.delta)
|
|
499
|
+
elif event.type == "content_block_stop":
|
|
500
|
+
content_block = current_snapshot.content[event.index]
|
|
501
|
+
if content_block.type == "text" and is_given(output_format):
|
|
502
|
+
content_block.parsed_output = parse_text(content_block.text, output_format)
|
|
469
503
|
elif event.type == "message_delta":
|
|
470
504
|
current_snapshot.stop_reason = event.delta.stop_reason
|
|
471
505
|
current_snapshot.stop_sequence = event.delta.stop_sequence
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
from typing import Union
|
|
1
|
+
from typing import TYPE_CHECKING, Any, Dict, Union, Generic, cast
|
|
2
2
|
from typing_extensions import List, Literal, Annotated
|
|
3
3
|
|
|
4
|
+
import jiter
|
|
5
|
+
|
|
4
6
|
from ...types import (
|
|
5
7
|
Message,
|
|
6
8
|
ContentBlock,
|
|
@@ -11,8 +13,10 @@ from ...types import (
|
|
|
11
13
|
ContentBlockStartEvent as RawContentBlockStartEvent,
|
|
12
14
|
RawContentBlockStopEvent,
|
|
13
15
|
)
|
|
14
|
-
from ..._models import BaseModel
|
|
16
|
+
from ..._models import BaseModel, GenericModel
|
|
17
|
+
from .._parse._response import ResponseFormatT
|
|
15
18
|
from ..._utils._transform import PropertyInfo
|
|
19
|
+
from ...types.parsed_message import ParsedMessage, ParsedContentBlock
|
|
16
20
|
from ...types.citations_delta import Citation
|
|
17
21
|
|
|
18
22
|
|
|
@@ -25,6 +29,9 @@ class TextEvent(BaseModel):
|
|
|
25
29
|
snapshot: str
|
|
26
30
|
"""The entire accumulated text"""
|
|
27
31
|
|
|
32
|
+
def parsed_snapshot(self) -> Dict[str, Any]:
|
|
33
|
+
return cast(Dict[str, Any], jiter.from_json(self.snapshot.encode("utf-8"), partial_mode="trailing-strings"))
|
|
34
|
+
|
|
28
35
|
|
|
29
36
|
class CitationEvent(BaseModel):
|
|
30
37
|
type: Literal["citation"]
|
|
@@ -98,3 +105,36 @@ MessageStreamEvent = Annotated[
|
|
|
98
105
|
],
|
|
99
106
|
PropertyInfo(discriminator="type"),
|
|
100
107
|
]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class ParsedMessageStopEvent(RawMessageStopEvent, GenericModel, Generic[ResponseFormatT]):
|
|
111
|
+
type: Literal["message_stop"]
|
|
112
|
+
|
|
113
|
+
message: ParsedMessage[ResponseFormatT]
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class ParsedContentBlockStopEvent(RawContentBlockStopEvent, GenericModel, Generic[ResponseFormatT]):
|
|
117
|
+
type: Literal["content_block_stop"]
|
|
118
|
+
|
|
119
|
+
if TYPE_CHECKING:
|
|
120
|
+
content_block: ParsedContentBlock[ResponseFormatT]
|
|
121
|
+
else:
|
|
122
|
+
content_block: ParsedContentBlock
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
ParsedMessageStreamEvent = Annotated[
|
|
126
|
+
Union[
|
|
127
|
+
TextEvent,
|
|
128
|
+
CitationEvent,
|
|
129
|
+
ThinkingEvent,
|
|
130
|
+
SignatureEvent,
|
|
131
|
+
InputJsonEvent,
|
|
132
|
+
RawMessageStartEvent,
|
|
133
|
+
RawMessageDeltaEvent,
|
|
134
|
+
ParsedMessageStopEvent[ResponseFormatT],
|
|
135
|
+
RawContentBlockStartEvent,
|
|
136
|
+
RawContentBlockDeltaEvent,
|
|
137
|
+
ParsedContentBlockStopEvent[ResponseFormatT],
|
|
138
|
+
],
|
|
139
|
+
PropertyInfo(discriminator="type"),
|
|
140
|
+
]
|