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
mirascope/llm/__init__.py CHANGED
@@ -92,8 +92,10 @@ from .providers import (
92
92
  Params,
93
93
  Provider,
94
94
  ProviderId,
95
- load_provider,
95
+ ThinkingConfig,
96
+ ThinkingLevel,
96
97
  register_provider,
98
+ reset_provider_registry,
97
99
  )
98
100
  from .responses import (
99
101
  AsyncChunkIterator,
@@ -205,6 +207,8 @@ __all__ = [
205
207
  "TextEndChunk",
206
208
  "TextStartChunk",
207
209
  "TextStream",
210
+ "ThinkingConfig",
211
+ "ThinkingLevel",
208
212
  "Thought",
209
213
  "ThoughtChunk",
210
214
  "ThoughtEndChunk",
@@ -232,7 +236,6 @@ __all__ = [
232
236
  "exceptions",
233
237
  "format",
234
238
  "formatting",
235
- "load_provider",
236
239
  "mcp",
237
240
  "messages",
238
241
  "model",
@@ -242,6 +245,7 @@ __all__ = [
242
245
  "prompts",
243
246
  "providers",
244
247
  "register_provider",
248
+ "reset_provider_registry",
245
249
  "responses",
246
250
  "tool",
247
251
  "tools",
@@ -49,6 +49,9 @@ class ToolCallChunk:
49
49
  content_type: Literal["tool_call"] = "tool_call"
50
50
  """The type of content reconstructed by this chunk."""
51
51
 
52
+ id: str
53
+ """A unique identifier for this tool call."""
54
+
52
55
  delta: str
53
56
  """The incremental json args added in this chunk."""
54
57
 
@@ -61,3 +64,6 @@ class ToolCallEndChunk:
61
64
 
62
65
  content_type: Literal["tool_call"] = "tool_call"
63
66
  """The type of content reconstructed by this chunk."""
67
+
68
+ id: str
69
+ """A unique identifier for this tool call."""
@@ -11,6 +11,7 @@ class MirascopeLLMError(Exception):
11
11
  """Base exception for all Mirascope LLM errors."""
12
12
 
13
13
  original_exception: Exception | None
14
+ provider: "ProviderId | None"
14
15
 
15
16
 
16
17
  class APIError(MirascopeLLMError):
@@ -18,6 +19,10 @@ class APIError(MirascopeLLMError):
18
19
 
19
20
  status_code: int | None
20
21
 
22
+ def __init__(self, message: str, status_code: int | None = None) -> None:
23
+ super().__init__(message)
24
+ self.status_code = status_code
25
+
21
26
 
22
27
  class ConnectionError(MirascopeLLMError):
23
28
  """Raised when unable to connect to the API (network issues, timeouts)."""
@@ -26,18 +31,30 @@ class ConnectionError(MirascopeLLMError):
26
31
  class AuthenticationError(APIError):
27
32
  """Raised for authentication failures (401, invalid API keys)."""
28
33
 
34
+ def __init__(self, message: str, status_code: int | None = None) -> None:
35
+ super().__init__(message, status_code=status_code or 401)
36
+
29
37
 
30
38
  class PermissionError(APIError):
31
39
  """Raised for permission/authorization failures (403)."""
32
40
 
41
+ def __init__(self, message: str, status_code: int | None = None) -> None:
42
+ super().__init__(message, status_code=status_code or 403)
43
+
33
44
 
34
45
  class BadRequestError(APIError):
35
46
  """Raised for malformed requests (400, 422)."""
36
47
 
48
+ def __init__(self, message: str, status_code: int | None = None) -> None:
49
+ super().__init__(message, status_code=status_code or 400)
50
+
37
51
 
38
52
  class NotFoundError(APIError):
39
53
  """Raised when requested resource is not found (404)."""
40
54
 
55
+ def __init__(self, message: str, status_code: int | None = None) -> None:
56
+ super().__init__(message, status_code=status_code or 404)
57
+
41
58
 
42
59
  class ToolNotFoundError(MirascopeLLMError):
43
60
  """Raised if a tool_call cannot be converted to any corresponding tool."""
@@ -96,15 +113,26 @@ class FormattingModeNotSupportedError(FeatureNotSupportedError):
96
113
  class RateLimitError(APIError):
97
114
  """Raised when rate limits are exceeded (429)."""
98
115
 
116
+ def __init__(self, message: str, status_code: int | None = None) -> None:
117
+ super().__init__(message, status_code=status_code or 429)
118
+
99
119
 
100
120
  class ServerError(APIError):
101
121
  """Raised for server-side errors (500+)."""
102
122
 
123
+ def __init__(self, message: str, status_code: int | None = None) -> None:
124
+ super().__init__(message, status_code=status_code or 500)
125
+
103
126
 
104
127
  class TimeoutError(MirascopeLLMError):
105
128
  """Raised when requests timeout or deadline exceeded."""
106
129
 
107
130
 
131
+ # This wraps the APIResponseValidationErrors that OpenAI and Anthropic both return.
132
+ class ResponseValidationError(MirascopeLLMError):
133
+ """Raised when API response fails validation."""
134
+
135
+
108
136
  class NoRegisteredProviderError(MirascopeLLMError):
109
137
  """Raised when no provider is registered for a given model_id."""
110
138
 
@@ -5,10 +5,10 @@ The `@format` decorator can be applied to classes to specify how LLM
5
5
  outputs should be structured and parsed.
6
6
  """
7
7
 
8
- from .format import format, resolve_format
8
+ from .format import Format, format, resolve_format
9
9
  from .from_call_args import FromCallArgs
10
10
  from .partial import Partial
11
- from .types import Format, FormattableT, FormattingMode
11
+ from .types import FormattableT, FormattingMode
12
12
 
13
13
  __all__ = [
14
14
  "Format",
@@ -1,10 +1,128 @@
1
1
  """The `llm.format` decorator for defining response formats as classes."""
2
2
 
3
3
  import inspect
4
+ import json
5
+ from dataclasses import dataclass
6
+ from typing import Any, Generic, cast
4
7
 
8
+ from ..tools import FORMAT_TOOL_NAME, ToolFn, ToolParameterSchema, ToolSchema
5
9
  from ..types import NoneType
6
- from ._utils import default_formatting_instructions
7
- from .types import Format, FormattableT, FormattingMode, HasFormattingInstructions
10
+ from .types import FormattableT, FormattingMode, HasFormattingInstructions
11
+
12
+ TOOL_MODE_INSTRUCTIONS = f"""Always respond to the user's query using the {FORMAT_TOOL_NAME} tool for structured output."""
13
+
14
+
15
+ JSON_MODE_INSTRUCTIONS = (
16
+ "Respond only with valid JSON that matches this exact schema:\n{json_schema}"
17
+ )
18
+
19
+
20
+ @dataclass(kw_only=True)
21
+ class Format(Generic[FormattableT]):
22
+ """Class representing a structured output format for LLM responses.
23
+
24
+ A `Format` contains metadata needed to describe a structured output type
25
+ to the LLM, including the expected schema. This class is not instantiated directly,
26
+ but is created by calling `llm.format`, or is automatically generated by LLM
27
+ providers when a `Formattable` is passed to a call method.
28
+
29
+ Example:
30
+
31
+ ```python
32
+ from mirascope import llm
33
+
34
+ class Book:
35
+ title: str
36
+ author: str
37
+
38
+ print(llm.format(Book, mode="tool"))
39
+ ```
40
+ """
41
+
42
+ name: str
43
+ """The name of the response format."""
44
+
45
+ description: str | None
46
+ """A description of the response format, if available."""
47
+
48
+ schema: dict[str, object]
49
+ """JSON schema representation of the structured output format."""
50
+
51
+ mode: FormattingMode
52
+ """The decorator-provided mode of the response format.
53
+
54
+ Determines how the LLM call may be modified in order to extract the expected format.
55
+ """
56
+
57
+ formattable: type[FormattableT]
58
+ """The `Formattable` type that this `Format` describes.
59
+
60
+ While the `FormattbleT` typevar allows for `None`, a `Format` will never be
61
+ constructed when the `FormattableT` is `None`, so you may treat this as
62
+ a `RequiredFormattableT` in practice.
63
+ """
64
+
65
+ @property
66
+ def formatting_instructions(self) -> str | None:
67
+ """The formatting instructions that will be added to the LLM system prompt.
68
+
69
+ If the format type has a `formatting_instructions` class method, the output of that
70
+ call will be used for instructions. Otherwise, instructions may be auto-generated
71
+ based on the formatting mode.
72
+ """
73
+ if isinstance(self.formattable, HasFormattingInstructions):
74
+ return self.formattable.formatting_instructions()
75
+ if self.mode == "tool":
76
+ return TOOL_MODE_INSTRUCTIONS
77
+ elif self.mode == "json":
78
+ json_schema = json.dumps(self.schema, indent=2)
79
+ instructions = JSON_MODE_INSTRUCTIONS.format(json_schema=json_schema)
80
+ return inspect.cleandoc(instructions)
81
+
82
+ def create_tool_schema(
83
+ self,
84
+ ) -> ToolSchema[ToolFn[..., None]]:
85
+ """Generate a `ToolSchema` for parsing this format.
86
+
87
+ Returns:
88
+ `ToolSchema` for the format tool
89
+ """
90
+
91
+ schema_dict: dict[str, Any] = self.schema.copy()
92
+ schema_dict["type"] = "object"
93
+
94
+ properties = schema_dict.get("properties")
95
+ if not properties or not isinstance(properties, dict):
96
+ properties = {} # pragma: no cover
97
+ properties = cast(dict[str, Any], properties)
98
+ required: list[str] = list(properties.keys())
99
+
100
+ description = (
101
+ f"Use this tool to extract data in {self.name} format for a final response."
102
+ )
103
+ if self.description:
104
+ description += "\n" + self.description
105
+
106
+ parameters = ToolParameterSchema(
107
+ properties=properties,
108
+ required=required,
109
+ additionalProperties=False,
110
+ )
111
+ if "$defs" in schema_dict and isinstance(schema_dict["$defs"], dict):
112
+ parameters.defs = schema_dict["$defs"]
113
+
114
+ def _unused_format_fn() -> None:
115
+ raise TypeError(
116
+ "Format tool function should not be called."
117
+ ) # pragma: no cover
118
+
119
+ return ToolSchema(
120
+ fn=cast(ToolFn[..., None], _unused_format_fn),
121
+ name=FORMAT_TOOL_NAME,
122
+ description=description,
123
+ parameters=parameters,
124
+ strict=True,
125
+ )
8
126
 
9
127
 
10
128
  def format(
@@ -77,18 +195,12 @@ def format(
77
195
  description = inspect.cleandoc(formattable.__doc__)
78
196
 
79
197
  schema = formattable.model_json_schema()
80
- formatting_instructions = None
81
- if isinstance(formattable, HasFormattingInstructions):
82
- formatting_instructions = formattable.formatting_instructions()
83
- else:
84
- formatting_instructions = default_formatting_instructions(schema, mode)
85
198
 
86
199
  return Format[FormattableT](
87
200
  name=formattable.__name__,
88
201
  description=description,
89
202
  schema=schema,
90
203
  mode=mode,
91
- formatting_instructions=formatting_instructions,
92
204
  formattable=formattable,
93
205
  )
94
206
 
@@ -1,7 +1,6 @@
1
1
  """Type for the formatting module."""
2
2
 
3
- from dataclasses import dataclass
4
- from typing import Generic, Literal, Protocol, runtime_checkable
3
+ from typing import Literal, Protocol, runtime_checkable
5
4
  from typing_extensions import TypeVar
6
5
 
7
6
  from pydantic import BaseModel
@@ -47,60 +46,6 @@ Note: When `llm.format` is not used, the provider will automatically choose a mo
47
46
  """
48
47
 
49
48
 
50
- @dataclass(kw_only=True)
51
- class Format(Generic[FormattableT]):
52
- """Class representing a structured output format for LLM responses.
53
-
54
- A `Format` contains metadata needed to describe a structured output type
55
- to the LLM, including the expected schema. This class is not instantiated directly,
56
- but is created by calling `llm.format`, or is automatically generated by LLM
57
- providers when a `Formattable` is passed to a call method.
58
-
59
- Example:
60
-
61
- ```python
62
- from mirascope import llm
63
-
64
- class Book:
65
- title: str
66
- author: str
67
-
68
- print(llm.format(Book, mode="tool"))
69
- ```
70
- """
71
-
72
- name: str
73
- """The name of the response format."""
74
-
75
- description: str | None
76
- """A description of the response format, if available."""
77
-
78
- schema: dict[str, object]
79
- """JSON schema representation of the structured output format."""
80
-
81
- mode: FormattingMode
82
- """The decorator-provided mode of the response format.
83
-
84
- Determines how the LLM call may be modified in order to extract the expected format.
85
- """
86
-
87
- formatting_instructions: str | None
88
- """The formatting instructions that will be added to the LLM system prompt.
89
-
90
- If the format type has a `formatting_instructions` class method, the output of that
91
- call will be used for instructions. Otherwise, instructions may be auto-generated
92
- based on the formatting mode.
93
- """
94
-
95
- formattable: type[FormattableT]
96
- """The `Formattable` type that this `Format` describes.
97
-
98
- While the `FormattbleT` typevar allows for `None`, a `Format` will never be
99
- constructed when the `FormattableT` is `None`, so you may treat this as
100
- a `RequiredFormattableT` in practice.
101
- """
102
-
103
-
104
49
  @runtime_checkable
105
50
  class HasFormattingInstructions(Protocol):
106
51
  """Protocol for classes that have been decorated with `@format()`."""
@@ -1,5 +1,5 @@
1
1
  """MCP compatibility module."""
2
2
 
3
- from .client import MCPClient, sse_client, stdio_client, streamablehttp_client
3
+ from .mcp_client import MCPClient, sse_client, stdio_client, streamable_http_client
4
4
 
5
- __all__ = ["MCPClient", "sse_client", "stdio_client", "streamablehttp_client"]
5
+ __all__ = ["MCPClient", "sse_client", "stdio_client", "streamable_http_client"]
@@ -0,0 +1,130 @@
1
+ import contextlib
2
+ from collections.abc import AsyncIterator
3
+ from datetime import timedelta
4
+ from typing import cast
5
+
6
+ from mcp import ClientSession
7
+ from mcp.client.sse import sse_client as mcp_sse_client
8
+ from mcp.client.stdio import StdioServerParameters, stdio_client as mcp_stdio_client
9
+ from mcp.client.streamable_http import (
10
+ streamable_http_client as mcp_streamable_http_client,
11
+ )
12
+ from mcp.types import CallToolResult, Tool as MCPTool
13
+
14
+ from ..tools import AsyncTool
15
+ from ..tools.tool_schema import ToolParameterSchema
16
+ from ..types import Jsonable
17
+
18
+
19
+ class MCPClient:
20
+ """Mirascope wrapper around a MCP ClientSession.
21
+
22
+ It provides a way to get MCP results that are pre-converted into Mirascope-friendly
23
+ types.
24
+
25
+ The underlying MCP ClientSession may be accessed by .session if needed.
26
+ """
27
+
28
+ def __init__(self, session: ClientSession) -> None:
29
+ self._session = session
30
+
31
+ @property
32
+ def session(self) -> ClientSession:
33
+ """Access the underlying MCP ClientSession if needed."""
34
+ return self._session
35
+
36
+ def _convert_mcp_tool_to_async_tool(self, mcp_tool: MCPTool) -> AsyncTool:
37
+ """Convert an MCP Tool to a Mirascope AsyncTool.
38
+
39
+ Args:
40
+ mcp_tool: The MCP tool to convert.
41
+
42
+ Returns:
43
+ An `AsyncTool` that wraps the MCP tool.
44
+ """
45
+
46
+ # Create an async function that calls the MCP tool
47
+ async def tool_fn(**kwargs: object) -> Jsonable:
48
+ tool_result: CallToolResult = await self._session.call_tool(
49
+ mcp_tool.name, kwargs
50
+ )
51
+ # Convert ContentBlock objects to JSON-serializable dicts
52
+ # Cast to Jsonable since model_dump() returns dict[str, Any]
53
+ return cast(
54
+ Jsonable, [content.model_dump() for content in tool_result.content]
55
+ )
56
+
57
+ # Convert MCP tool's inputSchema to Mirascope's ToolParameterSchema
58
+ input_schema = mcp_tool.inputSchema
59
+ parameters = ToolParameterSchema(
60
+ properties=input_schema.get("properties", {}),
61
+ required=input_schema.get("required", []),
62
+ additionalProperties=input_schema.get("additionalProperties", False),
63
+ )
64
+ if "$defs" in input_schema:
65
+ parameters.defs = input_schema["$defs"]
66
+
67
+ # Create the AsyncTool instance
68
+ return AsyncTool(
69
+ fn=tool_fn,
70
+ name=mcp_tool.name,
71
+ description=mcp_tool.description or mcp_tool.name,
72
+ parameters=parameters,
73
+ strict=False,
74
+ )
75
+
76
+ async def list_tools(self) -> list[AsyncTool]:
77
+ """List all tools available on the MCP server.
78
+
79
+ Returns:
80
+ A list of dynamically created `AsyncTool`s.
81
+ """
82
+ result = await self._session.list_tools()
83
+ return [self._convert_mcp_tool_to_async_tool(tool) for tool in result.tools]
84
+
85
+
86
+ @contextlib.asynccontextmanager
87
+ async def streamable_http_client(
88
+ url: str,
89
+ ) -> AsyncIterator[MCPClient]: # pragma: no cover
90
+ """Create a Mirascope MCPClient using StreamableHTTP."""
91
+ # NOTE: If updating this function, unskip and manually run the TestTransportModes
92
+ # tests in test_mcp_client.py. (Skipped because they are flaky)
93
+ async with (
94
+ mcp_streamable_http_client(url) as (read, write, _),
95
+ ClientSession(read, write) as session,
96
+ ):
97
+ await session.initialize()
98
+ yield MCPClient(session)
99
+
100
+
101
+ @contextlib.asynccontextmanager
102
+ async def stdio_client(
103
+ server_parameters: StdioServerParameters,
104
+ name: str | None = None,
105
+ ) -> AsyncIterator[MCPClient]:
106
+ """Create a Mirascope MCPClient using stdio."""
107
+ async with (
108
+ mcp_stdio_client(server_parameters) as (read, write),
109
+ ClientSession(read, write) as session,
110
+ ):
111
+ await session.initialize()
112
+ yield MCPClient(session)
113
+
114
+
115
+ @contextlib.asynccontextmanager
116
+ async def sse_client(
117
+ url: str,
118
+ read_timeout_seconds: timedelta | None = None,
119
+ ) -> AsyncIterator[MCPClient]: # pragma: no cover
120
+ """Create a Mirascope MCPClient using sse."""
121
+ # NOTE: If updating this function, unskip and manually run the TestTransportModes
122
+ # tests in test_mcp_client.py. (Skipped because they are flaky)
123
+ async with (
124
+ mcp_sse_client(url) as (read, write),
125
+ ClientSession(
126
+ read, write, read_timeout_seconds=read_timeout_seconds
127
+ ) as session,
128
+ ):
129
+ await session.initialize()
130
+ yield MCPClient(session)
@@ -1,12 +1,27 @@
1
1
  """Interfaces for LLM providers."""
2
2
 
3
+ from ..._stubs import stub_module_if_missing
4
+
5
+ # Stub modules for missing optional dependencies BEFORE importing
6
+ # This must happen before any imports from these modules
7
+ # Note: We only stub top-level provider modules, not their submodules.
8
+ # The _StubModule will automatically handle nested attribute access.
9
+ stub_module_if_missing("mirascope.llm.providers.anthropic", "anthropic")
10
+ stub_module_if_missing("mirascope.llm.providers.google", "google")
11
+ stub_module_if_missing("mirascope.llm.providers.mlx", "mlx")
12
+ stub_module_if_missing("mirascope.llm.providers.openai", "openai")
13
+ stub_module_if_missing("mirascope.llm.providers.together", "openai")
14
+ stub_module_if_missing("mirascope.llm.providers.ollama", "openai")
15
+
16
+ # Now imports work regardless of which packages are installed
17
+ # ruff: noqa: E402
3
18
  from .anthropic import (
4
19
  AnthropicModelId,
5
20
  AnthropicProvider,
6
21
  )
7
- from .base import BaseProvider, Params, Provider
22
+ from .base import BaseProvider, Params, Provider, ThinkingConfig, ThinkingLevel
8
23
  from .google import GoogleModelId, GoogleProvider
9
- from .load_provider import load, load_provider
24
+ from .mirascope import MirascopeProvider
10
25
  from .mlx import MLXModelId, MLXProvider
11
26
  from .model_id import ModelId
12
27
  from .ollama import OllamaProvider
@@ -16,7 +31,11 @@ from .openai import (
16
31
  )
17
32
  from .openai.completions import BaseOpenAICompletionsProvider
18
33
  from .provider_id import KNOWN_PROVIDER_IDS, ProviderId
19
- from .provider_registry import get_provider_for_model, register_provider
34
+ from .provider_registry import (
35
+ get_provider_for_model,
36
+ register_provider,
37
+ reset_provider_registry,
38
+ )
20
39
  from .together import TogetherProvider
21
40
 
22
41
  __all__ = [
@@ -29,6 +48,7 @@ __all__ = [
29
48
  "GoogleProvider",
30
49
  "MLXModelId",
31
50
  "MLXProvider",
51
+ "MirascopeProvider",
32
52
  "ModelId",
33
53
  "OllamaProvider",
34
54
  "OpenAIModelId",
@@ -36,9 +56,10 @@ __all__ = [
36
56
  "Params",
37
57
  "Provider",
38
58
  "ProviderId",
59
+ "ThinkingConfig",
60
+ "ThinkingLevel",
39
61
  "TogetherProvider",
40
62
  "get_provider_for_model",
41
- "load",
42
- "load_provider",
43
63
  "register_provider",
64
+ "reset_provider_registry",
44
65
  ]
@@ -1,26 +1,8 @@
1
1
  """Anthropic client implementation."""
2
2
 
3
- from typing import TYPE_CHECKING
4
-
5
- if TYPE_CHECKING:
6
- from .beta_provider import AnthropicBetaProvider
7
- from .model_id import AnthropicModelId
8
- from .provider import AnthropicProvider
9
- else:
10
- try:
11
- from .beta_provider import AnthropicBetaProvider
12
- from .model_id import AnthropicModelId
13
- from .provider import AnthropicProvider
14
- except ImportError: # pragma: no cover
15
- from .._missing_import_stubs import (
16
- create_provider_stub,
17
- )
18
-
19
- AnthropicBetaProvider = create_provider_stub(
20
- "anthropic", "AnthropicBetaProvider"
21
- )
22
- AnthropicProvider = create_provider_stub("anthropic", "AnthropicProvider")
23
- AnthropicModelId = str
3
+ from .beta_provider import AnthropicBetaProvider
4
+ from .model_id import AnthropicModelId
5
+ from .provider import AnthropicProvider
24
6
 
25
7
  __all__ = [
26
8
  "AnthropicBetaProvider",
@@ -9,8 +9,10 @@ from .encode import (
9
9
  encode_request,
10
10
  process_params,
11
11
  )
12
+ from .errors import ANTHROPIC_ERROR_MAP
12
13
 
13
14
  __all__ = [
15
+ "ANTHROPIC_ERROR_MAP",
14
16
  "DEFAULT_FORMAT_MODE",
15
17
  "DEFAULT_MAX_TOKENS",
16
18
  "AnthropicImageMimeType",
@@ -174,7 +174,9 @@ class _BetaChunkProcessor:
174
174
  f"Received input_json_delta for {self.current_block_param['type']} block"
175
175
  )
176
176
  self.accumulated_tool_json += delta.partial_json
177
- yield ToolCallChunk(delta=delta.partial_json)
177
+ yield ToolCallChunk(
178
+ id=self.current_block_param["id"], delta=delta.partial_json
179
+ )
178
180
  elif delta.type == "thinking_delta":
179
181
  if self.current_block_param["type"] != "thinking": # pragma: no cover
180
182
  raise RuntimeError(
@@ -211,7 +213,7 @@ class _BetaChunkProcessor:
211
213
  if self.accumulated_tool_json
212
214
  else {}
213
215
  )
214
- yield ToolCallEndChunk()
216
+ yield ToolCallEndChunk(id=self.current_block_param["id"])
215
217
  elif block_type == "thinking":
216
218
  yield ThoughtEndChunk()
217
219
  else: