mirascope 2.0.0a4__py3-none-any.whl → 2.0.0a6__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 (215) hide show
  1. mirascope/__init__.py +10 -1
  2. mirascope/_stubs.py +363 -0
  3. mirascope/api/__init__.py +8 -0
  4. mirascope/api/_generated/__init__.py +119 -1
  5. mirascope/api/_generated/annotations/__init__.py +33 -0
  6. mirascope/api/_generated/annotations/client.py +474 -0
  7. mirascope/api/_generated/annotations/raw_client.py +1095 -0
  8. mirascope/api/_generated/annotations/types/__init__.py +31 -0
  9. mirascope/api/_generated/annotations/types/annotations_create_request_label.py +5 -0
  10. mirascope/api/_generated/annotations/types/annotations_create_response.py +35 -0
  11. mirascope/api/_generated/annotations/types/annotations_create_response_label.py +5 -0
  12. mirascope/api/_generated/annotations/types/annotations_get_response.py +35 -0
  13. mirascope/api/_generated/annotations/types/annotations_get_response_label.py +5 -0
  14. mirascope/api/_generated/annotations/types/annotations_list_request_label.py +5 -0
  15. mirascope/api/_generated/annotations/types/annotations_list_response.py +21 -0
  16. mirascope/api/_generated/annotations/types/annotations_list_response_annotations_item.py +35 -0
  17. mirascope/api/_generated/annotations/types/annotations_list_response_annotations_item_label.py +5 -0
  18. mirascope/api/_generated/annotations/types/annotations_update_request_label.py +5 -0
  19. mirascope/api/_generated/annotations/types/annotations_update_response.py +35 -0
  20. mirascope/api/_generated/annotations/types/annotations_update_response_label.py +5 -0
  21. mirascope/api/_generated/api_keys/__init__.py +7 -0
  22. mirascope/api/_generated/api_keys/client.py +429 -0
  23. mirascope/api/_generated/api_keys/raw_client.py +788 -0
  24. mirascope/api/_generated/api_keys/types/__init__.py +9 -0
  25. mirascope/api/_generated/api_keys/types/api_keys_create_response.py +28 -0
  26. mirascope/api/_generated/api_keys/types/api_keys_get_response.py +27 -0
  27. mirascope/api/_generated/api_keys/types/api_keys_list_response_item.py +27 -0
  28. mirascope/api/_generated/client.py +12 -0
  29. mirascope/api/_generated/core/client_wrapper.py +2 -14
  30. mirascope/api/_generated/core/datetime_utils.py +1 -3
  31. mirascope/api/_generated/core/file.py +2 -5
  32. mirascope/api/_generated/core/http_client.py +36 -112
  33. mirascope/api/_generated/core/jsonable_encoder.py +1 -3
  34. mirascope/api/_generated/core/pydantic_utilities.py +19 -74
  35. mirascope/api/_generated/core/query_encoder.py +1 -3
  36. mirascope/api/_generated/core/serialization.py +4 -10
  37. mirascope/api/_generated/docs/client.py +2 -6
  38. mirascope/api/_generated/docs/raw_client.py +4 -20
  39. mirascope/api/_generated/environments/__init__.py +17 -0
  40. mirascope/api/_generated/environments/client.py +500 -0
  41. mirascope/api/_generated/environments/raw_client.py +999 -0
  42. mirascope/api/_generated/environments/types/__init__.py +15 -0
  43. mirascope/api/_generated/environments/types/environments_create_response.py +24 -0
  44. mirascope/api/_generated/environments/types/environments_get_response.py +24 -0
  45. mirascope/api/_generated/environments/types/environments_list_response_item.py +24 -0
  46. mirascope/api/_generated/environments/types/environments_update_response.py +24 -0
  47. mirascope/api/_generated/errors/__init__.py +2 -0
  48. mirascope/api/_generated/errors/bad_request_error.py +1 -5
  49. mirascope/api/_generated/errors/conflict_error.py +1 -5
  50. mirascope/api/_generated/errors/forbidden_error.py +1 -5
  51. mirascope/api/_generated/errors/internal_server_error.py +1 -6
  52. mirascope/api/_generated/errors/not_found_error.py +1 -5
  53. mirascope/api/_generated/errors/unauthorized_error.py +11 -0
  54. mirascope/api/_generated/functions/__init__.py +29 -0
  55. mirascope/api/_generated/functions/client.py +433 -0
  56. mirascope/api/_generated/functions/raw_client.py +1049 -0
  57. mirascope/api/_generated/functions/types/__init__.py +29 -0
  58. mirascope/api/_generated/functions/types/functions_create_request_dependencies_value.py +20 -0
  59. mirascope/api/_generated/functions/types/functions_create_response.py +37 -0
  60. mirascope/api/_generated/functions/types/functions_create_response_dependencies_value.py +20 -0
  61. mirascope/api/_generated/functions/types/functions_find_by_hash_response.py +39 -0
  62. mirascope/api/_generated/functions/types/functions_find_by_hash_response_dependencies_value.py +20 -0
  63. mirascope/api/_generated/functions/types/functions_get_response.py +37 -0
  64. mirascope/api/_generated/functions/types/functions_get_response_dependencies_value.py +20 -0
  65. mirascope/api/_generated/functions/types/functions_list_response.py +21 -0
  66. mirascope/api/_generated/functions/types/functions_list_response_functions_item.py +41 -0
  67. mirascope/api/_generated/functions/types/functions_list_response_functions_item_dependencies_value.py +20 -0
  68. mirascope/api/_generated/health/client.py +2 -6
  69. mirascope/api/_generated/health/raw_client.py +5 -23
  70. mirascope/api/_generated/health/types/health_check_response.py +1 -3
  71. mirascope/api/_generated/organizations/__init__.py +2 -0
  72. mirascope/api/_generated/organizations/client.py +94 -27
  73. mirascope/api/_generated/organizations/raw_client.py +246 -128
  74. mirascope/api/_generated/organizations/types/__init__.py +2 -0
  75. mirascope/api/_generated/organizations/types/organizations_create_response.py +5 -3
  76. mirascope/api/_generated/organizations/types/organizations_create_response_role.py +1 -3
  77. mirascope/api/_generated/organizations/types/organizations_credits_response.py +19 -0
  78. mirascope/api/_generated/organizations/types/organizations_get_response.py +5 -3
  79. mirascope/api/_generated/organizations/types/organizations_get_response_role.py +1 -3
  80. mirascope/api/_generated/organizations/types/organizations_list_response_item.py +5 -3
  81. mirascope/api/_generated/organizations/types/organizations_list_response_item_role.py +1 -3
  82. mirascope/api/_generated/organizations/types/organizations_update_response.py +5 -3
  83. mirascope/api/_generated/organizations/types/organizations_update_response_role.py +1 -3
  84. mirascope/api/_generated/projects/__init__.py +2 -12
  85. mirascope/api/_generated/projects/client.py +38 -68
  86. mirascope/api/_generated/projects/raw_client.py +92 -163
  87. mirascope/api/_generated/projects/types/__init__.py +1 -6
  88. mirascope/api/_generated/projects/types/projects_create_response.py +4 -9
  89. mirascope/api/_generated/projects/types/projects_get_response.py +4 -9
  90. mirascope/api/_generated/projects/types/projects_list_response_item.py +4 -9
  91. mirascope/api/_generated/projects/types/projects_update_response.py +4 -9
  92. mirascope/api/_generated/reference.md +1862 -70
  93. mirascope/api/_generated/traces/__init__.py +22 -0
  94. mirascope/api/_generated/traces/client.py +398 -0
  95. mirascope/api/_generated/traces/raw_client.py +902 -18
  96. mirascope/api/_generated/traces/types/__init__.py +32 -0
  97. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item.py +4 -11
  98. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource.py +2 -6
  99. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item.py +1 -3
  100. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value.py +8 -24
  101. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_array_value.py +2 -6
  102. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_kvlist_value.py +3 -9
  103. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_kvlist_value_values_item.py +2 -6
  104. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item.py +3 -9
  105. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope.py +4 -8
  106. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item.py +2 -6
  107. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value.py +8 -24
  108. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_array_value.py +2 -6
  109. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_kvlist_value.py +3 -9
  110. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_kvlist_value_values_item.py +1 -3
  111. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item.py +6 -18
  112. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item.py +3 -9
  113. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value.py +8 -24
  114. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_array_value.py +2 -6
  115. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_kvlist_value.py +2 -6
  116. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_kvlist_value_values_item.py +1 -3
  117. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_status.py +2 -6
  118. mirascope/api/_generated/traces/types/traces_create_response.py +2 -5
  119. mirascope/api/_generated/traces/types/traces_create_response_partial_success.py +3 -9
  120. mirascope/api/_generated/traces/types/traces_get_analytics_summary_response.py +54 -0
  121. mirascope/api/_generated/traces/types/traces_get_analytics_summary_response_top_functions_item.py +24 -0
  122. mirascope/api/_generated/traces/types/traces_get_analytics_summary_response_top_models_item.py +22 -0
  123. mirascope/api/_generated/traces/types/traces_get_trace_detail_response.py +33 -0
  124. mirascope/api/_generated/traces/types/traces_get_trace_detail_response_spans_item.py +90 -0
  125. mirascope/api/_generated/traces/types/traces_search_request_attribute_filters_item.py +26 -0
  126. mirascope/api/_generated/traces/types/traces_search_request_attribute_filters_item_operator.py +7 -0
  127. mirascope/api/_generated/traces/types/traces_search_request_sort_by.py +7 -0
  128. mirascope/api/_generated/traces/types/traces_search_request_sort_order.py +5 -0
  129. mirascope/api/_generated/traces/types/traces_search_response.py +26 -0
  130. mirascope/api/_generated/traces/types/traces_search_response_spans_item.py +41 -0
  131. mirascope/api/_generated/types/__init__.py +18 -0
  132. mirascope/api/_generated/types/already_exists_error.py +1 -3
  133. mirascope/api/_generated/types/click_house_error.py +22 -0
  134. mirascope/api/_generated/types/database_error.py +1 -3
  135. mirascope/api/_generated/types/http_api_decode_error.py +1 -3
  136. mirascope/api/_generated/types/internal_server_error_body.py +49 -0
  137. mirascope/api/_generated/types/issue.py +1 -3
  138. mirascope/api/_generated/types/issue_tag.py +1 -8
  139. mirascope/api/_generated/types/not_found_error_body.py +1 -3
  140. mirascope/api/_generated/types/number_from_string.py +3 -0
  141. mirascope/api/_generated/types/permission_denied_error.py +1 -3
  142. mirascope/api/_generated/types/permission_denied_error_tag.py +1 -3
  143. mirascope/api/_generated/types/property_key_key.py +1 -3
  144. mirascope/api/_generated/types/stripe_error.py +20 -0
  145. mirascope/api/_generated/types/unauthorized_error_body.py +21 -0
  146. mirascope/api/_generated/types/unauthorized_error_tag.py +5 -0
  147. mirascope/llm/__init__.py +6 -2
  148. mirascope/llm/content/tool_call.py +6 -0
  149. mirascope/llm/exceptions.py +28 -0
  150. mirascope/llm/formatting/__init__.py +2 -2
  151. mirascope/llm/formatting/format.py +120 -8
  152. mirascope/llm/formatting/types.py +1 -56
  153. mirascope/llm/mcp/__init__.py +2 -2
  154. mirascope/llm/mcp/mcp_client.py +130 -0
  155. mirascope/llm/providers/__init__.py +26 -5
  156. mirascope/llm/providers/anthropic/__init__.py +3 -21
  157. mirascope/llm/providers/anthropic/_utils/__init__.py +2 -0
  158. mirascope/llm/providers/anthropic/_utils/beta_decode.py +4 -2
  159. mirascope/llm/providers/anthropic/_utils/beta_encode.py +13 -12
  160. mirascope/llm/providers/anthropic/_utils/decode.py +4 -2
  161. mirascope/llm/providers/anthropic/_utils/encode.py +57 -14
  162. mirascope/llm/providers/anthropic/_utils/errors.py +46 -0
  163. mirascope/llm/providers/anthropic/beta_provider.py +6 -0
  164. mirascope/llm/providers/anthropic/provider.py +5 -0
  165. mirascope/llm/providers/base/__init__.py +5 -2
  166. mirascope/llm/providers/base/_utils.py +2 -7
  167. mirascope/llm/providers/base/base_provider.py +173 -58
  168. mirascope/llm/providers/base/params.py +63 -34
  169. mirascope/llm/providers/google/__init__.py +2 -17
  170. mirascope/llm/providers/google/_utils/__init__.py +2 -0
  171. mirascope/llm/providers/google/_utils/decode.py +17 -8
  172. mirascope/llm/providers/google/_utils/encode.py +105 -16
  173. mirascope/llm/providers/google/_utils/errors.py +49 -0
  174. mirascope/llm/providers/google/model_info.py +1 -0
  175. mirascope/llm/providers/google/provider.py +9 -5
  176. mirascope/llm/providers/mirascope/__init__.py +5 -0
  177. mirascope/llm/providers/mirascope/_utils.py +77 -0
  178. mirascope/llm/providers/mirascope/provider.py +318 -0
  179. mirascope/llm/providers/mlx/__init__.py +2 -17
  180. mirascope/llm/providers/mlx/_utils.py +9 -2
  181. mirascope/llm/providers/mlx/provider.py +8 -0
  182. mirascope/llm/providers/ollama/__init__.py +1 -13
  183. mirascope/llm/providers/openai/__init__.py +10 -1
  184. mirascope/llm/providers/openai/_utils/__init__.py +5 -0
  185. mirascope/llm/providers/openai/_utils/errors.py +46 -0
  186. mirascope/llm/providers/openai/completions/__init__.py +2 -20
  187. mirascope/llm/providers/openai/completions/_utils/decode.py +14 -3
  188. mirascope/llm/providers/openai/completions/_utils/encode.py +15 -12
  189. mirascope/llm/providers/openai/completions/base_provider.py +6 -6
  190. mirascope/llm/providers/openai/provider.py +14 -1
  191. mirascope/llm/providers/openai/responses/__init__.py +1 -17
  192. mirascope/llm/providers/openai/responses/_utils/decode.py +2 -2
  193. mirascope/llm/providers/openai/responses/_utils/encode.py +43 -15
  194. mirascope/llm/providers/openai/responses/provider.py +13 -7
  195. mirascope/llm/providers/provider_id.py +1 -0
  196. mirascope/llm/providers/provider_registry.py +59 -3
  197. mirascope/llm/providers/together/__init__.py +1 -13
  198. mirascope/llm/responses/base_stream_response.py +24 -20
  199. mirascope/llm/tools/decorator.py +8 -4
  200. mirascope/llm/tools/tool_schema.py +33 -6
  201. mirascope/llm/tools/tools.py +84 -16
  202. mirascope/ops/__init__.py +60 -109
  203. mirascope/ops/_internal/closure.py +62 -11
  204. mirascope/ops/_internal/instrumentation/llm/llm.py +1 -2
  205. mirascope/ops/_internal/traced_functions.py +23 -4
  206. mirascope/ops/_internal/versioned_functions.py +54 -43
  207. {mirascope-2.0.0a4.dist-info → mirascope-2.0.0a6.dist-info}/METADATA +7 -7
  208. mirascope-2.0.0a6.dist-info/RECORD +316 -0
  209. mirascope/llm/formatting/_utils.py +0 -78
  210. mirascope/llm/mcp/client.py +0 -118
  211. mirascope/llm/providers/_missing_import_stubs.py +0 -49
  212. mirascope/llm/providers/load_provider.py +0 -54
  213. mirascope-2.0.0a4.dist-info/RECORD +0 -247
  214. {mirascope-2.0.0a4.dist-info → mirascope-2.0.0a6.dist-info}/WHEEL +0 -0
  215. {mirascope-2.0.0a4.dist-info → mirascope-2.0.0a6.dist-info}/licenses/LICENSE +0 -0
@@ -3,18 +3,22 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from abc import ABC, abstractmethod
6
- from collections.abc import Sequence
7
- from typing import TYPE_CHECKING, Any, ClassVar, Generic, TypeAlias, overload
6
+ from collections.abc import Callable, Generator, Mapping, Sequence
7
+ from contextlib import contextmanager
8
+ from typing import TYPE_CHECKING, Any, ClassVar, Generic, TypeAlias, cast, overload
8
9
  from typing_extensions import TypeVar, Unpack
9
10
 
10
11
  from ...context import Context, DepsT
12
+ from ...exceptions import APIError, MirascopeLLMError
11
13
  from ...formatting import Format, FormattableT
12
14
  from ...messages import Message, UserContent, user
13
15
  from ...responses import (
16
+ AsyncChunkIterator,
14
17
  AsyncContextResponse,
15
18
  AsyncContextStreamResponse,
16
19
  AsyncResponse,
17
20
  AsyncStreamResponse,
21
+ ChunkIterator,
18
22
  ContextResponse,
19
23
  ContextStreamResponse,
20
24
  Response,
@@ -33,6 +37,7 @@ from ...tools import (
33
37
  from .params import Params
34
38
 
35
39
  if TYPE_CHECKING:
40
+ from ...exceptions import MirascopeLLMError
36
41
  from ..provider_id import ProviderId
37
42
 
38
43
  ProviderClientT = TypeVar("ProviderClientT")
@@ -40,6 +45,18 @@ ProviderClientT = TypeVar("ProviderClientT")
40
45
  Provider: TypeAlias = "BaseProvider[Any]"
41
46
  """Type alias for `BaseProvider` with any client type."""
42
47
 
48
+ ProviderErrorMap: TypeAlias = Mapping[
49
+ type[Exception],
50
+ "type[MirascopeLLMError] | Callable[[Exception], type[MirascopeLLMError]]",
51
+ ]
52
+ """Mapping from provider SDK exceptions to Mirascope error types.
53
+
54
+ Keys are provider SDK exception types (e.g., OpenAIError, AnthropicError).
55
+ Values can be:
56
+ - Error type: Simple 1:1 mapping (e.g., RateLimitError)
57
+ - Callable: Transform function returning error type based on exception details
58
+ """
59
+
43
60
 
44
61
  class BaseProvider(Generic[ProviderClientT], ABC):
45
62
  """Base abstract provider for LLM interactions.
@@ -59,8 +76,67 @@ class BaseProvider(Generic[ProviderClientT], ABC):
59
76
  - ["anthropic/", "openai/"] - Multiple scopes (e.g., for AWS Bedrock)
60
77
  """
61
78
 
79
+ error_map: ClassVar[ProviderErrorMap]
80
+ """Mapping from provider SDK exceptions to Mirascope error types.
81
+
82
+ Values can be:
83
+ - Error type: Simple 1:1 mapping (e.g., AnthropicRateLimitError -> RateLimitError)
84
+ - Callable: Transform function returning error type based on exception details
85
+ (e.g., lambda e: NotFoundError if e.code == "model_not_found" else BadRequestError)
86
+
87
+ The mapping is walked via the exception's MRO, allowing both specific error handling
88
+ and fallback to base SDK error types (e.g., AnthropicError -> APIError).
89
+ """
90
+
62
91
  client: ProviderClientT
63
92
 
93
+ @contextmanager
94
+ def _wrap_errors(self) -> Generator[None, None, None]:
95
+ """Wrap provider API calls and convert errors to Mirascope exceptions.
96
+
97
+ Walks the exception's MRO to find the first matching error type in the
98
+ provider's error_map, allowing both specific error handling and fallback
99
+ to base SDK error types (e.g., AnthropicError -> APIError).
100
+ """
101
+ try:
102
+ yield
103
+ except Exception as e:
104
+ # Walk MRO to find first matching error type in provider's error_map
105
+ for error_class in type(e).__mro__:
106
+ if error_class in self.error_map:
107
+ error_type_or_fn = self.error_map[error_class]
108
+
109
+ if isinstance(error_type_or_fn, type):
110
+ error_type = cast(type[MirascopeLLMError], error_type_or_fn)
111
+ else:
112
+ error_type = error_type_or_fn(e)
113
+
114
+ # Construct Mirascope error with metadata
115
+ error: MirascopeLLMError = error_type(str(e))
116
+ if isinstance(error, APIError):
117
+ error.status_code = self.get_error_status(e)
118
+ error.provider = self.id
119
+ error.original_exception = e
120
+ raise error from e
121
+
122
+ # Not in error_map - not a provider error, re-raise as-is
123
+ raise
124
+
125
+ def _wrap_iterator_errors(self, iterator: ChunkIterator) -> ChunkIterator:
126
+ """Wrap sync chunk iterator to handle errors during iteration."""
127
+ # TODO: Consider moving this logic into BaseSyncStreamResponse if appropriate.
128
+ with self._wrap_errors():
129
+ yield from iterator
130
+
131
+ async def _wrap_async_iterator_errors(
132
+ self, iterator: AsyncChunkIterator
133
+ ) -> AsyncChunkIterator:
134
+ """Wrap async chunk iterator to handle errors during iteration."""
135
+ # TODO: Consider moving this logic into BaseAsyncStreamResponse if appropriate.
136
+ with self._wrap_errors():
137
+ async for chunk in iterator:
138
+ yield chunk
139
+
64
140
  @overload
65
141
  def call(
66
142
  self,
@@ -121,13 +197,14 @@ class BaseProvider(Generic[ProviderClientT], ABC):
121
197
  Returns:
122
198
  An `llm.Response` object containing the LLM-generated content.
123
199
  """
124
- return self._call(
125
- model_id=model_id,
126
- messages=messages,
127
- tools=tools,
128
- format=format,
129
- **params,
130
- )
200
+ with self._wrap_errors():
201
+ return self._call(
202
+ model_id=model_id,
203
+ messages=messages,
204
+ tools=tools,
205
+ format=format,
206
+ **params,
207
+ )
131
208
 
132
209
  @abstractmethod
133
210
  def _call(
@@ -215,14 +292,15 @@ class BaseProvider(Generic[ProviderClientT], ABC):
215
292
  Returns:
216
293
  An `llm.ContextResponse` object containing the LLM-generated content.
217
294
  """
218
- return self._context_call(
219
- ctx=ctx,
220
- model_id=model_id,
221
- messages=messages,
222
- tools=tools,
223
- format=format,
224
- **params,
225
- )
295
+ with self._wrap_errors():
296
+ return self._context_call(
297
+ ctx=ctx,
298
+ model_id=model_id,
299
+ messages=messages,
300
+ tools=tools,
301
+ format=format,
302
+ **params,
303
+ )
226
304
 
227
305
  @abstractmethod
228
306
  def _context_call(
@@ -300,13 +378,14 @@ class BaseProvider(Generic[ProviderClientT], ABC):
300
378
  Returns:
301
379
  An `llm.AsyncResponse` object containing the LLM-generated content.
302
380
  """
303
- return await self._call_async(
304
- model_id=model_id,
305
- messages=messages,
306
- tools=tools,
307
- format=format,
308
- **params,
309
- )
381
+ with self._wrap_errors():
382
+ return await self._call_async(
383
+ model_id=model_id,
384
+ messages=messages,
385
+ tools=tools,
386
+ format=format,
387
+ **params,
388
+ )
310
389
 
311
390
  @abstractmethod
312
391
  async def _call_async(
@@ -394,14 +473,15 @@ class BaseProvider(Generic[ProviderClientT], ABC):
394
473
  Returns:
395
474
  An `llm.AsyncContextResponse` object containing the LLM-generated content.
396
475
  """
397
- return await self._context_call_async(
398
- ctx=ctx,
399
- model_id=model_id,
400
- messages=messages,
401
- tools=tools,
402
- format=format,
403
- **params,
404
- )
476
+ with self._wrap_errors():
477
+ return await self._context_call_async(
478
+ ctx=ctx,
479
+ model_id=model_id,
480
+ messages=messages,
481
+ tools=tools,
482
+ format=format,
483
+ **params,
484
+ )
405
485
 
406
486
  @abstractmethod
407
487
  async def _context_call_async(
@@ -479,13 +559,18 @@ class BaseProvider(Generic[ProviderClientT], ABC):
479
559
  Returns:
480
560
  An `llm.StreamResponse` object for iterating over the LLM-generated content.
481
561
  """
482
- return self._stream(
483
- model_id=model_id,
484
- messages=messages,
485
- tools=tools,
486
- format=format,
487
- **params,
562
+ with self._wrap_errors():
563
+ stream_response = self._stream(
564
+ model_id=model_id,
565
+ messages=messages,
566
+ tools=tools,
567
+ format=format,
568
+ **params,
569
+ )
570
+ stream_response._chunk_iterator = self._wrap_iterator_errors( # pyright: ignore[reportPrivateUsage]
571
+ stream_response._chunk_iterator # pyright: ignore[reportPrivateUsage]
488
572
  )
573
+ return stream_response
489
574
 
490
575
  @abstractmethod
491
576
  def _stream(
@@ -577,14 +662,19 @@ class BaseProvider(Generic[ProviderClientT], ABC):
577
662
  Returns:
578
663
  An `llm.ContextStreamResponse` object for iterating over the LLM-generated content.
579
664
  """
580
- return self._context_stream(
581
- ctx=ctx,
582
- model_id=model_id,
583
- messages=messages,
584
- tools=tools,
585
- format=format,
586
- **params,
665
+ with self._wrap_errors():
666
+ stream_response = self._context_stream(
667
+ ctx=ctx,
668
+ model_id=model_id,
669
+ messages=messages,
670
+ tools=tools,
671
+ format=format,
672
+ **params,
673
+ )
674
+ stream_response._chunk_iterator = self._wrap_iterator_errors( # pyright: ignore[reportPrivateUsage]
675
+ stream_response._chunk_iterator # pyright: ignore[reportPrivateUsage]
587
676
  )
677
+ return stream_response
588
678
 
589
679
  @abstractmethod
590
680
  def _context_stream(
@@ -664,13 +754,18 @@ class BaseProvider(Generic[ProviderClientT], ABC):
664
754
  Returns:
665
755
  An `llm.AsyncStreamResponse` object for asynchronously iterating over the LLM-generated content.
666
756
  """
667
- return await self._stream_async(
668
- model_id=model_id,
669
- messages=messages,
670
- tools=tools,
671
- format=format,
672
- **params,
757
+ with self._wrap_errors():
758
+ stream_response = await self._stream_async(
759
+ model_id=model_id,
760
+ messages=messages,
761
+ tools=tools,
762
+ format=format,
763
+ **params,
764
+ )
765
+ stream_response._chunk_iterator = self._wrap_async_iterator_errors( # pyright: ignore[reportPrivateUsage]
766
+ stream_response._chunk_iterator # pyright: ignore[reportPrivateUsage]
673
767
  )
768
+ return stream_response
674
769
 
675
770
  @abstractmethod
676
771
  async def _stream_async(
@@ -764,14 +859,19 @@ class BaseProvider(Generic[ProviderClientT], ABC):
764
859
  Returns:
765
860
  An `llm.AsyncContextStreamResponse` object for asynchronously iterating over the LLM-generated content.
766
861
  """
767
- return await self._context_stream_async(
768
- ctx=ctx,
769
- model_id=model_id,
770
- messages=messages,
771
- tools=tools,
772
- format=format,
773
- **params,
862
+ with self._wrap_errors():
863
+ stream_response = await self._context_stream_async(
864
+ ctx=ctx,
865
+ model_id=model_id,
866
+ messages=messages,
867
+ tools=tools,
868
+ format=format,
869
+ **params,
870
+ )
871
+ stream_response._chunk_iterator = self._wrap_async_iterator_errors( # pyright: ignore[reportPrivateUsage]
872
+ stream_response._chunk_iterator # pyright: ignore[reportPrivateUsage]
774
873
  )
874
+ return stream_response
775
875
 
776
876
  @abstractmethod
777
877
  async def _context_stream_async(
@@ -1383,3 +1483,18 @@ class BaseProvider(Generic[ProviderClientT], ABC):
1383
1483
  format=response.format,
1384
1484
  **params,
1385
1485
  )
1486
+
1487
+ @abstractmethod
1488
+ def get_error_status(self, e: Exception) -> int | None:
1489
+ """Extract HTTP status code from provider-specific exception.
1490
+
1491
+ Different SDKs store status codes differently (e.g., .status_code vs .code).
1492
+ Each provider implements this to handle their SDK's convention.
1493
+
1494
+ Args:
1495
+ e: The exception to extract status code from.
1496
+
1497
+ Returns:
1498
+ The HTTP status code if available, None otherwise.
1499
+ """
1500
+ ...
@@ -1,6 +1,58 @@
1
1
  """Base parameters for LLM providers."""
2
2
 
3
- from typing import TypedDict
3
+ from typing import Literal, TypedDict
4
+ from typing_extensions import Required
5
+
6
+ ThinkingLevel = Literal["none", "default", "minimal", "low", "medium", "high", "max"]
7
+ """Level of effort/reasoning to apply to thinking."""
8
+
9
+
10
+ class ThinkingConfig(TypedDict, total=False):
11
+ """Configuration for extended reasoning/thinking in LLM responses.
12
+
13
+ Thinking is a process where the model spends additional tokens reasoning about
14
+ the prompt before generating a response. Providing any `ThinkingConfig` will enable
15
+ thinking (unless it is specifically disabled via level="minimal"). Depending on
16
+ the provider and model, thinking may always be active regardless of user settings.
17
+ """
18
+
19
+ level: Required[ThinkingLevel]
20
+ """Level of effort/reasoning to apply to thinking.
21
+
22
+ - none: Disable thinking entirely. Minimizes cost and latency.
23
+ - default: Use the provider's default
24
+ - minimal: Use the provider's lowest setting for reasoning
25
+ - medium: Use a moderate amount of reasoning tokens
26
+ - high: Allow extensive resources for thinking
27
+ - max: Uses as much thinking as allowed by the provider.
28
+
29
+ Mirascope makes a best effort to apply the chosen thinking level, but exact behavior
30
+ varies by provider and model. For example, some models may not support thinking,
31
+ while other models may not allow disabling it.
32
+ """
33
+
34
+ include_summaries: bool
35
+ """Whether to generate reasoning summaries (human readable Thoughts) from model output.
36
+
37
+ Generally, providers do not return raw model thinking output, but may produce
38
+ thought summaries. When `include_summaries` is true, these will be requested from
39
+ the provider (if available). Otherwise, they will not be requested.
40
+ """
41
+
42
+ encode_thoughts_as_text: bool
43
+ """Re-encode Thought content as text for model consumption.
44
+
45
+ If `True`, when an `AssistantMessage` contains `Thoughts` and is passed back
46
+ to an LLM, those `Thoughts` will be encoded as `Text`, ensuring the assistant
47
+ can read its prior reasoning. This contrasts with provider defaults which may
48
+ ignore prior thoughts, particularly if tool calls are not involved.
49
+
50
+ When `True`, Mirascope will re-encode messages rather than reusing raw provider
51
+ response content, which may disable provider-specific optimizations like cached
52
+ reasoning tokens.
53
+
54
+ Defaults to `False` if unset.
55
+ """
4
56
 
5
57
 
6
58
  class Params(TypedDict, total=False):
@@ -55,39 +107,16 @@ class Params(TypedDict, total=False):
55
107
  response.
56
108
  """
57
109
 
58
- thinking: bool
59
- """Configures whether the model should use thinking.
60
-
61
- Thinking is a process where the model spends additional tokens thinking about the
62
- prompt before generating a response. You may configure thinking either by passing
63
- a bool to enable or disable it.
64
-
65
- If `params.thinking` is `True`, then thinking and thought summaries will be enabled
66
- (if supported by the model/provider), with a default budget for thinking tokens.
67
-
68
- If `params.thinking` is `False`, then thinking will be wholly disabled, assuming
69
- the model allows this (some models, e.g. `google:gemini-2.5-pro`, do not allow
70
- disabling thinking).
71
-
72
- If `params.thinking` is unset (or `None`), then we will use provider-specific default
73
- behavior for the chosen model.
74
- """
75
-
76
- encode_thoughts_as_text: bool
77
- """Configures whether `Thought` content should be re-encoded as text for model consumption.
78
-
79
- If `True`, then when an `AssistantMessage` contains `Thoughts` and is being passed back
80
- to an LLM, those `Thoughts` will be encoded as `Text`, so that the assistant can read
81
- those thoughts. That ensures the assistant has access to (at least the summarized output of)
82
- its reasoning process, and contrasts with provider default behaviors which may ignore
83
- prior thoughts, particularly if tool calls are not involved.
84
-
85
- When `True`, we will always re-encode Mirascope messages being passed to the provider,
86
- rather than reusing raw provider response content. This may disable provider-specific
87
- behavior like cached reasoning tokens.
110
+ thinking: ThinkingConfig | None
111
+ """Configuration for extended reasoning/thinking.
88
112
 
89
- If `False`, then `Thoughts` will not be encoded as text, and whether reasoning context
90
- is available to the model depends entirely on the provider's behavior.
113
+ Pass a `ThinkingConfig` to configure thinking behavior. The `level` field controls
114
+ whether thinking is enabled and how much reasoning to use. Level may be one of
115
+ "minimal", "low", "medium", or "high". If level is unset, then thinking is enabled
116
+ with a provider-specific default level.
91
117
 
92
- Defaults to `False` if unset.
118
+ `ThinkingConfig` can also include `encode_thoughts_as_text`, which is an advanced
119
+ feature for providing past thoughts back to the model as text content. This is
120
+ primarily useful for making thoughts transferable when passing a conversation
121
+ to a different model or provider than the one that generated the thinking.
93
122
  """
@@ -1,21 +1,6 @@
1
1
  """Google client implementation."""
2
2
 
3
- from typing import TYPE_CHECKING
4
-
5
- if TYPE_CHECKING:
6
- from .model_id import GoogleModelId
7
- from .provider import GoogleProvider
8
- else:
9
- try:
10
- from .model_id import GoogleModelId
11
- from .provider import GoogleProvider
12
- except ImportError: # pragma: no cover
13
- from .._missing_import_stubs import (
14
- create_import_error_stub,
15
- create_provider_stub,
16
- )
17
-
18
- GoogleProvider = create_provider_stub("google", "GoogleProvider")
19
- GoogleModelId = str
3
+ from .model_id import GoogleModelId
4
+ from .provider import GoogleProvider
20
5
 
21
6
  __all__ = ["GoogleModelId", "GoogleProvider"]
@@ -4,8 +4,10 @@ from .decode import (
4
4
  decode_stream,
5
5
  )
6
6
  from .encode import encode_request
7
+ from .errors import GOOGLE_ERROR_MAP
7
8
 
8
9
  __all__ = [
10
+ "GOOGLE_ERROR_MAP",
9
11
  "decode_async_stream",
10
12
  "decode_response",
11
13
  "decode_stream",
@@ -177,15 +177,19 @@ class _GoogleChunkProcessor:
177
177
  if self.current_content_type == "thought" and not part.thought:
178
178
  yield ThoughtEndChunk()
179
179
  self.current_content_type = None
180
- elif self.current_content_type == "text" and not part.text:
181
- yield TextEndChunk() # pragma: no cover
182
- self.current_content_type = None # pragma: no cover
183
- elif self.current_content_type == "tool_call" and not part.function_call:
180
+ elif (
181
+ self.current_content_type == "text" and not part.text
182
+ ): # pragma: no cover
183
+ yield TextEndChunk()
184
+ self.current_content_type = None
185
+ elif (
186
+ self.current_content_type == "tool_call" and not part.function_call
187
+ ): # pragma: no cover
184
188
  # In testing, Gemini never emits tool calls and text in the same message
185
189
  # (even when specifically asked in system and user prompt), so
186
190
  # the following code is uncovered but included for completeness
187
- yield ToolCallEndChunk() # pragma: no cover
188
- self.current_content_type = None # pragma: no cover
191
+ yield ToolCallEndChunk(id=UNKNOWN_TOOL_ID)
192
+ self.current_content_type = None
189
193
 
190
194
  if part.thought:
191
195
  if self.current_content_type is None:
@@ -210,17 +214,22 @@ class _GoogleChunkProcessor:
210
214
  "Required name missing on Google function call"
211
215
  ) # pragma: no cover
212
216
 
217
+ tool_id = function_call.id or UNKNOWN_TOOL_ID
218
+ self.current_content_type = "tool_call"
219
+
213
220
  yield ToolCallStartChunk(
214
- id=function_call.id or UNKNOWN_TOOL_ID,
221
+ id=tool_id,
215
222
  name=function_call.name,
216
223
  )
217
224
 
218
225
  yield ToolCallChunk(
226
+ id=tool_id,
219
227
  delta=json.dumps(function_call.args)
220
228
  if function_call.args
221
229
  else "{}",
222
230
  )
223
- yield ToolCallEndChunk()
231
+ yield ToolCallEndChunk(id=tool_id)
232
+ self.current_content_type = None
224
233
 
225
234
  if candidate.finish_reason:
226
235
  if self.current_content_type == "text":