mirascope 2.0.0a6__py3-none-any.whl → 2.0.1__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/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 +3 -1
- 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 +131 -68
- 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 +12 -6
- mirascope/llm/tools/toolkit.py +35 -27
- mirascope/llm/tools/tools.py +45 -20
- mirascope/ops/__init__.py +4 -0
- mirascope/ops/_internal/configuration.py +82 -31
- mirascope/ops/_internal/exporters/exporters.py +64 -11
- 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 +4 -0
- mirascope/ops/_internal/traced_functions.py +118 -8
- mirascope/ops/_internal/tracing.py +78 -1
- mirascope/ops/_internal/utils.py +52 -4
- {mirascope-2.0.0a6.dist-info → mirascope-2.0.1.dist-info}/METADATA +12 -11
- mirascope-2.0.1.dist-info/RECORD +423 -0
- {mirascope-2.0.0a6.dist-info → mirascope-2.0.1.dist-info}/licenses/LICENSE +1 -1
- mirascope-2.0.0a6.dist-info/RECORD +0 -316
- {mirascope-2.0.0a6.dist-info → mirascope-2.0.1.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""The `llm.output_parser` decorator for creating custom output parsers."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Generic, TypeVar
|
|
5
|
+
from typing_extensions import TypeIs
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from ..responses import AnyResponse
|
|
9
|
+
|
|
10
|
+
OutputT = TypeVar("OutputT")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class OutputParser(Generic[OutputT]):
|
|
14
|
+
"""Represents a custom output parser created with @llm.output_parser.
|
|
15
|
+
|
|
16
|
+
This class wraps a parsing function and stores formatting instructions.
|
|
17
|
+
It is created by the @llm.output_parser decorator and used as a format
|
|
18
|
+
argument in LLM calls.
|
|
19
|
+
|
|
20
|
+
Unlike BaseModel and primitive type formats that use structured outputs
|
|
21
|
+
(JSON schema, tools, strict mode), OutputParser works with raw text responses
|
|
22
|
+
and custom parsing logic.
|
|
23
|
+
|
|
24
|
+
Example:
|
|
25
|
+
```python
|
|
26
|
+
@llm.output_parser(
|
|
27
|
+
formatting_instructions="Return XML: <book><title>...</title></book>"
|
|
28
|
+
)
|
|
29
|
+
def parse_book_xml(response: llm.AnyResponse) -> Book:
|
|
30
|
+
text = "".join(part.text for part in response.texts)
|
|
31
|
+
root = ET.fromstring(text)
|
|
32
|
+
return Book(title=root.find("title").text, ...)
|
|
33
|
+
|
|
34
|
+
@llm.call("openai/gpt-4o", format=parse_book_xml)
|
|
35
|
+
def recommend_book(genre: str):
|
|
36
|
+
return f"Recommend a {genre} book."
|
|
37
|
+
|
|
38
|
+
response = recommend_book("fantasy")
|
|
39
|
+
book = response.parse() # Returns Book instance
|
|
40
|
+
```
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
func: Callable[["AnyResponse"], OutputT],
|
|
46
|
+
formatting_instructions: str,
|
|
47
|
+
) -> None:
|
|
48
|
+
"""Initialize the OutputParser.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
func: The parsing function that takes a Response and returns parsed output.
|
|
52
|
+
formatting_instructions: Instructions for the LLM on how to format output.
|
|
53
|
+
"""
|
|
54
|
+
self.func = func
|
|
55
|
+
self._formatting_instructions = formatting_instructions
|
|
56
|
+
self.__name__ = func.__name__
|
|
57
|
+
self.__doc__ = func.__doc__
|
|
58
|
+
|
|
59
|
+
def formatting_instructions(self) -> str:
|
|
60
|
+
"""Return the formatting instructions for the LLM.
|
|
61
|
+
|
|
62
|
+
These instructions are added to the system prompt to guide the LLM
|
|
63
|
+
on how to format its output for parsing.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
The formatting instructions string.
|
|
67
|
+
"""
|
|
68
|
+
return self._formatting_instructions
|
|
69
|
+
|
|
70
|
+
def __call__(self, response: "AnyResponse") -> OutputT:
|
|
71
|
+
"""Parse the response using the wrapped function.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
response: The response object from the LLM call.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
The parsed output of type OutputT.
|
|
78
|
+
|
|
79
|
+
Raises:
|
|
80
|
+
Any exception raised by the wrapped parsing function.
|
|
81
|
+
"""
|
|
82
|
+
return self.func(response)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def output_parser(
|
|
86
|
+
*,
|
|
87
|
+
formatting_instructions: str,
|
|
88
|
+
) -> Callable[[Callable[["AnyResponse"], OutputT]], OutputParser[OutputT]]:
|
|
89
|
+
"""Decorator to create an output parser for custom format parsing.
|
|
90
|
+
|
|
91
|
+
Use this decorator to create custom parsers for non-JSON formats like
|
|
92
|
+
XML, YAML, CSV, or any custom text structure. The decorated function
|
|
93
|
+
receives the full Response object and returns the parsed output.
|
|
94
|
+
|
|
95
|
+
This is the recommended way to handle custom output formats that don't
|
|
96
|
+
fit the JSON/BaseModel paradigm. The formatting instructions guide the
|
|
97
|
+
LLM on how to structure its output, and the parsing function extracts
|
|
98
|
+
the data you need.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
formatting_instructions: Instructions for the LLM on how to format
|
|
102
|
+
the output. These will be added to the system prompt.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Decorator that converts a function into an OutputParser.
|
|
106
|
+
|
|
107
|
+
Example:
|
|
108
|
+
|
|
109
|
+
XML parsing:
|
|
110
|
+
```python
|
|
111
|
+
@llm.output_parser(
|
|
112
|
+
formatting_instructions='''
|
|
113
|
+
Return the book information in this XML structure:
|
|
114
|
+
<book>
|
|
115
|
+
<title>Book Title</title>
|
|
116
|
+
<author>Author Name</author>
|
|
117
|
+
<rating>5</rating>
|
|
118
|
+
</book>
|
|
119
|
+
'''
|
|
120
|
+
)
|
|
121
|
+
def parse_book_xml(response: llm.AnyResponse) -> Book:
|
|
122
|
+
import xml.etree.ElementTree as ET
|
|
123
|
+
text = "".join(part.text for part in response.texts)
|
|
124
|
+
root = ET.fromstring(text)
|
|
125
|
+
return Book(
|
|
126
|
+
title=root.find("title").text,
|
|
127
|
+
author=root.find("author").text,
|
|
128
|
+
rating=int(root.find("rating").text),
|
|
129
|
+
)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Example:
|
|
133
|
+
|
|
134
|
+
CSV parsing:
|
|
135
|
+
```python
|
|
136
|
+
@llm.output_parser(
|
|
137
|
+
formatting_instructions='''
|
|
138
|
+
Return book information as CSV format with header:
|
|
139
|
+
title,author,rating
|
|
140
|
+
Book 1,Author 1,5
|
|
141
|
+
Book 2,Author 2,4
|
|
142
|
+
'''
|
|
143
|
+
)
|
|
144
|
+
def parse_books_csv(response: llm.AnyResponse) -> list[Book]:
|
|
145
|
+
text = "".join(part.text for part in response.texts)
|
|
146
|
+
lines = text.strip().split('\\n')[1:] # Skip header
|
|
147
|
+
return [
|
|
148
|
+
Book(
|
|
149
|
+
title=line.split(',')[0].strip(),
|
|
150
|
+
author=line.split(',')[1].strip(),
|
|
151
|
+
rating=int(line.split(',')[2]),
|
|
152
|
+
)
|
|
153
|
+
for line in lines
|
|
154
|
+
]
|
|
155
|
+
```
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
def decorator(
|
|
159
|
+
func: Callable[["AnyResponse"], OutputT],
|
|
160
|
+
) -> OutputParser[OutputT]:
|
|
161
|
+
return OutputParser(func, formatting_instructions)
|
|
162
|
+
|
|
163
|
+
return decorator
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def is_output_parser(obj: Any) -> TypeIs[OutputParser[Any]]: # noqa: ANN401
|
|
167
|
+
"""Check if an object is an OutputParser.
|
|
168
|
+
|
|
169
|
+
This is a type guard function that narrows the type of `obj` to
|
|
170
|
+
`OutputParser[Any, Any]` when it returns True.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
obj: The object to check.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
True if the object is an OutputParser instance, False otherwise.
|
|
177
|
+
"""
|
|
178
|
+
return isinstance(obj, OutputParser)
|
|
@@ -8,10 +8,16 @@ serves as an acknowledgment of the original author's contribution to this projec
|
|
|
8
8
|
--------------------------------------------------------------------------------
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
import inspect
|
|
12
|
+
from typing import Any, Generic, NoReturn, Union, cast, get_args, get_origin
|
|
13
|
+
|
|
14
|
+
from pydantic import BaseModel, create_model
|
|
12
15
|
|
|
13
16
|
from .format import FormattableT
|
|
14
17
|
|
|
18
|
+
# Cache for generated partial models to avoid recreation
|
|
19
|
+
_partial_model_cache: dict[type[Any], type[Any]] = {}
|
|
20
|
+
|
|
15
21
|
|
|
16
22
|
class Partial(Generic[FormattableT]):
|
|
17
23
|
"""Generate a new class with all attributes optionals.
|
|
@@ -34,7 +40,9 @@ class Partial(Generic[FormattableT]):
|
|
|
34
40
|
Raises:
|
|
35
41
|
TypeError: Direct instantiation not allowed.
|
|
36
42
|
"""
|
|
37
|
-
raise TypeError(
|
|
43
|
+
raise TypeError(
|
|
44
|
+
"Cannot instantiate abstract Partial class."
|
|
45
|
+
) # pragma: no cover
|
|
38
46
|
|
|
39
47
|
def __init_subclass__(
|
|
40
48
|
cls,
|
|
@@ -46,13 +54,78 @@ class Partial(Generic[FormattableT]):
|
|
|
46
54
|
Raises:
|
|
47
55
|
TypeError: Subclassing not allowed.
|
|
48
56
|
"""
|
|
49
|
-
raise TypeError(f"Cannot subclass {cls.__module__}.Partial")
|
|
57
|
+
raise TypeError(f"Cannot subclass {cls.__module__}.Partial") # pragma: no cover
|
|
50
58
|
|
|
51
59
|
def __class_getitem__(
|
|
52
60
|
cls,
|
|
53
61
|
wrapped_class: type[FormattableT],
|
|
54
62
|
) -> type[FormattableT]:
|
|
55
|
-
"""Convert model to a partial model with all fields being optionals.
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
63
|
+
"""Convert model to a partial model with all fields being optionals.
|
|
64
|
+
|
|
65
|
+
Recursively converts all fields in a Pydantic BaseModel to optional,
|
|
66
|
+
handling nested models and generic types like list[Book].
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
wrapped_class: The BaseModel class to make partial
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
A new BaseModel class with all fields optional (or original if not BaseModel)
|
|
73
|
+
|
|
74
|
+
Example:
|
|
75
|
+
>>> class Author(BaseModel):
|
|
76
|
+
... first_name: str
|
|
77
|
+
... last_name: str
|
|
78
|
+
>>> class Book(BaseModel):
|
|
79
|
+
... title: str
|
|
80
|
+
... author: Author
|
|
81
|
+
>>> PartialBook = Partial[Book]
|
|
82
|
+
>>> partial = PartialBook(title="The Name")
|
|
83
|
+
>>> partial.author # None
|
|
84
|
+
"""
|
|
85
|
+
# Return non-BaseModel types unchanged
|
|
86
|
+
if not (
|
|
87
|
+
inspect.isclass(wrapped_class) and issubclass(wrapped_class, BaseModel)
|
|
88
|
+
):
|
|
89
|
+
return wrapped_class
|
|
90
|
+
|
|
91
|
+
# Check cache to avoid regenerating
|
|
92
|
+
if wrapped_class in _partial_model_cache:
|
|
93
|
+
return cast(type[FormattableT], _partial_model_cache[wrapped_class])
|
|
94
|
+
|
|
95
|
+
# Recursively make all fields optional
|
|
96
|
+
partial_fields: dict[str, Any] = {}
|
|
97
|
+
for field_name, field_info in wrapped_class.model_fields.items():
|
|
98
|
+
field_type = field_info.annotation
|
|
99
|
+
|
|
100
|
+
# Recursively handle nested BaseModel fields
|
|
101
|
+
if inspect.isclass(field_type) and issubclass(field_type, BaseModel):
|
|
102
|
+
field_type = Partial[field_type]
|
|
103
|
+
|
|
104
|
+
# Handle generic types with BaseModel args (e.g., list[Book], dict[str, Book])
|
|
105
|
+
origin = get_origin(field_type)
|
|
106
|
+
if origin is not None:
|
|
107
|
+
args = get_args(field_type)
|
|
108
|
+
# Recursively convert BaseModel args to partial
|
|
109
|
+
new_args = tuple(
|
|
110
|
+
Partial[arg]
|
|
111
|
+
if inspect.isclass(arg) and issubclass(arg, BaseModel)
|
|
112
|
+
else arg
|
|
113
|
+
for arg in args
|
|
114
|
+
)
|
|
115
|
+
# Reconstruct generic type with new args
|
|
116
|
+
if new_args != args:
|
|
117
|
+
field_type = origin[new_args]
|
|
118
|
+
|
|
119
|
+
# Make field optional with None default
|
|
120
|
+
optional_type = Union[field_type, None] # noqa: UP007
|
|
121
|
+
partial_fields[field_name] = (optional_type, None)
|
|
122
|
+
|
|
123
|
+
# Create new model with "Partial" prefix
|
|
124
|
+
partial_model = create_model(
|
|
125
|
+
f"Partial{wrapped_class.__name__}", __base__=BaseModel, **partial_fields
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Cache the generated model
|
|
129
|
+
_partial_model_cache[wrapped_class] = partial_model
|
|
130
|
+
|
|
131
|
+
return cast(type[FormattableT], partial_model)
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""Utilities for handling primitive types in formatting."""
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from types import UnionType
|
|
6
|
+
from typing import (
|
|
7
|
+
Annotated,
|
|
8
|
+
Any,
|
|
9
|
+
Literal,
|
|
10
|
+
Protocol,
|
|
11
|
+
TypeAlias,
|
|
12
|
+
Union,
|
|
13
|
+
cast,
|
|
14
|
+
get_args,
|
|
15
|
+
get_origin,
|
|
16
|
+
)
|
|
17
|
+
from typing_extensions import TypeIs
|
|
18
|
+
|
|
19
|
+
from pydantic import create_model
|
|
20
|
+
|
|
21
|
+
PrimitiveType: TypeAlias = (
|
|
22
|
+
str
|
|
23
|
+
| int
|
|
24
|
+
| float
|
|
25
|
+
| bool
|
|
26
|
+
| bytes
|
|
27
|
+
| list[Any]
|
|
28
|
+
| set[Any]
|
|
29
|
+
| tuple[Any, ...]
|
|
30
|
+
| dict[Any, Any]
|
|
31
|
+
)
|
|
32
|
+
"""Primitive types that can be used with format parameter.
|
|
33
|
+
|
|
34
|
+
These types are automatically wrapped in a BaseModel for schema generation,
|
|
35
|
+
then unwrapped after validation to return the primitive value.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class PrimitiveWrapperModel(Protocol):
|
|
40
|
+
"""Protocol for wrapper models with an output field."""
|
|
41
|
+
|
|
42
|
+
output: Any
|
|
43
|
+
model_fields: Any
|
|
44
|
+
|
|
45
|
+
def __init__(self, *, output: Any) -> None: ... # noqa: ANN401
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def model_json_schema(cls) -> dict[str, Any]: ...
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def model_validate_json(cls, json_data: str) -> "PrimitiveWrapperModel": ...
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def is_primitive_type(
|
|
55
|
+
type_: Any, # noqa: ANN401
|
|
56
|
+
) -> TypeIs[type[PrimitiveType]]:
|
|
57
|
+
"""Check if a type is a primitive type that needs wrapping.
|
|
58
|
+
|
|
59
|
+
Returns True for:
|
|
60
|
+
- Basic primitives: str, int, float, bool, bytes, list, set, tuple, dict
|
|
61
|
+
- Enum types
|
|
62
|
+
- Generic types with primitive origins: list[Book], dict[str, int]
|
|
63
|
+
- Literal types
|
|
64
|
+
- Union types (including Optional)
|
|
65
|
+
- Annotated types
|
|
66
|
+
|
|
67
|
+
Returns False for:
|
|
68
|
+
- BaseModel subclasses (already have model_json_schema)
|
|
69
|
+
- None/NoneType
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
type_: The type to check
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
True if the type is a primitive that needs wrapping
|
|
76
|
+
|
|
77
|
+
Example:
|
|
78
|
+
>>> is_primitive_type(str)
|
|
79
|
+
True
|
|
80
|
+
>>> is_primitive_type(list[int])
|
|
81
|
+
True
|
|
82
|
+
>>> from pydantic import BaseModel
|
|
83
|
+
>>> class Book(BaseModel):
|
|
84
|
+
... title: str
|
|
85
|
+
>>> is_primitive_type(Book)
|
|
86
|
+
False
|
|
87
|
+
"""
|
|
88
|
+
primitive_types: set[type[PrimitiveType]] = {
|
|
89
|
+
str,
|
|
90
|
+
int,
|
|
91
|
+
float,
|
|
92
|
+
bool,
|
|
93
|
+
bytes,
|
|
94
|
+
list,
|
|
95
|
+
set,
|
|
96
|
+
tuple,
|
|
97
|
+
dict,
|
|
98
|
+
}
|
|
99
|
+
special_types: set[Any] = {Annotated, Literal, Union, UnionType}
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
(inspect.isclass(type_) and issubclass(type_, Enum))
|
|
103
|
+
or type_ in primitive_types
|
|
104
|
+
or get_origin(type_) in primitive_types.union(special_types)
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _get_type_name(type_: Any) -> str: # noqa: ANN401
|
|
109
|
+
"""Extract a clean name from a type for use in model naming.
|
|
110
|
+
|
|
111
|
+
Handles Annotated types by extracting the underlying type,
|
|
112
|
+
and generates clean names for generic types.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
type_: The type to extract a name from
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
A clean string suitable for use in a Python class name
|
|
119
|
+
|
|
120
|
+
Example:
|
|
121
|
+
>>> _get_type_name(str)
|
|
122
|
+
'str'
|
|
123
|
+
>>> _get_type_name(list[int])
|
|
124
|
+
'list_int_'
|
|
125
|
+
"""
|
|
126
|
+
# Import Annotated locally
|
|
127
|
+
# Check if this is an Annotated type
|
|
128
|
+
if get_origin(type_) in {Annotated}:
|
|
129
|
+
# For Annotated types, use the first arg (the actual type)
|
|
130
|
+
return _get_type_name(get_args(type_)[0])
|
|
131
|
+
|
|
132
|
+
# If the type has a __name__ attribute, use it
|
|
133
|
+
if hasattr(type_, "__name__"):
|
|
134
|
+
return type_.__name__
|
|
135
|
+
|
|
136
|
+
# For complex generics like list[Book], use string representation
|
|
137
|
+
type_str = str(type_)
|
|
138
|
+
|
|
139
|
+
# Clean up the string to make it a valid Python identifier
|
|
140
|
+
# Replace brackets and commas with underscores
|
|
141
|
+
clean = (
|
|
142
|
+
type_str.replace("[", "_")
|
|
143
|
+
.replace("]", "_")
|
|
144
|
+
.replace(", ", "_")
|
|
145
|
+
.replace(" ", "")
|
|
146
|
+
.replace("'", "")
|
|
147
|
+
.replace('"', "")
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
return clean
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def create_wrapper_model(
|
|
154
|
+
primitive_type: Any, # noqa: ANN401
|
|
155
|
+
) -> type[PrimitiveWrapperModel]:
|
|
156
|
+
"""Create a wrapper BaseModel for a primitive type.
|
|
157
|
+
|
|
158
|
+
The wrapper has a single field called "output" containing the primitive value.
|
|
159
|
+
Uses Pydantic's create_model() to generate the wrapper dynamically.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
primitive_type: The primitive type to wrap
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
A dynamically created BaseModel with an "output" field
|
|
166
|
+
|
|
167
|
+
Example:
|
|
168
|
+
>>> wrapper = create_wrapper_model(str)
|
|
169
|
+
>>> instance = wrapper(output="hello")
|
|
170
|
+
>>> instance.output
|
|
171
|
+
'hello'
|
|
172
|
+
|
|
173
|
+
>>> from pydantic import BaseModel
|
|
174
|
+
>>> class Book(BaseModel):
|
|
175
|
+
... title: str
|
|
176
|
+
>>> wrapper = create_wrapper_model(list[Book])
|
|
177
|
+
>>> books = [Book(title="Test")]
|
|
178
|
+
>>> instance = wrapper(output=books)
|
|
179
|
+
>>> len(instance.output)
|
|
180
|
+
1
|
|
181
|
+
"""
|
|
182
|
+
# Get a clean name for the wrapper class
|
|
183
|
+
type_name = _get_type_name(primitive_type)
|
|
184
|
+
|
|
185
|
+
# Create wrapper model with "output" field (required)
|
|
186
|
+
wrapper = create_model(
|
|
187
|
+
f"{type_name}Output",
|
|
188
|
+
__doc__=f"Wrapper for primitive type {type_name}",
|
|
189
|
+
output=(primitive_type, ...), # ... means required
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
return cast(type[PrimitiveWrapperModel], wrapper)
|
|
@@ -5,13 +5,22 @@ from typing_extensions import TypeVar
|
|
|
5
5
|
|
|
6
6
|
from pydantic import BaseModel
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
from .primitives import PrimitiveType
|
|
9
|
+
|
|
10
|
+
FormattableT = TypeVar(
|
|
11
|
+
"FormattableT", bound=BaseModel | PrimitiveType | None, default=None
|
|
12
|
+
)
|
|
10
13
|
"""Type variable for structured response format types.
|
|
11
14
|
|
|
12
15
|
This TypeVar represents the type of structured output format that LLM responses
|
|
13
|
-
can be parsed into, or None if no format is specified.
|
|
14
|
-
|
|
16
|
+
can be parsed into, or None if no format is specified.
|
|
17
|
+
|
|
18
|
+
Supported format types:
|
|
19
|
+
- Pydantic BaseModel subclasses
|
|
20
|
+
- Primitive types: str, int, float, bool, bytes, list, set, tuple, dict
|
|
21
|
+
- Generic collections: list[Book], dict[str, int], etc.
|
|
22
|
+
- Union, Literal, and Annotated types
|
|
23
|
+
- Enum types
|
|
15
24
|
"""
|
|
16
25
|
|
|
17
26
|
|
|
@@ -19,15 +28,14 @@ FormattingMode = Literal[
|
|
|
19
28
|
"strict",
|
|
20
29
|
"json",
|
|
21
30
|
"tool",
|
|
31
|
+
"parser",
|
|
22
32
|
]
|
|
23
33
|
"""Available modes for response format generation.
|
|
24
34
|
|
|
25
35
|
- "strict": Use strict mode for structured outputs, asking the LLM to strictly adhere
|
|
26
36
|
to a given JSON schema. Not all providers or models support it, and may not be
|
|
27
|
-
compatible with tool calling. When making a call using this mode, an
|
|
28
|
-
`llm.
|
|
29
|
-
unsupported), or an `llm.FeatureNotSupportedError` may be raised (if trying to use
|
|
30
|
-
strict along with tools and that is unsupported).
|
|
37
|
+
compatible with tool calling. When making a call using this mode, an
|
|
38
|
+
`llm.FeatureNotSupportedError` error may be raised if the mode is unsupported.
|
|
31
39
|
|
|
32
40
|
- "json": Use JSON mode for structured outputs. In contrast to strict mode, we ask the
|
|
33
41
|
LLM to output JSON as text, though without guarantees that the model will output
|
|
@@ -42,6 +50,10 @@ FormattingMode = Literal[
|
|
|
42
50
|
content (abstracting over the tool call). If other tools are present, they will
|
|
43
51
|
be handled as regular tool calls.
|
|
44
52
|
|
|
53
|
+
- "parser": Use custom parsing with formatting instructions. No schema generation or
|
|
54
|
+
structured output features. The LLM receives only formatting instructions and the
|
|
55
|
+
response is parsed using a custom parser function created with `@llm.output_parser`.
|
|
56
|
+
|
|
45
57
|
Note: When `llm.format` is not used, the provider will automatically choose a mode at call time.
|
|
46
58
|
"""
|
|
47
59
|
|
|
@@ -5,6 +5,7 @@ as a unified `Message` class with different roles (system, user, assistant) and
|
|
|
5
5
|
content arrays that can include text, images, audio, documents, and tool interactions.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
from ._utils import is_messages, promote_to_messages
|
|
8
9
|
from .message import (
|
|
9
10
|
AssistantContent,
|
|
10
11
|
AssistantMessage,
|
|
@@ -27,6 +28,8 @@ __all__ = [
|
|
|
27
28
|
"UserContent",
|
|
28
29
|
"UserMessage",
|
|
29
30
|
"assistant",
|
|
31
|
+
"is_messages",
|
|
32
|
+
"promote_to_messages",
|
|
30
33
|
"system",
|
|
31
34
|
"user",
|
|
32
35
|
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Utility functions for message handling."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
from typing_extensions import TypeIs
|
|
5
|
+
|
|
6
|
+
from .message import (
|
|
7
|
+
AssistantMessage,
|
|
8
|
+
Message,
|
|
9
|
+
SystemMessage,
|
|
10
|
+
UserContent,
|
|
11
|
+
UserMessage,
|
|
12
|
+
user,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def is_messages(
|
|
17
|
+
content: UserContent | Sequence[Message],
|
|
18
|
+
) -> TypeIs[Sequence[Message]]:
|
|
19
|
+
if isinstance(content, list):
|
|
20
|
+
if not content:
|
|
21
|
+
raise ValueError("Empty array may not be used as message content")
|
|
22
|
+
return isinstance(content[0], SystemMessage | UserMessage | AssistantMessage)
|
|
23
|
+
return False
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def promote_to_messages(content: UserContent | Sequence[Message]) -> Sequence[Message]:
|
|
27
|
+
"""Promote a prompt result to a list of messages.
|
|
28
|
+
|
|
29
|
+
If the result is already a list of Messages, returns it as-is.
|
|
30
|
+
If the result is str/UserContentPart/Sequence of content parts, wraps it in a user message.
|
|
31
|
+
"""
|
|
32
|
+
if is_messages(content):
|
|
33
|
+
return content
|
|
34
|
+
return [user(content)]
|
mirascope/llm/models/__init__.py
CHANGED
|
@@ -7,9 +7,14 @@ creates a default one.
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
from .models import Model, model, model_from_context, use_model
|
|
10
|
+
from .params import Params
|
|
11
|
+
from .thinking_config import ThinkingConfig, ThinkingLevel
|
|
10
12
|
|
|
11
13
|
__all__ = [
|
|
12
14
|
"Model",
|
|
15
|
+
"Params",
|
|
16
|
+
"ThinkingConfig",
|
|
17
|
+
"ThinkingLevel",
|
|
13
18
|
"model",
|
|
14
19
|
"model_from_context",
|
|
15
20
|
"use_model",
|