mirascope 1.19.0__py3-none-any.whl → 1.20.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.
- mirascope/__init__.py +4 -0
- mirascope/beta/openai/realtime/realtime.py +7 -8
- mirascope/beta/openai/realtime/tool.py +2 -2
- mirascope/core/__init__.py +10 -1
- mirascope/core/anthropic/_utils/__init__.py +0 -2
- mirascope/core/anthropic/_utils/_convert_message_params.py +1 -7
- mirascope/core/anthropic/_utils/_message_param_converter.py +48 -31
- mirascope/core/anthropic/call_response.py +7 -9
- mirascope/core/anthropic/call_response_chunk.py +10 -0
- mirascope/core/anthropic/stream.py +6 -8
- mirascope/core/azure/_utils/__init__.py +0 -2
- mirascope/core/azure/call_response.py +7 -10
- mirascope/core/azure/call_response_chunk.py +6 -1
- mirascope/core/azure/stream.py +6 -8
- mirascope/core/base/__init__.py +10 -1
- mirascope/core/base/_utils/__init__.py +2 -0
- mirascope/core/base/_utils/_get_image_dimensions.py +39 -0
- mirascope/core/base/call_response.py +36 -6
- mirascope/core/base/call_response_chunk.py +15 -1
- mirascope/core/base/stream.py +25 -3
- mirascope/core/base/types.py +276 -2
- mirascope/core/bedrock/_utils/__init__.py +0 -2
- mirascope/core/bedrock/call_response.py +7 -10
- mirascope/core/bedrock/call_response_chunk.py +6 -0
- mirascope/core/bedrock/stream.py +6 -10
- mirascope/core/cohere/_utils/__init__.py +0 -2
- mirascope/core/cohere/call_response.py +7 -10
- mirascope/core/cohere/call_response_chunk.py +6 -0
- mirascope/core/cohere/stream.py +5 -8
- mirascope/core/costs/__init__.py +5 -0
- mirascope/core/{anthropic/_utils/_calculate_cost.py → costs/_anthropic_calculate_cost.py} +45 -14
- mirascope/core/{azure/_utils/_calculate_cost.py → costs/_azure_calculate_cost.py} +3 -3
- mirascope/core/{bedrock/_utils/_calculate_cost.py → costs/_bedrock_calculate_cost.py} +3 -3
- mirascope/core/{cohere/_utils/_calculate_cost.py → costs/_cohere_calculate_cost.py} +12 -8
- mirascope/core/{gemini/_utils/_calculate_cost.py → costs/_gemini_calculate_cost.py} +7 -7
- mirascope/core/costs/_google_calculate_cost.py +427 -0
- mirascope/core/costs/_groq_calculate_cost.py +156 -0
- mirascope/core/costs/_litellm_calculate_cost.py +11 -0
- mirascope/core/costs/_mistral_calculate_cost.py +64 -0
- mirascope/core/costs/_openai_calculate_cost.py +416 -0
- mirascope/core/{vertex/_utils/_calculate_cost.py → costs/_vertex_calculate_cost.py} +8 -7
- mirascope/core/{xai/_utils/_calculate_cost.py → costs/_xai_calculate_cost.py} +9 -9
- mirascope/core/costs/calculate_cost.py +86 -0
- mirascope/core/gemini/_utils/__init__.py +0 -2
- mirascope/core/gemini/call_response.py +7 -10
- mirascope/core/gemini/call_response_chunk.py +6 -1
- mirascope/core/gemini/stream.py +5 -8
- mirascope/core/google/_utils/__init__.py +0 -2
- mirascope/core/google/_utils/_setup_call.py +21 -2
- mirascope/core/google/call_response.py +9 -10
- mirascope/core/google/call_response_chunk.py +6 -1
- mirascope/core/google/stream.py +5 -8
- mirascope/core/groq/_utils/__init__.py +0 -2
- mirascope/core/groq/call_response.py +22 -10
- mirascope/core/groq/call_response_chunk.py +6 -0
- mirascope/core/groq/stream.py +5 -8
- mirascope/core/litellm/call_response.py +3 -4
- mirascope/core/litellm/stream.py +30 -22
- mirascope/core/mistral/_utils/__init__.py +0 -2
- mirascope/core/mistral/call_response.py +7 -10
- mirascope/core/mistral/call_response_chunk.py +6 -0
- mirascope/core/mistral/stream.py +5 -8
- mirascope/core/openai/_utils/__init__.py +0 -2
- mirascope/core/openai/_utils/_convert_message_params.py +4 -4
- mirascope/core/openai/call_response.py +30 -10
- mirascope/core/openai/call_response_chunk.py +6 -0
- mirascope/core/openai/stream.py +5 -8
- mirascope/core/vertex/_utils/__init__.py +0 -2
- mirascope/core/vertex/call_response.py +5 -10
- mirascope/core/vertex/call_response_chunk.py +6 -0
- mirascope/core/vertex/stream.py +5 -8
- mirascope/core/xai/_utils/__init__.py +1 -2
- mirascope/core/xai/call_response.py +0 -11
- mirascope/llm/__init__.py +10 -2
- mirascope/llm/_protocols.py +8 -28
- mirascope/llm/call_response.py +6 -6
- mirascope/llm/call_response_chunk.py +12 -3
- mirascope/llm/llm_call.py +21 -23
- mirascope/llm/llm_override.py +56 -27
- mirascope/llm/stream.py +7 -7
- mirascope/llm/tool.py +1 -1
- mirascope/retries/fallback.py +1 -1
- {mirascope-1.19.0.dist-info → mirascope-1.20.1.dist-info}/METADATA +1 -1
- {mirascope-1.19.0.dist-info → mirascope-1.20.1.dist-info}/RECORD +86 -82
- mirascope/core/google/_utils/_calculate_cost.py +0 -215
- mirascope/core/groq/_utils/_calculate_cost.py +0 -69
- mirascope/core/mistral/_utils/_calculate_cost.py +0 -48
- mirascope/core/openai/_utils/_calculate_cost.py +0 -246
- {mirascope-1.19.0.dist-info → mirascope-1.20.1.dist-info}/WHEEL +0 -0
- {mirascope-1.19.0.dist-info → mirascope-1.20.1.dist-info}/licenses/LICENSE +0 -0
mirascope/__init__.py
CHANGED
|
@@ -17,7 +17,9 @@ from .core import (
|
|
|
17
17
|
DocumentPart,
|
|
18
18
|
ImagePart,
|
|
19
19
|
ImageURLPart,
|
|
20
|
+
LocalProvider,
|
|
20
21
|
Messages,
|
|
22
|
+
Provider,
|
|
21
23
|
TextPart,
|
|
22
24
|
ToolCallPart,
|
|
23
25
|
ToolResultPart,
|
|
@@ -43,7 +45,9 @@ __all__ = [
|
|
|
43
45
|
"DocumentPart",
|
|
44
46
|
"ImagePart",
|
|
45
47
|
"ImageURLPart",
|
|
48
|
+
"LocalProvider",
|
|
46
49
|
"Messages",
|
|
50
|
+
"Provider",
|
|
47
51
|
"TextPart",
|
|
48
52
|
"ToolCallPart",
|
|
49
53
|
"ToolResultPart",
|
|
@@ -5,6 +5,7 @@ import base64
|
|
|
5
5
|
import inspect
|
|
6
6
|
import json
|
|
7
7
|
import os
|
|
8
|
+
from collections import defaultdict
|
|
8
9
|
from collections.abc import Callable
|
|
9
10
|
from functools import lru_cache
|
|
10
11
|
from io import BytesIO
|
|
@@ -17,23 +18,21 @@ from typing import (
|
|
|
17
18
|
)
|
|
18
19
|
|
|
19
20
|
import websockets
|
|
20
|
-
from black.trans import defaultdict
|
|
21
21
|
from pydantic import BaseModel
|
|
22
22
|
from pydub import AudioSegment
|
|
23
23
|
from typing_extensions import NotRequired, overload
|
|
24
24
|
from websockets.asyncio.client import ClientConnection, connect
|
|
25
25
|
|
|
26
|
-
from
|
|
26
|
+
from ....core import BaseTool
|
|
27
|
+
from ....core.base._utils import (
|
|
28
|
+
convert_base_model_to_base_tool,
|
|
29
|
+
convert_function_to_base_tool,
|
|
30
|
+
)
|
|
31
|
+
from ._utils._audio import (
|
|
27
32
|
async_audio_input_audio_buffer_append_event,
|
|
28
33
|
async_audio_to_item_create_event,
|
|
29
34
|
audio_chunk_to_audio_segment,
|
|
30
35
|
)
|
|
31
|
-
from mirascope.core import BaseTool
|
|
32
|
-
from mirascope.core.base._utils import (
|
|
33
|
-
convert_base_model_to_base_tool,
|
|
34
|
-
convert_function_to_base_tool,
|
|
35
|
-
)
|
|
36
|
-
|
|
37
36
|
from ._utils._protocols import FunctionCallHandlerFunc, ReceiverFunc, SenderFunc
|
|
38
37
|
from .tool import FunctionCallArguments, OpenAIRealtimeTool, RealtimeToolParam
|
|
39
38
|
|
|
@@ -6,8 +6,8 @@ import jiter
|
|
|
6
6
|
from pydantic.json_schema import SkipJsonSchema
|
|
7
7
|
from typing_extensions import NotRequired, TypedDict
|
|
8
8
|
|
|
9
|
-
from
|
|
10
|
-
from
|
|
9
|
+
from ....core import BaseTool
|
|
10
|
+
from ....core.base import GenerateJsonSchemaNoTitles, ToolConfig
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class OpenAIRealtimeToolConfig(ToolConfig, total=False):
|
mirascope/core/__init__.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from contextlib import suppress
|
|
4
4
|
|
|
5
|
-
from . import base
|
|
5
|
+
from . import base, costs
|
|
6
6
|
from .base import (
|
|
7
7
|
AudioPart,
|
|
8
8
|
AudioURLPart,
|
|
@@ -14,11 +14,14 @@ from .base import (
|
|
|
14
14
|
BaseTool,
|
|
15
15
|
BaseToolKit,
|
|
16
16
|
CacheControlPart,
|
|
17
|
+
CostMetadata,
|
|
17
18
|
DocumentPart,
|
|
18
19
|
FromCallArgs,
|
|
19
20
|
ImagePart,
|
|
20
21
|
ImageURLPart,
|
|
22
|
+
LocalProvider,
|
|
21
23
|
Messages,
|
|
24
|
+
Provider,
|
|
22
25
|
ResponseModelConfigDict,
|
|
23
26
|
TextPart,
|
|
24
27
|
ToolCallPart,
|
|
@@ -28,6 +31,7 @@ from .base import (
|
|
|
28
31
|
prompt_template,
|
|
29
32
|
toolkit_tool,
|
|
30
33
|
)
|
|
34
|
+
from .costs import calculate_cost
|
|
31
35
|
|
|
32
36
|
with suppress(ImportError):
|
|
33
37
|
from . import anthropic as anthropic
|
|
@@ -71,11 +75,14 @@ __all__ = [
|
|
|
71
75
|
"BaseTool",
|
|
72
76
|
"BaseToolKit",
|
|
73
77
|
"CacheControlPart",
|
|
78
|
+
"CostMetadata",
|
|
74
79
|
"DocumentPart",
|
|
75
80
|
"FromCallArgs",
|
|
76
81
|
"ImagePart",
|
|
77
82
|
"ImageURLPart",
|
|
83
|
+
"LocalProvider",
|
|
78
84
|
"Messages",
|
|
85
|
+
"Provider",
|
|
79
86
|
"ResponseModelConfigDict",
|
|
80
87
|
"TextPart",
|
|
81
88
|
"ToolCallPart",
|
|
@@ -83,7 +90,9 @@ __all__ = [
|
|
|
83
90
|
"anthropic",
|
|
84
91
|
"azure",
|
|
85
92
|
"base",
|
|
93
|
+
"calculate_cost",
|
|
86
94
|
"cohere",
|
|
95
|
+
"costs",
|
|
87
96
|
"gemini",
|
|
88
97
|
"google",
|
|
89
98
|
"groq",
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"""Anthropic utilities for decorator factories."""
|
|
2
2
|
|
|
3
|
-
from ._calculate_cost import calculate_cost
|
|
4
3
|
from ._convert_common_call_params import convert_common_call_params
|
|
5
4
|
from ._convert_message_params import convert_message_params
|
|
6
5
|
from ._get_json_output import get_json_output
|
|
@@ -8,7 +7,6 @@ from ._handle_stream import handle_stream, handle_stream_async
|
|
|
8
7
|
from ._setup_call import setup_call
|
|
9
8
|
|
|
10
9
|
__all__ = [
|
|
11
|
-
"calculate_cost",
|
|
12
10
|
"convert_common_call_params",
|
|
13
11
|
"convert_message_params",
|
|
14
12
|
"get_json_output",
|
|
@@ -5,7 +5,6 @@ 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
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
def convert_message_params(
|
|
@@ -47,15 +46,10 @@ def convert_message_params(
|
|
|
47
46
|
}
|
|
48
47
|
)
|
|
49
48
|
elif part.type == "image_url":
|
|
50
|
-
image = _load_media(part.url)
|
|
51
49
|
converted_content.append(
|
|
52
50
|
{
|
|
53
51
|
"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
|
-
},
|
|
52
|
+
"source": {"url": part.url, "type": "url"},
|
|
59
53
|
}
|
|
60
54
|
)
|
|
61
55
|
elif part.type == "document":
|
|
@@ -6,7 +6,13 @@ from anthropic.types import MessageParam
|
|
|
6
6
|
|
|
7
7
|
from mirascope.core import BaseMessageParam
|
|
8
8
|
from mirascope.core.anthropic._utils import convert_message_params
|
|
9
|
-
from mirascope.core.base import
|
|
9
|
+
from mirascope.core.base import (
|
|
10
|
+
ImagePart,
|
|
11
|
+
ImageURLPart,
|
|
12
|
+
TextPart,
|
|
13
|
+
ToolCallPart,
|
|
14
|
+
ToolResultPart,
|
|
15
|
+
)
|
|
10
16
|
from mirascope.core.base._utils._base_message_param_converter import (
|
|
11
17
|
BaseMessageParamConverter,
|
|
12
18
|
)
|
|
@@ -53,41 +59,52 @@ class AnthropicMessageParamConverter(BaseMessageParamConverter):
|
|
|
53
59
|
|
|
54
60
|
elif block["type"] == "image":
|
|
55
61
|
source = block.get("source")
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
"ImageBlockParam must have a 'source' with type='base64'."
|
|
59
|
-
)
|
|
60
|
-
image_data = source.get("data")
|
|
61
|
-
media_type = source.get("media_type")
|
|
62
|
-
if not image_data or not media_type:
|
|
62
|
+
source_type = source.get("type") if source else None
|
|
63
|
+
if not source or source_type not in ["base64", "url"]:
|
|
63
64
|
raise ValueError(
|
|
64
|
-
"ImageBlockParam
|
|
65
|
+
"ImageBlockParam must have a 'source' with type='base64' or type='url'."
|
|
65
66
|
)
|
|
66
|
-
if
|
|
67
|
-
"
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
"BaseMessageParam currently only supports JPEG, PNG, GIF, and WebP images."
|
|
67
|
+
if source_type == "url":
|
|
68
|
+
url = source.get("url")
|
|
69
|
+
if not url:
|
|
70
|
+
raise ValueError(
|
|
71
|
+
"ImageBlockParam source with type='url' must have a 'url'."
|
|
72
|
+
)
|
|
73
|
+
converted_content.append(
|
|
74
|
+
ImageURLPart(type="image_url", url=url, detail=None)
|
|
75
75
|
)
|
|
76
|
-
if isinstance(image_data, str):
|
|
77
|
-
decoded_image_data = base64.b64decode(image_data)
|
|
78
|
-
elif isinstance(image_data, PathLike):
|
|
79
|
-
with open(image_data, "rb") as image_data:
|
|
80
|
-
decoded_image_data = image_data.read()
|
|
81
76
|
else:
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
77
|
+
image_data = source.get("data")
|
|
78
|
+
media_type = source.get("media_type")
|
|
79
|
+
if not image_data or not media_type:
|
|
80
|
+
raise ValueError(
|
|
81
|
+
"ImageBlockParam source with type='base64' must have 'data' and 'media_type'."
|
|
82
|
+
)
|
|
83
|
+
if media_type not in [
|
|
84
|
+
"image/jpeg",
|
|
85
|
+
"image/png",
|
|
86
|
+
"image/gif",
|
|
87
|
+
"image/webp",
|
|
88
|
+
]:
|
|
89
|
+
raise ValueError(
|
|
90
|
+
f"Unsupported image media type: {media_type}. "
|
|
91
|
+
"BaseMessageParam currently only supports JPEG, PNG, GIF, and WebP images."
|
|
92
|
+
)
|
|
93
|
+
if isinstance(image_data, str):
|
|
94
|
+
decoded_image_data = base64.b64decode(image_data)
|
|
95
|
+
elif isinstance(image_data, PathLike):
|
|
96
|
+
with open(image_data, "rb") as image_data:
|
|
97
|
+
decoded_image_data = image_data.read()
|
|
98
|
+
else:
|
|
99
|
+
decoded_image_data = image_data.read()
|
|
100
|
+
converted_content.append(
|
|
101
|
+
ImagePart(
|
|
102
|
+
type="image",
|
|
103
|
+
media_type=media_type,
|
|
104
|
+
image=decoded_image_data,
|
|
105
|
+
detail=None,
|
|
106
|
+
)
|
|
89
107
|
)
|
|
90
|
-
)
|
|
91
108
|
|
|
92
109
|
elif block["type"] == "tool_use":
|
|
93
110
|
if converted_content:
|
|
@@ -16,7 +16,7 @@ from pydantic import SerializeAsAny, computed_field
|
|
|
16
16
|
|
|
17
17
|
from .. import BaseMessageParam
|
|
18
18
|
from ..base import BaseCallResponse, transform_tool_outputs, types
|
|
19
|
-
from .
|
|
19
|
+
from ..base.types import CostMetadata
|
|
20
20
|
from ._utils._convert_finish_reason_to_common_finish_reasons import (
|
|
21
21
|
_convert_finish_reasons_to_common_finish_reasons,
|
|
22
22
|
)
|
|
@@ -112,14 +112,6 @@ class AnthropicCallResponse(
|
|
|
112
112
|
"""Returns the number of output tokens."""
|
|
113
113
|
return self.usage.output_tokens
|
|
114
114
|
|
|
115
|
-
@computed_field
|
|
116
|
-
@property
|
|
117
|
-
def cost(self) -> float | None:
|
|
118
|
-
"""Returns the cost of the call."""
|
|
119
|
-
return calculate_cost(
|
|
120
|
-
self.input_tokens, self.cached_tokens, self.output_tokens, self.model
|
|
121
|
-
)
|
|
122
|
-
|
|
123
115
|
@computed_field
|
|
124
116
|
@cached_property
|
|
125
117
|
def message_param(self) -> SerializeAsAny[MessageParam]:
|
|
@@ -202,3 +194,9 @@ class AnthropicCallResponse(
|
|
|
202
194
|
return AnthropicMessageParamConverter.from_provider(
|
|
203
195
|
[(self.user_message_param)]
|
|
204
196
|
)[0]
|
|
197
|
+
|
|
198
|
+
@computed_field
|
|
199
|
+
@property
|
|
200
|
+
def cost_metadata(self) -> CostMetadata:
|
|
201
|
+
"""Get metadata required for cost calculation."""
|
|
202
|
+
return super().cost_metadata
|
|
@@ -16,6 +16,7 @@ from anthropic.types import (
|
|
|
16
16
|
)
|
|
17
17
|
|
|
18
18
|
from ..base import BaseCallResponseChunk, types
|
|
19
|
+
from ..base.types import CostMetadata
|
|
19
20
|
from ._utils._convert_finish_reason_to_common_finish_reasons import (
|
|
20
21
|
_convert_finish_reasons_to_common_finish_reasons,
|
|
21
22
|
)
|
|
@@ -114,6 +115,15 @@ class AnthropicCallResponseChunk(
|
|
|
114
115
|
return self.usage.output_tokens
|
|
115
116
|
return None
|
|
116
117
|
|
|
118
|
+
@property
|
|
119
|
+
def cost_metadata(self) -> CostMetadata:
|
|
120
|
+
"""Returns the cost metadata."""
|
|
121
|
+
return CostMetadata(
|
|
122
|
+
input_tokens=self.input_tokens,
|
|
123
|
+
output_tokens=self.output_tokens,
|
|
124
|
+
cached_tokens=self.cached_tokens,
|
|
125
|
+
)
|
|
126
|
+
|
|
117
127
|
@property
|
|
118
128
|
def common_finish_reasons(self) -> list[types.FinishReason] | None:
|
|
119
129
|
"""Provider-agnostic finish reasons."""
|
|
@@ -17,7 +17,7 @@ from anthropic.types.tool_use_block_param import ToolUseBlockParam
|
|
|
17
17
|
from pydantic import BaseModel
|
|
18
18
|
|
|
19
19
|
from ..base.stream import BaseStream
|
|
20
|
-
from .
|
|
20
|
+
from ..base.types import CostMetadata
|
|
21
21
|
from .call_params import AnthropicCallParams
|
|
22
22
|
from .call_response import AnthropicCallResponse
|
|
23
23
|
from .call_response_chunk import AnthropicCallResponseChunk
|
|
@@ -64,13 +64,6 @@ class AnthropicStream(
|
|
|
64
64
|
|
|
65
65
|
_provider = "anthropic"
|
|
66
66
|
|
|
67
|
-
@property
|
|
68
|
-
def cost(self) -> float | None:
|
|
69
|
-
"""Returns the cost of the call."""
|
|
70
|
-
return calculate_cost(
|
|
71
|
-
self.input_tokens, self.cached_tokens, self.output_tokens, self.model
|
|
72
|
-
)
|
|
73
|
-
|
|
74
67
|
def _construct_message_param(
|
|
75
68
|
self, tool_calls: list[ToolUseBlock] | None = None, content: str | None = None
|
|
76
69
|
) -> MessageParam:
|
|
@@ -147,3 +140,8 @@ class AnthropicStream(
|
|
|
147
140
|
start_time=self.start_time,
|
|
148
141
|
end_time=self.end_time,
|
|
149
142
|
)
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def cost_metadata(self) -> CostMetadata:
|
|
146
|
+
"""Get metadata required for cost calculation."""
|
|
147
|
+
return super().cost_metadata
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
"""Azure utilities for decorator factories."""
|
|
2
2
|
|
|
3
|
-
from ._calculate_cost import calculate_cost
|
|
4
3
|
from ._convert_message_params import convert_message_params
|
|
5
4
|
from ._get_json_output import get_json_output
|
|
6
5
|
from ._handle_stream import handle_stream, handle_stream_async
|
|
7
6
|
from ._setup_call import setup_call
|
|
8
7
|
|
|
9
8
|
__all__ = [
|
|
10
|
-
"calculate_cost",
|
|
11
9
|
"convert_message_params",
|
|
12
10
|
"get_json_output",
|
|
13
11
|
"handle_stream",
|
|
@@ -19,8 +19,7 @@ from pydantic import SerializeAsAny, SkipValidation, computed_field
|
|
|
19
19
|
|
|
20
20
|
from .. import BaseMessageParam
|
|
21
21
|
from ..base import BaseCallResponse, transform_tool_outputs
|
|
22
|
-
from ..base.types import FinishReason
|
|
23
|
-
from ._utils import calculate_cost
|
|
22
|
+
from ..base.types import CostMetadata, FinishReason
|
|
24
23
|
from ._utils._convert_finish_reason_to_common_finish_reasons import (
|
|
25
24
|
_convert_finish_reasons_to_common_finish_reasons,
|
|
26
25
|
)
|
|
@@ -116,14 +115,6 @@ class AzureCallResponse(
|
|
|
116
115
|
"""Returns the number of output tokens."""
|
|
117
116
|
return self.usage.completion_tokens if self.usage else None
|
|
118
117
|
|
|
119
|
-
@computed_field
|
|
120
|
-
@property
|
|
121
|
-
def cost(self) -> float | None:
|
|
122
|
-
"""Returns the cost of the call."""
|
|
123
|
-
return calculate_cost(
|
|
124
|
-
self.input_tokens, self.cached_tokens, self.output_tokens, self.model
|
|
125
|
-
)
|
|
126
|
-
|
|
127
118
|
@computed_field
|
|
128
119
|
@cached_property
|
|
129
120
|
def message_param(self) -> SerializeAsAny[AssistantMessage]:
|
|
@@ -213,3 +204,9 @@ class AzureCallResponse(
|
|
|
213
204
|
if not self.user_message_param:
|
|
214
205
|
return None
|
|
215
206
|
return AzureMessageParamConverter.from_provider([self.user_message_param])[0]
|
|
207
|
+
|
|
208
|
+
@computed_field
|
|
209
|
+
@property
|
|
210
|
+
def cost_metadata(self) -> CostMetadata:
|
|
211
|
+
"""Get metadata required for cost calculation."""
|
|
212
|
+
return super().cost_metadata
|
|
@@ -13,7 +13,7 @@ from azure.ai.inference.models import (
|
|
|
13
13
|
from pydantic import SkipValidation
|
|
14
14
|
|
|
15
15
|
from ..base import BaseCallResponseChunk
|
|
16
|
-
from ..base.types import FinishReason
|
|
16
|
+
from ..base.types import CostMetadata, FinishReason
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class AzureCallResponseChunk(
|
|
@@ -94,6 +94,11 @@ class AzureCallResponseChunk(
|
|
|
94
94
|
"""Returns the number of output tokens."""
|
|
95
95
|
return self.usage.completion_tokens
|
|
96
96
|
|
|
97
|
+
@property
|
|
98
|
+
def cost_metadata(self) -> CostMetadata:
|
|
99
|
+
"""Returns the cost metadata."""
|
|
100
|
+
return super().cost_metadata
|
|
101
|
+
|
|
97
102
|
@property
|
|
98
103
|
def common_finish_reasons(self) -> list[FinishReason] | None:
|
|
99
104
|
"""Provider-agnostic finish reasons."""
|
mirascope/core/azure/stream.py
CHANGED
|
@@ -21,7 +21,7 @@ from azure.ai.inference.models import (
|
|
|
21
21
|
)
|
|
22
22
|
|
|
23
23
|
from ..base.stream import BaseStream
|
|
24
|
-
from .
|
|
24
|
+
from ..base.types import CostMetadata
|
|
25
25
|
from .call_params import AzureCallParams
|
|
26
26
|
from .call_response import AzureCallResponse
|
|
27
27
|
from .call_response_chunk import AzureCallResponseChunk
|
|
@@ -66,13 +66,6 @@ class AzureStream(
|
|
|
66
66
|
|
|
67
67
|
_provider = "azure"
|
|
68
68
|
|
|
69
|
-
@property
|
|
70
|
-
def cost(self) -> float | None:
|
|
71
|
-
"""Returns the cost of the call."""
|
|
72
|
-
return calculate_cost(
|
|
73
|
-
self.input_tokens, self.cached_tokens, self.output_tokens, self.model
|
|
74
|
-
)
|
|
75
|
-
|
|
76
69
|
def _construct_message_param(
|
|
77
70
|
self,
|
|
78
71
|
tool_calls: list[ChatCompletionsToolCall] | None = None,
|
|
@@ -147,3 +140,8 @@ class AzureStream(
|
|
|
147
140
|
start_time=self.start_time,
|
|
148
141
|
end_time=self.end_time,
|
|
149
142
|
)
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def cost_metadata(self) -> CostMetadata:
|
|
146
|
+
"""Get metadata required for cost calculation."""
|
|
147
|
+
return super().cost_metadata
|
mirascope/core/base/__init__.py
CHANGED
|
@@ -30,7 +30,14 @@ from .stream import BaseStream
|
|
|
30
30
|
from .structured_stream import BaseStructuredStream
|
|
31
31
|
from .tool import BaseTool, GenerateJsonSchemaNoTitles, ToolConfig
|
|
32
32
|
from .toolkit import BaseToolKit, toolkit_tool
|
|
33
|
-
from .types import
|
|
33
|
+
from .types import (
|
|
34
|
+
AudioSegment,
|
|
35
|
+
CostMetadata,
|
|
36
|
+
JsonableType,
|
|
37
|
+
LocalProvider,
|
|
38
|
+
Provider,
|
|
39
|
+
Usage,
|
|
40
|
+
)
|
|
34
41
|
|
|
35
42
|
__all__ = [
|
|
36
43
|
"AudioPart",
|
|
@@ -50,12 +57,14 @@ __all__ = [
|
|
|
50
57
|
"BaseType",
|
|
51
58
|
"CacheControlPart",
|
|
52
59
|
"CommonCallParams",
|
|
60
|
+
"CostMetadata",
|
|
53
61
|
"DocumentPart",
|
|
54
62
|
"FromCallArgs",
|
|
55
63
|
"GenerateJsonSchemaNoTitles",
|
|
56
64
|
"ImagePart",
|
|
57
65
|
"ImageURLPart",
|
|
58
66
|
"JsonableType",
|
|
67
|
+
"LocalProvider",
|
|
59
68
|
"Messages",
|
|
60
69
|
"Metadata",
|
|
61
70
|
"ResponseModelConfigDict",
|
|
@@ -14,6 +14,7 @@ from ._get_create_fn_or_async_create_fn import get_async_create_fn, get_create_f
|
|
|
14
14
|
from ._get_document_type import get_document_type
|
|
15
15
|
from ._get_dynamic_configuration import get_dynamic_configuration
|
|
16
16
|
from ._get_fn_args import get_fn_args
|
|
17
|
+
from ._get_image_dimensions import get_image_dimensions
|
|
17
18
|
from ._get_image_type import get_image_type
|
|
18
19
|
from ._get_metadata import get_metadata
|
|
19
20
|
from ._get_possible_user_message_param import get_possible_user_message_param
|
|
@@ -68,6 +69,7 @@ __all__ = [
|
|
|
68
69
|
"get_document_type",
|
|
69
70
|
"get_dynamic_configuration",
|
|
70
71
|
"get_fn_args",
|
|
72
|
+
"get_image_dimensions",
|
|
71
73
|
"get_image_type",
|
|
72
74
|
"get_metadata",
|
|
73
75
|
"get_possible_user_message_param",
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import io
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from ...base.types import Image, ImageMetadata, has_pil_module
|
|
6
|
+
from ._parse_content_template import _load_media
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_image_dimensions(data_url: str) -> ImageMetadata | None:
|
|
10
|
+
"""
|
|
11
|
+
Extract width and height from a base64 encoded image.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
data_url: The data URL containing base64 encoded image data or External URL
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
Dictionary with width and height, or None if extraction failed
|
|
18
|
+
"""
|
|
19
|
+
try:
|
|
20
|
+
if data_url.startswith("http"):
|
|
21
|
+
binary_data = _load_media(data_url)
|
|
22
|
+
else:
|
|
23
|
+
# Extract the base64 data part from the URL
|
|
24
|
+
# Format is: data:[<media type>][;base64],<data>
|
|
25
|
+
pattern = r"data:image/[^;]+;base64,"
|
|
26
|
+
base64_data = re.sub(pattern, "", data_url)
|
|
27
|
+
|
|
28
|
+
# Decode the base64 data
|
|
29
|
+
binary_data = base64.b64decode(base64_data)
|
|
30
|
+
|
|
31
|
+
# Use PIL to open the image and get dimensions
|
|
32
|
+
if not has_pil_module: # pragma: no cover
|
|
33
|
+
raise ImportError("PIL module is not available")
|
|
34
|
+
with Image.open(io.BytesIO(binary_data)) as image:
|
|
35
|
+
width, height = image.size
|
|
36
|
+
|
|
37
|
+
return ImageMetadata(width=width, height=height)
|
|
38
|
+
except Exception:
|
|
39
|
+
return None
|
|
@@ -7,7 +7,7 @@ import json
|
|
|
7
7
|
from abc import ABC, abstractmethod
|
|
8
8
|
from collections.abc import Callable
|
|
9
9
|
from functools import cached_property, wraps
|
|
10
|
-
from typing import TYPE_CHECKING, Any, ClassVar, Generic, TypeVar
|
|
10
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Generic, TypeVar, cast
|
|
11
11
|
|
|
12
12
|
from pydantic import (
|
|
13
13
|
BaseModel,
|
|
@@ -18,13 +18,14 @@ from pydantic import (
|
|
|
18
18
|
field_serializer,
|
|
19
19
|
)
|
|
20
20
|
|
|
21
|
+
from ..costs import calculate_cost
|
|
21
22
|
from ._utils import BaseType, get_common_usage
|
|
22
23
|
from .call_kwargs import BaseCallKwargs
|
|
23
24
|
from .call_params import BaseCallParams
|
|
24
25
|
from .dynamic_config import BaseDynamicConfig
|
|
25
26
|
from .metadata import Metadata
|
|
26
27
|
from .tool import BaseTool
|
|
27
|
-
from .types import FinishReason, JsonableType, Usage
|
|
28
|
+
from .types import CostMetadata, FinishReason, JsonableType, Provider, Usage
|
|
28
29
|
|
|
29
30
|
if TYPE_CHECKING:
|
|
30
31
|
from ...llm.tool import Tool
|
|
@@ -225,12 +226,41 @@ class BaseCallResponse(
|
|
|
225
226
|
@computed_field
|
|
226
227
|
@property
|
|
227
228
|
@abstractmethod
|
|
228
|
-
def
|
|
229
|
-
"""
|
|
229
|
+
def cost_metadata(self) -> CostMetadata:
|
|
230
|
+
"""Get metadata required for cost calculation.
|
|
230
231
|
|
|
231
|
-
|
|
232
|
+
Returns:
|
|
233
|
+
Metadata relevant to cost calculation
|
|
232
234
|
"""
|
|
233
|
-
|
|
235
|
+
|
|
236
|
+
return CostMetadata(
|
|
237
|
+
input_tokens=self.input_tokens,
|
|
238
|
+
output_tokens=self.output_tokens,
|
|
239
|
+
cached_tokens=self.cached_tokens,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
@computed_field
|
|
243
|
+
@property
|
|
244
|
+
def cost(self) -> float | None:
|
|
245
|
+
"""Calculate the cost of this API call using the unified calculate_cost function."""
|
|
246
|
+
|
|
247
|
+
model = self.model
|
|
248
|
+
if not model:
|
|
249
|
+
return None
|
|
250
|
+
|
|
251
|
+
if self.input_tokens is None or self.output_tokens is None:
|
|
252
|
+
return None
|
|
253
|
+
|
|
254
|
+
return calculate_cost(
|
|
255
|
+
provider=self.provider,
|
|
256
|
+
model=model,
|
|
257
|
+
metadata=self.cost_metadata,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
@property
|
|
261
|
+
def provider(self) -> Provider:
|
|
262
|
+
"""Get the provider used for this API call."""
|
|
263
|
+
return cast(Provider, self._provider)
|
|
234
264
|
|
|
235
265
|
@computed_field
|
|
236
266
|
@cached_property
|
|
@@ -11,7 +11,7 @@ from typing import Any, Generic, TypeVar
|
|
|
11
11
|
from pydantic import BaseModel, ConfigDict
|
|
12
12
|
|
|
13
13
|
from mirascope.core.base._utils import get_common_usage
|
|
14
|
-
from mirascope.core.base.types import FinishReason, Usage
|
|
14
|
+
from mirascope.core.base.types import CostMetadata, FinishReason, Usage
|
|
15
15
|
|
|
16
16
|
_ChunkT = TypeVar("_ChunkT", bound=Any)
|
|
17
17
|
_FinishReasonT = TypeVar("_FinishReasonT", bound=Any)
|
|
@@ -102,6 +102,20 @@ class BaseCallResponseChunk(BaseModel, Generic[_ChunkT, _FinishReasonT], ABC):
|
|
|
102
102
|
"""
|
|
103
103
|
...
|
|
104
104
|
|
|
105
|
+
@property
|
|
106
|
+
@abstractmethod
|
|
107
|
+
def cost_metadata(self) -> CostMetadata:
|
|
108
|
+
"""Get metadata required for cost calculation.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
A CostMetadata object with information relevant to cost calculation
|
|
112
|
+
"""
|
|
113
|
+
return CostMetadata(
|
|
114
|
+
input_tokens=self.input_tokens,
|
|
115
|
+
output_tokens=self.output_tokens,
|
|
116
|
+
cached_tokens=self.cached_tokens,
|
|
117
|
+
)
|
|
118
|
+
|
|
105
119
|
@property
|
|
106
120
|
@abstractmethod
|
|
107
121
|
def common_finish_reasons(self) -> list[FinishReason] | None:
|