mirascope 1.16.9__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/azure/_utils/_convert_message_params.py +10 -0
- mirascope/core/azure/_utils/_message_param_converter.py +46 -12
- mirascope/core/base/__init__.py +4 -0
- 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/message_param.py +30 -2
- mirascope/core/base/messages.py +10 -0
- mirascope/core/bedrock/_utils/_convert_message_params.py +18 -1
- mirascope/core/gemini/_utils/_convert_message_params.py +48 -5
- mirascope/core/gemini/_utils/_message_param_converter.py +51 -5
- mirascope/core/gemini/_utils/_setup_call.py +12 -2
- mirascope/core/groq/_utils/_convert_message_params.py +9 -0
- mirascope/core/groq/_utils/_message_param_converter.py +9 -2
- mirascope/core/mistral/_utils/_convert_message_params.py +7 -0
- mirascope/core/mistral/_utils/_message_param_converter.py +41 -35
- mirascope/core/openai/_utils/_convert_message_params.py +38 -1
- mirascope/core/openai/_utils/_message_param_converter.py +28 -4
- mirascope/core/vertex/_utils/_convert_message_params.py +56 -6
- mirascope/core/vertex/_utils/_message_param_converter.py +13 -5
- mirascope/core/vertex/_utils/_setup_call.py +10 -1
- mirascope/llm/call_response.py +5 -1
- mirascope/retries/__init__.py +5 -0
- mirascope/retries/fallback.py +128 -0
- {mirascope-1.16.9.dist-info → mirascope-1.17.0.dist-info}/METADATA +1 -1
- {mirascope-1.16.9.dist-info → mirascope-1.17.0.dist-info}/RECORD +29 -28
- {mirascope-1.16.9.dist-info → mirascope-1.17.0.dist-info}/WHEEL +0 -0
- {mirascope-1.16.9.dist-info → mirascope-1.17.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -8,7 +8,14 @@ from google.generativeai.types import (
|
|
|
8
8
|
)
|
|
9
9
|
|
|
10
10
|
from mirascope.core import BaseMessageParam
|
|
11
|
-
from mirascope.core.base import
|
|
11
|
+
from mirascope.core.base import (
|
|
12
|
+
AudioPart,
|
|
13
|
+
AudioURLPart,
|
|
14
|
+
DocumentPart,
|
|
15
|
+
ImagePart,
|
|
16
|
+
ImageURLPart,
|
|
17
|
+
TextPart,
|
|
18
|
+
)
|
|
12
19
|
from mirascope.core.base._utils._base_message_param_converter import (
|
|
13
20
|
BaseMessageParamConverter,
|
|
14
21
|
)
|
|
@@ -29,6 +36,28 @@ def _to_image_part(mime_type: str, data: bytes) -> ImagePart:
|
|
|
29
36
|
return ImagePart(type="image", media_type=mime_type, image=data, detail=None)
|
|
30
37
|
|
|
31
38
|
|
|
39
|
+
def _is_audio_mime(mime_type: str) -> bool:
|
|
40
|
+
return mime_type in [
|
|
41
|
+
"audio/wav",
|
|
42
|
+
"audio/mp3",
|
|
43
|
+
"audio/wav",
|
|
44
|
+
"audio/mp3",
|
|
45
|
+
"audio/aiff",
|
|
46
|
+
"audio/aac",
|
|
47
|
+
"audio/ogg",
|
|
48
|
+
"audio/flac",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _to_audio_part(mime_type: str, data: bytes) -> AudioPart:
|
|
53
|
+
if not _is_audio_mime(mime_type):
|
|
54
|
+
raise ValueError(
|
|
55
|
+
f"Unsupported audio media type: {mime_type}. "
|
|
56
|
+
"Expected one of: audio/wav, audio/mp3, audio/aiff, audio/aac, audio/ogg, audio/flac."
|
|
57
|
+
)
|
|
58
|
+
return AudioPart(type="audio", media_type=mime_type, audio=data)
|
|
59
|
+
|
|
60
|
+
|
|
32
61
|
def _to_document_part(mime_type: str, data: bytes) -> DocumentPart:
|
|
33
62
|
if mime_type != "application/pdf":
|
|
34
63
|
raise ValueError(
|
|
@@ -77,6 +106,8 @@ class GeminiMessageParamConverter(BaseMessageParamConverter):
|
|
|
77
106
|
data = blob.data
|
|
78
107
|
if _is_image_mime(mime):
|
|
79
108
|
content_list.append(_to_image_part(mime, data))
|
|
109
|
+
elif _is_audio_mime(mime):
|
|
110
|
+
content_list.append(_to_audio_part(mime, data))
|
|
80
111
|
elif mime == "application/pdf":
|
|
81
112
|
content_list.append(_to_document_part(mime, data))
|
|
82
113
|
else:
|
|
@@ -85,10 +116,25 @@ class GeminiMessageParamConverter(BaseMessageParamConverter):
|
|
|
85
116
|
)
|
|
86
117
|
|
|
87
118
|
elif part.file_data:
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
119
|
+
if _is_image_mime(part.file_data.mime_type):
|
|
120
|
+
content_list.append(
|
|
121
|
+
ImageURLPart(
|
|
122
|
+
type="image_url",
|
|
123
|
+
url=part.file_data.file_uri,
|
|
124
|
+
detail=None,
|
|
125
|
+
)
|
|
126
|
+
)
|
|
127
|
+
elif _is_audio_mime(part.file_data.mime_type):
|
|
128
|
+
content_list.append(
|
|
129
|
+
AudioURLPart(
|
|
130
|
+
type="audio_url",
|
|
131
|
+
url=part.file_data.file_uri,
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
else:
|
|
135
|
+
raise ValueError(
|
|
136
|
+
f"Unsupported file_data mime type: {part.file_data.mime_type}. Cannot convert to BaseMessageParam."
|
|
137
|
+
)
|
|
92
138
|
elif part.function_call:
|
|
93
139
|
converted.append(
|
|
94
140
|
BaseMessageParam(
|
|
@@ -11,7 +11,7 @@ from google.generativeai.types import (
|
|
|
11
11
|
GenerateContentResponse,
|
|
12
12
|
GenerationConfigDict,
|
|
13
13
|
)
|
|
14
|
-
from google.generativeai.types.content_types import ToolConfigDict
|
|
14
|
+
from google.generativeai.types.content_types import ToolConfigDict, to_content
|
|
15
15
|
from pydantic import BaseModel
|
|
16
16
|
|
|
17
17
|
from ...base import BaseMessageParam, BaseTool, _utils
|
|
@@ -108,6 +108,7 @@ def setup_call(
|
|
|
108
108
|
call_kwargs = cast(GeminiCallKwargs, base_call_kwargs)
|
|
109
109
|
messages = cast(list[BaseMessageParam | ContentDict], messages)
|
|
110
110
|
messages = convert_message_params(messages)
|
|
111
|
+
|
|
111
112
|
if json_mode:
|
|
112
113
|
generation_config = call_kwargs.get("generation_config", {})
|
|
113
114
|
if is_dataclass(generation_config):
|
|
@@ -125,11 +126,20 @@ def setup_call(
|
|
|
125
126
|
"allowed_function_names": [tool_types[0]._name()],
|
|
126
127
|
}
|
|
127
128
|
call_kwargs["tool_config"] = tool_config
|
|
128
|
-
call_kwargs |= {"contents": messages}
|
|
129
129
|
|
|
130
130
|
if client is None:
|
|
131
131
|
client = GenerativeModel(model_name=model)
|
|
132
132
|
|
|
133
|
+
if messages and messages[0]["role"] == "system":
|
|
134
|
+
system_instruction = client._system_instruction
|
|
135
|
+
system_instruction = (
|
|
136
|
+
list(system_instruction.parts) if system_instruction else []
|
|
137
|
+
)
|
|
138
|
+
system_instruction.extend(messages.pop(0)["parts"]) # pyright: ignore [reportArgumentType]
|
|
139
|
+
client._system_instruction = to_content(system_instruction) # pyright: ignore [reportArgumentType]
|
|
140
|
+
|
|
141
|
+
call_kwargs |= {"contents": messages}
|
|
142
|
+
|
|
133
143
|
create = (
|
|
134
144
|
get_async_create_fn(client.generate_content_async)
|
|
135
145
|
if fn_is_async(fn)
|
|
@@ -45,6 +45,15 @@ def convert_message_params(
|
|
|
45
45
|
},
|
|
46
46
|
}
|
|
47
47
|
)
|
|
48
|
+
elif part.type == "image_url":
|
|
49
|
+
converted_content.append(
|
|
50
|
+
{
|
|
51
|
+
"type": "image_url",
|
|
52
|
+
"image_url": {
|
|
53
|
+
"url": part.url,
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
)
|
|
48
57
|
elif part.type == "tool_call":
|
|
49
58
|
converted_message_param = {
|
|
50
59
|
"role": "assistant",
|
|
@@ -8,7 +8,7 @@ from mirascope.core.base import TextPart, ToolResultPart
|
|
|
8
8
|
from mirascope.core.base._utils._base_message_param_converter import (
|
|
9
9
|
BaseMessageParamConverter,
|
|
10
10
|
)
|
|
11
|
-
from mirascope.core.base.message_param import ToolCallPart
|
|
11
|
+
from mirascope.core.base.message_param import ImageURLPart, ToolCallPart
|
|
12
12
|
from mirascope.core.groq._utils import convert_message_params
|
|
13
13
|
|
|
14
14
|
|
|
@@ -70,7 +70,14 @@ class GroqMessageParamConverter(BaseMessageParamConverter):
|
|
|
70
70
|
for part in content:
|
|
71
71
|
if "text" in part:
|
|
72
72
|
contents.append(TextPart(type="text", text=part["text"]))
|
|
73
|
-
|
|
73
|
+
elif "image_url" in part:
|
|
74
|
+
contents.append(
|
|
75
|
+
ImageURLPart(
|
|
76
|
+
type="image_url",
|
|
77
|
+
url=part["image_url"]["url"],
|
|
78
|
+
detail=part["image_url"].get("detail"),
|
|
79
|
+
)
|
|
80
|
+
)
|
|
74
81
|
if contents:
|
|
75
82
|
converted.append(
|
|
76
83
|
BaseMessageParam(role=message_param["role"], content=contents)
|
|
@@ -69,6 +69,13 @@ def convert_message_params(
|
|
|
69
69
|
)
|
|
70
70
|
)
|
|
71
71
|
)
|
|
72
|
+
elif part.type == "image_url":
|
|
73
|
+
converted_content.append(
|
|
74
|
+
{
|
|
75
|
+
"type": "image_url",
|
|
76
|
+
"image_url": part.url,
|
|
77
|
+
}
|
|
78
|
+
)
|
|
72
79
|
elif part.type == "tool_call":
|
|
73
80
|
converted_message_params.append(
|
|
74
81
|
AssistantMessage(
|
|
@@ -19,7 +19,22 @@ from mirascope.core.base._utils._base_message_param_converter import (
|
|
|
19
19
|
from mirascope.core.mistral._utils import convert_message_params
|
|
20
20
|
|
|
21
21
|
from ...base import BaseMessageParam, ImagePart, TextPart, ToolResultPart
|
|
22
|
-
from ...base.message_param import ToolCallPart
|
|
22
|
+
from ...base.message_param import ImageURLPart, ToolCallPart
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _create_image_part_from_data_url(image_url: str) -> ImagePart | None:
|
|
26
|
+
match = re.match(r"data:(image/\w+);base64,(.+)", image_url)
|
|
27
|
+
if not match:
|
|
28
|
+
return None
|
|
29
|
+
mime_type = match.group(1)
|
|
30
|
+
image_base64 = match.group(2)
|
|
31
|
+
image_data = base64.b64decode(image_base64)
|
|
32
|
+
return ImagePart(
|
|
33
|
+
type="image",
|
|
34
|
+
media_type=mime_type,
|
|
35
|
+
image=image_data,
|
|
36
|
+
detail=None,
|
|
37
|
+
)
|
|
23
38
|
|
|
24
39
|
|
|
25
40
|
class MistralMessageParamConverter(BaseMessageParamConverter):
|
|
@@ -102,44 +117,35 @@ class MistralMessageParamConverter(BaseMessageParamConverter):
|
|
|
102
117
|
elif isinstance(chunk, ImageURLChunk):
|
|
103
118
|
image_url = chunk.image_url
|
|
104
119
|
if isinstance(image_url, str):
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
type="image",
|
|
117
|
-
media_type=mime_type,
|
|
118
|
-
image=image_data,
|
|
119
|
-
detail=None,
|
|
120
|
+
if image_part := _create_image_part_from_data_url(
|
|
121
|
+
image_url
|
|
122
|
+
):
|
|
123
|
+
converted_parts.append(image_part)
|
|
124
|
+
else:
|
|
125
|
+
converted_parts.append(
|
|
126
|
+
ImageURLPart(
|
|
127
|
+
type="image_url",
|
|
128
|
+
url=image_url,
|
|
129
|
+
detail=None,
|
|
130
|
+
)
|
|
120
131
|
)
|
|
121
|
-
|
|
132
|
+
|
|
122
133
|
else:
|
|
123
134
|
img_url_str = image_url.url # type: ignore
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
media_type=mime_type,
|
|
138
|
-
image=image_data,
|
|
139
|
-
detail=None,
|
|
135
|
+
if image_part := _create_image_part_from_data_url(
|
|
136
|
+
img_url_str
|
|
137
|
+
):
|
|
138
|
+
converted_parts.append(image_part)
|
|
139
|
+
else:
|
|
140
|
+
converted_parts.append(
|
|
141
|
+
ImageURLPart(
|
|
142
|
+
type="image_url",
|
|
143
|
+
url=img_url_str,
|
|
144
|
+
detail=image_url.detail
|
|
145
|
+
if isinstance(image_url.detail, str)
|
|
146
|
+
else None,
|
|
147
|
+
)
|
|
140
148
|
)
|
|
141
|
-
)
|
|
142
|
-
|
|
143
149
|
elif isinstance(chunk, ReferenceChunk):
|
|
144
150
|
raise ValueError(
|
|
145
151
|
"ReferenceChunk is not supported for conversion to BaseMessageParam."
|
|
@@ -6,6 +6,8 @@ import json
|
|
|
6
6
|
from openai.types.chat import ChatCompletionMessageParam
|
|
7
7
|
|
|
8
8
|
from ...base import BaseMessageParam
|
|
9
|
+
from ...base._utils import get_audio_type
|
|
10
|
+
from ...base._utils._parse_content_template import _load_media
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
def convert_message_params(
|
|
@@ -44,6 +46,16 @@ def convert_message_params(
|
|
|
44
46
|
},
|
|
45
47
|
}
|
|
46
48
|
)
|
|
49
|
+
elif part.type == "image_url":
|
|
50
|
+
converted_content.append(
|
|
51
|
+
{
|
|
52
|
+
"type": "image_url",
|
|
53
|
+
"image_url": {
|
|
54
|
+
"url": part.url,
|
|
55
|
+
"detail": part.detail if part.detail else "auto",
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
)
|
|
47
59
|
elif part.type == "audio":
|
|
48
60
|
if part.media_type not in [
|
|
49
61
|
"audio/wav",
|
|
@@ -53,11 +65,36 @@ def convert_message_params(
|
|
|
53
65
|
f"Unsupported audio media type: {part.media_type}. "
|
|
54
66
|
"OpenAI currently only supports WAV and MP3 audio file types."
|
|
55
67
|
)
|
|
68
|
+
data = (
|
|
69
|
+
part.audio
|
|
70
|
+
if isinstance(part.audio, str)
|
|
71
|
+
else base64.b64encode(part.audio).decode("utf-8")
|
|
72
|
+
)
|
|
56
73
|
converted_content.append(
|
|
57
74
|
{
|
|
58
75
|
"input_audio": {
|
|
59
76
|
"format": part.media_type.split("/")[-1],
|
|
60
|
-
"data":
|
|
77
|
+
"data": data,
|
|
78
|
+
},
|
|
79
|
+
"type": "input_audio",
|
|
80
|
+
}
|
|
81
|
+
)
|
|
82
|
+
elif part.type == "audio_url":
|
|
83
|
+
audio = _load_media(part.url)
|
|
84
|
+
audio_type = get_audio_type(audio)
|
|
85
|
+
if audio_type not in [
|
|
86
|
+
"audio/wav",
|
|
87
|
+
"audio/mp3",
|
|
88
|
+
]:
|
|
89
|
+
raise ValueError(
|
|
90
|
+
f"Unsupported audio media type: {audio_type}. "
|
|
91
|
+
"OpenAI currently only supports WAV and MP3 audio file types."
|
|
92
|
+
)
|
|
93
|
+
converted_content.append(
|
|
94
|
+
{
|
|
95
|
+
"input_audio": {
|
|
96
|
+
"format": audio_type.split("/")[-1],
|
|
97
|
+
"data": base64.b64encode(audio).decode("utf-8"),
|
|
61
98
|
},
|
|
62
99
|
"type": "input_audio",
|
|
63
100
|
}
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
"""This module contains the OpenAIMessageParamConverter class."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
from collections.abc import Iterable
|
|
4
5
|
from typing import cast
|
|
5
6
|
|
|
6
7
|
from openai.types.chat import ChatCompletionMessageParam
|
|
7
8
|
|
|
8
9
|
from mirascope.core import BaseMessageParam
|
|
9
|
-
from mirascope.core.base import
|
|
10
|
+
from mirascope.core.base import (
|
|
11
|
+
AudioPart,
|
|
12
|
+
ImageURLPart,
|
|
13
|
+
TextPart,
|
|
14
|
+
ToolCallPart,
|
|
15
|
+
ToolResultPart,
|
|
16
|
+
)
|
|
10
17
|
from mirascope.core.base._utils._base_message_param_converter import (
|
|
11
18
|
BaseMessageParamConverter,
|
|
12
19
|
)
|
|
@@ -57,12 +64,29 @@ class OpenAIMessageParamConverter(BaseMessageParamConverter):
|
|
|
57
64
|
BaseMessageParam(role=message_param["role"], content=content)
|
|
58
65
|
)
|
|
59
66
|
continue
|
|
60
|
-
elif isinstance(content,
|
|
67
|
+
elif isinstance(content, Iterable):
|
|
61
68
|
for part in content:
|
|
62
|
-
if "
|
|
69
|
+
if part["type"] == "text":
|
|
63
70
|
contents.append(TextPart(type="text", text=part["text"]))
|
|
71
|
+
elif part["type"] == "image_url":
|
|
72
|
+
image_url = part["image_url"]
|
|
73
|
+
contents.append(
|
|
74
|
+
ImageURLPart(
|
|
75
|
+
type="image_url",
|
|
76
|
+
url=image_url["url"],
|
|
77
|
+
detail=image_url.get("detail", None),
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
elif part["type"] == "input_audio":
|
|
81
|
+
input_audio = part["input_audio"]
|
|
82
|
+
contents.append(
|
|
83
|
+
AudioPart(
|
|
84
|
+
type="audio",
|
|
85
|
+
media_type=f"audio/{input_audio['format']}",
|
|
86
|
+
audio=input_audio["data"],
|
|
87
|
+
)
|
|
88
|
+
)
|
|
64
89
|
else:
|
|
65
|
-
# TODO: add support for image and audio parts here
|
|
66
90
|
raise ValueError(part["refusal"]) # pyright: ignore [reportGeneralTypeIssues]
|
|
67
91
|
if tool_calls := message_param.get("tool_calls"):
|
|
68
92
|
for tool_call in tool_calls:
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
"""Utility for converting `BaseMessageParam` to `Content`"""
|
|
2
2
|
|
|
3
|
+
import base64
|
|
4
|
+
import io
|
|
5
|
+
|
|
6
|
+
import PIL.Image
|
|
3
7
|
from google.cloud.aiplatform_v1beta1.types import content as gapic_content_types
|
|
4
8
|
from google.cloud.aiplatform_v1beta1.types import tool as gapic_tool_types
|
|
5
9
|
from vertexai.generative_models import Content, Image, Part
|
|
6
10
|
|
|
7
11
|
from ...base import BaseMessageParam
|
|
12
|
+
from ...base._utils import get_audio_type
|
|
13
|
+
from ...base._utils._parse_content_template import _load_media
|
|
8
14
|
|
|
9
15
|
|
|
10
16
|
def convert_message_params(
|
|
@@ -22,15 +28,11 @@ def convert_message_params(
|
|
|
22
28
|
) # pragma: no cover
|
|
23
29
|
converted_message_params += [
|
|
24
30
|
Content(
|
|
25
|
-
role="
|
|
31
|
+
role="system",
|
|
26
32
|
parts=[
|
|
27
33
|
Part.from_text(content) if isinstance(content, str) else content
|
|
28
34
|
],
|
|
29
35
|
),
|
|
30
|
-
Content(
|
|
31
|
-
role="model",
|
|
32
|
-
parts=[Part.from_text("Ok! I will adhere to this system message.")],
|
|
33
|
-
),
|
|
34
36
|
]
|
|
35
37
|
elif isinstance((content := message_param.content), str):
|
|
36
38
|
converted_message_params.append(
|
|
@@ -56,6 +58,29 @@ def convert_message_params(
|
|
|
56
58
|
)
|
|
57
59
|
image = Image.from_bytes(part.image)
|
|
58
60
|
converted_content.append(Part.from_image(image))
|
|
61
|
+
elif part.type == "image_url":
|
|
62
|
+
# Should download the image to determine the media type
|
|
63
|
+
image = PIL.Image.open(io.BytesIO(_load_media(part.url)))
|
|
64
|
+
media_type = (
|
|
65
|
+
PIL.Image.MIME[image.format]
|
|
66
|
+
if image.format
|
|
67
|
+
else "image/unknown"
|
|
68
|
+
)
|
|
69
|
+
if media_type not in [
|
|
70
|
+
"image/jpeg",
|
|
71
|
+
"image/png",
|
|
72
|
+
"image/webp",
|
|
73
|
+
"image/heic",
|
|
74
|
+
"image/heif",
|
|
75
|
+
]:
|
|
76
|
+
raise ValueError(
|
|
77
|
+
f"Unsupported image media type: {media_type}. "
|
|
78
|
+
"Gemini currently only supports JPEG, PNG, WebP, HEIC, "
|
|
79
|
+
"and HEIF images."
|
|
80
|
+
)
|
|
81
|
+
converted_content.append(
|
|
82
|
+
Part.from_uri(part.url, mime_type=media_type)
|
|
83
|
+
)
|
|
59
84
|
elif part.type == "audio":
|
|
60
85
|
if part.media_type not in [
|
|
61
86
|
"audio/wav",
|
|
@@ -71,7 +96,32 @@ def convert_message_params(
|
|
|
71
96
|
"and FLAC audio file types."
|
|
72
97
|
)
|
|
73
98
|
converted_content.append(
|
|
74
|
-
Part.from_data(
|
|
99
|
+
Part.from_data(
|
|
100
|
+
mime_type=part.media_type,
|
|
101
|
+
data=part.audio
|
|
102
|
+
if isinstance(part.audio, bytes)
|
|
103
|
+
else base64.b64decode(part.audio),
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
elif part.type == "audio_url":
|
|
107
|
+
# Should download the audio to determine the media type
|
|
108
|
+
audio = _load_media(part.url)
|
|
109
|
+
audio_type = get_audio_type(audio)
|
|
110
|
+
if audio_type not in [
|
|
111
|
+
"audio/wav",
|
|
112
|
+
"audio/mp3",
|
|
113
|
+
"audio/aiff",
|
|
114
|
+
"audio/aac",
|
|
115
|
+
"audio/ogg",
|
|
116
|
+
"audio/flac",
|
|
117
|
+
]:
|
|
118
|
+
raise ValueError(
|
|
119
|
+
f"Unsupported audio media type: {audio_type}. "
|
|
120
|
+
"Gemini currently only supports WAV, MP3, AIFF, AAC, OGG, "
|
|
121
|
+
"and FLAC audio file types."
|
|
122
|
+
)
|
|
123
|
+
converted_content.append(
|
|
124
|
+
Part.from_uri(part.url, mime_type=audio_type)
|
|
75
125
|
)
|
|
76
126
|
elif part.type == "tool_call":
|
|
77
127
|
if converted_content:
|
|
@@ -7,7 +7,7 @@ from mirascope.core.base import DocumentPart, ImagePart, TextPart
|
|
|
7
7
|
from mirascope.core.base._utils._base_message_param_converter import (
|
|
8
8
|
BaseMessageParamConverter,
|
|
9
9
|
)
|
|
10
|
-
from mirascope.core.base.message_param import ToolCallPart, ToolResultPart
|
|
10
|
+
from mirascope.core.base.message_param import ImageURLPart, ToolCallPart, ToolResultPart
|
|
11
11
|
from mirascope.core.vertex._utils import convert_message_params
|
|
12
12
|
|
|
13
13
|
|
|
@@ -87,10 +87,18 @@ class VertexMessageParamConverter(BaseMessageParamConverter):
|
|
|
87
87
|
)
|
|
88
88
|
|
|
89
89
|
elif part.file_data:
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
90
|
+
if _is_image_mime(part.file_data.mime_type):
|
|
91
|
+
contents.append(
|
|
92
|
+
ImageURLPart(
|
|
93
|
+
type="image_url",
|
|
94
|
+
url=part.file_data.file_uri,
|
|
95
|
+
detail=None,
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
else:
|
|
99
|
+
raise ValueError(
|
|
100
|
+
f"FileData.file_uri is not support: {part.file_data}. Cannot convert to BaseMessageParam."
|
|
101
|
+
)
|
|
94
102
|
elif part.function_call:
|
|
95
103
|
converted.append(
|
|
96
104
|
BaseMessageParam(
|
|
@@ -108,6 +108,7 @@ def setup_call(
|
|
|
108
108
|
call_kwargs = cast(VertexCallKwargs, base_call_kwargs)
|
|
109
109
|
messages = cast(list[BaseMessageParam | Content], messages)
|
|
110
110
|
messages = convert_message_params(messages)
|
|
111
|
+
|
|
111
112
|
if json_mode:
|
|
112
113
|
generation_config = call_kwargs.get(
|
|
113
114
|
"generation_config",
|
|
@@ -131,11 +132,19 @@ def setup_call(
|
|
|
131
132
|
)
|
|
132
133
|
)
|
|
133
134
|
call_kwargs["tool_config"] = tool_config
|
|
134
|
-
call_kwargs |= {"contents": messages}
|
|
135
135
|
|
|
136
136
|
if client is None:
|
|
137
137
|
client = GenerativeModel(model_name=model)
|
|
138
138
|
|
|
139
|
+
if messages and messages[0].role == "system":
|
|
140
|
+
system_instruction = client._system_instruction
|
|
141
|
+
if not isinstance(system_instruction, list):
|
|
142
|
+
system_instruction = [system_instruction] if system_instruction else []
|
|
143
|
+
system_instruction.extend(messages.pop(0).parts)
|
|
144
|
+
client._system_instruction = system_instruction
|
|
145
|
+
|
|
146
|
+
call_kwargs |= {"contents": messages}
|
|
147
|
+
|
|
139
148
|
create = (
|
|
140
149
|
cast(
|
|
141
150
|
AsyncCreateFn[GenerationResponse, AsyncIterable[GenerationResponse]],
|
mirascope/llm/call_response.py
CHANGED
|
@@ -51,7 +51,11 @@ class CallResponse(
|
|
|
51
51
|
response: BaseCallResponse[_ResponseT, _BaseToolT, Any, Any, Any, Any, Any],
|
|
52
52
|
) -> None:
|
|
53
53
|
super().__init__(
|
|
54
|
-
**{
|
|
54
|
+
**{
|
|
55
|
+
field: getattr(response, field)
|
|
56
|
+
for field in response.model_fields
|
|
57
|
+
if field != "user_message_param"
|
|
58
|
+
}
|
|
55
59
|
)
|
|
56
60
|
object.__setattr__(self, "_response", response)
|
|
57
61
|
object.__setattr__(
|