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
mirascope/llm/tools/tools.py
CHANGED
|
@@ -9,6 +9,7 @@ from typing_extensions import TypeVar
|
|
|
9
9
|
|
|
10
10
|
from ..content import ToolCall, ToolOutput
|
|
11
11
|
from ..context import Context, DepsT
|
|
12
|
+
from ..exceptions import ToolError, ToolExecutionError
|
|
12
13
|
from ..types import AnyP, JsonableCovariantT
|
|
13
14
|
from .protocols import (
|
|
14
15
|
AsyncContextToolFn,
|
|
@@ -42,13 +43,14 @@ class Tool(
|
|
|
42
43
|
|
|
43
44
|
@classmethod
|
|
44
45
|
def from_function( # pyright: ignore[reportIncompatibleMethodOverride]
|
|
45
|
-
cls, fn: ToolFn[AnyP, JsonableCovariantT], *, strict: bool =
|
|
46
|
+
cls, fn: ToolFn[AnyP, JsonableCovariantT], *, strict: bool | None = None
|
|
46
47
|
) -> Tool[AnyP, JsonableCovariantT]:
|
|
47
48
|
"""Create a `Tool` by inspecting a function and its docstring.
|
|
48
49
|
|
|
49
50
|
Args:
|
|
50
51
|
fn: The function to extract schema from
|
|
51
|
-
strict: Whether the tool should use strict mode when supported
|
|
52
|
+
strict: Whether the tool should use strict mode when supported.
|
|
53
|
+
If None, uses provider's default (usually as strict as possible).
|
|
52
54
|
|
|
53
55
|
Returns:
|
|
54
56
|
a `Tool` representing the function
|
|
@@ -68,10 +70,15 @@ class Tool(
|
|
|
68
70
|
|
|
69
71
|
def execute(self, tool_call: ToolCall) -> ToolOutput[JsonableCovariantT]:
|
|
70
72
|
"""Execute the tool using an LLM-provided `ToolCall`."""
|
|
71
|
-
kwargs_from_json = json.loads(tool_call.args)
|
|
72
73
|
kwargs_callable = cast(KwargsCallable[JsonableCovariantT], self.fn)
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
error: ToolError | None = None
|
|
75
|
+
try:
|
|
76
|
+
kwargs_from_json = json.loads(tool_call.args)
|
|
77
|
+
result = kwargs_callable(**kwargs_from_json)
|
|
78
|
+
except Exception as e:
|
|
79
|
+
result = str(e)
|
|
80
|
+
error = ToolExecutionError(e)
|
|
81
|
+
return ToolOutput(id=tool_call.id, result=result, error=error, name=self.name)
|
|
75
82
|
|
|
76
83
|
|
|
77
84
|
class AsyncTool(
|
|
@@ -88,13 +95,14 @@ class AsyncTool(
|
|
|
88
95
|
|
|
89
96
|
@classmethod
|
|
90
97
|
def from_function( # pyright: ignore[reportIncompatibleMethodOverride]
|
|
91
|
-
cls, fn: AsyncToolFn[AnyP, JsonableCovariantT], *, strict: bool =
|
|
98
|
+
cls, fn: AsyncToolFn[AnyP, JsonableCovariantT], *, strict: bool | None = None
|
|
92
99
|
) -> AsyncTool[AnyP, JsonableCovariantT]:
|
|
93
100
|
"""Create an `AsyncTool` by inspecting a function and its docstring.
|
|
94
101
|
|
|
95
102
|
Args:
|
|
96
103
|
fn: The function to extract schema from
|
|
97
|
-
strict: Whether the tool should use strict mode when supported
|
|
104
|
+
strict: Whether the tool should use strict mode when supported.
|
|
105
|
+
If None, uses provider's default (usually as strict as possible).
|
|
98
106
|
|
|
99
107
|
Returns:
|
|
100
108
|
an `AsyncTool` representing the function
|
|
@@ -116,10 +124,15 @@ class AsyncTool(
|
|
|
116
124
|
|
|
117
125
|
async def execute(self, tool_call: ToolCall) -> ToolOutput[JsonableCovariantT]:
|
|
118
126
|
"""Execute the async tool using an LLM-provided `ToolCall`."""
|
|
119
|
-
kwargs_from_json = json.loads(tool_call.args)
|
|
120
127
|
kwargs_callable = cast(AsyncKwargsCallable[JsonableCovariantT], self.fn)
|
|
121
|
-
|
|
122
|
-
|
|
128
|
+
error: ToolError | None = None
|
|
129
|
+
try:
|
|
130
|
+
kwargs_from_json = json.loads(tool_call.args)
|
|
131
|
+
result = await kwargs_callable(**kwargs_from_json)
|
|
132
|
+
except Exception as e:
|
|
133
|
+
result = str(e)
|
|
134
|
+
error = ToolExecutionError(e)
|
|
135
|
+
return ToolOutput(id=tool_call.id, result=result, error=error, name=self.name)
|
|
123
136
|
|
|
124
137
|
|
|
125
138
|
class ContextTool(
|
|
@@ -139,13 +152,14 @@ class ContextTool(
|
|
|
139
152
|
cls,
|
|
140
153
|
fn: ContextToolFn[DepsT, AnyP, JsonableCovariantT],
|
|
141
154
|
*,
|
|
142
|
-
strict: bool =
|
|
155
|
+
strict: bool | None = None,
|
|
143
156
|
) -> ContextTool[DepsT, JsonableCovariantT, AnyP]:
|
|
144
157
|
"""Create a `ContextTool` by inspecting a function and its docstring.
|
|
145
158
|
|
|
146
159
|
Args:
|
|
147
160
|
fn: The function to extract schema from
|
|
148
|
-
strict: Whether the tool should use strict mode when supported
|
|
161
|
+
strict: Whether the tool should use strict mode when supported.
|
|
162
|
+
If None, uses provider's default (usually as strict as possible).
|
|
149
163
|
|
|
150
164
|
Returns:
|
|
151
165
|
a `ContextTool` representing the function
|
|
@@ -172,12 +186,17 @@ class ContextTool(
|
|
|
172
186
|
self, ctx: Context[DepsT], tool_call: ToolCall
|
|
173
187
|
) -> ToolOutput[JsonableCovariantT]:
|
|
174
188
|
"""Execute the context tool using an LLM-provided `ToolCall`."""
|
|
175
|
-
kwargs_from_json = json.loads(tool_call.args)
|
|
176
189
|
kwargs_callable = cast(
|
|
177
190
|
ContextKwargsCallable[DepsT, JsonableCovariantT], self.fn
|
|
178
191
|
)
|
|
179
|
-
|
|
180
|
-
|
|
192
|
+
error: ToolError | None = None
|
|
193
|
+
try:
|
|
194
|
+
kwargs_from_json = json.loads(tool_call.args)
|
|
195
|
+
result = kwargs_callable(ctx, **kwargs_from_json)
|
|
196
|
+
except Exception as e:
|
|
197
|
+
result = str(e)
|
|
198
|
+
error = ToolExecutionError(e)
|
|
199
|
+
return ToolOutput(id=tool_call.id, result=result, error=error, name=self.name)
|
|
181
200
|
|
|
182
201
|
|
|
183
202
|
class AsyncContextTool(
|
|
@@ -197,13 +216,14 @@ class AsyncContextTool(
|
|
|
197
216
|
cls,
|
|
198
217
|
fn: AsyncContextToolFn[DepsT, AnyP, JsonableCovariantT],
|
|
199
218
|
*,
|
|
200
|
-
strict: bool =
|
|
219
|
+
strict: bool | None = None,
|
|
201
220
|
) -> AsyncContextTool[DepsT, JsonableCovariantT, AnyP]:
|
|
202
221
|
"""Create an `AsyncContextTool` by inspecting a function and its docstring.
|
|
203
222
|
|
|
204
223
|
Args:
|
|
205
224
|
fn: The function to extract schema from
|
|
206
|
-
strict: Whether the tool should use strict mode when supported
|
|
225
|
+
strict: Whether the tool should use strict mode when supported.
|
|
226
|
+
If None, uses provider's default (usually as strict as possible).
|
|
207
227
|
|
|
208
228
|
Returns:
|
|
209
229
|
an `AsyncContextTool` representing the function
|
|
@@ -230,9 +250,14 @@ class AsyncContextTool(
|
|
|
230
250
|
self, ctx: Context[DepsT], tool_call: ToolCall
|
|
231
251
|
) -> ToolOutput[JsonableCovariantT]:
|
|
232
252
|
"""Execute the async context tool using an LLM-provided `ToolCall`."""
|
|
233
|
-
kwargs_from_json = json.loads(tool_call.args)
|
|
234
253
|
kwargs_callable = cast(
|
|
235
254
|
AsyncJsonKwargsCallable[DepsT, JsonableCovariantT], self.fn
|
|
236
255
|
)
|
|
237
|
-
|
|
238
|
-
|
|
256
|
+
error: ToolError | None = None
|
|
257
|
+
try:
|
|
258
|
+
kwargs_from_json = json.loads(tool_call.args)
|
|
259
|
+
result = await kwargs_callable(ctx, **kwargs_from_json)
|
|
260
|
+
except Exception as e:
|
|
261
|
+
result = str(e)
|
|
262
|
+
error = ToolExecutionError(e)
|
|
263
|
+
return ToolOutput(id=tool_call.id, result=result, error=error, name=self.name)
|
mirascope/ops/__init__.py
CHANGED
|
@@ -41,8 +41,10 @@ from ._internal.traced_calls import (
|
|
|
41
41
|
from ._internal.traced_functions import (
|
|
42
42
|
AsyncTrace,
|
|
43
43
|
AsyncTracedFunction,
|
|
44
|
+
AsyncTracedSpanFunction,
|
|
44
45
|
Trace,
|
|
45
46
|
TracedFunction,
|
|
47
|
+
TracedSpanFunction,
|
|
46
48
|
)
|
|
47
49
|
from ._internal.tracing import (
|
|
48
50
|
TraceDecorator,
|
|
@@ -69,6 +71,7 @@ __all__ = [
|
|
|
69
71
|
"SESSION_HEADER_NAME",
|
|
70
72
|
"AsyncTrace",
|
|
71
73
|
"AsyncTracedFunction",
|
|
74
|
+
"AsyncTracedSpanFunction",
|
|
72
75
|
"AsyncVersionedFunction",
|
|
73
76
|
"ClosureComputationError",
|
|
74
77
|
"ContextPropagator",
|
|
@@ -82,6 +85,7 @@ __all__ = [
|
|
|
82
85
|
"TracedCall",
|
|
83
86
|
"TracedContextCall",
|
|
84
87
|
"TracedFunction",
|
|
88
|
+
"TracedSpanFunction",
|
|
85
89
|
"VersionDecorator",
|
|
86
90
|
"VersionInfo",
|
|
87
91
|
"VersionedAsyncCall",
|
|
@@ -2,22 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import logging
|
|
5
6
|
from collections.abc import Iterator
|
|
6
7
|
from contextlib import contextmanager
|
|
7
8
|
from typing import TYPE_CHECKING
|
|
8
9
|
|
|
10
|
+
from opentelemetry import trace as otel_trace
|
|
11
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
12
|
+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
13
|
+
|
|
14
|
+
from ...api.client import Mirascope
|
|
15
|
+
from ...api.settings import update_settings
|
|
16
|
+
from .exporters import MirascopeOTLPExporter
|
|
17
|
+
|
|
9
18
|
if TYPE_CHECKING:
|
|
10
|
-
from opentelemetry.sdk.trace import TracerProvider
|
|
11
19
|
from opentelemetry.trace import Tracer
|
|
12
|
-
else:
|
|
13
|
-
Tracer = None
|
|
14
20
|
|
|
15
21
|
DEFAULT_TRACER_NAME = "mirascope.llm"
|
|
16
22
|
|
|
17
|
-
|
|
18
|
-
from opentelemetry import trace as otel_trace
|
|
19
|
-
except ImportError: # pragma: no cover
|
|
20
|
-
otel_trace = None
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
21
24
|
|
|
22
25
|
_tracer_provider: TracerProvider | None = None
|
|
23
26
|
_tracer_name: str = DEFAULT_TRACER_NAME
|
|
@@ -25,59 +28,107 @@ _tracer_version: str | None = None
|
|
|
25
28
|
_tracer: Tracer | None = None
|
|
26
29
|
|
|
27
30
|
|
|
31
|
+
def _create_mirascope_cloud_provider(
|
|
32
|
+
api_key: str | None = None, base_url: str | None = None
|
|
33
|
+
) -> TracerProvider:
|
|
34
|
+
"""Create a TracerProvider configured for Mirascope Cloud.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
api_key: Optional API key. If not provided, uses MIRASCOPE_API_KEY env var.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Configured TracerProvider with MirascopeOTLPExporter.
|
|
41
|
+
|
|
42
|
+
Raises:
|
|
43
|
+
RuntimeError: If API key is not available.
|
|
44
|
+
"""
|
|
45
|
+
try:
|
|
46
|
+
client = Mirascope(api_key=api_key, base_url=base_url)
|
|
47
|
+
except (ValueError, RuntimeError) as e:
|
|
48
|
+
raise RuntimeError(
|
|
49
|
+
"Failed to create Mirascope Cloud client. "
|
|
50
|
+
"Set MIRASCOPE_API_KEY environment variable or pass api_key parameter."
|
|
51
|
+
) from e
|
|
52
|
+
|
|
53
|
+
exporter = MirascopeOTLPExporter(client=client)
|
|
54
|
+
provider = TracerProvider()
|
|
55
|
+
provider.add_span_processor(BatchSpanProcessor(exporter))
|
|
56
|
+
|
|
57
|
+
return provider
|
|
58
|
+
|
|
59
|
+
|
|
28
60
|
def configure(
|
|
29
61
|
*,
|
|
62
|
+
api_key: str | None = None,
|
|
63
|
+
base_url: str | None = None,
|
|
30
64
|
tracer_provider: TracerProvider | None = None,
|
|
31
65
|
tracer_name: str = DEFAULT_TRACER_NAME,
|
|
32
66
|
tracer_version: str | None = None,
|
|
33
67
|
) -> None:
|
|
34
|
-
"""Configure the ops module
|
|
68
|
+
"""Configure the ops module for tracing.
|
|
35
69
|
|
|
36
|
-
|
|
37
|
-
|
|
70
|
+
When called without arguments, automatically configures Mirascope Cloud
|
|
71
|
+
using the MIRASCOPE_API_KEY environment variable.
|
|
38
72
|
|
|
39
73
|
Args:
|
|
40
|
-
tracer_provider: Optional
|
|
41
|
-
|
|
74
|
+
tracer_provider: Optional custom TracerProvider. If provided, this takes
|
|
75
|
+
precedence over automatic Mirascope Cloud configuration.
|
|
76
|
+
api_key: Optional Mirascope Cloud API key. If not provided, uses
|
|
77
|
+
MIRASCOPE_API_KEY environment variable.
|
|
42
78
|
tracer_name: Tracer name to use when creating a tracer.
|
|
43
79
|
Defaults to "mirascope.llm".
|
|
44
80
|
tracer_version: Optional tracer version.
|
|
45
81
|
|
|
82
|
+
Raises:
|
|
83
|
+
RuntimeError: If no tracer_provider is given and Mirascope Cloud
|
|
84
|
+
cannot be configured (missing API key).
|
|
85
|
+
|
|
46
86
|
Example:
|
|
87
|
+
Simple Mirascope Cloud configuration (recommended):
|
|
88
|
+
```python
|
|
89
|
+
import os
|
|
90
|
+
os.environ["MIRASCOPE_API_KEY"] = "your-api-key"
|
|
47
91
|
|
|
48
|
-
|
|
92
|
+
from mirascope import ops
|
|
93
|
+
|
|
94
|
+
ops.configure() # Automatically uses Mirascope Cloud
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
With explicit API key:
|
|
98
|
+
```python
|
|
99
|
+
from mirascope import ops
|
|
100
|
+
|
|
101
|
+
ops.configure(api_key="your-api-key")
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
With custom tracer provider:
|
|
49
105
|
```python
|
|
50
106
|
from mirascope import ops
|
|
51
107
|
from opentelemetry.sdk.trace import TracerProvider
|
|
52
108
|
|
|
53
109
|
provider = TracerProvider()
|
|
54
110
|
ops.configure(tracer_provider=provider)
|
|
55
|
-
ops.instrument_llm()
|
|
56
111
|
```
|
|
57
112
|
"""
|
|
58
|
-
# TODO: refactor alongside other import error handling improvements
|
|
59
|
-
if otel_trace is None: # pragma: no cover
|
|
60
|
-
raise ImportError(
|
|
61
|
-
"OpenTelemetry is not installed. Run `pip install mirascope[otel]` "
|
|
62
|
-
"before calling `ops.configure(tracer_provider=...)`."
|
|
63
|
-
)
|
|
64
|
-
|
|
65
113
|
global _tracer_provider, _tracer_name, _tracer_version, _tracer
|
|
66
114
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
115
|
+
# Update settings so get_sync_client/get_async_client can use these values
|
|
116
|
+
if api_key is not None or base_url is not None:
|
|
117
|
+
update_settings(api_key=api_key, base_url=base_url)
|
|
118
|
+
|
|
119
|
+
# If no tracer_provider given, auto-configure Mirascope Cloud
|
|
120
|
+
if tracer_provider is None:
|
|
121
|
+
tracer_provider = _create_mirascope_cloud_provider(
|
|
122
|
+
api_key=api_key, base_url=base_url
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
_tracer_provider = tracer_provider
|
|
126
|
+
otel_trace.set_tracer_provider(tracer_provider)
|
|
70
127
|
|
|
71
128
|
_tracer_name = tracer_name
|
|
72
129
|
_tracer_version = tracer_version
|
|
73
130
|
|
|
74
|
-
|
|
75
|
-
provider = (
|
|
76
|
-
otel_trace.get_tracer_provider()
|
|
77
|
-
if _tracer_provider is None
|
|
78
|
-
else _tracer_provider
|
|
79
|
-
)
|
|
80
|
-
_tracer = provider.get_tracer(_tracer_name, _tracer_version)
|
|
131
|
+
_tracer = tracer_provider.get_tracer(_tracer_name, _tracer_version)
|
|
81
132
|
|
|
82
133
|
|
|
83
134
|
def set_tracer(tracer: Tracer | None) -> None:
|
|
@@ -233,24 +233,51 @@ class MirascopeOTLPExporter(SpanExporter):
|
|
|
233
233
|
message=span.status.description or "",
|
|
234
234
|
)
|
|
235
235
|
|
|
236
|
+
# Convert events
|
|
237
|
+
events = None
|
|
238
|
+
if span.events:
|
|
239
|
+
events = []
|
|
240
|
+
for event in span.events:
|
|
241
|
+
event_attrs: list[dict[str, object]] = []
|
|
242
|
+
if event.attributes:
|
|
243
|
+
for key, value in event.attributes.items():
|
|
244
|
+
# Convert to OTLP attribute format with typed values
|
|
245
|
+
attr_value = self._convert_event_attribute_value(value)
|
|
246
|
+
event_attrs.append({"key": key, "value": attr_value})
|
|
247
|
+
events.append(
|
|
248
|
+
{
|
|
249
|
+
"name": event.name,
|
|
250
|
+
"timeUnixNano": str(event.timestamp)
|
|
251
|
+
if event.timestamp
|
|
252
|
+
else None,
|
|
253
|
+
"attributes": event_attrs if event_attrs else None,
|
|
254
|
+
}
|
|
255
|
+
)
|
|
256
|
+
|
|
236
257
|
trace_id = format(context.trace_id, "032x")
|
|
237
258
|
span_id = format(context.span_id, "016x")
|
|
238
259
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
260
|
+
# Build kwargs, only including events if present (to avoid sending null)
|
|
261
|
+
kwargs: dict[str, object] = {
|
|
262
|
+
"trace_id": trace_id,
|
|
263
|
+
"span_id": span_id,
|
|
264
|
+
"parent_span_id": (
|
|
243
265
|
format(span.parent.span_id, "016x")
|
|
244
266
|
if span.parent and span.parent.span_id
|
|
245
267
|
else None
|
|
246
268
|
),
|
|
247
|
-
name
|
|
248
|
-
kind
|
|
249
|
-
start_time_unix_nano
|
|
250
|
-
end_time_unix_nano
|
|
251
|
-
attributes
|
|
252
|
-
status
|
|
253
|
-
|
|
269
|
+
"name": span.name,
|
|
270
|
+
"kind": span.kind.value if span.kind else 0,
|
|
271
|
+
"start_time_unix_nano": str(span.start_time) if span.start_time else "0",
|
|
272
|
+
"end_time_unix_nano": str(span.end_time) if span.end_time else "0",
|
|
273
|
+
"attributes": attributes or None,
|
|
274
|
+
"status": status,
|
|
275
|
+
}
|
|
276
|
+
# Only include events if present (omit entirely to avoid sending null)
|
|
277
|
+
if events:
|
|
278
|
+
kwargs["events"] = events
|
|
279
|
+
|
|
280
|
+
return TracesCreateRequestResourceSpansItemScopeSpansItemSpansItem(**kwargs) # type: ignore[arg-type]
|
|
254
281
|
|
|
255
282
|
def _convert_attribute_value(
|
|
256
283
|
self, value: AttributeValue
|
|
@@ -288,6 +315,32 @@ class MirascopeOTLPExporter(SpanExporter):
|
|
|
288
315
|
string_value=str(list(value))
|
|
289
316
|
)
|
|
290
317
|
|
|
318
|
+
def _convert_event_attribute_value(
|
|
319
|
+
self, value: AttributeValue
|
|
320
|
+
) -> dict[str, object]:
|
|
321
|
+
"""Convert OpenTelemetry AttributeValue to OTLP event attribute value format.
|
|
322
|
+
|
|
323
|
+
This uses the OTLP JSON format with typed value wrappers (e.g., stringValue, intValue).
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
value: An OpenTelemetry AttributeValue (bool, int, float, str, or Sequence)
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
A dict with the typed value (e.g., {"stringValue": "..."})
|
|
330
|
+
"""
|
|
331
|
+
match value:
|
|
332
|
+
case str():
|
|
333
|
+
return {"stringValue": value}
|
|
334
|
+
case bool():
|
|
335
|
+
return {"boolValue": value}
|
|
336
|
+
case int():
|
|
337
|
+
return {"intValue": str(value)}
|
|
338
|
+
case float():
|
|
339
|
+
return {"doubleValue": value}
|
|
340
|
+
case _:
|
|
341
|
+
# Sequences - convert to string representation
|
|
342
|
+
return {"stringValue": str(list(value))}
|
|
343
|
+
|
|
291
344
|
def _convert_resource_attribute_value(
|
|
292
345
|
self, value: AttributeValue
|
|
293
346
|
) -> TracesCreateRequestResourceSpansItemResourceAttributesItemValue:
|