mirascope 1.18.3__py3-none-any.whl → 1.19.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.
Files changed (101) hide show
  1. mirascope/__init__.py +20 -2
  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 +26 -8
  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 -1
  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 -1
  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 -20
  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 -1
  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 -1
  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 -1
  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 +23 -51
  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 +26 -4
  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 -1
  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 -1
  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 -1
  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 -1
  75. mirascope/core/vertex/call_response_chunk.py +5 -0
  76. mirascope/core/vertex/stream.py +3 -1
  77. mirascope/core/xai/__init__.py +28 -0
  78. mirascope/core/xai/_call.py +67 -0
  79. mirascope/core/xai/_utils/__init__.py +6 -0
  80. mirascope/core/xai/_utils/_calculate_cost.py +104 -0
  81. mirascope/core/xai/_utils/_setup_call.py +113 -0
  82. mirascope/core/xai/call_params.py +10 -0
  83. mirascope/core/xai/call_response.py +27 -0
  84. mirascope/core/xai/call_response_chunk.py +14 -0
  85. mirascope/core/xai/dynamic_config.py +8 -0
  86. mirascope/core/xai/py.typed +0 -0
  87. mirascope/core/xai/stream.py +57 -0
  88. mirascope/core/xai/tool.py +13 -0
  89. mirascope/integrations/_middleware_factory.py +6 -6
  90. mirascope/integrations/logfire/_utils.py +1 -1
  91. mirascope/llm/__init__.py +2 -2
  92. mirascope/llm/_protocols.py +34 -28
  93. mirascope/llm/call_response.py +16 -7
  94. mirascope/llm/llm_call.py +50 -46
  95. mirascope/llm/stream.py +43 -31
  96. mirascope/retries/__init__.py +1 -1
  97. mirascope/tools/__init__.py +2 -2
  98. {mirascope-1.18.3.dist-info → mirascope-1.19.0.dist-info}/METADATA +3 -1
  99. {mirascope-1.18.3.dist-info → mirascope-1.19.0.dist-info}/RECORD +101 -88
  100. {mirascope-1.18.3.dist-info → mirascope-1.19.0.dist-info}/WHEEL +0 -0
  101. {mirascope-1.18.3.dist-info → mirascope-1.19.0.dist-info}/licenses/LICENSE +0 -0
@@ -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 = "gpt-3.5-turbo-16k",
8
9
  ) -> float | None:
@@ -10,196 +11,236 @@ def calculate_cost(
10
11
 
11
12
  https://openai.com/pricing
12
13
 
13
- Model Input Output
14
- gpt-4o $2.50 / 1M tokens $10.00 / 1M tokens
15
- gpt-4o-2024-11-20 $2.50 / 1M tokens $10.00 / 1M tokens
16
- gpt-4o-2024-08-06 $2.50 / 1M tokens $10.00 / 1M tokens
17
- gpt-4o-2024-05-13 $5.00 / 1M tokens $15.00 / 1M tokens
18
- gpt-4o-audio-preview $2.50 / 1M tokens $10.00 / 1M tokens
19
- gpt-4o-audio-preview-2024-12-17 $2.50 / 1M tokens $10.00 / 1M tokens
20
- gpt-4o-audio-preview-2024-10-01 $2.50 / 1M tokens $10.00 / 1M tokens
21
- gpt-4o-realtime-preview $5.00 / 1M tokens $20.00 / 1M tokens
22
- gpt-4o-realtime-preview-2024-12-17 $5.00 / 1M tokens $20.00 / 1M tokens
23
- gpt-4o-realtime-preview-2024-10-01 $5.00 / 1M tokens $20.00 / 1M tokens
24
- gpt-4o-mini $0.15 / 1M tokens $0.60 / 1M tokens
25
- gpt-4o-mini-2024-07-18 $0.15 / 1M tokens $0.60 / 1M tokens
26
- gpt-4o-mini-audio-preview $0.15 / 1M tokens $0.60 / 1M tokens
27
- gpt-4o-mini-audio-preview-2024-12-17 $0.15 / 1M tokens $0.60 / 1M tokens
28
- gpt-4o-mini-realtime-preview $0.60 / 1M tokens $2.40 / 1M tokens
29
- gpt-4o-mini-realtime-preview-2024-12-17 $0.60 / 1M tokens $2.40 / 1M tokens
30
- o1 $15.00 / 1M tokens $60.00 / 1M tokens
31
- o1-2024-12-17 $15.00 / 1M tokens $60.00 / 1M tokens
32
- o1-preview-2024-09-12 $15.00 / 1M tokens $60.00 / 1M tokens
33
- o3-mini $1.10 / 1M tokens $4.40 / 1M tokens
34
- o3-mini-2025-01-31 $1.10 / 1M tokens $4.40 / 1M tokens
35
- o1-mini $1.10 / 1M tokens $4.40 / 1M tokens
36
- o1-mini-2024-09-12 $1.10 / 1M tokens $4.40 / 1M tokens
37
- gpt-4-turbo $10.00 / 1M tokens $30.00 / 1M tokens
38
- gpt-4-turbo-2024-04-09 $10.00 / 1M tokens $30.00 / 1M tokens
39
- gpt-3.5-turbo-0125 $0.50 / 1M tokens $1.50 / 1M tokens
40
- gpt-3.5-turbo-1106 $1.00 / 1M tokens $2.00 / 1M tokens
41
- gpt-4-1106-preview $10.00 / 1M tokens $30.00 / 1M tokens
42
- gpt-4 $30.00 / 1M tokens $60.00 / 1M tokens
43
- text-embedding-3-small $0.02 / 1M tokens
44
- text-embedding-3-large $0.13 / 1M tokens
45
- text-embedding-ada-0002 $0.10 / 1M tokens
14
+ Model Input Cached Output
15
+ gpt-4o $2.50 / 1M tokens $1.25 / 1M tokens $10.00 / 1M tokens
16
+ gpt-4o-2024-11-20 $2.50 / 1M tokens $1.25 / 1M tokens $10.00 / 1M tokens
17
+ gpt-4o-2024-08-06 $2.50 / 1M tokens $1.25 / 1M tokens $10.00 / 1M tokens
18
+ gpt-4o-2024-05-13 $5.00 / 1M tokens $2.50 / 1M tokens $15.00 / 1M tokens
19
+ gpt-4o-audio-preview $2.50 / 1M tokens $1.25 / 1M tokens $10.00 / 1M tokens
20
+ gpt-4o-audio-preview-2024-12-17 $2.50 / 1M tokens $1.25 / 1M tokens $10.00 / 1M tokens
21
+ gpt-4o-audio-preview-2024-10-01 $2.50 / 1M tokens $1.25 / 1M tokens $10.00 / 1M tokens
22
+ gpt-4o-realtime-preview $5.00 / 1M tokens $2.50 / 1M tokens $20.00 / 1M tokens
23
+ gpt-4o-realtime-preview-2024-12-17 $5.00 / 1M tokens $2.50 / 1M tokens $20.00 / 1M tokens
24
+ gpt-4o-realtime-preview-2024-10-01 $5.00 / 1M tokens $2.50 / 1M tokens $20.00 / 1M tokens
25
+ gpt-4o-mini $0.15 / 1M tokens $0.08 / 1M tokens $0.60 / 1M tokens
26
+ gpt-4o-mini-2024-07-18 $0.15 / 1M tokens $0.08 / 1M tokens $0.60 / 1M tokens
27
+ gpt-4o-mini-audio-preview $0.15 / 1M tokens $0.08 / 1M tokens $0.60 / 1M tokens
28
+ gpt-4o-mini-audio-preview-2024-12-17 $0.15 / 1M tokens $0.08 / 1M tokens $0.60 / 1M tokens
29
+ gpt-4o-mini-realtime-preview $0.60 / 1M tokens $0.30 / 1M tokens $2.40 / 1M tokens
30
+ gpt-4o-mini-realtime-preview-2024-12-17 $0.60 / 1M tokens $0.30 / 1M tokens $2.40 / 1M tokens
31
+ o1 $15.00 / 1M tokens $7.50 / 1M tokens $60.00 / 1M tokens
32
+ o1-2024-12-17 $15.00 / 1M tokens $7.50 / 1M tokens $60.00 / 1M tokens
33
+ o1-preview-2024-09-12 $15.00 / 1M tokens $7.50 / 1M tokens $60.00 / 1M tokens
34
+ o3-mini $1.10 / 1M tokens $0.55 / 1M tokens $4.40 / 1M tokens
35
+ o3-mini-2025-01-31 $1.10 / 1M tokens $0.55 / 1M tokens $4.40 / 1M tokens
36
+ o1-mini $1.10 / 1M tokens $0.55 / 1M tokens $4.40 / 1M tokens
37
+ o1-mini-2024-09-12 $1.10 / 1M tokens $0.55 / 1M tokens $4.40 / 1M tokens
38
+ gpt-4-turbo $10.00 / 1M tokens $30.00 / 1M tokens
39
+ gpt-4-turbo-2024-04-09 $10.00 / 1M tokens $30.00 / 1M tokens
40
+ gpt-3.5-turbo-0125 $0.50 / 1M tokens $1.50 / 1M tokens
41
+ gpt-3.5-turbo-1106 $1.00 / 1M tokens $2.00 / 1M tokens
42
+ gpt-4-1106-preview $10.00 / 1M tokens $30.00 / 1M tokens
43
+ gpt-4 $30.00 / 1M tokens $60.00 / 1M tokens
44
+ text-embedding-3-small $0.02 / 1M tokens
45
+ text-embedding-3-large $0.13 / 1M tokens
46
+ text-embedding-ada-0002 $0.10 / 1M tokens
46
47
  """
47
48
  pricing = {
48
49
  "gpt-4o": {
49
50
  "prompt": 0.000_002_5,
51
+ "cached": 0.000_001_25,
50
52
  "completion": 0.000_01,
51
53
  },
52
54
  "gpt-4o-2024-11-20": {
53
55
  "prompt": 0.000_002_5,
56
+ "cached": 0.000_001_25,
54
57
  "completion": 0.000_01,
55
58
  },
56
59
  "gpt-4o-2024-08-06": {
57
60
  "prompt": 0.000_002_5,
61
+ "cached": 0.000_001_25,
58
62
  "completion": 0.000_01,
59
63
  },
60
64
  "gpt-4o-2024-05-13": {
61
65
  "prompt": 0.000_005,
66
+ "cached": 0.000_002_5,
62
67
  "completion": 0.000_015,
63
68
  },
64
69
  "gpt-4o-audio-preview": {
65
70
  "prompt": 0.000_002_5,
71
+ "cached": 0.000_001_25,
66
72
  "completion": 0.000_01,
67
73
  },
68
74
  "gpt-4o-audio-preview-2024-12-17": {
69
75
  "prompt": 0.000_002_5,
76
+ "cached": 0.000_001_25,
70
77
  "completion": 0.000_01,
71
78
  },
72
79
  "gpt-4o-audio-preview-2024-10-01": {
73
80
  "prompt": 0.000_002_5,
81
+ "cached": 0.000_001_25,
74
82
  "completion": 0.000_01,
75
83
  },
76
84
  "gpt-4o-realtime-preview": {
77
85
  "prompt": 0.000_005,
86
+ "cached": 0.000_002_5,
78
87
  "completion": 0.000_02,
79
88
  },
80
89
  "gpt-4o-realtime-preview-2024-12-17": {
81
90
  "prompt": 0.000_005,
91
+ "cached": 0.000_002_5,
82
92
  "completion": 0.000_02,
83
93
  },
84
94
  "gpt-4o-realtime-preview-2024-10-01": {
85
95
  "prompt": 0.000_005,
96
+ "cached": 0.000_002_5,
86
97
  "completion": 0.000_02,
87
98
  },
88
99
  "gpt-4o-mini": {
89
100
  "prompt": 0.000_000_15,
101
+ "cached": 0.000_000_08,
90
102
  "completion": 0.000_000_6,
91
103
  },
92
104
  "gpt-4o-mini-2024-07-18": {
93
105
  "prompt": 0.000_000_15,
106
+ "cached": 0.000_000_08,
94
107
  "completion": 0.000_000_6,
95
108
  },
96
109
  "gpt-4o-mini-audio-preview": {
97
110
  "prompt": 0.000_000_15,
111
+ "cached": 0.000_000_08,
98
112
  "completion": 0.000_000_6,
99
113
  },
100
114
  "gpt-4o-mini-audio-preview-2024-12-17": {
101
115
  "prompt": 0.000_000_15,
116
+ "cached": 0.000_000_08,
102
117
  "completion": 0.000_000_6,
103
118
  },
104
119
  "gpt-4o-mini-realtime-preview": {
105
120
  "prompt": 0.000_000_6,
121
+ "cached": 0.000_000_3,
106
122
  "completion": 0.000_002_4,
107
123
  },
108
124
  "gpt-4o-mini-realtime-preview-2024-12-17": {
109
125
  "prompt": 0.000_000_6,
126
+ "cached": 0.000_000_3,
110
127
  "completion": 0.000_002_4,
111
128
  },
112
129
  "o1": {
113
130
  "prompt": 0.000_015,
131
+ "cached": 0.000_007_5,
114
132
  "completion": 0.000_06,
115
133
  },
116
134
  "o1-2024-12-17": {
117
135
  "prompt": 0.000_015,
136
+ "cached": 0.000_007_5,
118
137
  "completion": 0.000_06,
119
138
  },
120
139
  "o1-preview-2024-09-12": {
121
140
  "prompt": 0.000_015,
141
+ "cached": 0.000_007_5,
122
142
  "completion": 0.000_06,
123
143
  },
124
144
  "o3-mini": {
125
145
  "prompt": 0.000_001_1,
146
+ "cached": 0.000_000_55,
126
147
  "completion": 0.000_004_4,
127
148
  },
128
149
  "o3-mini-2025-01-31": {
129
150
  "prompt": 0.000_001_1,
151
+ "cached": 0.000_000_55,
130
152
  "completion": 0.000_004_4,
131
153
  },
132
154
  "o1-mini": {
133
155
  "prompt": 0.000_001_1,
156
+ "cached": 0.000_000_55,
134
157
  "completion": 0.000_004_4,
135
158
  },
136
159
  "o1-mini-2024-09-12": {
137
160
  "prompt": 0.000_001_1,
161
+ "cached": 0.000_000_55,
138
162
  "completion": 0.000_004_4,
139
163
  },
140
164
  "gpt-4-turbo": {
141
165
  "prompt": 0.000_01,
166
+ "cached": 0,
142
167
  "completion": 0.000_03,
143
168
  },
144
169
  "gpt-4-turbo-2024-04-09": {
145
170
  "prompt": 0.000_01,
171
+ "cached": 0,
146
172
  "completion": 0.000_03,
147
173
  },
148
174
  "gpt-3.5-turbo-0125": {
149
175
  "prompt": 0.000_000_5,
176
+ "cached": 0,
150
177
  "completion": 0.000_001_5,
151
178
  },
152
179
  "gpt-3.5-turbo-1106": {
153
180
  "prompt": 0.000_001,
181
+ "cached": 0,
154
182
  "completion": 0.000_002,
155
183
  },
156
184
  "gpt-4-1106-preview": {
157
185
  "prompt": 0.000_01,
186
+ "cached": 0,
158
187
  "completion": 0.000_03,
159
188
  },
160
189
  "gpt-4": {
161
190
  "prompt": 0.000_003,
191
+ "cached": 0,
162
192
  "completion": 0.000_006,
163
193
  },
164
194
  "gpt-3.5-turbo-4k": {
165
195
  "prompt": 0.000_015,
196
+ "cached": 0,
166
197
  "completion": 0.000_02,
167
198
  },
168
199
  "gpt-3.5-turbo-16k": {
169
200
  "prompt": 0.000_003,
201
+ "cached": 0,
170
202
  "completion": 0.000_004,
171
203
  },
172
204
  "gpt-4-8k": {
173
205
  "prompt": 0.000_003,
206
+ "cached": 0,
174
207
  "completion": 0.000_006,
175
208
  },
176
209
  "gpt-4-32k": {
177
210
  "prompt": 0.000_006,
211
+ "cached": 0,
178
212
  "completion": 0.000_012,
179
213
  },
180
214
  "text-embedding-3-small": {
181
215
  "prompt": 0.000_000_02,
182
- "completion": 0.000_000_02,
216
+ "cached": 0,
217
+ "completion": 0,
183
218
  },
184
219
  "text-embedding-ada-002": {
185
220
  "prompt": 0.000_000_1,
186
- "completion": 0.000_000_1,
221
+ "cached": 0,
222
+ "completion": 0,
187
223
  },
188
224
  "text-embedding-3-large": {
189
225
  "prompt": 0.000_000_13,
190
- "completion": 0.000_000_13,
226
+ "cached": 0,
227
+ "completion": 0,
191
228
  },
192
229
  }
193
230
  if input_tokens is None or output_tokens is None:
194
231
  return None
195
232
 
233
+ if cached_tokens is None:
234
+ cached_tokens = 0
235
+
196
236
  try:
197
237
  model_pricing = pricing[model]
198
238
  except KeyError:
199
239
  return None
200
240
 
201
241
  prompt_cost = input_tokens * model_pricing["prompt"]
242
+ cached_cost = cached_tokens * model_pricing["cached"]
202
243
  completion_cost = output_tokens * model_pricing["completion"]
203
- total_cost = prompt_cost + completion_cost
244
+ total_cost = prompt_cost + cached_cost + completion_cost
204
245
 
205
246
  return total_cost
@@ -18,6 +18,9 @@ if TYPE_CHECKING:
18
18
  ChatCompletionAudioParam,
19
19
  ChatCompletionModality, # pyright: ignore [reportAttributeAccessIssue]
20
20
  )
21
+ from openai.types.chat.chat_completion_reasoning_effort import ( # pyright: ignore [reportMissingImports]
22
+ ChatCompletionReasoningEffort, # pyright: ignore [reportAttributeAccessIssue]
23
+ )
21
24
  else:
22
25
  try:
23
26
  from openai.types.chat.chat_completion_audio_param import ( # pyright: ignore [reportMissingImports]
@@ -30,6 +33,14 @@ else:
30
33
 
31
34
  class ChatCompletionModality: ...
32
35
 
36
+ try:
37
+ from openai.types.chat.chat_completion_reasoning_effort import ( # pyright: ignore [reportMissingImports]
38
+ ChatCompletionReasoningEffort,
39
+ )
40
+ except ImportError:
41
+
42
+ class ChatCompletionReasoningEffort: ...
43
+
33
44
 
34
45
  class OpenAICallParams(BaseCallParams):
35
46
  """The parameters to use when calling the OpenAI API.
@@ -46,6 +57,7 @@ class OpenAICallParams(BaseCallParams):
46
57
  n: ...
47
58
  parallel_tool_calls: ...
48
59
  presence_penalty: ...
60
+ reasoning_effort: ...
49
61
  response_format: ...
50
62
  seed: ...
51
63
  stop: ...
@@ -67,6 +79,7 @@ class OpenAICallParams(BaseCallParams):
67
79
  n: NotRequired[int | None]
68
80
  parallel_tool_calls: NotRequired[bool]
69
81
  presence_penalty: NotRequired[float | None]
82
+ reasoning_effort: NotRequired[ChatCompletionReasoningEffort | None]
70
83
  response_format: NotRequired[ResponseFormat]
71
84
  seed: NotRequired[int | None]
72
85
  stop: NotRequired[str | list[str] | None]
@@ -118,6 +118,17 @@ class OpenAICallResponse(
118
118
  """Returns the number of input tokens."""
119
119
  return self.usage.prompt_tokens if self.usage else None
120
120
 
121
+ @computed_field
122
+ @property
123
+ def cached_tokens(self) -> int | None:
124
+ """Returns the number of cached tokens."""
125
+ return (
126
+ details.cached_tokens
127
+ if self.usage
128
+ and (details := getattr(self.usage, "prompt_tokens_details", None))
129
+ else None
130
+ )
131
+
121
132
  @computed_field
122
133
  @property
123
134
  def output_tokens(self) -> int | None:
@@ -128,7 +139,9 @@ class OpenAICallResponse(
128
139
  @property
129
140
  def cost(self) -> float | None:
130
141
  """Returns the cost of the call."""
131
- return calculate_cost(self.input_tokens, self.output_tokens, self.model)
142
+ return calculate_cost(
143
+ self.input_tokens, self.cached_tokens, self.output_tokens, self.model
144
+ )
132
145
 
133
146
  @computed_field
134
147
  @cached_property
@@ -79,6 +79,18 @@ class OpenAICallResponseChunk(BaseCallResponseChunk[ChatCompletionChunk, FinishR
79
79
  return self.chunk.usage
80
80
  return None
81
81
 
82
+ @computed_field
83
+ @property
84
+ def cached_tokens(self) -> int | None:
85
+ """Returns the number of cached tokens."""
86
+ return (
87
+ details.cached_tokens
88
+ if hasattr(self.chunk, "usage")
89
+ and self.usage
90
+ and (details := getattr(self.usage, "prompt_tokens_details", None))
91
+ else None
92
+ )
93
+
82
94
  @property
83
95
  def input_tokens(self) -> int | None:
84
96
  """Returns the number of input tokens."""
@@ -87,9 +87,9 @@ class OpenAIStream(
87
87
  ) -> AsyncGenerator[tuple[OpenAICallResponseChunk, OpenAITool | None], None]:
88
88
  aiter = super().__aiter__()
89
89
 
90
- async def generator() -> (
91
- AsyncGenerator[tuple[OpenAICallResponseChunk, OpenAITool | None], None]
92
- ):
90
+ async def generator() -> AsyncGenerator[
91
+ tuple[OpenAICallResponseChunk, OpenAITool | None], None
92
+ ]:
93
93
  async for chunk, tool in aiter:
94
94
  if (
95
95
  (choices := chunk.chunk.choices)
@@ -104,7 +104,9 @@ class OpenAIStream(
104
104
  @property
105
105
  def cost(self) -> float | None:
106
106
  """Returns the cost of the call."""
107
- return calculate_cost(self.input_tokens, self.output_tokens, self.model)
107
+ return calculate_cost(
108
+ self.input_tokens, self.cached_tokens, self.output_tokens, self.model
109
+ )
108
110
 
109
111
  def _construct_message_param(
110
112
  self,
@@ -33,7 +33,6 @@ warnings.warn(
33
33
  )
34
34
 
35
35
  __all__ = [
36
- "call",
37
36
  "VertexCallParams",
38
37
  "VertexCallResponse",
39
38
  "VertexCallResponseChunk",
@@ -41,5 +40,6 @@ __all__ = [
41
40
  "VertexMessageParam",
42
41
  "VertexStream",
43
42
  "VertexTool",
43
+ "call",
44
44
  "vertex_call",
45
45
  ]
@@ -3,6 +3,7 @@
3
3
 
4
4
  def calculate_cost(
5
5
  input_chars: int | float | None,
6
+ cached_chars: int | float | None,
6
7
  output_chars: int | float | None,
7
8
  model: str = "gemini-1.5-pro",
8
9
  context_length: int = 0,
@@ -106,7 +106,7 @@ def convert_message_params(
106
106
  elif part.type == "audio_url":
107
107
  # Should download the audio to determine the media type
108
108
  audio = _load_media(part.url)
109
- audio_type = get_audio_type(audio)
109
+ audio_type = f"audio/{get_audio_type(audio)}"
110
110
  if audio_type not in [
111
111
  "audio/wav",
112
112
  "audio/mp3",
@@ -112,6 +112,12 @@ class VertexCallResponse(
112
112
  """Returns the number of input tokens."""
113
113
  return self.usage.prompt_token_count
114
114
 
115
+ @computed_field
116
+ @property
117
+ def cached_tokens(self) -> int:
118
+ """Returns the number of cached tokens."""
119
+ return 0
120
+
115
121
  @computed_field
116
122
  @property
117
123
  def output_tokens(self) -> int:
@@ -122,7 +128,9 @@ class VertexCallResponse(
122
128
  @property
123
129
  def cost(self) -> float | None:
124
130
  """Returns the cost of the call."""
125
- return calculate_cost(self.input_tokens, self.output_tokens, self.model)
131
+ return calculate_cost(
132
+ self.input_tokens, self.cached_tokens, self.output_tokens, self.model
133
+ )
126
134
 
127
135
  @computed_field
128
136
  @cached_property
@@ -76,6 +76,11 @@ class VertexCallResponseChunk(
76
76
  """Returns the number of input tokens."""
77
77
  return None
78
78
 
79
+ @property
80
+ def cached_tokens(self) -> None:
81
+ """Returns the number of cached tokens."""
82
+ return None
83
+
79
84
  @property
80
85
  def output_tokens(self) -> None:
81
86
  """Returns the number of output tokens."""
@@ -64,7 +64,9 @@ class VertexStream(
64
64
  @property
65
65
  def cost(self) -> float | None:
66
66
  """Returns the cost of the call."""
67
- return calculate_cost(self.input_tokens, self.output_tokens, self.model)
67
+ return calculate_cost(
68
+ self.input_tokens, self.cached_tokens, self.output_tokens, self.model
69
+ )
68
70
 
69
71
  def _construct_message_param(
70
72
  self,
@@ -0,0 +1,28 @@
1
+ """The Mirascope xAI Module."""
2
+
3
+ from typing import TypeAlias
4
+
5
+ from ..openai import OpenAIMessageParam
6
+ from ._call import xai_call
7
+ from ._call import xai_call as call
8
+ from .call_params import XAICallParams
9
+ from .call_response import XAICallResponse
10
+ from .call_response_chunk import XAICallResponseChunk
11
+ from .dynamic_config import AsyncXAIDynamicConfig, XAIDynamicConfig
12
+ from .stream import XAIStream
13
+ from .tool import XAITool
14
+
15
+ XAIMessageParam: TypeAlias = OpenAIMessageParam
16
+
17
+ __all__ = [
18
+ "AsyncXAIDynamicConfig",
19
+ "XAICallParams",
20
+ "XAICallResponse",
21
+ "XAICallResponseChunk",
22
+ "XAIDynamicConfig",
23
+ "XAIMessageParam",
24
+ "XAIStream",
25
+ "XAITool",
26
+ "call",
27
+ "xai_call",
28
+ ]
@@ -0,0 +1,67 @@
1
+ """The `xai_call` decorator for functions as LLM calls."""
2
+
3
+ from ..base import call_factory
4
+ from ..openai._utils import (
5
+ get_json_output,
6
+ handle_stream,
7
+ handle_stream_async,
8
+ )
9
+ from ._utils import setup_call
10
+ from .call_params import XAICallParams
11
+ from .call_response import XAICallResponse
12
+ from .call_response_chunk import XAICallResponseChunk
13
+ from .stream import XAIStream
14
+ from .tool import XAITool
15
+
16
+ xai_call = call_factory(
17
+ TCallResponse=XAICallResponse,
18
+ TCallResponseChunk=XAICallResponseChunk,
19
+ TToolType=XAITool,
20
+ TStream=XAIStream,
21
+ default_call_params=XAICallParams(),
22
+ setup_call=setup_call, # pyright: ignore [reportArgumentType]
23
+ get_json_output=get_json_output,
24
+ handle_stream=handle_stream, # pyright: ignore [reportArgumentType]
25
+ handle_stream_async=handle_stream_async, # pyright: ignore [reportArgumentType]
26
+ )
27
+ """A decorator for calling the xAI API with a typed function.
28
+
29
+ usage docs: learn/calls.md
30
+
31
+ This decorator is used to wrap a typed function that calls the xAI API. It parses
32
+ the prompt template of the wrapped function as the messages array and templates the input
33
+ arguments for the function into each message's template.
34
+
35
+ Example:
36
+
37
+ ```python
38
+ from mirascope.core import prompt_template
39
+ from mirascope.core.xai import xai_call
40
+
41
+
42
+ @xai_call("grok-2-latest")
43
+ def recommend_book(genre: str) -> str:
44
+ return f"Recommend a {genre} book"
45
+
46
+ response = recommend_book("fantasy")
47
+ print(response.content)
48
+ ```
49
+
50
+ Args:
51
+ model (str): The model to use in the API call.
52
+ stream (bool): Whether to stream the response from the API call.
53
+ tools (list[BaseTool | Callable]): The tools to use in the API call.
54
+ response_model (BaseModel | BaseType): The response model into which the response
55
+ should be structured.
56
+ output_parser (Callable[[OpenAICallResponse | ResponseModelT], Any]): A function for
57
+ parsing the call response whose value will be returned in place of the original
58
+ call response.
59
+ json_mode (bool): Whether to use JSON Mode.
60
+ client (None): xAI does not support a custom client.
61
+ call_params (OpenAICallParams): The `OpenAICallParams` call parameters to use in the
62
+ API call.
63
+
64
+ Returns:
65
+ decorator (Callable): The decorator for turning a typed function into a xAI
66
+ routed LLM API call.
67
+ """
@@ -0,0 +1,6 @@
1
+ """xAI utilities for decorator factories."""
2
+
3
+ from ._calculate_cost import calculate_cost
4
+ from ._setup_call import setup_call
5
+
6
+ __all__ = ["calculate_cost", "setup_call"]