mirascope 1.16.8__py3-none-any.whl → 1.17.0__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/__init__.py +20 -1
- mirascope/core/anthropic/_utils/_convert_message_params.py +13 -0
- mirascope/core/anthropic/call_response.py +10 -2
- mirascope/core/azure/_utils/_convert_message_params.py +10 -0
- mirascope/core/azure/_utils/_message_param_converter.py +80 -19
- mirascope/core/azure/call_response.py +8 -2
- mirascope/core/base/__init__.py +4 -0
- mirascope/core/base/_create.py +1 -1
- mirascope/core/base/_utils/_convert_function_to_base_tool.py +2 -2
- mirascope/core/base/_utils/_convert_messages_to_message_params.py +36 -3
- mirascope/core/base/_utils/_parse_content_template.py +35 -9
- mirascope/core/base/call_response.py +7 -1
- mirascope/core/base/message_param.py +30 -2
- mirascope/core/base/messages.py +10 -0
- mirascope/core/bedrock/_utils/_convert_message_params.py +18 -1
- mirascope/core/bedrock/call_response.py +8 -2
- mirascope/core/cohere/_utils/_message_param_converter.py +3 -2
- mirascope/core/cohere/call_response.py +8 -2
- mirascope/core/gemini/_utils/_convert_message_params.py +48 -5
- mirascope/core/gemini/_utils/_message_param_converter.py +56 -6
- mirascope/core/gemini/_utils/_setup_call.py +12 -2
- mirascope/core/gemini/call_response.py +8 -2
- mirascope/core/groq/_utils/_convert_message_params.py +9 -0
- mirascope/core/groq/_utils/_message_param_converter.py +44 -15
- mirascope/core/groq/call_response.py +8 -2
- mirascope/core/mistral/_utils/_convert_message_params.py +7 -0
- mirascope/core/mistral/_utils/_message_param_converter.py +41 -35
- mirascope/core/mistral/call_response.py +8 -2
- mirascope/core/openai/_utils/_convert_message_params.py +39 -1
- mirascope/core/openai/_utils/_message_param_converter.py +36 -10
- mirascope/core/openai/call_response.py +8 -2
- mirascope/core/vertex/_utils/_convert_message_params.py +56 -6
- mirascope/core/vertex/_utils/_message_param_converter.py +17 -7
- mirascope/core/vertex/_utils/_setup_call.py +10 -1
- mirascope/core/vertex/call_response.py +8 -2
- mirascope/llm/call_response.py +11 -3
- mirascope/llm/stream.py +3 -3
- mirascope/retries/__init__.py +5 -0
- mirascope/retries/fallback.py +128 -0
- {mirascope-1.16.8.dist-info → mirascope-1.17.0.dist-info}/METADATA +1 -1
- {mirascope-1.16.8.dist-info → mirascope-1.17.0.dist-info}/RECORD +43 -42
- {mirascope-1.16.8.dist-info → mirascope-1.17.0.dist-info}/WHEEL +0 -0
- {mirascope-1.16.8.dist-info → mirascope-1.17.0.dist-info}/licenses/LICENSE +0 -0
mirascope/__init__.py
CHANGED
|
@@ -6,6 +6,15 @@ from contextlib import suppress
|
|
|
6
6
|
with suppress(ImportError):
|
|
7
7
|
from . import core as core
|
|
8
8
|
|
|
9
|
+
from .core import (
|
|
10
|
+
BaseDynamicConfig,
|
|
11
|
+
BaseMessageParam,
|
|
12
|
+
BaseTool,
|
|
13
|
+
BaseToolKit,
|
|
14
|
+
Messages,
|
|
15
|
+
prompt_template,
|
|
16
|
+
)
|
|
17
|
+
|
|
9
18
|
with suppress(ImportError):
|
|
10
19
|
from . import integrations as integrations
|
|
11
20
|
|
|
@@ -14,4 +23,14 @@ with suppress(ImportError):
|
|
|
14
23
|
|
|
15
24
|
__version__ = importlib.metadata.version("mirascope")
|
|
16
25
|
|
|
17
|
-
__all__ = [
|
|
26
|
+
__all__ = [
|
|
27
|
+
"BaseDynamicConfig",
|
|
28
|
+
"BaseMessageParam",
|
|
29
|
+
"BaseTool",
|
|
30
|
+
"BaseToolKit",
|
|
31
|
+
"core",
|
|
32
|
+
"integrations",
|
|
33
|
+
"prompt_template",
|
|
34
|
+
"retries",
|
|
35
|
+
"__version__",
|
|
36
|
+
]
|
|
@@ -5,6 +5,7 @@ import base64
|
|
|
5
5
|
from anthropic.types import MessageParam
|
|
6
6
|
|
|
7
7
|
from ...base import BaseMessageParam
|
|
8
|
+
from ...base._utils._parse_content_template import _load_media, get_image_type
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
def convert_message_params(
|
|
@@ -45,6 +46,18 @@ def convert_message_params(
|
|
|
45
46
|
},
|
|
46
47
|
}
|
|
47
48
|
)
|
|
49
|
+
elif part.type == "image_url":
|
|
50
|
+
image = _load_media(part.url)
|
|
51
|
+
converted_content.append(
|
|
52
|
+
{
|
|
53
|
+
"type": "image",
|
|
54
|
+
"source": {
|
|
55
|
+
"data": base64.b64encode(image).decode("utf-8"),
|
|
56
|
+
"media_type": f"image/{get_image_type(image)}",
|
|
57
|
+
"type": "base64",
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
)
|
|
48
61
|
elif part.type == "document":
|
|
49
62
|
if part.media_type != "application/pdf":
|
|
50
63
|
raise ValueError(
|
|
@@ -186,5 +186,13 @@ class AnthropicCallResponse(
|
|
|
186
186
|
return _convert_finish_reasons_to_common_finish_reasons(self.finish_reasons)
|
|
187
187
|
|
|
188
188
|
@property
|
|
189
|
-
def common_message_param(self) ->
|
|
190
|
-
return AnthropicMessageParamConverter.from_provider([(self.message_param)])
|
|
189
|
+
def common_message_param(self) -> BaseMessageParam:
|
|
190
|
+
return AnthropicMessageParamConverter.from_provider([(self.message_param)])[0]
|
|
191
|
+
|
|
192
|
+
@property
|
|
193
|
+
def common_user_message_param(self) -> BaseMessageParam | None:
|
|
194
|
+
if not self.user_message_param:
|
|
195
|
+
return None
|
|
196
|
+
return AnthropicMessageParamConverter.from_provider(
|
|
197
|
+
[(self.user_message_param)]
|
|
198
|
+
)[0]
|
|
@@ -51,6 +51,16 @@ def convert_message_params(
|
|
|
51
51
|
},
|
|
52
52
|
}
|
|
53
53
|
)
|
|
54
|
+
elif part.type == "image_url":
|
|
55
|
+
converted_content.append(
|
|
56
|
+
{
|
|
57
|
+
"type": "image_url",
|
|
58
|
+
"image_url": {
|
|
59
|
+
"url": part.url,
|
|
60
|
+
"detail": part.detail if part.detail else "auto",
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
)
|
|
54
64
|
elif part.type == "tool_call":
|
|
55
65
|
converted_message_param = AssistantMessage(
|
|
56
66
|
tool_calls=[
|
|
@@ -4,14 +4,41 @@ from typing import cast
|
|
|
4
4
|
from azure.ai.inference.models import (
|
|
5
5
|
AssistantMessage,
|
|
6
6
|
ChatRequestMessage,
|
|
7
|
+
ContentItem,
|
|
8
|
+
ImageContentItem,
|
|
9
|
+
TextContentItem,
|
|
10
|
+
ToolMessage,
|
|
11
|
+
UserMessage,
|
|
7
12
|
)
|
|
8
13
|
|
|
9
|
-
from mirascope.core import BaseMessageParam
|
|
10
14
|
from mirascope.core.azure._utils import convert_message_params
|
|
15
|
+
from mirascope.core.base import (
|
|
16
|
+
BaseMessageParam,
|
|
17
|
+
ImageURLPart,
|
|
18
|
+
TextPart,
|
|
19
|
+
ToolCallPart,
|
|
20
|
+
ToolResultPart,
|
|
21
|
+
)
|
|
11
22
|
from mirascope.core.base._utils._base_message_param_converter import (
|
|
12
23
|
BaseMessageParamConverter,
|
|
13
24
|
)
|
|
14
|
-
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _parse_content(content: list[ContentItem]) -> list[TextPart | ImageURLPart]:
|
|
28
|
+
converted_parts = []
|
|
29
|
+
for part in content:
|
|
30
|
+
if isinstance(part, TextContentItem):
|
|
31
|
+
converted_parts.append(TextPart(type="text", text=part.text))
|
|
32
|
+
elif isinstance(part, ImageContentItem):
|
|
33
|
+
converted_parts.append(
|
|
34
|
+
ImageURLPart(
|
|
35
|
+
type="image_url",
|
|
36
|
+
url=part.image_url.url,
|
|
37
|
+
detail=part.image_url.detail,
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
return converted_parts
|
|
15
42
|
|
|
16
43
|
|
|
17
44
|
class AzureMessageParamConverter(BaseMessageParamConverter):
|
|
@@ -19,38 +46,72 @@ class AzureMessageParamConverter(BaseMessageParamConverter):
|
|
|
19
46
|
|
|
20
47
|
@staticmethod
|
|
21
48
|
def to_provider(message_params: list[BaseMessageParam]) -> list[ChatRequestMessage]:
|
|
22
|
-
"""
|
|
23
|
-
Convert from Mirascope `BaseMessageParam` to Azure's `ChatRequestMessage`.
|
|
24
|
-
"""
|
|
49
|
+
"""Convert from Mirascope `BaseMessageParam` to Azure's `ChatRequestMessage`."""
|
|
25
50
|
return convert_message_params(
|
|
26
51
|
cast(list[BaseMessageParam | ChatRequestMessage], message_params)
|
|
27
52
|
)
|
|
28
53
|
|
|
29
54
|
@staticmethod
|
|
30
|
-
def from_provider(
|
|
55
|
+
def from_provider(
|
|
56
|
+
message_params: list[ChatRequestMessage],
|
|
57
|
+
) -> list[BaseMessageParam]:
|
|
31
58
|
"""
|
|
32
59
|
Convert from Azure's `AssistantMessage` back to Mirascope `BaseMessageParam`.
|
|
33
60
|
"""
|
|
34
61
|
converted: list[BaseMessageParam] = []
|
|
35
62
|
for message_param in message_params:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
63
|
+
if isinstance(message_param, UserMessage):
|
|
64
|
+
if isinstance(message_param.content, str):
|
|
65
|
+
converted.append(
|
|
66
|
+
BaseMessageParam(role="user", content=message_param.content)
|
|
67
|
+
)
|
|
68
|
+
elif isinstance(message_param.content, list):
|
|
69
|
+
converted_parts = _parse_content(message_param.content)
|
|
70
|
+
converted.append(
|
|
71
|
+
BaseMessageParam(role="user", content=converted_parts)
|
|
72
|
+
)
|
|
73
|
+
elif isinstance(message_param, AssistantMessage):
|
|
74
|
+
converted_parts = []
|
|
75
|
+
if tool_calls := message_param.tool_calls:
|
|
76
|
+
converted_parts.extend(
|
|
47
77
|
ToolCallPart(
|
|
48
78
|
type="tool_call",
|
|
49
79
|
name=tool_call.function.name,
|
|
50
80
|
id=tool_call.id,
|
|
51
81
|
args=json.loads(tool_call.function.arguments),
|
|
52
82
|
)
|
|
83
|
+
for tool_call in tool_calls
|
|
53
84
|
)
|
|
54
|
-
|
|
55
|
-
|
|
85
|
+
if isinstance(message_param.content, str):
|
|
86
|
+
converted_parts.append(
|
|
87
|
+
TextPart(type="text", text=message_param.content)
|
|
88
|
+
)
|
|
89
|
+
elif isinstance(message_param.content, list):
|
|
90
|
+
converted_parts = _parse_content(message_param.content)
|
|
91
|
+
else:
|
|
92
|
+
converted_parts.append(TextPart(type="text", text=""))
|
|
93
|
+
converted.append(
|
|
94
|
+
BaseMessageParam(
|
|
95
|
+
role="assistant",
|
|
96
|
+
content=converted_parts[0].text
|
|
97
|
+
if len(converted_parts) == 1
|
|
98
|
+
and isinstance(converted_parts[0], TextPart)
|
|
99
|
+
else converted_parts,
|
|
100
|
+
)
|
|
101
|
+
)
|
|
102
|
+
elif isinstance(message_param, ToolMessage):
|
|
103
|
+
converted.append(
|
|
104
|
+
BaseMessageParam(
|
|
105
|
+
role="tool",
|
|
106
|
+
content=[
|
|
107
|
+
ToolResultPart(
|
|
108
|
+
type="tool_result",
|
|
109
|
+
name="",
|
|
110
|
+
content=message_param.content,
|
|
111
|
+
id=message_param.tool_call_id,
|
|
112
|
+
is_error=False,
|
|
113
|
+
)
|
|
114
|
+
],
|
|
115
|
+
)
|
|
116
|
+
)
|
|
56
117
|
return converted
|
|
@@ -199,5 +199,11 @@ class AzureCallResponse(
|
|
|
199
199
|
)
|
|
200
200
|
|
|
201
201
|
@property
|
|
202
|
-
def common_message_param(self) ->
|
|
203
|
-
return AzureMessageParamConverter.from_provider([self.message_param])
|
|
202
|
+
def common_message_param(self) -> BaseMessageParam:
|
|
203
|
+
return AzureMessageParamConverter.from_provider([self.message_param])[0]
|
|
204
|
+
|
|
205
|
+
@property
|
|
206
|
+
def common_user_message_param(self) -> BaseMessageParam | None:
|
|
207
|
+
if not self.user_message_param:
|
|
208
|
+
return None
|
|
209
|
+
return AzureMessageParamConverter.from_provider([self.user_message_param])[0]
|
mirascope/core/base/__init__.py
CHANGED
|
@@ -12,10 +12,12 @@ from .from_call_args import FromCallArgs
|
|
|
12
12
|
from .merge_decorators import merge_decorators
|
|
13
13
|
from .message_param import (
|
|
14
14
|
AudioPart,
|
|
15
|
+
AudioURLPart,
|
|
15
16
|
BaseMessageParam,
|
|
16
17
|
CacheControlPart,
|
|
17
18
|
DocumentPart,
|
|
18
19
|
ImagePart,
|
|
20
|
+
ImageURLPart,
|
|
19
21
|
TextPart,
|
|
20
22
|
ToolCallPart,
|
|
21
23
|
ToolResultPart,
|
|
@@ -32,6 +34,7 @@ from .types import AudioSegment
|
|
|
32
34
|
|
|
33
35
|
__all__ = [
|
|
34
36
|
"AudioPart",
|
|
37
|
+
"AudioURLPart",
|
|
35
38
|
"AudioSegment",
|
|
36
39
|
"BaseCallKwargs",
|
|
37
40
|
"BaseCallParams",
|
|
@@ -52,6 +55,7 @@ __all__ = [
|
|
|
52
55
|
"FromCallArgs",
|
|
53
56
|
"GenerateJsonSchemaNoTitles",
|
|
54
57
|
"ImagePart",
|
|
58
|
+
"ImageURLPart",
|
|
55
59
|
"merge_decorators",
|
|
56
60
|
"metadata",
|
|
57
61
|
"Messages",
|
mirascope/core/base/_create.py
CHANGED
|
@@ -166,7 +166,7 @@ def create_factory( # noqa: ANN202
|
|
|
166
166
|
@wraps(fn)
|
|
167
167
|
async def inner_async(
|
|
168
168
|
*args: _P.args, **kwargs: _P.kwargs
|
|
169
|
-
) -> TCallResponse | _ParsedOutputT:
|
|
169
|
+
) -> TCallResponse | _ParsedOutputT: # pyright: ignore [reportInvalidTypeForm]
|
|
170
170
|
fn_args = get_fn_args(fn, args, kwargs)
|
|
171
171
|
dynamic_config = await get_dynamic_configuration(fn, args, kwargs)
|
|
172
172
|
nonlocal client
|
|
@@ -114,7 +114,7 @@ def convert_function_to_base_tool(
|
|
|
114
114
|
if examples:
|
|
115
115
|
model.model_config["json_schema_extra"] = {"examples": examples}
|
|
116
116
|
|
|
117
|
-
def call(self: base) -> Any: # noqa: ANN401
|
|
117
|
+
def call(self: base) -> Any: # pyright: ignore [reportInvalidTypeForm] # noqa: ANN401
|
|
118
118
|
return fn(
|
|
119
119
|
**(
|
|
120
120
|
({"self": self} if has_self else {})
|
|
@@ -129,7 +129,7 @@ def convert_function_to_base_tool(
|
|
|
129
129
|
)
|
|
130
130
|
)
|
|
131
131
|
|
|
132
|
-
async def call_async(self: base) -> Callable:
|
|
132
|
+
async def call_async(self: base) -> Callable: # pyright: ignore [reportInvalidTypeForm]
|
|
133
133
|
return await call(self)
|
|
134
134
|
|
|
135
135
|
if inspect.iscoroutinefunction(fn):
|
|
@@ -10,10 +10,12 @@ from typing_extensions import TypeIs
|
|
|
10
10
|
|
|
11
11
|
from ..message_param import (
|
|
12
12
|
AudioPart,
|
|
13
|
+
AudioURLPart,
|
|
13
14
|
BaseMessageParam,
|
|
14
15
|
CacheControlPart,
|
|
15
16
|
DocumentPart,
|
|
16
17
|
ImagePart,
|
|
18
|
+
ImageURLPart,
|
|
17
19
|
TextPart,
|
|
18
20
|
)
|
|
19
21
|
from ..types import AudioSegment, Image, has_pil_module, has_pydub_module
|
|
@@ -29,17 +31,33 @@ def _convert_message_sequence_part_to_content_part(
|
|
|
29
31
|
| TextPart
|
|
30
32
|
| CacheControlPart
|
|
31
33
|
| ImagePart
|
|
34
|
+
| ImageURLPart
|
|
32
35
|
| Image.Image
|
|
33
36
|
| AudioPart
|
|
37
|
+
| AudioURLPart
|
|
34
38
|
| AudioSegment
|
|
35
39
|
| Wave_read
|
|
36
40
|
| DocumentPart,
|
|
37
|
-
) ->
|
|
41
|
+
) -> (
|
|
42
|
+
TextPart
|
|
43
|
+
| ImagePart
|
|
44
|
+
| ImageURLPart
|
|
45
|
+
| AudioPart
|
|
46
|
+
| AudioURLPart
|
|
47
|
+
| CacheControlPart
|
|
48
|
+
| DocumentPart
|
|
49
|
+
):
|
|
38
50
|
if isinstance(message_sequence_part, str):
|
|
39
51
|
return TextPart(text=message_sequence_part, type="text")
|
|
40
52
|
elif isinstance(
|
|
41
53
|
message_sequence_part,
|
|
42
|
-
TextPart
|
|
54
|
+
TextPart
|
|
55
|
+
| ImagePart
|
|
56
|
+
| ImageURLPart
|
|
57
|
+
| AudioPart
|
|
58
|
+
| AudioURLPart
|
|
59
|
+
| CacheControlPart
|
|
60
|
+
| DocumentPart,
|
|
43
61
|
):
|
|
44
62
|
return message_sequence_part
|
|
45
63
|
elif has_pil_module and isinstance(message_sequence_part, Image.Image):
|
|
@@ -82,13 +100,26 @@ def convert_message_content_to_message_param_content(
|
|
|
82
100
|
| TextPart
|
|
83
101
|
| CacheControlPart
|
|
84
102
|
| ImagePart
|
|
103
|
+
| ImageURLPart
|
|
85
104
|
| Image.Image
|
|
86
105
|
| AudioPart
|
|
106
|
+
| AudioURLPart
|
|
87
107
|
| AudioSegment
|
|
88
108
|
| Wave_read
|
|
89
109
|
| DocumentPart
|
|
90
110
|
],
|
|
91
|
-
) ->
|
|
111
|
+
) -> (
|
|
112
|
+
list[
|
|
113
|
+
TextPart
|
|
114
|
+
| ImagePart
|
|
115
|
+
| ImageURLPart
|
|
116
|
+
| AudioPart
|
|
117
|
+
| AudioURLPart
|
|
118
|
+
| CacheControlPart
|
|
119
|
+
| DocumentPart
|
|
120
|
+
]
|
|
121
|
+
| str
|
|
122
|
+
):
|
|
92
123
|
if isinstance(message_sequence, str):
|
|
93
124
|
return message_sequence
|
|
94
125
|
return [
|
|
@@ -113,8 +144,10 @@ def convert_messages_to_message_params(
|
|
|
113
144
|
| TextPart
|
|
114
145
|
| CacheControlPart
|
|
115
146
|
| ImagePart
|
|
147
|
+
| ImageURLPart
|
|
116
148
|
| Image.Image
|
|
117
149
|
| AudioPart
|
|
150
|
+
| AudioURLPart
|
|
118
151
|
| AudioSegment
|
|
119
152
|
| Wave_read
|
|
120
153
|
| DocumentPart
|
|
@@ -9,10 +9,12 @@ from typing_extensions import TypedDict
|
|
|
9
9
|
|
|
10
10
|
from ..message_param import (
|
|
11
11
|
AudioPart,
|
|
12
|
+
AudioURLPart,
|
|
12
13
|
BaseMessageParam,
|
|
13
14
|
CacheControlPart,
|
|
14
15
|
DocumentPart,
|
|
15
16
|
ImagePart,
|
|
17
|
+
ImageURLPart,
|
|
16
18
|
TextPart,
|
|
17
19
|
)
|
|
18
20
|
from ..types import Image, has_pil_module
|
|
@@ -138,7 +140,12 @@ def _load_media(source: str | bytes) -> bytes:
|
|
|
138
140
|
|
|
139
141
|
def _construct_image_part(
|
|
140
142
|
source: str | bytes | Image.Image, options: dict[str, str] | None
|
|
141
|
-
) -> ImagePart:
|
|
143
|
+
) -> ImagePart | ImageURLPart:
|
|
144
|
+
detail = None
|
|
145
|
+
if options:
|
|
146
|
+
detail = options.get("detail", None)
|
|
147
|
+
if isinstance(source, str) and source.startswith(("http://", "https://", "gs://")):
|
|
148
|
+
return ImageURLPart(type="image_url", url=source, detail=detail)
|
|
142
149
|
if isinstance(source, Image.Image):
|
|
143
150
|
image = pil_image_to_bytes(source)
|
|
144
151
|
media_type = (
|
|
@@ -149,9 +156,6 @@ def _construct_image_part(
|
|
|
149
156
|
else:
|
|
150
157
|
image = _load_media(source)
|
|
151
158
|
media_type = f"image/{get_image_type(image)}"
|
|
152
|
-
detail = None
|
|
153
|
-
if options:
|
|
154
|
-
detail = options.get("detail", None)
|
|
155
159
|
return ImagePart(
|
|
156
160
|
type="image",
|
|
157
161
|
media_type=media_type,
|
|
@@ -160,8 +164,10 @@ def _construct_image_part(
|
|
|
160
164
|
)
|
|
161
165
|
|
|
162
166
|
|
|
163
|
-
def _construct_audio_part(source: str | bytes) -> AudioPart:
|
|
167
|
+
def _construct_audio_part(source: str | bytes) -> AudioPart | AudioURLPart:
|
|
164
168
|
# Note: audio does not currently support additional options, at least for now.
|
|
169
|
+
if isinstance(source, str) and source.startswith(("http://", "https://", "gs://")):
|
|
170
|
+
return AudioURLPart(type="audio_url", url=source)
|
|
165
171
|
audio = _load_media(source)
|
|
166
172
|
return AudioPart(
|
|
167
173
|
type="audio", media_type=f"audio/{get_audio_type(audio)}", audio=audio
|
|
@@ -179,8 +185,16 @@ def _construct_document_part(source: str | bytes) -> DocumentPart:
|
|
|
179
185
|
|
|
180
186
|
def _construct_parts(
|
|
181
187
|
part: _Part, attrs: dict[str, Any]
|
|
182
|
-
) -> list[
|
|
183
|
-
|
|
188
|
+
) -> list[
|
|
189
|
+
TextPart
|
|
190
|
+
| ImagePart
|
|
191
|
+
| ImageURLPart
|
|
192
|
+
| AudioPart
|
|
193
|
+
| AudioURLPart
|
|
194
|
+
| CacheControlPart
|
|
195
|
+
| DocumentPart
|
|
196
|
+
]:
|
|
197
|
+
if part["type"] in "image":
|
|
184
198
|
source = attrs[part["template"]]
|
|
185
199
|
return [_construct_image_part(source, part["options"])] if source else []
|
|
186
200
|
elif part["type"] == "images":
|
|
@@ -229,7 +243,13 @@ def _construct_parts(
|
|
|
229
243
|
source = attrs[part["template"]]
|
|
230
244
|
if not isinstance(
|
|
231
245
|
source,
|
|
232
|
-
TextPart
|
|
246
|
+
TextPart
|
|
247
|
+
| ImagePart
|
|
248
|
+
| ImageURLPart
|
|
249
|
+
| AudioPart
|
|
250
|
+
| AudioURLPart
|
|
251
|
+
| CacheControlPart
|
|
252
|
+
| DocumentPart,
|
|
233
253
|
):
|
|
234
254
|
raise ValueError(
|
|
235
255
|
f"When using 'part' template, '{part['template']}' must be a valid content part."
|
|
@@ -246,7 +266,13 @@ def _construct_parts(
|
|
|
246
266
|
for source in sources:
|
|
247
267
|
if not isinstance(
|
|
248
268
|
source,
|
|
249
|
-
TextPart
|
|
269
|
+
TextPart
|
|
270
|
+
| ImagePart
|
|
271
|
+
| ImageURLPart
|
|
272
|
+
| AudioPart
|
|
273
|
+
| AudioURLPart
|
|
274
|
+
| CacheControlPart
|
|
275
|
+
| DocumentPart,
|
|
250
276
|
):
|
|
251
277
|
raise ValueError(
|
|
252
278
|
f"When using 'parts' template, '{part['template']}' must be a list of valid content parts."
|
|
@@ -275,10 +275,16 @@ class BaseCallResponse(
|
|
|
275
275
|
|
|
276
276
|
@property
|
|
277
277
|
@abstractmethod
|
|
278
|
-
def common_message_param(self) ->
|
|
278
|
+
def common_message_param(self) -> BaseMessageParam:
|
|
279
279
|
"""Provider-agnostic assistant message param."""
|
|
280
280
|
...
|
|
281
281
|
|
|
282
|
+
@property
|
|
283
|
+
@abstractmethod
|
|
284
|
+
def common_user_message_param(self) -> BaseMessageParam | None:
|
|
285
|
+
"""Provider-agnostic user message param."""
|
|
286
|
+
...
|
|
287
|
+
|
|
282
288
|
@property
|
|
283
289
|
def common_tools(self) -> list[Tool] | None:
|
|
284
290
|
"""Provider-agnostic tools."""
|
|
@@ -49,18 +49,44 @@ class ImagePart(BaseModel):
|
|
|
49
49
|
detail: str | None
|
|
50
50
|
|
|
51
51
|
|
|
52
|
+
class ImageURLPart(BaseModel):
|
|
53
|
+
"""A content part for images with a URL or base64 encoded image data.
|
|
54
|
+
|
|
55
|
+
Attributes:
|
|
56
|
+
type: Always "image_url"
|
|
57
|
+
url: The URL to the image
|
|
58
|
+
detail: (Optional) The detail to use for the image (supported by OpenAI)
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
type: Literal["image_url"]
|
|
62
|
+
url: str
|
|
63
|
+
detail: str | None
|
|
64
|
+
|
|
65
|
+
|
|
52
66
|
class AudioPart(BaseModel):
|
|
53
67
|
"""A content part for audio.
|
|
54
68
|
|
|
55
69
|
Attributes:
|
|
56
70
|
type: Always "audio"
|
|
57
71
|
media_type: The media type (e.g. audio/wav)
|
|
58
|
-
audio: The raw audio bytes
|
|
72
|
+
audio: The raw audio bytes or base64 encoded audio data
|
|
59
73
|
"""
|
|
60
74
|
|
|
61
75
|
type: Literal["audio"]
|
|
62
76
|
media_type: str
|
|
63
|
-
audio: bytes
|
|
77
|
+
audio: bytes | str
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class AudioURLPart(BaseModel):
|
|
81
|
+
"""A content part for audio with a URL or base64 encoded audio data.
|
|
82
|
+
|
|
83
|
+
Attributes:
|
|
84
|
+
type: Always "audio_url"
|
|
85
|
+
url: The URL to the audio
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
type: Literal["audio_url"]
|
|
89
|
+
url: str
|
|
64
90
|
|
|
65
91
|
|
|
66
92
|
class DocumentPart(BaseModel):
|
|
@@ -125,7 +151,9 @@ class BaseMessageParam(BaseModel):
|
|
|
125
151
|
| Sequence[
|
|
126
152
|
TextPart
|
|
127
153
|
| ImagePart
|
|
154
|
+
| ImageURLPart
|
|
128
155
|
| AudioPart
|
|
156
|
+
| AudioURLPart
|
|
129
157
|
| CacheControlPart
|
|
130
158
|
| DocumentPart
|
|
131
159
|
| ToolCallPart
|
mirascope/core/base/messages.py
CHANGED
|
@@ -10,10 +10,12 @@ from ._utils._convert_messages_to_message_params import (
|
|
|
10
10
|
)
|
|
11
11
|
from .message_param import (
|
|
12
12
|
AudioPart,
|
|
13
|
+
AudioURLPart,
|
|
13
14
|
BaseMessageParam,
|
|
14
15
|
CacheControlPart,
|
|
15
16
|
DocumentPart,
|
|
16
17
|
ImagePart,
|
|
18
|
+
ImageURLPart,
|
|
17
19
|
TextPart,
|
|
18
20
|
)
|
|
19
21
|
from .types import AudioSegment
|
|
@@ -27,8 +29,10 @@ class Messages:
|
|
|
27
29
|
| TextPart
|
|
28
30
|
| CacheControlPart
|
|
29
31
|
| ImagePart
|
|
32
|
+
| ImageURLPart
|
|
30
33
|
| Image.Image
|
|
31
34
|
| AudioPart
|
|
35
|
+
| AudioURLPart
|
|
32
36
|
| AudioSegment
|
|
33
37
|
| Wave_read
|
|
34
38
|
| DocumentPart
|
|
@@ -46,8 +50,10 @@ class Messages:
|
|
|
46
50
|
| TextPart
|
|
47
51
|
| CacheControlPart
|
|
48
52
|
| ImagePart
|
|
53
|
+
| ImageURLPart
|
|
49
54
|
| Image.Image
|
|
50
55
|
| AudioPart
|
|
56
|
+
| AudioURLPart
|
|
51
57
|
| AudioSegment
|
|
52
58
|
| Wave_read
|
|
53
59
|
| DocumentPart
|
|
@@ -67,8 +73,10 @@ class Messages:
|
|
|
67
73
|
| TextPart
|
|
68
74
|
| CacheControlPart
|
|
69
75
|
| ImagePart
|
|
76
|
+
| ImageURLPart
|
|
70
77
|
| Image.Image
|
|
71
78
|
| AudioPart
|
|
79
|
+
| AudioURLPart
|
|
72
80
|
| AudioSegment
|
|
73
81
|
| Wave_read
|
|
74
82
|
| DocumentPart
|
|
@@ -88,8 +96,10 @@ class Messages:
|
|
|
88
96
|
| TextPart
|
|
89
97
|
| CacheControlPart
|
|
90
98
|
| ImagePart
|
|
99
|
+
| ImageURLPart
|
|
91
100
|
| Image.Image
|
|
92
101
|
| AudioPart
|
|
102
|
+
| AudioURLPart
|
|
93
103
|
| AudioSegment
|
|
94
104
|
| Wave_read
|
|
95
105
|
| DocumentPart
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
from typing import cast
|
|
4
4
|
|
|
5
5
|
from ...base import BaseMessageParam
|
|
6
|
+
from ...base._utils import get_image_type
|
|
7
|
+
from ...base._utils._parse_content_template import _load_media
|
|
6
8
|
from .._types import ConversationRoleType, InternalBedrockMessageParam
|
|
7
9
|
|
|
8
10
|
|
|
@@ -37,7 +39,22 @@ def convert_message_params(
|
|
|
37
39
|
" currently only supports JPEG, PNG, GIF, and WebP images."
|
|
38
40
|
)
|
|
39
41
|
converted_content.append(
|
|
40
|
-
{
|
|
42
|
+
{
|
|
43
|
+
"image": {
|
|
44
|
+
"format": part.media_type.split("/")[-1],
|
|
45
|
+
"source": {"bytes": part.image},
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
elif part.type == "image_url":
|
|
50
|
+
image = _load_media(part.url)
|
|
51
|
+
converted_content.append(
|
|
52
|
+
{
|
|
53
|
+
"image": {
|
|
54
|
+
"format": get_image_type(image),
|
|
55
|
+
"source": {"bytes": image},
|
|
56
|
+
},
|
|
57
|
+
}
|
|
41
58
|
)
|
|
42
59
|
elif part.type == "tool_result":
|
|
43
60
|
if converted_content:
|
|
@@ -232,5 +232,11 @@ class BedrockCallResponse(
|
|
|
232
232
|
return _convert_finish_reasons_to_common_finish_reasons(self.finish_reasons)
|
|
233
233
|
|
|
234
234
|
@property
|
|
235
|
-
def common_message_param(self) ->
|
|
236
|
-
return BedrockMessageParamConverter.from_provider([self.message_param])
|
|
235
|
+
def common_message_param(self) -> BaseMessageParam:
|
|
236
|
+
return BedrockMessageParamConverter.from_provider([self.message_param])[0]
|
|
237
|
+
|
|
238
|
+
@property
|
|
239
|
+
def common_user_message_param(self) -> BaseMessageParam | None:
|
|
240
|
+
if not self.user_message_param:
|
|
241
|
+
return None
|
|
242
|
+
return BedrockMessageParamConverter.from_provider([self.user_message_param])[0] # pyright: ignore [reportArgumentType]
|
|
@@ -29,9 +29,10 @@ class CohereMessageParamConverter(BaseMessageParamConverter):
|
|
|
29
29
|
"""
|
|
30
30
|
converted = []
|
|
31
31
|
for message_param in message_params:
|
|
32
|
+
role = getattr(message_param, "role", "assistant")
|
|
32
33
|
if not message_param.tool_calls:
|
|
33
34
|
converted.append(
|
|
34
|
-
BaseMessageParam(role=
|
|
35
|
+
BaseMessageParam(role=role, content=message_param.message)
|
|
35
36
|
)
|
|
36
37
|
continue
|
|
37
38
|
|
|
@@ -49,5 +50,5 @@ class CohereMessageParamConverter(BaseMessageParamConverter):
|
|
|
49
50
|
)
|
|
50
51
|
)
|
|
51
52
|
|
|
52
|
-
converted.append(BaseMessageParam(role=
|
|
53
|
+
converted.append(BaseMessageParam(role=role, content=converted_content))
|
|
53
54
|
return converted
|