mirascope 2.0.0a3__py3-none-any.whl → 2.0.0a5__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.
- mirascope/api/_generated/__init__.py +78 -6
- mirascope/api/_generated/api_keys/__init__.py +7 -0
- mirascope/api/_generated/api_keys/client.py +453 -0
- mirascope/api/_generated/api_keys/raw_client.py +853 -0
- mirascope/api/_generated/api_keys/types/__init__.py +9 -0
- mirascope/api/_generated/api_keys/types/api_keys_create_response.py +36 -0
- mirascope/api/_generated/api_keys/types/api_keys_get_response.py +35 -0
- mirascope/api/_generated/api_keys/types/api_keys_list_response_item.py +35 -0
- mirascope/api/_generated/client.py +14 -0
- mirascope/api/_generated/environments/__init__.py +17 -0
- mirascope/api/_generated/environments/client.py +532 -0
- mirascope/api/_generated/environments/raw_client.py +1088 -0
- mirascope/api/_generated/environments/types/__init__.py +15 -0
- mirascope/api/_generated/environments/types/environments_create_response.py +26 -0
- mirascope/api/_generated/environments/types/environments_get_response.py +26 -0
- mirascope/api/_generated/environments/types/environments_list_response_item.py +26 -0
- mirascope/api/_generated/environments/types/environments_update_response.py +26 -0
- mirascope/api/_generated/errors/__init__.py +11 -1
- mirascope/api/_generated/errors/conflict_error.py +15 -0
- mirascope/api/_generated/errors/forbidden_error.py +15 -0
- mirascope/api/_generated/errors/internal_server_error.py +15 -0
- mirascope/api/_generated/errors/not_found_error.py +15 -0
- mirascope/api/_generated/organizations/__init__.py +25 -0
- mirascope/api/_generated/organizations/client.py +404 -0
- mirascope/api/_generated/organizations/raw_client.py +902 -0
- mirascope/api/_generated/organizations/types/__init__.py +23 -0
- mirascope/api/_generated/organizations/types/organizations_create_response.py +25 -0
- mirascope/api/_generated/organizations/types/organizations_create_response_role.py +7 -0
- mirascope/api/_generated/organizations/types/organizations_get_response.py +25 -0
- mirascope/api/_generated/organizations/types/organizations_get_response_role.py +7 -0
- mirascope/api/_generated/organizations/types/organizations_list_response_item.py +25 -0
- mirascope/api/_generated/organizations/types/organizations_list_response_item_role.py +7 -0
- mirascope/api/_generated/organizations/types/organizations_update_response.py +25 -0
- mirascope/api/_generated/organizations/types/organizations_update_response_role.py +7 -0
- mirascope/api/_generated/projects/__init__.py +17 -0
- mirascope/api/_generated/projects/client.py +482 -0
- mirascope/api/_generated/projects/raw_client.py +1058 -0
- mirascope/api/_generated/projects/types/__init__.py +15 -0
- mirascope/api/_generated/projects/types/projects_create_response.py +31 -0
- mirascope/api/_generated/projects/types/projects_get_response.py +31 -0
- mirascope/api/_generated/projects/types/projects_list_response_item.py +31 -0
- mirascope/api/_generated/projects/types/projects_update_response.py +31 -0
- mirascope/api/_generated/reference.md +1311 -0
- mirascope/api/_generated/types/__init__.py +20 -4
- mirascope/api/_generated/types/already_exists_error.py +24 -0
- mirascope/api/_generated/types/already_exists_error_tag.py +5 -0
- mirascope/api/_generated/types/database_error.py +24 -0
- mirascope/api/_generated/types/database_error_tag.py +5 -0
- mirascope/api/_generated/types/http_api_decode_error.py +1 -3
- mirascope/api/_generated/types/issue.py +1 -5
- mirascope/api/_generated/types/not_found_error_body.py +24 -0
- mirascope/api/_generated/types/not_found_error_tag.py +5 -0
- mirascope/api/_generated/types/permission_denied_error.py +24 -0
- mirascope/api/_generated/types/permission_denied_error_tag.py +7 -0
- mirascope/api/_generated/types/property_key.py +2 -2
- mirascope/api/_generated/types/{property_key_tag.py → property_key_key.py} +3 -5
- mirascope/api/_generated/types/{property_key_tag_tag.py → property_key_key_tag.py} +1 -1
- mirascope/llm/__init__.py +6 -2
- mirascope/llm/exceptions.py +28 -0
- mirascope/llm/providers/__init__.py +12 -4
- mirascope/llm/providers/anthropic/__init__.py +6 -1
- mirascope/llm/providers/anthropic/_utils/__init__.py +17 -5
- mirascope/llm/providers/anthropic/_utils/beta_decode.py +271 -0
- mirascope/llm/providers/anthropic/_utils/beta_encode.py +216 -0
- mirascope/llm/providers/anthropic/_utils/decode.py +39 -7
- mirascope/llm/providers/anthropic/_utils/encode.py +156 -64
- mirascope/llm/providers/anthropic/_utils/errors.py +46 -0
- mirascope/llm/providers/anthropic/beta_provider.py +328 -0
- mirascope/llm/providers/anthropic/model_id.py +10 -27
- mirascope/llm/providers/anthropic/model_info.py +87 -0
- mirascope/llm/providers/anthropic/provider.py +132 -145
- mirascope/llm/providers/base/__init__.py +2 -1
- mirascope/llm/providers/base/_utils.py +15 -1
- mirascope/llm/providers/base/base_provider.py +173 -58
- mirascope/llm/providers/google/_utils/__init__.py +2 -0
- mirascope/llm/providers/google/_utils/decode.py +55 -3
- mirascope/llm/providers/google/_utils/encode.py +14 -6
- mirascope/llm/providers/google/_utils/errors.py +49 -0
- mirascope/llm/providers/google/model_id.py +7 -13
- mirascope/llm/providers/google/model_info.py +62 -0
- mirascope/llm/providers/google/provider.py +13 -8
- mirascope/llm/providers/mlx/_utils.py +31 -2
- mirascope/llm/providers/mlx/encoding/transformers.py +17 -1
- mirascope/llm/providers/mlx/provider.py +12 -0
- mirascope/llm/providers/ollama/__init__.py +19 -0
- mirascope/llm/providers/ollama/provider.py +71 -0
- mirascope/llm/providers/openai/__init__.py +10 -1
- mirascope/llm/providers/openai/_utils/__init__.py +5 -0
- mirascope/llm/providers/openai/_utils/errors.py +46 -0
- mirascope/llm/providers/openai/completions/__init__.py +6 -1
- mirascope/llm/providers/openai/completions/_utils/decode.py +57 -5
- mirascope/llm/providers/openai/completions/_utils/encode.py +9 -8
- mirascope/llm/providers/openai/completions/base_provider.py +513 -0
- mirascope/llm/providers/openai/completions/provider.py +13 -447
- mirascope/llm/providers/openai/model_info.py +57 -0
- mirascope/llm/providers/openai/provider.py +30 -5
- mirascope/llm/providers/openai/responses/_utils/decode.py +55 -4
- mirascope/llm/providers/openai/responses/_utils/encode.py +9 -9
- mirascope/llm/providers/openai/responses/provider.py +33 -28
- mirascope/llm/providers/provider_id.py +11 -1
- mirascope/llm/providers/provider_registry.py +59 -4
- mirascope/llm/providers/together/__init__.py +19 -0
- mirascope/llm/providers/together/provider.py +40 -0
- mirascope/llm/responses/__init__.py +3 -0
- mirascope/llm/responses/base_response.py +4 -0
- mirascope/llm/responses/base_stream_response.py +25 -1
- mirascope/llm/responses/finish_reason.py +1 -0
- mirascope/llm/responses/response.py +9 -0
- mirascope/llm/responses/root_response.py +5 -1
- mirascope/llm/responses/usage.py +95 -0
- mirascope/ops/_internal/closure.py +62 -11
- {mirascope-2.0.0a3.dist-info → mirascope-2.0.0a5.dist-info}/METADATA +3 -3
- {mirascope-2.0.0a3.dist-info → mirascope-2.0.0a5.dist-info}/RECORD +115 -56
- mirascope/llm/providers/load_provider.py +0 -48
- mirascope/llm/providers/openai/shared/__init__.py +0 -7
- mirascope/llm/providers/openai/shared/_utils.py +0 -59
- {mirascope-2.0.0a3.dist-info → mirascope-2.0.0a5.dist-info}/WHEEL +0 -0
- {mirascope-2.0.0a3.dist-info → mirascope-2.0.0a5.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"""Beta Anthropic response decoding."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any, TypeAlias, cast
|
|
5
|
+
|
|
6
|
+
from anthropic.lib.streaming._beta_messages import (
|
|
7
|
+
BetaAsyncMessageStreamManager,
|
|
8
|
+
BetaMessageStreamManager,
|
|
9
|
+
)
|
|
10
|
+
from anthropic.types.beta import (
|
|
11
|
+
BetaContentBlock,
|
|
12
|
+
BetaRawMessageStreamEvent,
|
|
13
|
+
BetaRedactedThinkingBlockParam,
|
|
14
|
+
BetaTextBlockParam,
|
|
15
|
+
BetaThinkingBlockParam,
|
|
16
|
+
BetaToolUseBlockParam,
|
|
17
|
+
)
|
|
18
|
+
from anthropic.types.beta.parsed_beta_message import ParsedBetaMessage
|
|
19
|
+
|
|
20
|
+
from ....content import (
|
|
21
|
+
AssistantContentPart,
|
|
22
|
+
Text,
|
|
23
|
+
TextChunk,
|
|
24
|
+
TextEndChunk,
|
|
25
|
+
TextStartChunk,
|
|
26
|
+
Thought,
|
|
27
|
+
ThoughtChunk,
|
|
28
|
+
ThoughtEndChunk,
|
|
29
|
+
ThoughtStartChunk,
|
|
30
|
+
ToolCall,
|
|
31
|
+
ToolCallChunk,
|
|
32
|
+
ToolCallEndChunk,
|
|
33
|
+
ToolCallStartChunk,
|
|
34
|
+
)
|
|
35
|
+
from ....messages import AssistantMessage
|
|
36
|
+
from ....responses import (
|
|
37
|
+
AsyncChunkIterator,
|
|
38
|
+
ChunkIterator,
|
|
39
|
+
FinishReason,
|
|
40
|
+
FinishReasonChunk,
|
|
41
|
+
RawMessageChunk,
|
|
42
|
+
RawStreamEventChunk,
|
|
43
|
+
Usage,
|
|
44
|
+
UsageDeltaChunk,
|
|
45
|
+
)
|
|
46
|
+
from ..model_id import model_name
|
|
47
|
+
from .decode import decode_usage
|
|
48
|
+
|
|
49
|
+
BETA_FINISH_REASON_MAP = {
|
|
50
|
+
"max_tokens": FinishReason.MAX_TOKENS,
|
|
51
|
+
"refusal": FinishReason.REFUSAL,
|
|
52
|
+
"model_context_window_exceeded": FinishReason.CONTEXT_LENGTH_EXCEEDED,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _decode_beta_assistant_content(content: BetaContentBlock) -> AssistantContentPart:
|
|
57
|
+
"""Convert Beta content block to mirascope AssistantContentPart."""
|
|
58
|
+
if content.type == "text":
|
|
59
|
+
return Text(text=content.text)
|
|
60
|
+
elif content.type == "tool_use":
|
|
61
|
+
return ToolCall(
|
|
62
|
+
id=content.id,
|
|
63
|
+
name=content.name,
|
|
64
|
+
args=json.dumps(content.input),
|
|
65
|
+
)
|
|
66
|
+
elif content.type == "thinking":
|
|
67
|
+
return Thought(thought=content.thinking)
|
|
68
|
+
else:
|
|
69
|
+
raise NotImplementedError(
|
|
70
|
+
f"Support for beta content type `{content.type}` is not yet implemented."
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def beta_decode_response(
|
|
75
|
+
response: ParsedBetaMessage[Any],
|
|
76
|
+
model_id: str,
|
|
77
|
+
) -> tuple[AssistantMessage, FinishReason | None, Usage]:
|
|
78
|
+
"""Convert Beta message to mirascope AssistantMessage and usage."""
|
|
79
|
+
assistant_message = AssistantMessage(
|
|
80
|
+
content=[_decode_beta_assistant_content(part) for part in response.content],
|
|
81
|
+
provider_id="anthropic",
|
|
82
|
+
model_id=model_id,
|
|
83
|
+
provider_model_name=model_name(model_id),
|
|
84
|
+
raw_message={
|
|
85
|
+
"role": response.role,
|
|
86
|
+
"content": [
|
|
87
|
+
part.model_dump(exclude_none=True) for part in response.content
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
)
|
|
91
|
+
finish_reason = (
|
|
92
|
+
BETA_FINISH_REASON_MAP.get(response.stop_reason)
|
|
93
|
+
if response.stop_reason
|
|
94
|
+
else None
|
|
95
|
+
)
|
|
96
|
+
usage = decode_usage(response.usage)
|
|
97
|
+
return assistant_message, finish_reason, usage
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
BetaContentBlockParam: TypeAlias = (
|
|
101
|
+
BetaTextBlockParam
|
|
102
|
+
| BetaThinkingBlockParam
|
|
103
|
+
| BetaToolUseBlockParam
|
|
104
|
+
| BetaRedactedThinkingBlockParam
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class _BetaChunkProcessor:
|
|
109
|
+
"""Processes Beta stream events and maintains state across events."""
|
|
110
|
+
|
|
111
|
+
def __init__(self) -> None:
|
|
112
|
+
self.current_block_param: BetaContentBlockParam | None = None
|
|
113
|
+
self.accumulated_tool_json: str = ""
|
|
114
|
+
self.accumulated_blocks: list[BetaContentBlockParam] = []
|
|
115
|
+
|
|
116
|
+
def process_event(self, event: BetaRawMessageStreamEvent) -> ChunkIterator:
|
|
117
|
+
"""Process a single Beta event and yield the appropriate content chunks."""
|
|
118
|
+
yield RawStreamEventChunk(raw_stream_event=event)
|
|
119
|
+
|
|
120
|
+
if event.type == "content_block_start":
|
|
121
|
+
content_block = event.content_block
|
|
122
|
+
|
|
123
|
+
if content_block.type == "text":
|
|
124
|
+
self.current_block_param = {
|
|
125
|
+
"type": "text",
|
|
126
|
+
"text": content_block.text,
|
|
127
|
+
}
|
|
128
|
+
yield TextStartChunk()
|
|
129
|
+
elif content_block.type == "tool_use":
|
|
130
|
+
self.current_block_param = {
|
|
131
|
+
"type": "tool_use",
|
|
132
|
+
"id": content_block.id,
|
|
133
|
+
"name": content_block.name,
|
|
134
|
+
"input": {},
|
|
135
|
+
}
|
|
136
|
+
self.accumulated_tool_json = ""
|
|
137
|
+
yield ToolCallStartChunk(
|
|
138
|
+
id=content_block.id,
|
|
139
|
+
name=content_block.name,
|
|
140
|
+
)
|
|
141
|
+
elif content_block.type == "thinking":
|
|
142
|
+
self.current_block_param = {
|
|
143
|
+
"type": "thinking",
|
|
144
|
+
"thinking": "",
|
|
145
|
+
"signature": "",
|
|
146
|
+
}
|
|
147
|
+
yield ThoughtStartChunk()
|
|
148
|
+
elif content_block.type == "redacted_thinking": # pragma: no cover
|
|
149
|
+
self.current_block_param = {
|
|
150
|
+
"type": "redacted_thinking",
|
|
151
|
+
"data": content_block.data,
|
|
152
|
+
}
|
|
153
|
+
else:
|
|
154
|
+
raise NotImplementedError(
|
|
155
|
+
f"Support for beta content block type `{content_block.type}` "
|
|
156
|
+
"is not yet implemented."
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
elif event.type == "content_block_delta":
|
|
160
|
+
if self.current_block_param is None: # pragma: no cover
|
|
161
|
+
raise RuntimeError("Received delta without a current block")
|
|
162
|
+
|
|
163
|
+
delta = event.delta
|
|
164
|
+
if delta.type == "text_delta":
|
|
165
|
+
if self.current_block_param["type"] != "text": # pragma: no cover
|
|
166
|
+
raise RuntimeError(
|
|
167
|
+
f"Received text_delta for {self.current_block_param['type']} block"
|
|
168
|
+
)
|
|
169
|
+
self.current_block_param["text"] += delta.text
|
|
170
|
+
yield TextChunk(delta=delta.text)
|
|
171
|
+
elif delta.type == "input_json_delta":
|
|
172
|
+
if self.current_block_param["type"] != "tool_use": # pragma: no cover
|
|
173
|
+
raise RuntimeError(
|
|
174
|
+
f"Received input_json_delta for {self.current_block_param['type']} block"
|
|
175
|
+
)
|
|
176
|
+
self.accumulated_tool_json += delta.partial_json
|
|
177
|
+
yield ToolCallChunk(delta=delta.partial_json)
|
|
178
|
+
elif delta.type == "thinking_delta":
|
|
179
|
+
if self.current_block_param["type"] != "thinking": # pragma: no cover
|
|
180
|
+
raise RuntimeError(
|
|
181
|
+
f"Received thinking_delta for {self.current_block_param['type']} block"
|
|
182
|
+
)
|
|
183
|
+
self.current_block_param["thinking"] += delta.thinking
|
|
184
|
+
yield ThoughtChunk(delta=delta.thinking)
|
|
185
|
+
elif delta.type == "signature_delta":
|
|
186
|
+
if self.current_block_param["type"] != "thinking": # pragma: no cover
|
|
187
|
+
raise RuntimeError(
|
|
188
|
+
f"Received signature_delta for {self.current_block_param['type']} block"
|
|
189
|
+
)
|
|
190
|
+
self.current_block_param["signature"] += delta.signature
|
|
191
|
+
else:
|
|
192
|
+
raise RuntimeError(
|
|
193
|
+
f"Received unsupported delta type: {delta.type}"
|
|
194
|
+
) # pragma: no cover
|
|
195
|
+
|
|
196
|
+
elif event.type == "content_block_stop":
|
|
197
|
+
if self.current_block_param is None: # pragma: no cover
|
|
198
|
+
raise RuntimeError("Received stop without a current block")
|
|
199
|
+
|
|
200
|
+
block_type = self.current_block_param["type"]
|
|
201
|
+
|
|
202
|
+
if block_type == "text":
|
|
203
|
+
yield TextEndChunk()
|
|
204
|
+
elif block_type == "tool_use":
|
|
205
|
+
if self.current_block_param["type"] != "tool_use": # pragma: no cover
|
|
206
|
+
raise RuntimeError(
|
|
207
|
+
f"Block type mismatch: stored {self.current_block_param['type']}, expected tool_use"
|
|
208
|
+
)
|
|
209
|
+
self.current_block_param["input"] = (
|
|
210
|
+
json.loads(self.accumulated_tool_json)
|
|
211
|
+
if self.accumulated_tool_json
|
|
212
|
+
else {}
|
|
213
|
+
)
|
|
214
|
+
yield ToolCallEndChunk()
|
|
215
|
+
elif block_type == "thinking":
|
|
216
|
+
yield ThoughtEndChunk()
|
|
217
|
+
else:
|
|
218
|
+
raise NotImplementedError
|
|
219
|
+
|
|
220
|
+
self.accumulated_blocks.append(self.current_block_param)
|
|
221
|
+
self.current_block_param = None
|
|
222
|
+
|
|
223
|
+
elif event.type == "message_delta":
|
|
224
|
+
if event.delta.stop_reason:
|
|
225
|
+
finish_reason = BETA_FINISH_REASON_MAP.get(event.delta.stop_reason)
|
|
226
|
+
if finish_reason is not None:
|
|
227
|
+
yield FinishReasonChunk(finish_reason=finish_reason)
|
|
228
|
+
|
|
229
|
+
# Emit usage delta
|
|
230
|
+
usage = event.usage
|
|
231
|
+
yield UsageDeltaChunk(
|
|
232
|
+
input_tokens=usage.input_tokens or 0,
|
|
233
|
+
output_tokens=usage.output_tokens,
|
|
234
|
+
cache_read_tokens=usage.cache_read_input_tokens or 0,
|
|
235
|
+
cache_write_tokens=usage.cache_creation_input_tokens or 0,
|
|
236
|
+
reasoning_tokens=0,
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
def raw_message_chunk(self) -> RawMessageChunk:
|
|
240
|
+
return RawMessageChunk(
|
|
241
|
+
raw_message=cast(
|
|
242
|
+
dict[str, Any],
|
|
243
|
+
{
|
|
244
|
+
"role": "assistant",
|
|
245
|
+
"content": self.accumulated_blocks,
|
|
246
|
+
},
|
|
247
|
+
)
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def beta_decode_stream(
|
|
252
|
+
beta_stream_manager: BetaMessageStreamManager[Any],
|
|
253
|
+
) -> ChunkIterator:
|
|
254
|
+
"""Returns a ChunkIterator converted from a Beta MessageStreamManager."""
|
|
255
|
+
processor = _BetaChunkProcessor()
|
|
256
|
+
with beta_stream_manager as stream:
|
|
257
|
+
for event in stream._raw_stream: # pyright: ignore[reportPrivateUsage]
|
|
258
|
+
yield from processor.process_event(event)
|
|
259
|
+
yield processor.raw_message_chunk()
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
async def beta_decode_async_stream(
|
|
263
|
+
beta_stream_manager: BetaAsyncMessageStreamManager[Any],
|
|
264
|
+
) -> AsyncChunkIterator:
|
|
265
|
+
"""Returns an AsyncChunkIterator converted from a Beta MessageStreamManager."""
|
|
266
|
+
processor = _BetaChunkProcessor()
|
|
267
|
+
async with beta_stream_manager as stream:
|
|
268
|
+
async for event in stream._raw_stream: # pyright: ignore[reportPrivateUsage]
|
|
269
|
+
for item in processor.process_event(event):
|
|
270
|
+
yield item
|
|
271
|
+
yield processor.raw_message_chunk()
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"""Beta Anthropic message encoding and request preparation."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
from typing import Any, TypedDict, cast
|
|
5
|
+
from typing_extensions import Required
|
|
6
|
+
|
|
7
|
+
from anthropic import Omit
|
|
8
|
+
from anthropic.types.anthropic_beta_param import AnthropicBetaParam
|
|
9
|
+
from anthropic.types.beta import (
|
|
10
|
+
BetaContentBlockParam,
|
|
11
|
+
BetaMessageParam,
|
|
12
|
+
BetaTextBlockParam,
|
|
13
|
+
BetaThinkingConfigParam,
|
|
14
|
+
BetaToolChoiceParam,
|
|
15
|
+
BetaToolParam,
|
|
16
|
+
)
|
|
17
|
+
from pydantic import BaseModel
|
|
18
|
+
|
|
19
|
+
from ....content import ContentPart
|
|
20
|
+
from ....exceptions import FormattingModeNotSupportedError
|
|
21
|
+
from ....formatting import (
|
|
22
|
+
Format,
|
|
23
|
+
FormattableT,
|
|
24
|
+
_utils as _formatting_utils,
|
|
25
|
+
resolve_format,
|
|
26
|
+
)
|
|
27
|
+
from ....messages import AssistantMessage, Message, UserMessage
|
|
28
|
+
from ....tools import AnyToolSchema, BaseToolkit
|
|
29
|
+
from ...base import Params, _utils as _base_utils
|
|
30
|
+
from ..model_id import model_name
|
|
31
|
+
from ..model_info import MODELS_WITHOUT_STRICT_STRUCTURED_OUTPUTS
|
|
32
|
+
from .encode import (
|
|
33
|
+
DEFAULT_MAX_TOKENS,
|
|
34
|
+
FORMAT_TOOL_NAME,
|
|
35
|
+
convert_tool_to_tool_param,
|
|
36
|
+
encode_content,
|
|
37
|
+
process_params,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
DEFAULT_FORMAT_MODE = "strict"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class BetaParseKwargs(TypedDict, total=False):
|
|
44
|
+
"""Kwargs for Anthropic beta.messages.parse method."""
|
|
45
|
+
|
|
46
|
+
model: Required[str]
|
|
47
|
+
max_tokens: Required[int]
|
|
48
|
+
messages: Sequence[BetaMessageParam]
|
|
49
|
+
system: Sequence[BetaTextBlockParam] | Omit
|
|
50
|
+
tools: Sequence[BetaToolParam] | Omit
|
|
51
|
+
tool_choice: BetaToolChoiceParam | Omit
|
|
52
|
+
temperature: float | Omit
|
|
53
|
+
top_p: float | Omit
|
|
54
|
+
top_k: int | Omit
|
|
55
|
+
stop_sequences: list[str] | Omit
|
|
56
|
+
thinking: BetaThinkingConfigParam | Omit
|
|
57
|
+
betas: list[AnthropicBetaParam]
|
|
58
|
+
output_format: type[BaseModel]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _beta_encode_content(
|
|
62
|
+
content: Sequence[ContentPart],
|
|
63
|
+
encode_thoughts: bool,
|
|
64
|
+
add_cache_control: bool = False,
|
|
65
|
+
) -> str | Sequence[BetaContentBlockParam]:
|
|
66
|
+
"""Convert mirascope content to Beta Anthropic content format."""
|
|
67
|
+
result = encode_content(content, encode_thoughts, add_cache_control)
|
|
68
|
+
if isinstance(result, str):
|
|
69
|
+
return result
|
|
70
|
+
return cast(Sequence[BetaContentBlockParam], result)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _beta_encode_message(
|
|
74
|
+
message: UserMessage | AssistantMessage,
|
|
75
|
+
model_id: str,
|
|
76
|
+
encode_thoughts: bool,
|
|
77
|
+
add_cache_control: bool = False,
|
|
78
|
+
) -> BetaMessageParam:
|
|
79
|
+
"""Convert user or assistant Message to Beta MessageParam format.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
message: The message to encode
|
|
83
|
+
model_id: The Anthropic model ID
|
|
84
|
+
encode_thoughts: Whether to encode thought blocks as text
|
|
85
|
+
add_cache_control: Whether to add cache_control to the last content block
|
|
86
|
+
"""
|
|
87
|
+
if (
|
|
88
|
+
message.role == "assistant"
|
|
89
|
+
and message.provider_id == "anthropic"
|
|
90
|
+
and message.model_id == model_id
|
|
91
|
+
and message.raw_message
|
|
92
|
+
and not encode_thoughts
|
|
93
|
+
and not add_cache_control
|
|
94
|
+
):
|
|
95
|
+
raw = cast(dict[str, Any], message.raw_message)
|
|
96
|
+
return BetaMessageParam(
|
|
97
|
+
role=raw["role"],
|
|
98
|
+
content=raw["content"],
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
content = _beta_encode_content(message.content, encode_thoughts, add_cache_control)
|
|
102
|
+
|
|
103
|
+
return BetaMessageParam(
|
|
104
|
+
role=message.role,
|
|
105
|
+
content=content,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _beta_encode_messages(
|
|
110
|
+
messages: Sequence[UserMessage | AssistantMessage],
|
|
111
|
+
model_id: str,
|
|
112
|
+
encode_thoughts: bool,
|
|
113
|
+
) -> Sequence[BetaMessageParam]:
|
|
114
|
+
"""Encode messages and add cache control for multi-turn conversations.
|
|
115
|
+
|
|
116
|
+
If the conversation contains assistant messages (indicating multi-turn),
|
|
117
|
+
adds cache_control to the last content block of the last message.
|
|
118
|
+
"""
|
|
119
|
+
# Detect multi-turn conversations by checking for assistant messages
|
|
120
|
+
has_assistant_message = any(msg.role == "assistant" for msg in messages)
|
|
121
|
+
|
|
122
|
+
# Encode messages, adding cache_control to the last message if multi-turn
|
|
123
|
+
encoded_messages: list[BetaMessageParam] = []
|
|
124
|
+
for i, message in enumerate(messages):
|
|
125
|
+
is_last = i == len(messages) - 1
|
|
126
|
+
add_cache = has_assistant_message and is_last
|
|
127
|
+
encoded_messages.append(
|
|
128
|
+
_beta_encode_message(message, model_id, encode_thoughts, add_cache)
|
|
129
|
+
)
|
|
130
|
+
return encoded_messages
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _beta_convert_tool_to_tool_param(tool: AnyToolSchema) -> BetaToolParam:
|
|
134
|
+
"""Convert a single Mirascope tool to Beta Anthropic tool format."""
|
|
135
|
+
return cast(BetaToolParam, convert_tool_to_tool_param(tool))
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def beta_encode_request(
|
|
139
|
+
*,
|
|
140
|
+
model_id: str,
|
|
141
|
+
messages: Sequence[Message],
|
|
142
|
+
tools: Sequence[AnyToolSchema] | BaseToolkit[AnyToolSchema] | None,
|
|
143
|
+
format: type[FormattableT] | Format[FormattableT] | None,
|
|
144
|
+
params: Params,
|
|
145
|
+
) -> tuple[Sequence[Message], Format[FormattableT] | None, BetaParseKwargs]:
|
|
146
|
+
"""Prepares a request for the Anthropic beta.messages.parse method."""
|
|
147
|
+
|
|
148
|
+
processed = process_params(params, DEFAULT_MAX_TOKENS)
|
|
149
|
+
encode_thoughts = processed.pop("encode_thoughts", False)
|
|
150
|
+
max_tokens = processed.pop("max_tokens", DEFAULT_MAX_TOKENS)
|
|
151
|
+
|
|
152
|
+
kwargs: BetaParseKwargs = BetaParseKwargs(
|
|
153
|
+
{
|
|
154
|
+
"model": model_name(model_id),
|
|
155
|
+
"max_tokens": max_tokens,
|
|
156
|
+
"betas": ["structured-outputs-2025-11-13"],
|
|
157
|
+
**processed,
|
|
158
|
+
}
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
tools = tools.tools if isinstance(tools, BaseToolkit) else tools or []
|
|
162
|
+
anthropic_tools = [_beta_convert_tool_to_tool_param(tool) for tool in tools]
|
|
163
|
+
format = resolve_format(format, default_mode=DEFAULT_FORMAT_MODE)
|
|
164
|
+
|
|
165
|
+
if format is not None:
|
|
166
|
+
if format.mode == "strict":
|
|
167
|
+
if model_name(model_id) in MODELS_WITHOUT_STRICT_STRUCTURED_OUTPUTS:
|
|
168
|
+
raise FormattingModeNotSupportedError(
|
|
169
|
+
formatting_mode=format.mode,
|
|
170
|
+
provider_id="anthropic",
|
|
171
|
+
model_id=model_id,
|
|
172
|
+
)
|
|
173
|
+
else:
|
|
174
|
+
kwargs["output_format"] = cast(type[BaseModel], format.formattable)
|
|
175
|
+
|
|
176
|
+
if format.mode == "tool":
|
|
177
|
+
format_tool_schema = _formatting_utils.create_tool_schema(format)
|
|
178
|
+
anthropic_tools.append(_beta_convert_tool_to_tool_param(format_tool_schema))
|
|
179
|
+
if tools:
|
|
180
|
+
kwargs["tool_choice"] = {"type": "any"}
|
|
181
|
+
else:
|
|
182
|
+
kwargs["tool_choice"] = {
|
|
183
|
+
"type": "tool",
|
|
184
|
+
"name": FORMAT_TOOL_NAME,
|
|
185
|
+
"disable_parallel_tool_use": True,
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if format.formatting_instructions:
|
|
189
|
+
messages = _base_utils.add_system_instructions(
|
|
190
|
+
messages, format.formatting_instructions
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
if anthropic_tools:
|
|
194
|
+
# Add cache control to the last tool for prompt caching
|
|
195
|
+
last_tool = anthropic_tools[-1]
|
|
196
|
+
last_tool["cache_control"] = {"type": "ephemeral"}
|
|
197
|
+
kwargs["tools"] = anthropic_tools
|
|
198
|
+
|
|
199
|
+
system_message_content, remaining_messages = _base_utils.extract_system_message(
|
|
200
|
+
messages
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
kwargs["messages"] = _beta_encode_messages(
|
|
204
|
+
remaining_messages, model_id, encode_thoughts
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
if system_message_content:
|
|
208
|
+
kwargs["system"] = [
|
|
209
|
+
BetaTextBlockParam(
|
|
210
|
+
type="text",
|
|
211
|
+
text=system_message_content,
|
|
212
|
+
cache_control={"type": "ephemeral"},
|
|
213
|
+
)
|
|
214
|
+
]
|
|
215
|
+
|
|
216
|
+
return messages, format, kwargs
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
"""Anthropic response decoding."""
|
|
1
|
+
"""Standard Anthropic response decoding."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
from typing import Any, TypeAlias, cast
|
|
5
5
|
|
|
6
6
|
from anthropic import types as anthropic_types
|
|
7
7
|
from anthropic.lib.streaming import AsyncMessageStreamManager, MessageStreamManager
|
|
8
|
+
from anthropic.types.beta import BetaUsage
|
|
8
9
|
|
|
9
10
|
from ....content import (
|
|
10
11
|
AssistantContentPart,
|
|
@@ -29,6 +30,8 @@ from ....responses import (
|
|
|
29
30
|
FinishReasonChunk,
|
|
30
31
|
RawMessageChunk,
|
|
31
32
|
RawStreamEventChunk,
|
|
33
|
+
Usage,
|
|
34
|
+
UsageDeltaChunk,
|
|
32
35
|
)
|
|
33
36
|
from ..model_id import AnthropicModelId, model_name
|
|
34
37
|
|
|
@@ -58,11 +61,30 @@ def _decode_assistant_content(
|
|
|
58
61
|
)
|
|
59
62
|
|
|
60
63
|
|
|
64
|
+
def decode_usage(
|
|
65
|
+
usage: anthropic_types.Usage | BetaUsage,
|
|
66
|
+
) -> Usage:
|
|
67
|
+
"""Convert Anthropic Usage (or BetaUsage) to Mirascope Usage."""
|
|
68
|
+
|
|
69
|
+
cache_read_tokens = usage.cache_read_input_tokens or 0
|
|
70
|
+
cache_write_tokens = usage.cache_creation_input_tokens or 0
|
|
71
|
+
input_tokens = usage.input_tokens + cache_read_tokens + cache_write_tokens
|
|
72
|
+
output_tokens = usage.output_tokens
|
|
73
|
+
return Usage(
|
|
74
|
+
input_tokens=input_tokens,
|
|
75
|
+
output_tokens=output_tokens,
|
|
76
|
+
cache_read_tokens=cache_read_tokens,
|
|
77
|
+
cache_write_tokens=cache_write_tokens,
|
|
78
|
+
reasoning_tokens=0,
|
|
79
|
+
raw=usage,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
61
83
|
def decode_response(
|
|
62
84
|
response: anthropic_types.Message,
|
|
63
85
|
model_id: AnthropicModelId,
|
|
64
|
-
) -> tuple[AssistantMessage, FinishReason | None]:
|
|
65
|
-
"""Convert Anthropic message to mirascope AssistantMessage."""
|
|
86
|
+
) -> tuple[AssistantMessage, FinishReason | None, Usage]:
|
|
87
|
+
"""Convert Anthropic message to mirascope AssistantMessage and usage."""
|
|
66
88
|
assistant_message = AssistantMessage(
|
|
67
89
|
content=[_decode_assistant_content(part) for part in response.content],
|
|
68
90
|
provider_id="anthropic",
|
|
@@ -78,14 +100,14 @@ def decode_response(
|
|
|
78
100
|
if response.stop_reason
|
|
79
101
|
else None
|
|
80
102
|
)
|
|
81
|
-
|
|
103
|
+
usage = decode_usage(response.usage)
|
|
104
|
+
return assistant_message, finish_reason, usage
|
|
82
105
|
|
|
83
106
|
|
|
84
107
|
ContentBlock: TypeAlias = (
|
|
85
108
|
anthropic_types.TextBlockParam
|
|
86
109
|
| anthropic_types.ThinkingBlockParam
|
|
87
110
|
| anthropic_types.ToolUseBlockParam
|
|
88
|
-
| anthropic_types.ThinkingBlockParam
|
|
89
111
|
| anthropic_types.RedactedThinkingBlockParam
|
|
90
112
|
)
|
|
91
113
|
|
|
@@ -210,6 +232,16 @@ class _AnthropicChunkProcessor:
|
|
|
210
232
|
if finish_reason is not None:
|
|
211
233
|
yield FinishReasonChunk(finish_reason=finish_reason)
|
|
212
234
|
|
|
235
|
+
# Emit usage delta
|
|
236
|
+
usage = event.usage
|
|
237
|
+
yield UsageDeltaChunk(
|
|
238
|
+
input_tokens=usage.input_tokens or 0,
|
|
239
|
+
output_tokens=usage.output_tokens,
|
|
240
|
+
cache_read_tokens=usage.cache_read_input_tokens or 0,
|
|
241
|
+
cache_write_tokens=usage.cache_creation_input_tokens or 0,
|
|
242
|
+
reasoning_tokens=0,
|
|
243
|
+
)
|
|
244
|
+
|
|
213
245
|
def raw_message_chunk(self) -> RawMessageChunk:
|
|
214
246
|
return RawMessageChunk(
|
|
215
247
|
raw_message=cast(
|
|
@@ -225,7 +257,7 @@ class _AnthropicChunkProcessor:
|
|
|
225
257
|
def decode_stream(
|
|
226
258
|
anthropic_stream_manager: MessageStreamManager,
|
|
227
259
|
) -> ChunkIterator:
|
|
228
|
-
"""Returns a ChunkIterator converted from an Anthropic MessageStreamManager"""
|
|
260
|
+
"""Returns a ChunkIterator converted from an Anthropic MessageStreamManager."""
|
|
229
261
|
processor = _AnthropicChunkProcessor()
|
|
230
262
|
with anthropic_stream_manager as stream:
|
|
231
263
|
for event in stream._raw_stream: # pyright: ignore[reportPrivateUsage]
|
|
@@ -236,7 +268,7 @@ def decode_stream(
|
|
|
236
268
|
async def decode_async_stream(
|
|
237
269
|
anthropic_stream_manager: AsyncMessageStreamManager,
|
|
238
270
|
) -> AsyncChunkIterator:
|
|
239
|
-
"""Returns an AsyncChunkIterator converted from an Anthropic MessageStreamManager"""
|
|
271
|
+
"""Returns an AsyncChunkIterator converted from an Anthropic MessageStreamManager."""
|
|
240
272
|
processor = _AnthropicChunkProcessor()
|
|
241
273
|
async with anthropic_stream_manager as stream:
|
|
242
274
|
async for event in stream._raw_stream: # pyright: ignore[reportPrivateUsage]
|