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.
- mirascope/__init__.py +10 -1
- mirascope/_stubs.py +363 -0
- mirascope/api/__init__.py +8 -0
- mirascope/api/_generated/__init__.py +119 -1
- mirascope/api/_generated/annotations/__init__.py +33 -0
- mirascope/api/_generated/annotations/client.py +474 -0
- mirascope/api/_generated/annotations/raw_client.py +1095 -0
- mirascope/api/_generated/annotations/types/__init__.py +31 -0
- mirascope/api/_generated/annotations/types/annotations_create_request_label.py +5 -0
- mirascope/api/_generated/annotations/types/annotations_create_response.py +35 -0
- mirascope/api/_generated/annotations/types/annotations_create_response_label.py +5 -0
- mirascope/api/_generated/annotations/types/annotations_get_response.py +35 -0
- mirascope/api/_generated/annotations/types/annotations_get_response_label.py +5 -0
- mirascope/api/_generated/annotations/types/annotations_list_request_label.py +5 -0
- mirascope/api/_generated/annotations/types/annotations_list_response.py +21 -0
- mirascope/api/_generated/annotations/types/annotations_list_response_annotations_item.py +35 -0
- mirascope/api/_generated/annotations/types/annotations_list_response_annotations_item_label.py +5 -0
- mirascope/api/_generated/annotations/types/annotations_update_request_label.py +5 -0
- mirascope/api/_generated/annotations/types/annotations_update_response.py +35 -0
- mirascope/api/_generated/annotations/types/annotations_update_response_label.py +5 -0
- mirascope/api/_generated/api_keys/__init__.py +7 -0
- mirascope/api/_generated/api_keys/client.py +429 -0
- mirascope/api/_generated/api_keys/raw_client.py +788 -0
- mirascope/api/_generated/api_keys/types/__init__.py +9 -0
- mirascope/api/_generated/api_keys/types/api_keys_create_response.py +28 -0
- mirascope/api/_generated/api_keys/types/api_keys_get_response.py +27 -0
- mirascope/api/_generated/api_keys/types/api_keys_list_response_item.py +27 -0
- mirascope/api/_generated/client.py +12 -0
- mirascope/api/_generated/core/client_wrapper.py +2 -14
- mirascope/api/_generated/core/datetime_utils.py +1 -3
- mirascope/api/_generated/core/file.py +2 -5
- mirascope/api/_generated/core/http_client.py +36 -112
- mirascope/api/_generated/core/jsonable_encoder.py +1 -3
- mirascope/api/_generated/core/pydantic_utilities.py +19 -74
- mirascope/api/_generated/core/query_encoder.py +1 -3
- mirascope/api/_generated/core/serialization.py +4 -10
- mirascope/api/_generated/docs/client.py +2 -6
- mirascope/api/_generated/docs/raw_client.py +4 -20
- mirascope/api/_generated/environments/__init__.py +17 -0
- mirascope/api/_generated/environments/client.py +500 -0
- mirascope/api/_generated/environments/raw_client.py +999 -0
- mirascope/api/_generated/environments/types/__init__.py +15 -0
- mirascope/api/_generated/environments/types/environments_create_response.py +24 -0
- mirascope/api/_generated/environments/types/environments_get_response.py +24 -0
- mirascope/api/_generated/environments/types/environments_list_response_item.py +24 -0
- mirascope/api/_generated/environments/types/environments_update_response.py +24 -0
- mirascope/api/_generated/errors/__init__.py +2 -0
- mirascope/api/_generated/errors/bad_request_error.py +1 -5
- mirascope/api/_generated/errors/conflict_error.py +1 -5
- mirascope/api/_generated/errors/forbidden_error.py +1 -5
- mirascope/api/_generated/errors/internal_server_error.py +1 -6
- mirascope/api/_generated/errors/not_found_error.py +1 -5
- mirascope/api/_generated/errors/unauthorized_error.py +11 -0
- mirascope/api/_generated/functions/__init__.py +29 -0
- mirascope/api/_generated/functions/client.py +433 -0
- mirascope/api/_generated/functions/raw_client.py +1049 -0
- mirascope/api/_generated/functions/types/__init__.py +29 -0
- mirascope/api/_generated/functions/types/functions_create_request_dependencies_value.py +20 -0
- mirascope/api/_generated/functions/types/functions_create_response.py +37 -0
- mirascope/api/_generated/functions/types/functions_create_response_dependencies_value.py +20 -0
- mirascope/api/_generated/functions/types/functions_find_by_hash_response.py +39 -0
- mirascope/api/_generated/functions/types/functions_find_by_hash_response_dependencies_value.py +20 -0
- mirascope/api/_generated/functions/types/functions_get_response.py +37 -0
- mirascope/api/_generated/functions/types/functions_get_response_dependencies_value.py +20 -0
- mirascope/api/_generated/functions/types/functions_list_response.py +21 -0
- mirascope/api/_generated/functions/types/functions_list_response_functions_item.py +41 -0
- mirascope/api/_generated/functions/types/functions_list_response_functions_item_dependencies_value.py +20 -0
- mirascope/api/_generated/health/client.py +2 -6
- mirascope/api/_generated/health/raw_client.py +5 -23
- mirascope/api/_generated/health/types/health_check_response.py +1 -3
- mirascope/api/_generated/organizations/__init__.py +2 -0
- mirascope/api/_generated/organizations/client.py +94 -27
- mirascope/api/_generated/organizations/raw_client.py +246 -128
- mirascope/api/_generated/organizations/types/__init__.py +2 -0
- mirascope/api/_generated/organizations/types/organizations_create_response.py +5 -3
- mirascope/api/_generated/organizations/types/organizations_create_response_role.py +1 -3
- mirascope/api/_generated/organizations/types/organizations_credits_response.py +19 -0
- mirascope/api/_generated/organizations/types/organizations_get_response.py +5 -3
- mirascope/api/_generated/organizations/types/organizations_get_response_role.py +1 -3
- mirascope/api/_generated/organizations/types/organizations_list_response_item.py +5 -3
- mirascope/api/_generated/organizations/types/organizations_list_response_item_role.py +1 -3
- mirascope/api/_generated/organizations/types/organizations_update_response.py +5 -3
- mirascope/api/_generated/organizations/types/organizations_update_response_role.py +1 -3
- mirascope/api/_generated/projects/__init__.py +2 -12
- mirascope/api/_generated/projects/client.py +38 -68
- mirascope/api/_generated/projects/raw_client.py +92 -163
- mirascope/api/_generated/projects/types/__init__.py +1 -6
- mirascope/api/_generated/projects/types/projects_create_response.py +4 -9
- mirascope/api/_generated/projects/types/projects_get_response.py +4 -9
- mirascope/api/_generated/projects/types/projects_list_response_item.py +4 -9
- mirascope/api/_generated/projects/types/projects_update_response.py +4 -9
- mirascope/api/_generated/reference.md +1862 -70
- mirascope/api/_generated/traces/__init__.py +22 -0
- mirascope/api/_generated/traces/client.py +398 -0
- mirascope/api/_generated/traces/raw_client.py +902 -18
- mirascope/api/_generated/traces/types/__init__.py +32 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item.py +4 -11
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource.py +2 -6
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item.py +1 -3
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value.py +8 -24
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_array_value.py +2 -6
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_kvlist_value.py +3 -9
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_kvlist_value_values_item.py +2 -6
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item.py +3 -9
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope.py +4 -8
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item.py +2 -6
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value.py +8 -24
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_array_value.py +2 -6
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_kvlist_value.py +3 -9
- 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
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item.py +6 -18
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item.py +3 -9
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value.py +8 -24
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_array_value.py +2 -6
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_kvlist_value.py +2 -6
- 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
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_status.py +2 -6
- mirascope/api/_generated/traces/types/traces_create_response.py +2 -5
- mirascope/api/_generated/traces/types/traces_create_response_partial_success.py +3 -9
- mirascope/api/_generated/traces/types/traces_get_analytics_summary_response.py +54 -0
- mirascope/api/_generated/traces/types/traces_get_analytics_summary_response_top_functions_item.py +24 -0
- mirascope/api/_generated/traces/types/traces_get_analytics_summary_response_top_models_item.py +22 -0
- mirascope/api/_generated/traces/types/traces_get_trace_detail_response.py +33 -0
- mirascope/api/_generated/traces/types/traces_get_trace_detail_response_spans_item.py +90 -0
- mirascope/api/_generated/traces/types/traces_search_request_attribute_filters_item.py +26 -0
- mirascope/api/_generated/traces/types/traces_search_request_attribute_filters_item_operator.py +7 -0
- mirascope/api/_generated/traces/types/traces_search_request_sort_by.py +7 -0
- mirascope/api/_generated/traces/types/traces_search_request_sort_order.py +5 -0
- mirascope/api/_generated/traces/types/traces_search_response.py +26 -0
- mirascope/api/_generated/traces/types/traces_search_response_spans_item.py +41 -0
- mirascope/api/_generated/types/__init__.py +18 -0
- mirascope/api/_generated/types/already_exists_error.py +1 -3
- mirascope/api/_generated/types/click_house_error.py +22 -0
- mirascope/api/_generated/types/database_error.py +1 -3
- mirascope/api/_generated/types/http_api_decode_error.py +1 -3
- mirascope/api/_generated/types/internal_server_error_body.py +49 -0
- mirascope/api/_generated/types/issue.py +1 -3
- mirascope/api/_generated/types/issue_tag.py +1 -8
- mirascope/api/_generated/types/not_found_error_body.py +1 -3
- mirascope/api/_generated/types/number_from_string.py +3 -0
- mirascope/api/_generated/types/permission_denied_error.py +1 -3
- mirascope/api/_generated/types/permission_denied_error_tag.py +1 -3
- mirascope/api/_generated/types/property_key_key.py +1 -3
- mirascope/api/_generated/types/stripe_error.py +20 -0
- mirascope/api/_generated/types/unauthorized_error_body.py +21 -0
- mirascope/api/_generated/types/unauthorized_error_tag.py +5 -0
- mirascope/llm/__init__.py +6 -2
- mirascope/llm/content/tool_call.py +6 -0
- mirascope/llm/exceptions.py +28 -0
- mirascope/llm/formatting/__init__.py +2 -2
- mirascope/llm/formatting/format.py +120 -8
- mirascope/llm/formatting/types.py +1 -56
- mirascope/llm/mcp/__init__.py +2 -2
- mirascope/llm/mcp/mcp_client.py +130 -0
- mirascope/llm/providers/__init__.py +26 -5
- mirascope/llm/providers/anthropic/__init__.py +3 -21
- mirascope/llm/providers/anthropic/_utils/__init__.py +2 -0
- mirascope/llm/providers/anthropic/_utils/beta_decode.py +4 -2
- mirascope/llm/providers/anthropic/_utils/beta_encode.py +13 -12
- mirascope/llm/providers/anthropic/_utils/decode.py +4 -2
- mirascope/llm/providers/anthropic/_utils/encode.py +57 -14
- mirascope/llm/providers/anthropic/_utils/errors.py +46 -0
- mirascope/llm/providers/anthropic/beta_provider.py +6 -0
- mirascope/llm/providers/anthropic/provider.py +5 -0
- mirascope/llm/providers/base/__init__.py +5 -2
- mirascope/llm/providers/base/_utils.py +2 -7
- mirascope/llm/providers/base/base_provider.py +173 -58
- mirascope/llm/providers/base/params.py +63 -34
- mirascope/llm/providers/google/__init__.py +2 -17
- mirascope/llm/providers/google/_utils/__init__.py +2 -0
- mirascope/llm/providers/google/_utils/decode.py +17 -8
- mirascope/llm/providers/google/_utils/encode.py +105 -16
- mirascope/llm/providers/google/_utils/errors.py +49 -0
- mirascope/llm/providers/google/model_info.py +1 -0
- mirascope/llm/providers/google/provider.py +9 -5
- mirascope/llm/providers/mirascope/__init__.py +5 -0
- mirascope/llm/providers/mirascope/_utils.py +77 -0
- mirascope/llm/providers/mirascope/provider.py +318 -0
- mirascope/llm/providers/mlx/__init__.py +2 -17
- mirascope/llm/providers/mlx/_utils.py +9 -2
- mirascope/llm/providers/mlx/provider.py +8 -0
- mirascope/llm/providers/ollama/__init__.py +1 -13
- mirascope/llm/providers/openai/__init__.py +10 -1
- mirascope/llm/providers/openai/_utils/__init__.py +5 -0
- mirascope/llm/providers/openai/_utils/errors.py +46 -0
- mirascope/llm/providers/openai/completions/__init__.py +2 -20
- mirascope/llm/providers/openai/completions/_utils/decode.py +14 -3
- mirascope/llm/providers/openai/completions/_utils/encode.py +15 -12
- mirascope/llm/providers/openai/completions/base_provider.py +6 -6
- mirascope/llm/providers/openai/provider.py +14 -1
- mirascope/llm/providers/openai/responses/__init__.py +1 -17
- mirascope/llm/providers/openai/responses/_utils/decode.py +2 -2
- mirascope/llm/providers/openai/responses/_utils/encode.py +43 -15
- mirascope/llm/providers/openai/responses/provider.py +13 -7
- mirascope/llm/providers/provider_id.py +1 -0
- mirascope/llm/providers/provider_registry.py +59 -3
- mirascope/llm/providers/together/__init__.py +1 -13
- mirascope/llm/responses/base_stream_response.py +24 -20
- mirascope/llm/tools/decorator.py +8 -4
- mirascope/llm/tools/tool_schema.py +33 -6
- mirascope/llm/tools/tools.py +84 -16
- mirascope/ops/__init__.py +60 -109
- mirascope/ops/_internal/closure.py +62 -11
- mirascope/ops/_internal/instrumentation/llm/llm.py +1 -2
- mirascope/ops/_internal/traced_functions.py +23 -4
- mirascope/ops/_internal/versioned_functions.py +54 -43
- {mirascope-2.0.0a4.dist-info → mirascope-2.0.0a6.dist-info}/METADATA +7 -7
- mirascope-2.0.0a6.dist-info/RECORD +316 -0
- mirascope/llm/formatting/_utils.py +0 -78
- mirascope/llm/mcp/client.py +0 -118
- mirascope/llm/providers/_missing_import_stubs.py +0 -49
- mirascope/llm/providers/load_provider.py +0 -54
- mirascope-2.0.0a4.dist-info/RECORD +0 -247
- {mirascope-2.0.0a4.dist-info → mirascope-2.0.0a6.dist-info}/WHEEL +0 -0
- {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
|
-
|
|
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."""
|
mirascope/llm/exceptions.py
CHANGED
|
@@ -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
|
|
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 .
|
|
7
|
-
|
|
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
|
|
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()`."""
|
mirascope/llm/mcp/__init__.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""MCP compatibility module."""
|
|
2
2
|
|
|
3
|
-
from .
|
|
3
|
+
from .mcp_client import MCPClient, sse_client, stdio_client, streamable_http_client
|
|
4
4
|
|
|
5
|
-
__all__ = ["MCPClient", "sse_client", "stdio_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 .
|
|
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
|
|
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
|
|
4
|
-
|
|
5
|
-
|
|
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",
|
|
@@ -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(
|
|
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:
|