mirascope 1.18.2__py3-none-any.whl → 1.18.4__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.
Files changed (89) hide show
  1. mirascope/__init__.py +20 -1
  2. mirascope/beta/openai/__init__.py +1 -1
  3. mirascope/beta/openai/realtime/__init__.py +1 -1
  4. mirascope/beta/openai/realtime/tool.py +1 -1
  5. mirascope/beta/rag/__init__.py +2 -2
  6. mirascope/beta/rag/base/__init__.py +2 -2
  7. mirascope/beta/rag/weaviate/__init__.py +1 -1
  8. mirascope/core/__init__.py +29 -6
  9. mirascope/core/anthropic/__init__.py +3 -3
  10. mirascope/core/anthropic/_utils/_calculate_cost.py +114 -47
  11. mirascope/core/anthropic/call_response.py +9 -3
  12. mirascope/core/anthropic/call_response_chunk.py +7 -0
  13. mirascope/core/anthropic/stream.py +3 -1
  14. mirascope/core/azure/__init__.py +2 -2
  15. mirascope/core/azure/_utils/_calculate_cost.py +4 -1
  16. mirascope/core/azure/call_response.py +9 -3
  17. mirascope/core/azure/call_response_chunk.py +5 -0
  18. mirascope/core/azure/stream.py +3 -1
  19. mirascope/core/base/__init__.py +11 -9
  20. mirascope/core/base/_utils/__init__.py +10 -10
  21. mirascope/core/base/_utils/_get_common_usage.py +8 -4
  22. mirascope/core/base/_utils/_get_create_fn_or_async_create_fn.py +2 -2
  23. mirascope/core/base/_utils/_protocols.py +9 -8
  24. mirascope/core/base/call_response.py +22 -22
  25. mirascope/core/base/call_response_chunk.py +12 -1
  26. mirascope/core/base/stream.py +24 -21
  27. mirascope/core/base/tool.py +7 -5
  28. mirascope/core/base/types.py +22 -5
  29. mirascope/core/bedrock/__init__.py +3 -3
  30. mirascope/core/bedrock/_utils/_calculate_cost.py +4 -1
  31. mirascope/core/bedrock/call_response.py +8 -3
  32. mirascope/core/bedrock/call_response_chunk.py +5 -0
  33. mirascope/core/bedrock/stream.py +3 -1
  34. mirascope/core/cohere/__init__.py +2 -2
  35. mirascope/core/cohere/_utils/_calculate_cost.py +4 -3
  36. mirascope/core/cohere/call_response.py +9 -3
  37. mirascope/core/cohere/call_response_chunk.py +5 -0
  38. mirascope/core/cohere/stream.py +3 -1
  39. mirascope/core/gemini/__init__.py +2 -2
  40. mirascope/core/gemini/_utils/_calculate_cost.py +4 -1
  41. mirascope/core/gemini/_utils/_convert_message_params.py +1 -1
  42. mirascope/core/gemini/call_response.py +9 -3
  43. mirascope/core/gemini/call_response_chunk.py +5 -0
  44. mirascope/core/gemini/stream.py +3 -1
  45. mirascope/core/google/__init__.py +2 -2
  46. mirascope/core/google/_utils/_calculate_cost.py +141 -14
  47. mirascope/core/google/_utils/_convert_message_params.py +120 -115
  48. mirascope/core/google/_utils/_message_param_converter.py +34 -33
  49. mirascope/core/google/_utils/_validate_media_type.py +34 -0
  50. mirascope/core/google/call_response.py +38 -10
  51. mirascope/core/google/call_response_chunk.py +17 -9
  52. mirascope/core/google/stream.py +20 -2
  53. mirascope/core/groq/__init__.py +2 -2
  54. mirascope/core/groq/_utils/_calculate_cost.py +12 -11
  55. mirascope/core/groq/call_response.py +9 -3
  56. mirascope/core/groq/call_response_chunk.py +5 -0
  57. mirascope/core/groq/stream.py +3 -1
  58. mirascope/core/litellm/__init__.py +1 -1
  59. mirascope/core/litellm/_utils/_setup_call.py +7 -3
  60. mirascope/core/mistral/__init__.py +2 -2
  61. mirascope/core/mistral/_utils/_calculate_cost.py +10 -9
  62. mirascope/core/mistral/call_response.py +9 -3
  63. mirascope/core/mistral/call_response_chunk.py +5 -0
  64. mirascope/core/mistral/stream.py +3 -1
  65. mirascope/core/openai/__init__.py +2 -2
  66. mirascope/core/openai/_utils/_calculate_cost.py +78 -37
  67. mirascope/core/openai/call_params.py +13 -0
  68. mirascope/core/openai/call_response.py +14 -3
  69. mirascope/core/openai/call_response_chunk.py +12 -0
  70. mirascope/core/openai/stream.py +6 -4
  71. mirascope/core/vertex/__init__.py +1 -1
  72. mirascope/core/vertex/_utils/_calculate_cost.py +1 -0
  73. mirascope/core/vertex/_utils/_convert_message_params.py +1 -1
  74. mirascope/core/vertex/call_response.py +9 -3
  75. mirascope/core/vertex/call_response_chunk.py +5 -0
  76. mirascope/core/vertex/stream.py +3 -1
  77. mirascope/integrations/_middleware_factory.py +6 -6
  78. mirascope/integrations/logfire/_utils.py +1 -1
  79. mirascope/llm/__init__.py +3 -1
  80. mirascope/llm/_protocols.py +5 -5
  81. mirascope/llm/call_response.py +16 -9
  82. mirascope/llm/llm_call.py +53 -25
  83. mirascope/llm/stream.py +43 -31
  84. mirascope/retries/__init__.py +1 -1
  85. mirascope/tools/__init__.py +2 -2
  86. {mirascope-1.18.2.dist-info → mirascope-1.18.4.dist-info}/METADATA +2 -2
  87. {mirascope-1.18.2.dist-info → mirascope-1.18.4.dist-info}/RECORD +89 -88
  88. {mirascope-1.18.2.dist-info → mirascope-1.18.4.dist-info}/WHEEL +0 -0
  89. {mirascope-1.18.2.dist-info → mirascope-1.18.4.dist-info}/licenses/LICENSE +0 -0
@@ -13,35 +13,15 @@ from mirascope.core.base._utils._base_message_param_converter import (
13
13
  BaseMessageParamConverter,
14
14
  )
15
15
  from mirascope.core.base.message_param import (
16
+ AudioPart,
16
17
  AudioURLPart,
17
18
  ImageURLPart,
18
19
  ToolCallPart,
19
20
  ToolResultPart,
20
21
  )
21
- from mirascope.core.gemini._utils._message_param_converter import _is_audio_mime
22
22
  from mirascope.core.google._utils import convert_message_params
23
23
 
24
-
25
- def _is_image_mime(mime_type: str) -> bool:
26
- return mime_type in ["image/jpeg", "image/png", "image/gif", "image/webp"]
27
-
28
-
29
- def _to_image_part(mime_type: str, data: bytes) -> ImagePart:
30
- if not _is_image_mime(mime_type):
31
- raise ValueError(
32
- f"Unsupported image media type: {mime_type}. "
33
- "Expected one of: image/jpeg, image/png, image/gif, image/webp."
34
- )
35
- return ImagePart(type="image", media_type=mime_type, image=data, detail=None)
36
-
37
-
38
- def _to_document_part(mime_type: str, data: bytes) -> DocumentPart:
39
- if mime_type != "application/pdf":
40
- raise ValueError(
41
- f"Unsupported document media type: {mime_type}. "
42
- "Only application/pdf is supported."
43
- )
44
- return DocumentPart(type="document", media_type=mime_type, document=data)
24
+ from ._validate_media_type import _check_audio_media_type, _check_image_media_type
45
25
 
46
26
 
47
27
  class GoogleMessageParamConverter(BaseMessageParamConverter):
@@ -74,21 +54,42 @@ class GoogleMessageParamConverter(BaseMessageParamConverter):
74
54
  if part.text:
75
55
  content_list.append(TextPart(type="text", text=part.text))
76
56
 
77
- elif part.inline_data:
78
- blob = part.inline_data
79
- mime = blob.mime_type or ""
57
+ elif blob := part.inline_data:
58
+ mime_type = blob.mime_type or ""
80
59
  data = blob.data or b""
81
- if _is_image_mime(mime or ""):
82
- content_list.append(_to_image_part(mime, data))
83
- elif mime == "application/pdf":
84
- content_list.append(_to_document_part(mime, data))
60
+ if mime_type.startswith("image/"):
61
+ _check_image_media_type(mime_type)
62
+ content_list.append(
63
+ ImagePart(
64
+ type="image",
65
+ media_type=mime_type,
66
+ image=data,
67
+ detail=None,
68
+ )
69
+ )
70
+ elif mime_type.startswith("audio/"):
71
+ _check_audio_media_type(mime_type)
72
+ content_list.append(
73
+ AudioPart(
74
+ type="audio",
75
+ media_type=mime_type,
76
+ audio=data,
77
+ )
78
+ )
79
+ elif mime_type == "application/pdf":
80
+ content_list.append(
81
+ DocumentPart(
82
+ type="document", media_type=mime_type, document=data
83
+ )
84
+ )
85
85
  else:
86
86
  raise ValueError(
87
- f"Unsupported inline_data mime type: {mime}. Cannot convert to BaseMessageParam."
87
+ f"Unsupported inline_data mime type: {mime_type}. Cannot convert to BaseMessageParam."
88
88
  )
89
89
 
90
- elif part.file_data:
91
- if _is_image_mime(cast(str, part.file_data.mime_type)):
90
+ elif file_data := part.file_data:
91
+ mime_type = file_data.mime_type or ""
92
+ if mime_type.startswith("image/"):
92
93
  content_list.append(
93
94
  ImageURLPart(
94
95
  type="image_url",
@@ -96,7 +97,7 @@ class GoogleMessageParamConverter(BaseMessageParamConverter):
96
97
  detail=None,
97
98
  )
98
99
  )
99
- elif _is_audio_mime(cast(str, part.file_data.mime_type)):
100
+ elif mime_type.startswith("audio/"):
100
101
  content_list.append(
101
102
  AudioURLPart(
102
103
  type="audio_url",
@@ -0,0 +1,34 @@
1
+ """Utilities for validating supported media types for Google models."""
2
+
3
+
4
+ def _check_image_media_type(media_type: str) -> None:
5
+ """Raises a `ValueError` if the image media type is not supported."""
6
+ if media_type not in [
7
+ "image/jpeg",
8
+ "image/png",
9
+ "image/webp",
10
+ "image/heic",
11
+ "image/heif",
12
+ ]:
13
+ raise ValueError(
14
+ f"Unsupported image media type: {media_type}. "
15
+ "Google currently only supports JPEG, PNG, WebP, HEIC, "
16
+ "and HEIF images."
17
+ )
18
+
19
+
20
+ def _check_audio_media_type(media_type: str) -> None:
21
+ """Raises a `ValueError` if the audio media type is not supported."""
22
+ if media_type not in [
23
+ "audio/wav",
24
+ "audio/mp3",
25
+ "audio/aiff",
26
+ "audio/aac",
27
+ "audio/ogg",
28
+ "audio/flac",
29
+ ]:
30
+ raise ValueError(
31
+ f"Unsupported audio media type: {media_type}. "
32
+ "Google currently only supports WAV, MP3, AIFF, AAC, OGG, "
33
+ "and FLAC audio file types."
34
+ )
@@ -11,6 +11,7 @@ from google.genai.types import (
11
11
  ContentListUnionDict,
12
12
  FunctionResponseDict,
13
13
  GenerateContentResponse,
14
+ GenerateContentResponseUsageMetadata,
14
15
  PartDict,
15
16
  # Import manually SchemaDict to avoid Pydantic error
16
17
  SchemaDict, # noqa: F401
@@ -68,11 +69,13 @@ class GoogleCallResponse(
68
69
 
69
70
  _provider = "google"
70
71
 
72
+ @computed_field
71
73
  @property
72
74
  def content(self) -> str:
73
75
  """Returns the contained string content for the 0th choice."""
74
76
  return self.response.candidates[0].content.parts[0].text # pyright: ignore [reportOptionalSubscript, reportReturnType, reportOptionalMemberAccess, reportOptionalIterable]
75
77
 
78
+ @computed_field
76
79
  @property
77
80
  def finish_reasons(self) -> list[str]:
78
81
  """Returns the finish reasons of the response."""
@@ -83,6 +86,7 @@ class GoogleCallResponse(
83
86
  if candidate and candidate.finish_reason is not None
84
87
  ]
85
88
 
89
+ @computed_field
86
90
  @property
87
91
  def model(self) -> str:
88
92
  """Returns the model name.
@@ -90,8 +94,11 @@ class GoogleCallResponse(
90
94
  google.generativeai does not return model, so we return the model provided by
91
95
  the user.
92
96
  """
93
- return self._model
97
+ return (
98
+ self.response.model_version if self.response.model_version else self._model
99
+ )
94
100
 
101
+ @computed_field
95
102
  @property
96
103
  def id(self) -> str | None:
97
104
  """Returns the id of the response.
@@ -101,27 +108,50 @@ class GoogleCallResponse(
101
108
  return None
102
109
 
103
110
  @property
104
- def usage(self) -> None:
111
+ def usage(self) -> GenerateContentResponseUsageMetadata | None:
105
112
  """Returns the usage of the chat completion.
106
113
 
107
114
  google.generativeai does not have Usage, so we return None
108
115
  """
109
- return None
116
+ return self.response.usage_metadata
110
117
 
118
+ @computed_field
111
119
  @property
112
- def input_tokens(self) -> None:
120
+ def input_tokens(self) -> int | None:
113
121
  """Returns the number of input tokens."""
114
- return None
122
+ return (
123
+ self.response.usage_metadata.prompt_token_count
124
+ if self.response.usage_metadata
125
+ else None
126
+ )
115
127
 
128
+ @computed_field
116
129
  @property
117
- def output_tokens(self) -> None:
130
+ def cached_tokens(self) -> int | None:
131
+ """Returns the number of cached tokens."""
132
+ return (
133
+ self.response.usage_metadata.cached_content_token_count
134
+ if self.response.usage_metadata
135
+ else None
136
+ )
137
+
138
+ @computed_field
139
+ @property
140
+ def output_tokens(self) -> int | None:
118
141
  """Returns the number of output tokens."""
119
- return None
142
+ return (
143
+ self.response.usage_metadata.candidates_token_count
144
+ if self.response.usage_metadata
145
+ else None
146
+ )
120
147
 
148
+ @computed_field
121
149
  @property
122
150
  def cost(self) -> float | None:
123
151
  """Returns the cost of the call."""
124
- return calculate_cost(self.input_tokens, self.output_tokens, self.model)
152
+ return calculate_cost(
153
+ self.input_tokens, self.cached_tokens, self.output_tokens, self.model
154
+ )
125
155
 
126
156
  @computed_field
127
157
  @cached_property
@@ -129,7 +159,6 @@ class GoogleCallResponse(
129
159
  """Returns the models's response as a message parameter."""
130
160
  return {"role": "model", "parts": self.response.candidates[0].content.parts} # pyright: ignore [reportReturnType, reportOptionalSubscript, reportOptionalMemberAccess]
131
161
 
132
- @computed_field
133
162
  @cached_property
134
163
  def tools(self) -> list[GoogleTool] | None:
135
164
  """Returns the list of tools for the 0th candidate's 0th content part."""
@@ -146,7 +175,6 @@ class GoogleCallResponse(
146
175
 
147
176
  return extracted_tools
148
177
 
149
- @computed_field
150
178
  @cached_property
151
179
  def tool(self) -> GoogleTool | None:
152
180
  """Returns the 0th tool for the 0th candidate's 0th content part.
@@ -6,7 +6,10 @@ usage docs: learn/streams.md#handling-streamed-responses
6
6
  from typing import cast
7
7
 
8
8
  from google.genai.types import FinishReason as GoogleFinishReason
9
- from google.genai.types import GenerateContentResponse
9
+ from google.genai.types import (
10
+ GenerateContentResponse,
11
+ GenerateContentResponseUsageMetadata,
12
+ )
10
13
 
11
14
  from mirascope.core.base.types import FinishReason
12
15
 
@@ -57,12 +60,12 @@ class GoogleCallResponseChunk(
57
60
  ]
58
61
 
59
62
  @property
60
- def model(self) -> None:
63
+ def model(self) -> str | None:
61
64
  """Returns the model name.
62
65
 
63
66
  google.generativeai does not return model, so we return None
64
67
  """
65
- return None
68
+ return self.chunk.model_version
66
69
 
67
70
  @property
68
71
  def id(self) -> str | None:
@@ -73,22 +76,27 @@ class GoogleCallResponseChunk(
73
76
  return None
74
77
 
75
78
  @property
76
- def usage(self) -> None:
79
+ def usage(self) -> GenerateContentResponseUsageMetadata | None:
77
80
  """Returns the usage of the chat completion.
78
81
 
79
82
  google.generativeai does not have Usage, so we return None
80
83
  """
81
- return None
84
+ return self.chunk.usage_metadata
82
85
 
83
86
  @property
84
- def input_tokens(self) -> None:
87
+ def input_tokens(self) -> int | None:
85
88
  """Returns the number of input tokens."""
86
- return None
89
+ return self.usage.prompt_token_count if self.usage else None
87
90
 
88
91
  @property
89
- def output_tokens(self) -> None:
92
+ def cached_tokens(self) -> int | None:
93
+ """Returns the number of cached tokens."""
94
+ return self.usage.cached_content_token_count if self.usage else None
95
+
96
+ @property
97
+ def output_tokens(self) -> int | None:
90
98
  """Returns the number of output tokens."""
91
- return None
99
+ return self.usage.candidates_token_count if self.usage else None
92
100
 
93
101
  @property
94
102
  def common_finish_reasons(self) -> list[FinishReason] | None:
@@ -14,6 +14,7 @@ from google.genai.types import (
14
14
  FinishReason,
15
15
  FunctionCall,
16
16
  GenerateContentResponse,
17
+ GenerateContentResponseUsageMetadata,
17
18
  PartDict,
18
19
  Tool,
19
20
  )
@@ -66,7 +67,9 @@ class GoogleStream(
66
67
  @property
67
68
  def cost(self) -> float | None:
68
69
  """Returns the cost of the call."""
69
- return calculate_cost(self.input_tokens, self.output_tokens, self.model)
70
+ return calculate_cost(
71
+ self.input_tokens, self.cached_tokens, self.output_tokens, self.model
72
+ )
70
73
 
71
74
  def _construct_message_param(
72
75
  self, tool_calls: list[FunctionCall] | None = None, content: str | None = None
@@ -98,6 +101,15 @@ class GoogleStream(
98
101
  raise ValueError(
99
102
  "No stream response, check if the stream has been consumed."
100
103
  )
104
+ candidates_token_count = (
105
+ int(self.output_tokens) if self.output_tokens is not None else None
106
+ )
107
+ prompt_token_count = (
108
+ int(self.input_tokens) if self.input_tokens is not None else None
109
+ )
110
+ total_token_count = int(candidates_token_count or 0) + int(
111
+ prompt_token_count or 0
112
+ )
101
113
  response = GenerateContentResponse(
102
114
  candidates=[
103
115
  Candidate(
@@ -109,7 +121,13 @@ class GoogleStream(
109
121
  parts=self.message_param["parts"], # pyright: ignore [reportTypedDictNotRequiredAccess, reportArgumentType]
110
122
  ),
111
123
  )
112
- ]
124
+ ],
125
+ model_version=self.model,
126
+ usage_metadata=GenerateContentResponseUsageMetadata(
127
+ candidates_token_count=candidates_token_count,
128
+ prompt_token_count=prompt_token_count,
129
+ total_token_count=total_token_count,
130
+ ),
113
131
  )
114
132
 
115
133
  return GoogleCallResponse(
@@ -18,13 +18,13 @@ GroqMessageParam: TypeAlias = ChatCompletionMessageParam | BaseMessageParam
18
18
 
19
19
  __all__ = [
20
20
  "AsyncGroqDynamicConfig",
21
- "call",
22
- "GroqDynamicConfig",
23
21
  "GroqCallParams",
24
22
  "GroqCallResponse",
25
23
  "GroqCallResponseChunk",
24
+ "GroqDynamicConfig",
26
25
  "GroqMessageParam",
27
26
  "GroqStream",
28
27
  "GroqTool",
28
+ "call",
29
29
  "groq_call",
30
30
  ]
@@ -3,6 +3,7 @@
3
3
 
4
4
  def calculate_cost(
5
5
  input_tokens: int | float | None,
6
+ cached_tokens: int | float | None,
6
7
  output_tokens: int | float | None,
7
8
  model: str = "mixtral-8x7b-32768",
8
9
  ) -> float | None:
@@ -10,17 +11,17 @@ def calculate_cost(
10
11
 
11
12
  https://wow.groq.com/
12
13
 
13
- Model Input Output
14
- llama-3.1-405b-reasoning N/A N/A
15
- llama-3.1-70b-versatile N/A N/A
16
- llama-3.1-8b-instant N/A N/A
17
- llama3-groq-70b-8192-tool-use-preview $0.89 / 1M tokens $0.89 / 1M tokens
18
- llama3-groq-8b-8192-tool-use-preview $0.19 / 1M tokens $0.19 / 1M tokens
19
- llama3-70b-8192 $0.59 / 1M tokens $0.79 / 1M tokens
20
- llama3-8b-8192 $0.05 / 1M tokens $0.08 / 1M tokens
21
- mixtral-8x7b-32768 $0.27 / 1M tokens $0.27 / 1M tokens
22
- gemma-7b-it $0.07 / 1M tokens $0.07 / 1M tokens
23
- gemma2-9b-it $0.20 / 1M tokens $0.20 / 1M tokens
14
+ Model Input Cached Output
15
+ llama-3.1-405b-reasoning N/A N/A
16
+ llama-3.1-70b-versatile N/A N/A
17
+ llama-3.1-8b-instant N/A N/A
18
+ llama3-groq-70b-8192-tool-use-preview $0.89 / 1M tokens $0.89 / 1M tokens
19
+ llama3-groq-8b-8192-tool-use-preview $0.19 / 1M tokens $0.19 / 1M tokens
20
+ llama3-70b-8192 $0.59 / 1M tokens $0.79 / 1M tokens
21
+ llama3-8b-8192 $0.05 / 1M tokens $0.08 / 1M tokens
22
+ mixtral-8x7b-32768 $0.27 / 1M tokens $0.27 / 1M tokens
23
+ gemma-7b-it $0.07 / 1M tokens $0.07 / 1M tokens
24
+ gemma2-9b-it $0.20 / 1M tokens $0.20 / 1M tokens
24
25
  """
25
26
  pricing = {
26
27
  "llama3-groq-70b-8192-tool-use-preview": {
@@ -98,6 +98,12 @@ class GroqCallResponse(
98
98
  """Returns the number of input tokens."""
99
99
  return self.usage.prompt_tokens if self.usage else None
100
100
 
101
+ @computed_field
102
+ @property
103
+ def cached_tokens(self) -> int | None:
104
+ """Returns the number of cached tokens."""
105
+ return 0
106
+
101
107
  @computed_field
102
108
  @property
103
109
  def output_tokens(self) -> int | None:
@@ -108,7 +114,9 @@ class GroqCallResponse(
108
114
  @property
109
115
  def cost(self) -> float | None:
110
116
  """Returns the cost of the call."""
111
- return calculate_cost(self.input_tokens, self.output_tokens, self.model)
117
+ return calculate_cost(
118
+ self.input_tokens, self.cached_tokens, self.output_tokens, self.model
119
+ )
112
120
 
113
121
  @computed_field
114
122
  @cached_property
@@ -119,7 +127,6 @@ class GroqCallResponse(
119
127
  )
120
128
  return ChatCompletionAssistantMessageParam(**message_param)
121
129
 
122
- @computed_field
123
130
  @cached_property
124
131
  def tools(self) -> list[GroqTool] | None:
125
132
  """Returns any available tool calls as their `GroqTool` definition.
@@ -140,7 +147,6 @@ class GroqCallResponse(
140
147
 
141
148
  return extracted_tools
142
149
 
143
- @computed_field
144
150
  @cached_property
145
151
  def tool(self) -> GroqTool | None:
146
152
  """Returns the 0th tool for the 0th choice message.
@@ -81,6 +81,11 @@ class GroqCallResponseChunk(BaseCallResponseChunk[ChatCompletionChunk, FinishRea
81
81
  return self.usage.prompt_tokens
82
82
  return None
83
83
 
84
+ @property
85
+ def cached_tokens(self) -> int | None:
86
+ """Returns the number of cached tokens."""
87
+ return 0
88
+
84
89
  @property
85
90
  def output_tokens(self) -> int | None:
86
91
  """Returns the number of output tokens."""
@@ -66,7 +66,9 @@ class GroqStream(
66
66
  @property
67
67
  def cost(self) -> float | None:
68
68
  """Returns the cost of the call."""
69
- return calculate_cost(self.input_tokens, self.output_tokens, self.model)
69
+ return calculate_cost(
70
+ self.input_tokens, self.cached_tokens, self.output_tokens, self.model
71
+ )
70
72
 
71
73
  def _construct_message_param(
72
74
  self,
@@ -16,7 +16,6 @@ LiteLLMMessageParam: TypeAlias = OpenAIMessageParam
16
16
 
17
17
  __all__ = [
18
18
  "AsyncLiteLLMDynamicConfig",
19
- "call",
20
19
  "LiteLLMCallParams",
21
20
  "LiteLLMCallResponse",
22
21
  "LiteLLMCallResponseChunk",
@@ -24,5 +23,6 @@ __all__ = [
24
23
  "LiteLLMMessageParam",
25
24
  "LiteLLMStream",
26
25
  "LiteLLMTool",
26
+ "call",
27
27
  "litellm_call",
28
28
  ]
@@ -1,7 +1,7 @@
1
1
  """This module contains the setup_call function for OpenAI tools."""
2
2
 
3
3
  from collections.abc import Awaitable, Callable
4
- from typing import Any, cast, overload
4
+ from typing import Any, TypeAlias, cast, overload
5
5
 
6
6
  from litellm import acompletion, completion
7
7
  from openai import OpenAI
@@ -21,12 +21,16 @@ from ...openai import (
21
21
  from ...openai._call_kwargs import OpenAICallKwargs
22
22
  from ...openai._utils import setup_call as setup_call_openai
23
23
 
24
+ # Note: MyPy doesn't like `client: ...` so we use these aliases instead.
25
+ _AsyncLiteLLMClient: TypeAlias = Any
26
+ _SyncLiteLLMClient: TypeAlias = Any
27
+
24
28
 
25
29
  @overload
26
30
  def setup_call(
27
31
  *,
28
32
  model: str,
29
- client: ...,
33
+ client: _AsyncLiteLLMClient | None,
30
34
  fn: Callable[..., Awaitable[AsyncOpenAIDynamicConfig]],
31
35
  fn_args: dict[str, Any],
32
36
  dynamic_config: AsyncOpenAIDynamicConfig,
@@ -48,7 +52,7 @@ def setup_call(
48
52
  def setup_call(
49
53
  *,
50
54
  model: str,
51
- client: ...,
55
+ client: _SyncLiteLLMClient | None,
52
56
  fn: Callable[..., OpenAIDynamicConfig],
53
57
  fn_args: dict[str, Any],
54
58
  dynamic_config: OpenAIDynamicConfig,
@@ -24,13 +24,13 @@ MistralMessageParam: TypeAlias = (
24
24
  )
25
25
 
26
26
  __all__ = [
27
- "call",
28
- "MistralDynamicConfig",
29
27
  "MistralCallParams",
30
28
  "MistralCallResponse",
31
29
  "MistralCallResponseChunk",
30
+ "MistralDynamicConfig",
32
31
  "MistralMessageParam",
33
32
  "MistralStream",
34
33
  "MistralTool",
34
+ "call",
35
35
  "mistral_call",
36
36
  ]
@@ -3,6 +3,7 @@
3
3
 
4
4
  def calculate_cost(
5
5
  input_tokens: int | float | None,
6
+ cached_tokens: int | float | None,
6
7
  output_tokens: int | float | None,
7
8
  model: str = "open-mistral-7b",
8
9
  ) -> float | None:
@@ -10,15 +11,15 @@ def calculate_cost(
10
11
 
11
12
  https://mistral.ai/technology/#pricing
12
13
 
13
- Model Input Output
14
- open-mistral-nemo $0.3/1M tokens $0.3/1M tokens
15
- mistral-large-latest $3/1M tokens $9/1M tokens
16
- codestral-2405 $1/1M tokens $3/1M tokens
17
- open-mistral-7b $0.25/1M tokens $0.25/1M tokens
18
- open-mixtral-8x7b $0.7/1M tokens $0.7/1M tokens
19
- open-mixtral-8x22b $2/1M tokens $6/1M tokens
20
- mistral-small-latest $2/1M tokens $6/1M tokens
21
- mistral-medium-latest $2.75/1M tokens $8.1/1M tokens
14
+ Model Input Cached Output
15
+ open-mistral-nemo $0.3/1M tokens $0.3/1M tokens
16
+ mistral-large-latest $3/1M tokens $9/1M tokens
17
+ codestral-2405 $1/1M tokens $3/1M tokens
18
+ open-mistral-7b $0.25/1M tokens $0.25/1M tokens
19
+ open-mixtral-8x7b $0.7/1M tokens $0.7/1M tokens
20
+ open-mixtral-8x22b $2/1M tokens $6/1M tokens
21
+ mistral-small-latest $2/1M tokens $6/1M tokens
22
+ mistral-medium-latest $2.75/1M tokens $8.1/1M tokens
22
23
  """
23
24
  pricing = {
24
25
  "open-mistral-nemo": {"prompt": 0.000_000_3, "completion": 0.000_000_3},
@@ -107,6 +107,12 @@ class MistralCallResponse(
107
107
  """Returns the number of input tokens."""
108
108
  return self.usage.prompt_tokens
109
109
 
110
+ @computed_field
111
+ @property
112
+ def cached_tokens(self) -> int:
113
+ """Returns the number of cached tokens."""
114
+ return 0
115
+
110
116
  @computed_field
111
117
  @property
112
118
  def output_tokens(self) -> int | None:
@@ -117,7 +123,9 @@ class MistralCallResponse(
117
123
  @property
118
124
  def cost(self) -> float | None:
119
125
  """Returns the cost of the call."""
120
- return calculate_cost(self.input_tokens, self.output_tokens, self.model)
126
+ return calculate_cost(
127
+ self.input_tokens, self.cached_tokens, self.output_tokens, self.model
128
+ )
121
129
 
122
130
  @computed_field
123
131
  @cached_property
@@ -125,7 +133,6 @@ class MistralCallResponse(
125
133
  """Returns the assistants's response as a message parameter."""
126
134
  return self._response_choices[0].message
127
135
 
128
- @computed_field
129
136
  @cached_property
130
137
  def tools(self) -> list[MistralTool] | None:
131
138
  """Returns the tools for the 0th choice message.
@@ -146,7 +153,6 @@ class MistralCallResponse(
146
153
 
147
154
  return extracted_tools
148
155
 
149
- @computed_field
150
156
  @cached_property
151
157
  def tool(self) -> MistralTool | None:
152
158
  """Returns the 0th tool for the 0th choice message.
@@ -80,6 +80,11 @@ class MistralCallResponseChunk(BaseCallResponseChunk[CompletionChunk, FinishReas
80
80
  return self.usage.prompt_tokens
81
81
  return None
82
82
 
83
+ @property
84
+ def cached_tokens(self) -> int:
85
+ """Returns the number of cached tokens."""
86
+ return 0
87
+
83
88
  @property
84
89
  def output_tokens(self) -> int | None:
85
90
  """Returns the number of output tokens."""
@@ -65,7 +65,9 @@ class MistralStream(
65
65
  @property
66
66
  def cost(self) -> float | None:
67
67
  """Returns the cost of the call."""
68
- return calculate_cost(self.input_tokens, self.output_tokens, self.model)
68
+ return calculate_cost(
69
+ self.input_tokens, self.cached_tokens, self.output_tokens, self.model
70
+ )
69
71
 
70
72
  def _construct_message_param(
71
73
  self, tool_calls: list | None = None, content: str | None = None
@@ -18,14 +18,14 @@ OpenAIMessageParam: TypeAlias = ChatCompletionMessageParam | BaseMessageParam
18
18
 
19
19
  __all__ = [
20
20
  "AsyncOpenAIDynamicConfig",
21
- "call",
22
- "OpenAIDynamicConfig",
23
21
  "OpenAICallParams",
24
22
  "OpenAICallResponse",
25
23
  "OpenAICallResponseChunk",
24
+ "OpenAIDynamicConfig",
26
25
  "OpenAIMessageParam",
27
26
  "OpenAIStream",
28
27
  "OpenAITool",
29
28
  "OpenAIToolConfig",
29
+ "call",
30
30
  "openai_call",
31
31
  ]