mirascope 2.0.0a6__py3-none-any.whl → 2.0.2__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/_utils.py +34 -0
- mirascope/api/_generated/__init__.py +186 -5
- mirascope/api/_generated/annotations/client.py +38 -6
- mirascope/api/_generated/annotations/raw_client.py +366 -47
- mirascope/api/_generated/annotations/types/annotations_create_response.py +19 -6
- mirascope/api/_generated/annotations/types/annotations_get_response.py +19 -6
- mirascope/api/_generated/annotations/types/annotations_list_response_annotations_item.py +22 -7
- mirascope/api/_generated/annotations/types/annotations_update_response.py +19 -6
- mirascope/api/_generated/api_keys/__init__.py +12 -2
- mirascope/api/_generated/api_keys/client.py +107 -6
- mirascope/api/_generated/api_keys/raw_client.py +486 -38
- mirascope/api/_generated/api_keys/types/__init__.py +7 -1
- mirascope/api/_generated/api_keys/types/api_keys_list_all_for_org_response_item.py +40 -0
- mirascope/api/_generated/client.py +36 -0
- mirascope/api/_generated/docs/raw_client.py +71 -9
- mirascope/api/_generated/environment.py +3 -3
- mirascope/api/_generated/environments/__init__.py +6 -0
- mirascope/api/_generated/environments/client.py +158 -9
- mirascope/api/_generated/environments/raw_client.py +620 -52
- mirascope/api/_generated/environments/types/__init__.py +10 -0
- mirascope/api/_generated/environments/types/environments_get_analytics_response.py +60 -0
- mirascope/api/_generated/environments/types/environments_get_analytics_response_top_functions_item.py +24 -0
- mirascope/api/_generated/{organizations/types/organizations_credits_response.py → environments/types/environments_get_analytics_response_top_models_item.py} +6 -3
- mirascope/api/_generated/errors/__init__.py +6 -0
- mirascope/api/_generated/errors/bad_request_error.py +5 -2
- mirascope/api/_generated/errors/conflict_error.py +5 -2
- mirascope/api/_generated/errors/payment_required_error.py +15 -0
- mirascope/api/_generated/errors/service_unavailable_error.py +14 -0
- mirascope/api/_generated/errors/too_many_requests_error.py +15 -0
- mirascope/api/_generated/functions/__init__.py +10 -0
- mirascope/api/_generated/functions/client.py +222 -8
- mirascope/api/_generated/functions/raw_client.py +975 -134
- mirascope/api/_generated/functions/types/__init__.py +28 -4
- mirascope/api/_generated/functions/types/functions_get_by_env_response.py +53 -0
- mirascope/api/_generated/functions/types/functions_get_by_env_response_dependencies_value.py +22 -0
- mirascope/api/_generated/functions/types/functions_list_by_env_response.py +25 -0
- mirascope/api/_generated/functions/types/functions_list_by_env_response_functions_item.py +56 -0
- mirascope/api/_generated/functions/types/functions_list_by_env_response_functions_item_dependencies_value.py +22 -0
- mirascope/api/_generated/health/raw_client.py +74 -10
- mirascope/api/_generated/organization_invitations/__init__.py +33 -0
- mirascope/api/_generated/organization_invitations/client.py +546 -0
- mirascope/api/_generated/organization_invitations/raw_client.py +1519 -0
- mirascope/api/_generated/organization_invitations/types/__init__.py +53 -0
- mirascope/api/_generated/organization_invitations/types/organization_invitations_accept_response.py +34 -0
- mirascope/api/_generated/organization_invitations/types/organization_invitations_accept_response_role.py +7 -0
- mirascope/api/_generated/organization_invitations/types/organization_invitations_create_request_role.py +7 -0
- mirascope/api/_generated/organization_invitations/types/organization_invitations_create_response.py +48 -0
- mirascope/api/_generated/organization_invitations/types/organization_invitations_create_response_role.py +7 -0
- mirascope/api/_generated/organization_invitations/types/organization_invitations_create_response_status.py +7 -0
- mirascope/api/_generated/organization_invitations/types/organization_invitations_get_response.py +48 -0
- mirascope/api/_generated/organization_invitations/types/organization_invitations_get_response_role.py +7 -0
- mirascope/api/_generated/organization_invitations/types/organization_invitations_get_response_status.py +7 -0
- mirascope/api/_generated/organization_invitations/types/organization_invitations_list_response_item.py +48 -0
- mirascope/api/_generated/organization_invitations/types/organization_invitations_list_response_item_role.py +7 -0
- mirascope/api/_generated/organization_invitations/types/organization_invitations_list_response_item_status.py +7 -0
- mirascope/api/_generated/organization_memberships/__init__.py +19 -0
- mirascope/api/_generated/organization_memberships/client.py +302 -0
- mirascope/api/_generated/organization_memberships/raw_client.py +736 -0
- mirascope/api/_generated/organization_memberships/types/__init__.py +27 -0
- mirascope/api/_generated/organization_memberships/types/organization_memberships_list_response_item.py +33 -0
- mirascope/api/_generated/organization_memberships/types/organization_memberships_list_response_item_role.py +7 -0
- mirascope/api/_generated/organization_memberships/types/organization_memberships_update_request_role.py +7 -0
- mirascope/api/_generated/organization_memberships/types/organization_memberships_update_response.py +31 -0
- mirascope/api/_generated/organization_memberships/types/organization_memberships_update_response_role.py +7 -0
- mirascope/api/_generated/organizations/__init__.py +26 -2
- mirascope/api/_generated/organizations/client.py +442 -20
- mirascope/api/_generated/organizations/raw_client.py +1763 -164
- mirascope/api/_generated/organizations/types/__init__.py +48 -2
- mirascope/api/_generated/organizations/types/organizations_create_payment_intent_response.py +24 -0
- mirascope/api/_generated/organizations/types/organizations_preview_subscription_change_request_target_plan.py +7 -0
- mirascope/api/_generated/organizations/types/organizations_preview_subscription_change_response.py +47 -0
- mirascope/api/_generated/organizations/types/organizations_preview_subscription_change_response_validation_errors_item.py +33 -0
- mirascope/api/_generated/organizations/types/organizations_preview_subscription_change_response_validation_errors_item_resource.py +7 -0
- mirascope/api/_generated/organizations/types/organizations_router_balance_response.py +24 -0
- mirascope/api/_generated/organizations/types/organizations_subscription_response.py +53 -0
- mirascope/api/_generated/organizations/types/organizations_subscription_response_current_plan.py +7 -0
- mirascope/api/_generated/organizations/types/organizations_subscription_response_payment_method.py +26 -0
- mirascope/api/_generated/organizations/types/organizations_subscription_response_scheduled_change.py +34 -0
- mirascope/api/_generated/organizations/types/organizations_subscription_response_scheduled_change_target_plan.py +7 -0
- mirascope/api/_generated/organizations/types/organizations_update_subscription_request_target_plan.py +7 -0
- mirascope/api/_generated/organizations/types/organizations_update_subscription_response.py +35 -0
- mirascope/api/_generated/project_memberships/__init__.py +25 -0
- mirascope/api/_generated/project_memberships/client.py +437 -0
- mirascope/api/_generated/project_memberships/raw_client.py +1039 -0
- mirascope/api/_generated/project_memberships/types/__init__.py +29 -0
- mirascope/api/_generated/project_memberships/types/project_memberships_create_request_role.py +7 -0
- mirascope/api/_generated/project_memberships/types/project_memberships_create_response.py +35 -0
- mirascope/api/_generated/project_memberships/types/project_memberships_create_response_role.py +7 -0
- mirascope/api/_generated/project_memberships/types/project_memberships_list_response_item.py +33 -0
- mirascope/api/_generated/project_memberships/types/project_memberships_list_response_item_role.py +7 -0
- mirascope/api/_generated/project_memberships/types/project_memberships_update_request_role.py +7 -0
- mirascope/api/_generated/project_memberships/types/project_memberships_update_response.py +35 -0
- mirascope/api/_generated/project_memberships/types/project_memberships_update_response_role.py +7 -0
- mirascope/api/_generated/projects/raw_client.py +415 -58
- mirascope/api/_generated/reference.md +2767 -397
- mirascope/api/_generated/tags/__init__.py +19 -0
- mirascope/api/_generated/tags/client.py +504 -0
- mirascope/api/_generated/tags/raw_client.py +1288 -0
- mirascope/api/_generated/tags/types/__init__.py +17 -0
- mirascope/api/_generated/tags/types/tags_create_response.py +41 -0
- mirascope/api/_generated/tags/types/tags_get_response.py +41 -0
- mirascope/api/_generated/tags/types/tags_list_response.py +23 -0
- mirascope/api/_generated/tags/types/tags_list_response_tags_item.py +41 -0
- mirascope/api/_generated/tags/types/tags_update_response.py +41 -0
- mirascope/api/_generated/token_cost/__init__.py +7 -0
- mirascope/api/_generated/token_cost/client.py +160 -0
- mirascope/api/_generated/token_cost/raw_client.py +264 -0
- mirascope/api/_generated/token_cost/types/__init__.py +8 -0
- mirascope/api/_generated/token_cost/types/token_cost_calculate_request_usage.py +54 -0
- mirascope/api/_generated/token_cost/types/token_cost_calculate_response.py +52 -0
- mirascope/api/_generated/traces/__init__.py +20 -0
- mirascope/api/_generated/traces/client.py +543 -0
- mirascope/api/_generated/traces/raw_client.py +1366 -96
- mirascope/api/_generated/traces/types/__init__.py +28 -0
- mirascope/api/_generated/traces/types/traces_get_analytics_summary_response.py +6 -0
- mirascope/api/_generated/traces/types/traces_get_trace_detail_by_env_response.py +33 -0
- mirascope/api/_generated/traces/types/traces_get_trace_detail_by_env_response_spans_item.py +88 -0
- mirascope/api/_generated/traces/types/traces_get_trace_detail_response_spans_item.py +0 -2
- mirascope/api/_generated/traces/types/traces_list_by_function_hash_response.py +25 -0
- mirascope/api/_generated/traces/types/traces_list_by_function_hash_response_traces_item.py +44 -0
- mirascope/api/_generated/traces/types/traces_search_by_env_request_attribute_filters_item.py +26 -0
- mirascope/api/_generated/traces/types/traces_search_by_env_request_attribute_filters_item_operator.py +7 -0
- mirascope/api/_generated/traces/types/traces_search_by_env_request_sort_by.py +7 -0
- mirascope/api/_generated/traces/types/traces_search_by_env_request_sort_order.py +7 -0
- mirascope/api/_generated/traces/types/traces_search_by_env_response.py +26 -0
- mirascope/api/_generated/traces/types/traces_search_by_env_response_spans_item.py +50 -0
- mirascope/api/_generated/traces/types/traces_search_response_spans_item.py +10 -1
- mirascope/api/_generated/types/__init__.py +32 -2
- mirascope/api/_generated/types/bad_request_error_body.py +50 -0
- mirascope/api/_generated/types/date.py +3 -0
- mirascope/api/_generated/types/immutable_resource_error.py +22 -0
- mirascope/api/_generated/types/internal_server_error_body.py +3 -3
- mirascope/api/_generated/types/plan_limit_exceeded_error.py +32 -0
- mirascope/api/_generated/types/plan_limit_exceeded_error_tag.py +7 -0
- mirascope/api/_generated/types/pricing_unavailable_error.py +23 -0
- mirascope/api/_generated/types/rate_limit_error.py +31 -0
- mirascope/api/_generated/types/rate_limit_error_tag.py +5 -0
- mirascope/api/_generated/types/service_unavailable_error_body.py +24 -0
- mirascope/api/_generated/types/service_unavailable_error_tag.py +7 -0
- mirascope/api/_generated/types/subscription_past_due_error.py +31 -0
- mirascope/api/_generated/types/subscription_past_due_error_tag.py +7 -0
- mirascope/api/settings.py +19 -1
- mirascope/llm/__init__.py +53 -10
- mirascope/llm/calls/__init__.py +2 -1
- mirascope/llm/calls/calls.py +29 -20
- mirascope/llm/calls/decorator.py +21 -7
- mirascope/llm/content/tool_output.py +22 -5
- mirascope/llm/exceptions.py +284 -71
- mirascope/llm/formatting/__init__.py +17 -0
- mirascope/llm/formatting/format.py +112 -35
- mirascope/llm/formatting/output_parser.py +178 -0
- mirascope/llm/formatting/partial.py +80 -7
- mirascope/llm/formatting/primitives.py +192 -0
- mirascope/llm/formatting/types.py +20 -8
- mirascope/llm/messages/__init__.py +3 -0
- mirascope/llm/messages/_utils.py +34 -0
- mirascope/llm/models/__init__.py +5 -0
- mirascope/llm/models/models.py +137 -69
- mirascope/llm/{providers/base → models}/params.py +7 -57
- mirascope/llm/models/thinking_config.py +61 -0
- mirascope/llm/prompts/_utils.py +0 -32
- mirascope/llm/prompts/decorator.py +16 -5
- mirascope/llm/prompts/prompts.py +160 -92
- mirascope/llm/providers/__init__.py +1 -4
- mirascope/llm/providers/anthropic/_utils/__init__.py +2 -0
- mirascope/llm/providers/anthropic/_utils/beta_decode.py +18 -9
- mirascope/llm/providers/anthropic/_utils/beta_encode.py +62 -13
- mirascope/llm/providers/anthropic/_utils/decode.py +18 -9
- mirascope/llm/providers/anthropic/_utils/encode.py +26 -7
- mirascope/llm/providers/anthropic/_utils/errors.py +2 -2
- mirascope/llm/providers/anthropic/beta_provider.py +64 -18
- mirascope/llm/providers/anthropic/provider.py +91 -33
- mirascope/llm/providers/base/__init__.py +0 -4
- mirascope/llm/providers/base/_utils.py +55 -6
- mirascope/llm/providers/base/base_provider.py +116 -37
- mirascope/llm/providers/google/_utils/__init__.py +2 -0
- mirascope/llm/providers/google/_utils/decode.py +20 -7
- mirascope/llm/providers/google/_utils/encode.py +26 -7
- mirascope/llm/providers/google/_utils/errors.py +3 -2
- mirascope/llm/providers/google/provider.py +64 -18
- mirascope/llm/providers/mirascope/_utils.py +13 -17
- mirascope/llm/providers/mirascope/provider.py +49 -18
- mirascope/llm/providers/mlx/_utils.py +7 -2
- mirascope/llm/providers/mlx/encoding/base.py +5 -2
- mirascope/llm/providers/mlx/encoding/transformers.py +5 -2
- mirascope/llm/providers/mlx/mlx.py +23 -6
- mirascope/llm/providers/mlx/provider.py +42 -13
- mirascope/llm/providers/openai/_utils/errors.py +2 -2
- mirascope/llm/providers/openai/completions/_utils/encode.py +20 -16
- mirascope/llm/providers/openai/completions/base_provider.py +40 -11
- mirascope/llm/providers/openai/provider.py +40 -10
- mirascope/llm/providers/openai/responses/_utils/__init__.py +2 -0
- mirascope/llm/providers/openai/responses/_utils/decode.py +19 -6
- mirascope/llm/providers/openai/responses/_utils/encode.py +22 -10
- mirascope/llm/providers/openai/responses/provider.py +56 -18
- mirascope/llm/providers/provider_registry.py +93 -19
- mirascope/llm/responses/__init__.py +6 -1
- mirascope/llm/responses/_utils.py +102 -12
- mirascope/llm/responses/base_response.py +5 -2
- mirascope/llm/responses/base_stream_response.py +115 -25
- mirascope/llm/responses/response.py +2 -1
- mirascope/llm/responses/root_response.py +89 -17
- mirascope/llm/responses/stream_response.py +6 -9
- mirascope/llm/tools/decorator.py +9 -4
- mirascope/llm/tools/tool_schema.py +17 -6
- mirascope/llm/tools/toolkit.py +35 -27
- mirascope/llm/tools/tools.py +45 -20
- mirascope/ops/__init__.py +4 -0
- mirascope/ops/_internal/closure.py +4 -1
- mirascope/ops/_internal/configuration.py +82 -31
- mirascope/ops/_internal/exporters/exporters.py +55 -35
- mirascope/ops/_internal/exporters/utils.py +37 -0
- mirascope/ops/_internal/instrumentation/llm/common.py +530 -0
- mirascope/ops/_internal/instrumentation/llm/cost.py +190 -0
- mirascope/ops/_internal/instrumentation/llm/encode.py +1 -1
- mirascope/ops/_internal/instrumentation/llm/llm.py +116 -1242
- mirascope/ops/_internal/instrumentation/llm/model.py +1798 -0
- mirascope/ops/_internal/instrumentation/llm/response.py +521 -0
- mirascope/ops/_internal/instrumentation/llm/serialize.py +300 -0
- mirascope/ops/_internal/protocols.py +83 -1
- mirascope/ops/_internal/traced_calls.py +18 -0
- mirascope/ops/_internal/traced_functions.py +125 -10
- mirascope/ops/_internal/tracing.py +78 -1
- mirascope/ops/_internal/utils.py +60 -4
- mirascope/ops/_internal/versioned_functions.py +1 -1
- {mirascope-2.0.0a6.dist-info → mirascope-2.0.2.dist-info}/METADATA +12 -11
- mirascope-2.0.2.dist-info/RECORD +424 -0
- {mirascope-2.0.0a6.dist-info → mirascope-2.0.2.dist-info}/licenses/LICENSE +1 -1
- mirascope-2.0.0a6.dist-info/RECORD +0 -316
- {mirascope-2.0.0a6.dist-info → mirascope-2.0.2.dist-info}/WHEEL +0 -0
mirascope/llm/exceptions.py
CHANGED
|
@@ -1,70 +1,292 @@
|
|
|
1
1
|
"""Mirascope llm exception hierarchy for unified error handling across providers."""
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
from typing import TYPE_CHECKING
|
|
4
5
|
|
|
6
|
+
from pydantic import ValidationError
|
|
7
|
+
|
|
5
8
|
if TYPE_CHECKING:
|
|
6
|
-
from .formatting import FormattingMode
|
|
7
9
|
from .providers import ModelId, ProviderId
|
|
8
10
|
|
|
9
11
|
|
|
10
|
-
class
|
|
12
|
+
class Error(Exception):
|
|
11
13
|
"""Base exception for all Mirascope LLM errors."""
|
|
12
14
|
|
|
13
|
-
original_exception: Exception | None
|
|
14
|
-
provider: "ProviderId | None"
|
|
15
15
|
|
|
16
|
+
class ProviderError(Error):
|
|
17
|
+
"""Base class for errors that originate from a provider SDK.
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
This wraps exceptions from provider libraries (OpenAI, Anthropic, etc.)
|
|
20
|
+
and provides a unified interface for error handling.
|
|
21
|
+
"""
|
|
19
22
|
|
|
20
|
-
|
|
23
|
+
provider: "ProviderId"
|
|
24
|
+
"""The provider that raised this error."""
|
|
25
|
+
|
|
26
|
+
original_exception: Exception | None
|
|
27
|
+
"""The original exception from the provider SDK, if available."""
|
|
21
28
|
|
|
22
|
-
def __init__(
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
message: str,
|
|
32
|
+
provider: "ProviderId",
|
|
33
|
+
original_exception: Exception | None = None,
|
|
34
|
+
) -> None:
|
|
23
35
|
super().__init__(message)
|
|
24
|
-
self.
|
|
36
|
+
self.provider = provider
|
|
37
|
+
self.original_exception = original_exception
|
|
38
|
+
if original_exception is not None:
|
|
39
|
+
self.__cause__ = original_exception
|
|
25
40
|
|
|
26
41
|
|
|
27
|
-
class
|
|
28
|
-
"""
|
|
42
|
+
class APIError(ProviderError):
|
|
43
|
+
"""Base class for HTTP-level API errors."""
|
|
44
|
+
|
|
45
|
+
status_code: int | None
|
|
46
|
+
"""The HTTP status code, if available."""
|
|
47
|
+
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
message: str,
|
|
51
|
+
provider: "ProviderId",
|
|
52
|
+
status_code: int | None = None,
|
|
53
|
+
original_exception: Exception | None = None,
|
|
54
|
+
) -> None:
|
|
55
|
+
super().__init__(message, provider, original_exception)
|
|
56
|
+
self.status_code = status_code
|
|
29
57
|
|
|
30
58
|
|
|
31
59
|
class AuthenticationError(APIError):
|
|
32
60
|
"""Raised for authentication failures (401, invalid API keys)."""
|
|
33
61
|
|
|
34
|
-
def __init__(
|
|
35
|
-
|
|
62
|
+
def __init__(
|
|
63
|
+
self,
|
|
64
|
+
message: str,
|
|
65
|
+
provider: "ProviderId",
|
|
66
|
+
status_code: int | None = None,
|
|
67
|
+
original_exception: Exception | None = None,
|
|
68
|
+
) -> None:
|
|
69
|
+
super().__init__(
|
|
70
|
+
message,
|
|
71
|
+
provider,
|
|
72
|
+
status_code=status_code or 401,
|
|
73
|
+
original_exception=original_exception,
|
|
74
|
+
)
|
|
36
75
|
|
|
37
76
|
|
|
38
77
|
class PermissionError(APIError):
|
|
39
78
|
"""Raised for permission/authorization failures (403)."""
|
|
40
79
|
|
|
41
|
-
def __init__(
|
|
42
|
-
|
|
80
|
+
def __init__(
|
|
81
|
+
self,
|
|
82
|
+
message: str,
|
|
83
|
+
provider: "ProviderId",
|
|
84
|
+
status_code: int | None = None,
|
|
85
|
+
original_exception: Exception | None = None,
|
|
86
|
+
) -> None:
|
|
87
|
+
super().__init__(
|
|
88
|
+
message,
|
|
89
|
+
provider,
|
|
90
|
+
status_code=status_code or 403,
|
|
91
|
+
original_exception=original_exception,
|
|
92
|
+
)
|
|
43
93
|
|
|
44
94
|
|
|
45
95
|
class BadRequestError(APIError):
|
|
46
96
|
"""Raised for malformed requests (400, 422)."""
|
|
47
97
|
|
|
48
|
-
def __init__(
|
|
49
|
-
|
|
98
|
+
def __init__(
|
|
99
|
+
self,
|
|
100
|
+
message: str,
|
|
101
|
+
provider: "ProviderId",
|
|
102
|
+
status_code: int | None = None,
|
|
103
|
+
original_exception: Exception | None = None,
|
|
104
|
+
) -> None:
|
|
105
|
+
super().__init__(
|
|
106
|
+
message,
|
|
107
|
+
provider,
|
|
108
|
+
status_code=status_code or 400,
|
|
109
|
+
original_exception=original_exception,
|
|
110
|
+
)
|
|
50
111
|
|
|
51
112
|
|
|
52
113
|
class NotFoundError(APIError):
|
|
53
114
|
"""Raised when requested resource is not found (404)."""
|
|
54
115
|
|
|
55
|
-
def __init__(
|
|
56
|
-
|
|
116
|
+
def __init__(
|
|
117
|
+
self,
|
|
118
|
+
message: str,
|
|
119
|
+
provider: "ProviderId",
|
|
120
|
+
status_code: int | None = None,
|
|
121
|
+
original_exception: Exception | None = None,
|
|
122
|
+
) -> None:
|
|
123
|
+
super().__init__(
|
|
124
|
+
message,
|
|
125
|
+
provider,
|
|
126
|
+
status_code=status_code or 404,
|
|
127
|
+
original_exception=original_exception,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class RateLimitError(APIError):
|
|
132
|
+
"""Raised when rate limits are exceeded (429)."""
|
|
133
|
+
|
|
134
|
+
def __init__(
|
|
135
|
+
self,
|
|
136
|
+
message: str,
|
|
137
|
+
provider: "ProviderId",
|
|
138
|
+
status_code: int | None = None,
|
|
139
|
+
original_exception: Exception | None = None,
|
|
140
|
+
) -> None:
|
|
141
|
+
super().__init__(
|
|
142
|
+
message,
|
|
143
|
+
provider,
|
|
144
|
+
status_code=status_code or 429,
|
|
145
|
+
original_exception=original_exception,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class ServerError(APIError):
|
|
150
|
+
"""Raised for server-side errors (500+)."""
|
|
151
|
+
|
|
152
|
+
def __init__(
|
|
153
|
+
self,
|
|
154
|
+
message: str,
|
|
155
|
+
provider: "ProviderId",
|
|
156
|
+
status_code: int | None = None,
|
|
157
|
+
original_exception: Exception | None = None,
|
|
158
|
+
) -> None:
|
|
159
|
+
super().__init__(
|
|
160
|
+
message,
|
|
161
|
+
provider,
|
|
162
|
+
status_code=status_code or 500,
|
|
163
|
+
original_exception=original_exception,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class ConnectionError(ProviderError):
|
|
168
|
+
"""Raised when unable to connect to the API (network issues, timeouts)."""
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class TimeoutError(ProviderError):
|
|
172
|
+
"""Raised when requests timeout or deadline exceeded."""
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class ResponseValidationError(ProviderError):
|
|
176
|
+
"""Raised when API response fails validation.
|
|
177
|
+
|
|
178
|
+
This wraps the APIResponseValidationErrors that OpenAI and Anthropic both return.
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class ToolError(Error):
|
|
183
|
+
"""Base class for errors that occur during tool execution."""
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class ToolExecutionError(ToolError):
|
|
187
|
+
"""Raised if an uncaught exception is thrown while executing a tool."""
|
|
188
|
+
|
|
189
|
+
tool_exception: Exception
|
|
190
|
+
"""The exception that was thrown while executing the tool."""
|
|
191
|
+
|
|
192
|
+
def __init__(self, tool_exception: Exception | str) -> None:
|
|
193
|
+
if isinstance(tool_exception, str):
|
|
194
|
+
# Support string for snapshot reconstruction
|
|
195
|
+
message = tool_exception
|
|
196
|
+
tool_exception = ValueError(message)
|
|
197
|
+
else:
|
|
198
|
+
message = str(tool_exception)
|
|
199
|
+
super().__init__(message)
|
|
200
|
+
self.tool_exception = tool_exception
|
|
201
|
+
self.__cause__ = tool_exception
|
|
202
|
+
|
|
203
|
+
def __eq__(self, other: object) -> bool:
|
|
204
|
+
if not isinstance(other, ToolExecutionError):
|
|
205
|
+
return False
|
|
206
|
+
# Needed for snapshot tests.
|
|
207
|
+
return str(self) == str(other)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class ToolNotFoundError(ToolError):
|
|
211
|
+
"""Raised if a tool call does not match any registered tool."""
|
|
212
|
+
|
|
213
|
+
tool_name: str
|
|
214
|
+
"""The name of the tool that was not found."""
|
|
215
|
+
|
|
216
|
+
def __init__(self, tool_name: str) -> None:
|
|
217
|
+
super().__init__(f"Tool '{tool_name}' not found in registered tools")
|
|
218
|
+
self.tool_name = tool_name
|
|
219
|
+
|
|
220
|
+
def __repr__(self) -> str:
|
|
221
|
+
return f"ToolNotFoundError({self.tool_name!r})"
|
|
57
222
|
|
|
223
|
+
def __eq__(self, other: object) -> bool:
|
|
224
|
+
if not isinstance(other, ToolNotFoundError):
|
|
225
|
+
return NotImplemented
|
|
226
|
+
return self.tool_name == other.tool_name
|
|
58
227
|
|
|
59
|
-
|
|
60
|
-
|
|
228
|
+
def __hash__(self) -> int:
|
|
229
|
+
return hash((type(self), self.tool_name))
|
|
61
230
|
|
|
62
231
|
|
|
63
|
-
class
|
|
232
|
+
class ParseError(Error):
|
|
233
|
+
"""Raised when response.parse() fails to parse the response content.
|
|
234
|
+
|
|
235
|
+
This wraps errors from JSON extraction, JSON parsing, Pydantic validation,
|
|
236
|
+
or custom OutputParser functions.
|
|
237
|
+
"""
|
|
238
|
+
|
|
239
|
+
original_exception: Exception
|
|
240
|
+
"""The original exception that caused the parse failure."""
|
|
241
|
+
|
|
242
|
+
def __init__(
|
|
243
|
+
self,
|
|
244
|
+
message: str,
|
|
245
|
+
original_exception: Exception,
|
|
246
|
+
) -> None:
|
|
247
|
+
super().__init__(message)
|
|
248
|
+
self.original_exception = original_exception
|
|
249
|
+
self.__cause__ = original_exception
|
|
250
|
+
|
|
251
|
+
def retry_message(self) -> str:
|
|
252
|
+
"""Generate a message suitable for retrying with the LLM.
|
|
253
|
+
|
|
254
|
+
Returns a user-friendly message describing what went wrong,
|
|
255
|
+
suitable for including in a retry prompt.
|
|
256
|
+
"""
|
|
257
|
+
|
|
258
|
+
if isinstance(self.original_exception, ValidationError):
|
|
259
|
+
return (
|
|
260
|
+
f"Your response failed schema validation:\n"
|
|
261
|
+
f"{self.original_exception}\n\n"
|
|
262
|
+
"Please correct these issues and respond again."
|
|
263
|
+
)
|
|
264
|
+
elif isinstance(self.original_exception, json.JSONDecodeError):
|
|
265
|
+
# JSON syntax error
|
|
266
|
+
return (
|
|
267
|
+
"Your response could not be parsed because no valid JSON object "
|
|
268
|
+
"was found. Please ensure your response contains a JSON object "
|
|
269
|
+
"with opening '{' and closing '}' braces."
|
|
270
|
+
)
|
|
271
|
+
else:
|
|
272
|
+
# ValueError from JSON extraction, or OutputParser error
|
|
273
|
+
return (
|
|
274
|
+
f"Your response could not be parsed: {self.original_exception}\n\n"
|
|
275
|
+
"Please ensure your response matches the expected format."
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
def __eq__(self, other: object) -> bool:
|
|
279
|
+
if not isinstance(other, ParseError):
|
|
280
|
+
return False
|
|
281
|
+
return str(self) == str(other)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
class FeatureNotSupportedError(Error):
|
|
64
285
|
"""Raised if a Mirascope feature is unsupported by chosen provider.
|
|
65
286
|
|
|
66
287
|
If compatibility is model-specific, then `model_id` should be specified.
|
|
67
|
-
If the feature is not supported by the provider at all, then it may be `None`.
|
|
288
|
+
If the feature is not supported by the provider at all, then it may be `None`.
|
|
289
|
+
"""
|
|
68
290
|
|
|
69
291
|
provider_id: "ProviderId"
|
|
70
292
|
model_id: "ModelId | None"
|
|
@@ -86,54 +308,7 @@ class FeatureNotSupportedError(MirascopeLLMError):
|
|
|
86
308
|
self.model_id = model_id
|
|
87
309
|
|
|
88
310
|
|
|
89
|
-
class
|
|
90
|
-
"""Raised when trying to use a formatting mode that is not supported by the chosen model."""
|
|
91
|
-
|
|
92
|
-
formatting_mode: "FormattingMode"
|
|
93
|
-
|
|
94
|
-
def __init__(
|
|
95
|
-
self,
|
|
96
|
-
formatting_mode: "FormattingMode",
|
|
97
|
-
provider_id: "ProviderId",
|
|
98
|
-
model_id: "ModelId | None" = None,
|
|
99
|
-
message: str | None = None,
|
|
100
|
-
) -> None:
|
|
101
|
-
if message is None:
|
|
102
|
-
model_msg = f" for model '{model_id}'" if model_id is not None else ""
|
|
103
|
-
message = f"Formatting mode '{formatting_mode}' is not supported by provider '{provider_id}'{model_msg}"
|
|
104
|
-
super().__init__(
|
|
105
|
-
feature=f"formatting_mode:{formatting_mode}",
|
|
106
|
-
provider_id=provider_id,
|
|
107
|
-
model_id=model_id,
|
|
108
|
-
message=message,
|
|
109
|
-
)
|
|
110
|
-
self.formatting_mode = formatting_mode
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
class RateLimitError(APIError):
|
|
114
|
-
"""Raised when rate limits are exceeded (429)."""
|
|
115
|
-
|
|
116
|
-
def __init__(self, message: str, status_code: int | None = None) -> None:
|
|
117
|
-
super().__init__(message, status_code=status_code or 429)
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
class ServerError(APIError):
|
|
121
|
-
"""Raised for server-side errors (500+)."""
|
|
122
|
-
|
|
123
|
-
def __init__(self, message: str, status_code: int | None = None) -> None:
|
|
124
|
-
super().__init__(message, status_code=status_code or 500)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
class TimeoutError(MirascopeLLMError):
|
|
128
|
-
"""Raised when requests timeout or deadline exceeded."""
|
|
129
|
-
|
|
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
|
-
|
|
136
|
-
class NoRegisteredProviderError(MirascopeLLMError):
|
|
311
|
+
class NoRegisteredProviderError(Error):
|
|
137
312
|
"""Raised when no provider is registered for a given model_id."""
|
|
138
313
|
|
|
139
314
|
model_id: str
|
|
@@ -145,3 +320,41 @@ class NoRegisteredProviderError(MirascopeLLMError):
|
|
|
145
320
|
)
|
|
146
321
|
super().__init__(message)
|
|
147
322
|
self.model_id = model_id
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
class MissingAPIKeyError(Error):
|
|
326
|
+
"""Raised when no API key is available for a provider.
|
|
327
|
+
|
|
328
|
+
This error is raised during auto-registration when the required API key
|
|
329
|
+
environment variable is not set. If a Mirascope fallback is available,
|
|
330
|
+
the error message will suggest using MIRASCOPE_API_KEY as an alternative.
|
|
331
|
+
"""
|
|
332
|
+
|
|
333
|
+
provider_id: str
|
|
334
|
+
"""The provider that requires an API key."""
|
|
335
|
+
|
|
336
|
+
env_var: str
|
|
337
|
+
"""The environment variable that should contain the API key."""
|
|
338
|
+
|
|
339
|
+
def __init__(
|
|
340
|
+
self,
|
|
341
|
+
provider_id: str,
|
|
342
|
+
env_var: str,
|
|
343
|
+
has_mirascope_fallback: bool = False,
|
|
344
|
+
) -> None:
|
|
345
|
+
if has_mirascope_fallback:
|
|
346
|
+
message = (
|
|
347
|
+
f"No API key found for {provider_id}. Either:\n"
|
|
348
|
+
f" 1. Set {env_var} environment variable, or\n"
|
|
349
|
+
f" 2. Set MIRASCOPE_API_KEY for cross-provider support "
|
|
350
|
+
f"via Mirascope Router\n"
|
|
351
|
+
f" (Learn more: https://mirascope.com/docs/router)"
|
|
352
|
+
)
|
|
353
|
+
else:
|
|
354
|
+
message = (
|
|
355
|
+
f"No API key found for {provider_id}. "
|
|
356
|
+
f"Set the {env_var} environment variable."
|
|
357
|
+
)
|
|
358
|
+
super().__init__(message)
|
|
359
|
+
self.provider_id = provider_id
|
|
360
|
+
self.env_var = env_var
|
|
@@ -3,11 +3,21 @@
|
|
|
3
3
|
This module provides a way to define structured output formats for LLM responses.
|
|
4
4
|
The `@format` decorator can be applied to classes to specify how LLM
|
|
5
5
|
outputs should be structured and parsed.
|
|
6
|
+
|
|
7
|
+
The `@output_parser` decorator can be used to create custom parsers for non-JSON
|
|
8
|
+
formats like XML, YAML, CSV, or any custom text structure.
|
|
6
9
|
"""
|
|
7
10
|
|
|
8
11
|
from .format import Format, format, resolve_format
|
|
9
12
|
from .from_call_args import FromCallArgs
|
|
13
|
+
from .output_parser import OutputParser, is_output_parser, output_parser
|
|
10
14
|
from .partial import Partial
|
|
15
|
+
from .primitives import (
|
|
16
|
+
PrimitiveType,
|
|
17
|
+
PrimitiveWrapperModel,
|
|
18
|
+
create_wrapper_model,
|
|
19
|
+
is_primitive_type,
|
|
20
|
+
)
|
|
11
21
|
from .types import FormattableT, FormattingMode
|
|
12
22
|
|
|
13
23
|
__all__ = [
|
|
@@ -16,7 +26,14 @@ __all__ = [
|
|
|
16
26
|
"FormattableT",
|
|
17
27
|
"FormattingMode",
|
|
18
28
|
"FromCallArgs",
|
|
29
|
+
"OutputParser",
|
|
19
30
|
"Partial",
|
|
31
|
+
"PrimitiveType",
|
|
32
|
+
"PrimitiveWrapperModel",
|
|
33
|
+
"create_wrapper_model",
|
|
20
34
|
"format",
|
|
35
|
+
"is_output_parser",
|
|
36
|
+
"is_primitive_type",
|
|
37
|
+
"output_parser",
|
|
21
38
|
"resolve_format",
|
|
22
39
|
]
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""The `llm.format` decorator for defining response formats as classes."""
|
|
2
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
3
5
|
import inspect
|
|
4
6
|
import json
|
|
5
7
|
from dataclasses import dataclass
|
|
@@ -7,6 +9,8 @@ from typing import Any, Generic, cast
|
|
|
7
9
|
|
|
8
10
|
from ..tools import FORMAT_TOOL_NAME, ToolFn, ToolParameterSchema, ToolSchema
|
|
9
11
|
from ..types import NoneType
|
|
12
|
+
from .output_parser import OutputParser, is_output_parser
|
|
13
|
+
from .primitives import create_wrapper_model, is_primitive_type
|
|
10
14
|
from .types import FormattableT, FormattingMode, HasFormattingInstructions
|
|
11
15
|
|
|
12
16
|
TOOL_MODE_INSTRUCTIONS = f"""Always respond to the user's query using the {FORMAT_TOOL_NAME} tool for structured output."""
|
|
@@ -49,35 +53,45 @@ class Format(Generic[FormattableT]):
|
|
|
49
53
|
"""JSON schema representation of the structured output format."""
|
|
50
54
|
|
|
51
55
|
mode: FormattingMode
|
|
52
|
-
"""The decorator-provided mode of the response format.
|
|
53
|
-
|
|
56
|
+
"""The decorator-provided mode of the response format.
|
|
57
|
+
|
|
54
58
|
Determines how the LLM call may be modified in order to extract the expected format.
|
|
55
59
|
"""
|
|
56
60
|
|
|
57
|
-
formattable: type[FormattableT]
|
|
58
|
-
"""The
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
formattable: type[FormattableT] | OutputParser[FormattableT]
|
|
62
|
+
"""The formattable type or custom output parser.
|
|
63
|
+
|
|
64
|
+
Can be one of:
|
|
65
|
+
- type[BaseModel]: A Pydantic model class for structured output
|
|
66
|
+
- PrimitiveType: A primitive type (str, int, list, etc.) for simple output
|
|
67
|
+
- OutputParser[FormattableT]: A custom parser created with @llm.output_parser
|
|
68
|
+
|
|
69
|
+
The type determines how the response will be parsed in response.parse().
|
|
70
|
+
OutputParser uses Any for the response type since it works with any response.
|
|
63
71
|
"""
|
|
64
72
|
|
|
65
73
|
@property
|
|
66
74
|
def formatting_instructions(self) -> str | None:
|
|
67
75
|
"""The formatting instructions that will be added to the LLM system prompt.
|
|
68
76
|
|
|
69
|
-
If the format
|
|
70
|
-
|
|
71
|
-
|
|
77
|
+
If the format has a custom `OutputParser`, its formatting instructions will be used.
|
|
78
|
+
Otherwise, if the format type has a `formatting_instructions` class method,
|
|
79
|
+
the output of that call will be used. Otherwise, instructions may be
|
|
80
|
+
auto-generated based on the formatting mode.
|
|
72
81
|
"""
|
|
73
|
-
if
|
|
82
|
+
if is_output_parser(self.formattable) or isinstance(
|
|
83
|
+
self.formattable, HasFormattingInstructions
|
|
84
|
+
):
|
|
74
85
|
return self.formattable.formatting_instructions()
|
|
86
|
+
|
|
75
87
|
if self.mode == "tool":
|
|
76
88
|
return TOOL_MODE_INSTRUCTIONS
|
|
77
89
|
elif self.mode == "json":
|
|
78
90
|
json_schema = json.dumps(self.schema, indent=2)
|
|
79
91
|
instructions = JSON_MODE_INSTRUCTIONS.format(json_schema=json_schema)
|
|
80
92
|
return inspect.cleandoc(instructions)
|
|
93
|
+
elif self.mode == "parser":
|
|
94
|
+
return None # pragma: no cover
|
|
81
95
|
|
|
82
96
|
def create_tool_schema(
|
|
83
97
|
self,
|
|
@@ -121,30 +135,39 @@ class Format(Generic[FormattableT]):
|
|
|
121
135
|
name=FORMAT_TOOL_NAME,
|
|
122
136
|
description=description,
|
|
123
137
|
parameters=parameters,
|
|
124
|
-
strict=
|
|
138
|
+
strict=None, # Provider determines whether to use strict mode.
|
|
125
139
|
)
|
|
126
140
|
|
|
127
141
|
|
|
128
142
|
def format(
|
|
129
|
-
formattable: type[FormattableT] | None,
|
|
143
|
+
formattable: type[FormattableT] | OutputParser[FormattableT] | None,
|
|
130
144
|
*,
|
|
131
145
|
mode: FormattingMode,
|
|
132
146
|
) -> Format[FormattableT] | None:
|
|
133
|
-
"""Returns a `Format` that describes structured output
|
|
147
|
+
"""Returns a `Format` that describes structured output or custom parsing.
|
|
148
|
+
|
|
149
|
+
This function converts a Formattable type (e.g. Pydantic `BaseModel` or primitive type)
|
|
150
|
+
or an `OutputParser` into a `Format` object that describes how the output should be
|
|
151
|
+
formatted and parsed. Calling `llm.format` is optional, as all the APIs that expect
|
|
152
|
+
a `Format` can also take the Formattable type or `OutputParser` directly. However,
|
|
153
|
+
calling `llm.format` is necessary in order to specify the formatting mode for
|
|
154
|
+
`BaseModel`/primitive types.
|
|
134
155
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
is optional, as all the APIs that expect a `Format` can also take the Formattable
|
|
138
|
-
type directly. However, calling `llm.format` is necessary in order to specify the
|
|
139
|
-
formatting mode that will be used.
|
|
156
|
+
Primitive types are automatically wrapped in a `BaseModel` with an "output" field
|
|
157
|
+
for schema generation, then unwrapped during parsing.
|
|
140
158
|
|
|
141
159
|
Args:
|
|
142
|
-
|
|
160
|
+
formattable: The type or parser to format:
|
|
161
|
+
- BaseModel type: Uses structured output with JSON schema
|
|
162
|
+
- Primitive type: Wrapped in schema for structured output
|
|
163
|
+
- OutputParser: Uses custom parsing with instructions
|
|
164
|
+
mode: The format mode to use (required):
|
|
143
165
|
- "strict": Use model strict structured outputs, or fail if unavailable.
|
|
144
166
|
- "tool": Use forced tool calling with a special tool that represents a
|
|
145
167
|
formatted response.
|
|
146
168
|
- "json": Use provider json mode if available, or modify prompt to request
|
|
147
169
|
json if not.
|
|
170
|
+
- "parser": Must be used for OutputParser types.
|
|
148
171
|
|
|
149
172
|
The Formattable type may provide custom formatting instructions via a
|
|
150
173
|
`formatting_instructions(cls)` classmethod. If that method is present, it will be called,
|
|
@@ -155,34 +178,44 @@ def format(
|
|
|
155
178
|
you can add the `formatting_instructions` classmethod and have it return `None`.
|
|
156
179
|
|
|
157
180
|
Returns:
|
|
158
|
-
A `Format` object describing the
|
|
181
|
+
A `Format` object describing the format type or parser.
|
|
159
182
|
|
|
160
183
|
Example:
|
|
161
|
-
Using with
|
|
184
|
+
Using with a BaseModel:
|
|
162
185
|
|
|
163
186
|
```python
|
|
164
187
|
from pydantic import BaseModel
|
|
165
|
-
|
|
166
188
|
from mirascope import llm
|
|
167
189
|
|
|
168
|
-
|
|
169
190
|
class Book(BaseModel):
|
|
170
191
|
title: str
|
|
171
192
|
author: str
|
|
172
193
|
|
|
173
194
|
format = llm.format(Book, mode="strict")
|
|
174
195
|
|
|
175
|
-
@llm.call(
|
|
176
|
-
provider_id="openai",
|
|
177
|
-
model_id="openai/gpt-5-mini",
|
|
178
|
-
format=format,
|
|
179
|
-
)
|
|
196
|
+
@llm.call("openai/gpt-5-mini", format=format)
|
|
180
197
|
def recommend_book(genre: str):
|
|
181
198
|
return f"Recommend a {genre} book."
|
|
182
199
|
|
|
183
200
|
response = recommend_book("fantasy")
|
|
184
201
|
book: Book = response.parse()
|
|
185
|
-
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Example:
|
|
205
|
+
|
|
206
|
+
Using with an `OutputParser`:
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
@llm.output_parser(
|
|
210
|
+
formatting_instructions="Return XML: <book><title>...</title></book>"
|
|
211
|
+
)
|
|
212
|
+
def parse_book_xml(response: llm.AnyResponse) -> Book:
|
|
213
|
+
# ... parsing logic ...
|
|
214
|
+
return Book(...)
|
|
215
|
+
|
|
216
|
+
@llm.call("openai/gpt-5-mini", format=parse_book_xml)
|
|
217
|
+
def recommend_book(genre: str):
|
|
218
|
+
return f"Recommend a {genre} book."
|
|
186
219
|
```
|
|
187
220
|
"""
|
|
188
221
|
# TODO: Add caching or memoization to this function (e.g. functools.lru_cache)
|
|
@@ -190,6 +223,34 @@ def format(
|
|
|
190
223
|
if formattable is None or formattable is NoneType:
|
|
191
224
|
return None
|
|
192
225
|
|
|
226
|
+
if is_output_parser(formattable):
|
|
227
|
+
if mode != "parser":
|
|
228
|
+
raise ValueError(f"mode must be 'parser' for OutputParser, got '{mode}'")
|
|
229
|
+
return Format[Any](
|
|
230
|
+
name=formattable.__name__,
|
|
231
|
+
description=formattable.__doc__,
|
|
232
|
+
schema={},
|
|
233
|
+
mode="parser",
|
|
234
|
+
formattable=formattable,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
if is_primitive_type(formattable):
|
|
238
|
+
wrapper_model = create_wrapper_model(formattable)
|
|
239
|
+
schema = wrapper_model.model_json_schema()
|
|
240
|
+
name = (
|
|
241
|
+
formattable.__name__
|
|
242
|
+
if hasattr(formattable, "__name__")
|
|
243
|
+
else str(formattable)
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
return Format[FormattableT](
|
|
247
|
+
name=name,
|
|
248
|
+
description=None,
|
|
249
|
+
schema=schema,
|
|
250
|
+
mode=mode,
|
|
251
|
+
formattable=formattable,
|
|
252
|
+
)
|
|
253
|
+
|
|
193
254
|
description = None
|
|
194
255
|
if formattable.__doc__:
|
|
195
256
|
description = inspect.cleandoc(formattable.__doc__)
|
|
@@ -206,11 +267,27 @@ def format(
|
|
|
206
267
|
|
|
207
268
|
|
|
208
269
|
def resolve_format(
|
|
209
|
-
formattable:
|
|
270
|
+
formattable: (
|
|
271
|
+
type[FormattableT] | Format[FormattableT] | OutputParser[FormattableT] | None
|
|
272
|
+
),
|
|
210
273
|
default_mode: FormattingMode,
|
|
211
274
|
) -> Format[FormattableT] | None:
|
|
212
|
-
"""Resolve a `Format` (or None) from a possible `Format
|
|
275
|
+
"""Resolve a `Format` (or None) from a possible `Format`, Formattable, or `OutputParser`.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
formattable: The format specification:
|
|
279
|
+
- Format: Returned as-is
|
|
280
|
+
- BaseModel/primitive type: Converted to Format with default_mode
|
|
281
|
+
- OutputParser: Converted to Format with mode='parser'
|
|
282
|
+
default_mode: The mode to use for BaseModel/primitive types.
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
A Format object or None.
|
|
286
|
+
"""
|
|
213
287
|
if isinstance(formattable, Format):
|
|
214
288
|
return formattable
|
|
215
|
-
|
|
216
|
-
|
|
289
|
+
|
|
290
|
+
if is_output_parser(formattable):
|
|
291
|
+
return format(formattable, mode="parser")
|
|
292
|
+
|
|
293
|
+
return format(formattable, mode=default_mode)
|