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
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
"""Mirascope-specific serialization for span attributes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from collections.abc import Sequence
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Protocol
|
|
8
|
+
|
|
9
|
+
from .....llm.content import (
|
|
10
|
+
Audio,
|
|
11
|
+
Base64ImageSource,
|
|
12
|
+
Document,
|
|
13
|
+
Image,
|
|
14
|
+
Text,
|
|
15
|
+
Thought,
|
|
16
|
+
ToolCall,
|
|
17
|
+
ToolOutput,
|
|
18
|
+
)
|
|
19
|
+
from .....llm.content.document import Base64DocumentSource, TextDocumentSource
|
|
20
|
+
from .....llm.messages import AssistantMessage, Message, SystemMessage, UserMessage
|
|
21
|
+
from .....llm.responses.usage import Usage
|
|
22
|
+
from ...utils import json_dumps
|
|
23
|
+
from .cost import calculate_cost_async, calculate_cost_sync
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from opentelemetry.util.types import AttributeValue
|
|
27
|
+
|
|
28
|
+
from .....llm.responses.root_response import RootResponse
|
|
29
|
+
from .....llm.types import Jsonable
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class SpanProtocol(Protocol):
|
|
35
|
+
"""Protocol for span objects that support setting attributes."""
|
|
36
|
+
|
|
37
|
+
def set(self, **attributes: AttributeValue) -> None:
|
|
38
|
+
"""Set attributes on the span."""
|
|
39
|
+
...
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _serialize_content_part(
|
|
43
|
+
part: Text | ToolCall | Thought | Image | Audio | Document | ToolOutput[Jsonable],
|
|
44
|
+
) -> dict[str, Any]:
|
|
45
|
+
"""Serialize a single content part to a dict matching the Mirascope dataclass structure."""
|
|
46
|
+
if isinstance(part, Text):
|
|
47
|
+
return {"type": "text", "text": part.text}
|
|
48
|
+
elif isinstance(part, ToolCall):
|
|
49
|
+
return {
|
|
50
|
+
"type": "tool_call",
|
|
51
|
+
"id": part.id,
|
|
52
|
+
"name": part.name,
|
|
53
|
+
"args": part.args,
|
|
54
|
+
}
|
|
55
|
+
elif isinstance(part, Thought):
|
|
56
|
+
return {"type": "thought", "thought": part.thought}
|
|
57
|
+
elif isinstance(part, ToolOutput):
|
|
58
|
+
return {
|
|
59
|
+
"type": "tool_output",
|
|
60
|
+
"id": part.id,
|
|
61
|
+
"name": part.name,
|
|
62
|
+
"result": part.result,
|
|
63
|
+
}
|
|
64
|
+
elif isinstance(part, Image):
|
|
65
|
+
if isinstance(part.source, Base64ImageSource):
|
|
66
|
+
return {
|
|
67
|
+
"type": "image",
|
|
68
|
+
"source": {
|
|
69
|
+
"type": "base64_image_source",
|
|
70
|
+
"mime_type": part.source.mime_type,
|
|
71
|
+
"data": part.source.data,
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
else: # URLImageSource
|
|
75
|
+
return {
|
|
76
|
+
"type": "image",
|
|
77
|
+
"source": {"type": "url_image_source", "url": part.source.url},
|
|
78
|
+
}
|
|
79
|
+
elif isinstance(part, Audio):
|
|
80
|
+
return {
|
|
81
|
+
"type": "audio",
|
|
82
|
+
"source": {
|
|
83
|
+
"type": "base64_audio_source",
|
|
84
|
+
"mime_type": part.source.mime_type,
|
|
85
|
+
"data": part.source.data,
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
elif isinstance(part, Document):
|
|
89
|
+
# Document has multiple source types - serialize based on actual type
|
|
90
|
+
if isinstance(part.source, Base64DocumentSource):
|
|
91
|
+
return {
|
|
92
|
+
"type": "document",
|
|
93
|
+
"source": {
|
|
94
|
+
"type": "base64_document_source",
|
|
95
|
+
"data": part.source.data,
|
|
96
|
+
"media_type": part.source.media_type,
|
|
97
|
+
},
|
|
98
|
+
}
|
|
99
|
+
elif isinstance(part.source, TextDocumentSource):
|
|
100
|
+
return {
|
|
101
|
+
"type": "document",
|
|
102
|
+
"source": {
|
|
103
|
+
"type": "text_document_source",
|
|
104
|
+
"data": part.source.data,
|
|
105
|
+
"media_type": part.source.media_type,
|
|
106
|
+
},
|
|
107
|
+
}
|
|
108
|
+
else: # URLDocumentSource
|
|
109
|
+
return {
|
|
110
|
+
"type": "document",
|
|
111
|
+
"source": {
|
|
112
|
+
"type": "url_document_source",
|
|
113
|
+
"url": part.source.url,
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
return {"type": "unknown"} # pragma: no cover
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _serialize_message(message: Message) -> dict[str, Any]:
|
|
120
|
+
"""Serialize a Message to a dict matching the Mirascope dataclass structure."""
|
|
121
|
+
if isinstance(message, SystemMessage):
|
|
122
|
+
return {
|
|
123
|
+
"role": "system",
|
|
124
|
+
"content": _serialize_content_part(message.content),
|
|
125
|
+
}
|
|
126
|
+
elif isinstance(message, UserMessage):
|
|
127
|
+
return {
|
|
128
|
+
"role": "user",
|
|
129
|
+
"content": [_serialize_content_part(p) for p in message.content],
|
|
130
|
+
"name": message.name,
|
|
131
|
+
}
|
|
132
|
+
elif isinstance(message, AssistantMessage):
|
|
133
|
+
return {
|
|
134
|
+
"role": "assistant",
|
|
135
|
+
"content": [_serialize_content_part(p) for p in message.content],
|
|
136
|
+
"name": message.name,
|
|
137
|
+
}
|
|
138
|
+
return {"role": "unknown"} # pragma: no cover
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def serialize_mirascope_messages(messages: Sequence[Message]) -> str:
|
|
142
|
+
"""Serialize input messages to JSON for span attributes."""
|
|
143
|
+
return json_dumps([_serialize_message(m) for m in messages])
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def serialize_mirascope_content(
|
|
147
|
+
content: Sequence[Text | ToolCall | Thought],
|
|
148
|
+
) -> str:
|
|
149
|
+
"""Serialize response content to JSON for span attributes."""
|
|
150
|
+
return json_dumps([_serialize_content_part(p) for p in content])
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def serialize_mirascope_usage(usage: Usage | None) -> AttributeValue | None:
|
|
154
|
+
"""Serialize response usage to JSON for span attributes. Returns None if usage is None."""
|
|
155
|
+
if usage is None:
|
|
156
|
+
return None
|
|
157
|
+
return json_dumps(
|
|
158
|
+
{
|
|
159
|
+
"input_tokens": usage.input_tokens,
|
|
160
|
+
"output_tokens": usage.output_tokens,
|
|
161
|
+
"cache_read_tokens": usage.cache_read_tokens,
|
|
162
|
+
"cache_write_tokens": usage.cache_write_tokens,
|
|
163
|
+
"reasoning_tokens": usage.reasoning_tokens,
|
|
164
|
+
"total_tokens": usage.total_tokens,
|
|
165
|
+
}
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def serialize_mirascope_cost(
|
|
170
|
+
input_cost: float,
|
|
171
|
+
output_cost: float,
|
|
172
|
+
total_cost: float,
|
|
173
|
+
cache_read_cost: float | None = None,
|
|
174
|
+
cache_write_cost: float | None = None,
|
|
175
|
+
) -> str:
|
|
176
|
+
"""Serialize cost to JSON for span attributes.
|
|
177
|
+
|
|
178
|
+
All costs are in centicents (1 centicent = $0.0001).
|
|
179
|
+
Consumers can divide by 10,000 to get dollar amounts.
|
|
180
|
+
"""
|
|
181
|
+
return json_dumps(
|
|
182
|
+
{
|
|
183
|
+
"input_cost": input_cost,
|
|
184
|
+
"output_cost": output_cost,
|
|
185
|
+
"cache_read_cost": cache_read_cost,
|
|
186
|
+
"cache_write_cost": cache_write_cost,
|
|
187
|
+
"total_cost": total_cost,
|
|
188
|
+
}
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def attach_mirascope_response(
|
|
193
|
+
span: SpanProtocol, response: RootResponse[Any, Any]
|
|
194
|
+
) -> None:
|
|
195
|
+
"""Attach Mirascope-specific response attributes to a span.
|
|
196
|
+
|
|
197
|
+
Sets the following attributes:
|
|
198
|
+
- mirascope.trace.output: Pretty-printed response
|
|
199
|
+
- mirascope.response.messages: Serialized input messages (excluding final assistant message)
|
|
200
|
+
- mirascope.response.content: Serialized response content
|
|
201
|
+
- mirascope.response.usage: Serialized usage (if available)
|
|
202
|
+
- mirascope.response.cost: Serialized cost (if available)
|
|
203
|
+
"""
|
|
204
|
+
span.set(
|
|
205
|
+
**{
|
|
206
|
+
"mirascope.response.provider_id": response.provider_id,
|
|
207
|
+
"mirascope.response.model_id": response.model_id,
|
|
208
|
+
"mirascope.trace.output": response.pretty(),
|
|
209
|
+
"mirascope.response.messages": serialize_mirascope_messages(
|
|
210
|
+
response.messages[:-1]
|
|
211
|
+
),
|
|
212
|
+
"mirascope.response.content": serialize_mirascope_content(response.content),
|
|
213
|
+
}
|
|
214
|
+
)
|
|
215
|
+
if (usage_json := serialize_mirascope_usage(response.usage)) is not None:
|
|
216
|
+
span.set(**{"mirascope.response.usage": usage_json})
|
|
217
|
+
logger.debug("Attached usage to span")
|
|
218
|
+
else:
|
|
219
|
+
logger.debug("No usage available, skipping cost calculation")
|
|
220
|
+
|
|
221
|
+
# Calculate and attach cost if usage is available
|
|
222
|
+
if response.usage is not None:
|
|
223
|
+
logger.debug("Attempting cost calculation (sync)")
|
|
224
|
+
cost = calculate_cost_sync(
|
|
225
|
+
response.provider_id, response.model_id, response.usage
|
|
226
|
+
)
|
|
227
|
+
if cost is not None:
|
|
228
|
+
span.set(
|
|
229
|
+
**{
|
|
230
|
+
"mirascope.response.cost": serialize_mirascope_cost(
|
|
231
|
+
input_cost=cost.input_cost_centicents,
|
|
232
|
+
output_cost=cost.output_cost_centicents,
|
|
233
|
+
total_cost=cost.total_cost_centicents,
|
|
234
|
+
cache_read_cost=cost.cache_read_cost_centicents,
|
|
235
|
+
cache_write_cost=cost.cache_write_cost_centicents,
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
)
|
|
239
|
+
logger.debug(
|
|
240
|
+
"Attached cost to span: total=%s centicents", cost.total_cost_centicents
|
|
241
|
+
)
|
|
242
|
+
else:
|
|
243
|
+
logger.debug("Cost calculation returned None, not attaching cost to span")
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
async def attach_mirascope_response_async(
|
|
247
|
+
span: SpanProtocol, response: RootResponse[Any, Any]
|
|
248
|
+
) -> None:
|
|
249
|
+
"""Attach Mirascope-specific response attributes to a span (async version).
|
|
250
|
+
|
|
251
|
+
Sets the following attributes:
|
|
252
|
+
- mirascope.trace.output: Pretty-printed response
|
|
253
|
+
- mirascope.response.messages: Serialized input messages (excluding final assistant message)
|
|
254
|
+
- mirascope.response.content: Serialized response content
|
|
255
|
+
- mirascope.response.usage: Serialized usage (if available)
|
|
256
|
+
- mirascope.response.cost: Serialized cost (if available)
|
|
257
|
+
"""
|
|
258
|
+
span.set(
|
|
259
|
+
**{
|
|
260
|
+
"mirascope.response.provider_id": response.provider_id,
|
|
261
|
+
"mirascope.response.model_id": response.model_id,
|
|
262
|
+
"mirascope.trace.output": response.pretty(),
|
|
263
|
+
"mirascope.response.messages": serialize_mirascope_messages(
|
|
264
|
+
response.messages[:-1]
|
|
265
|
+
),
|
|
266
|
+
"mirascope.response.content": serialize_mirascope_content(response.content),
|
|
267
|
+
}
|
|
268
|
+
)
|
|
269
|
+
if (usage_json := serialize_mirascope_usage(response.usage)) is not None:
|
|
270
|
+
span.set(**{"mirascope.response.usage": usage_json})
|
|
271
|
+
logger.debug("Attached usage to span (async)")
|
|
272
|
+
else:
|
|
273
|
+
logger.debug("No usage available, skipping cost calculation (async)")
|
|
274
|
+
|
|
275
|
+
# Calculate and attach cost if usage is available (async)
|
|
276
|
+
if response.usage is not None:
|
|
277
|
+
logger.debug("Attempting cost calculation (async)")
|
|
278
|
+
cost = await calculate_cost_async(
|
|
279
|
+
response.provider_id, response.model_id, response.usage
|
|
280
|
+
)
|
|
281
|
+
if cost is not None:
|
|
282
|
+
span.set(
|
|
283
|
+
**{
|
|
284
|
+
"mirascope.response.cost": serialize_mirascope_cost(
|
|
285
|
+
input_cost=cost.input_cost_centicents,
|
|
286
|
+
output_cost=cost.output_cost_centicents,
|
|
287
|
+
total_cost=cost.total_cost_centicents,
|
|
288
|
+
cache_read_cost=cost.cache_read_cost_centicents,
|
|
289
|
+
cache_write_cost=cost.cache_write_cost_centicents,
|
|
290
|
+
)
|
|
291
|
+
}
|
|
292
|
+
)
|
|
293
|
+
logger.debug(
|
|
294
|
+
"Attached cost to span (async): total=%s centicents",
|
|
295
|
+
cost.total_cost_centicents,
|
|
296
|
+
)
|
|
297
|
+
else:
|
|
298
|
+
logger.debug(
|
|
299
|
+
"Cost calculation returned None, not attaching cost to span (async)"
|
|
300
|
+
)
|
|
@@ -3,12 +3,15 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import inspect
|
|
6
|
-
from typing import Protocol
|
|
6
|
+
from typing import TYPE_CHECKING, Protocol
|
|
7
7
|
from typing_extensions import TypeIs
|
|
8
8
|
|
|
9
9
|
from ...llm.context import Context, DepsT
|
|
10
10
|
from .types import P, R
|
|
11
11
|
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from .spans import Span
|
|
14
|
+
|
|
12
15
|
|
|
13
16
|
class SyncFunction(Protocol[P, R]):
|
|
14
17
|
"""Protocol for synchronous callable functions."""
|
|
@@ -26,6 +29,30 @@ class AsyncFunction(Protocol[P, R]):
|
|
|
26
29
|
... # pragma: no cover
|
|
27
30
|
|
|
28
31
|
|
|
32
|
+
class SyncSpanFunction(Protocol[P, R]):
|
|
33
|
+
"""Protocol for synchronous functions that receive injected Span.
|
|
34
|
+
|
|
35
|
+
Functions matching this protocol have `trace_ctx: Span` as their first
|
|
36
|
+
parameter. The trace decorator will inject the span automatically.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __call__(self, trace_ctx: Span, *args: P.args, **kwargs: P.kwargs) -> R:
|
|
40
|
+
"""The function receives a Span as first parameter."""
|
|
41
|
+
... # pragma: no cover
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class AsyncSpanFunction(Protocol[P, R]):
|
|
45
|
+
"""Protocol for asynchronous functions that receive injected Span.
|
|
46
|
+
|
|
47
|
+
Functions matching this protocol have `trace_ctx: Span` as their first
|
|
48
|
+
parameter. The trace decorator will inject the span automatically.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
async def __call__(self, trace_ctx: Span, *args: P.args, **kwargs: P.kwargs) -> R:
|
|
52
|
+
"""The function receives a Span as first parameter."""
|
|
53
|
+
... # pragma: no cover
|
|
54
|
+
|
|
55
|
+
|
|
29
56
|
class SyncContextFunction(Protocol[P, DepsT, R]):
|
|
30
57
|
"""Protocol for synchronous callable functions with Context parameter."""
|
|
31
58
|
|
|
@@ -49,3 +76,58 @@ def fn_is_async(
|
|
|
49
76
|
) -> TypeIs[AsyncFunction[P, R]]:
|
|
50
77
|
"""Type check to determine if a given function is asynchronous."""
|
|
51
78
|
return inspect.iscoroutinefunction(fn) or inspect.isasyncgenfunction(fn)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def fn_wants_span(
|
|
82
|
+
fn: (
|
|
83
|
+
SyncFunction[P, R]
|
|
84
|
+
| AsyncFunction[P, R]
|
|
85
|
+
| SyncSpanFunction[P, R]
|
|
86
|
+
| AsyncSpanFunction[P, R]
|
|
87
|
+
),
|
|
88
|
+
) -> TypeIs[SyncSpanFunction[P, R] | AsyncSpanFunction[P, R]]:
|
|
89
|
+
"""Check if function wants Span injection as first parameter.
|
|
90
|
+
|
|
91
|
+
Returns True if the function has a first parameter named `trace_ctx`
|
|
92
|
+
with type annotation `Span`.
|
|
93
|
+
"""
|
|
94
|
+
# Import here to avoid circular imports
|
|
95
|
+
from .spans import Span
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
sig = inspect.signature(fn)
|
|
99
|
+
except (ValueError, TypeError):
|
|
100
|
+
return False
|
|
101
|
+
|
|
102
|
+
params = list(sig.parameters.values())
|
|
103
|
+
if not params:
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
first_param = params[0]
|
|
107
|
+
if first_param.name != "trace_ctx":
|
|
108
|
+
return False
|
|
109
|
+
|
|
110
|
+
# Check annotation
|
|
111
|
+
annotation = first_param.annotation
|
|
112
|
+
if annotation is inspect.Parameter.empty:
|
|
113
|
+
return False
|
|
114
|
+
|
|
115
|
+
# Handle string annotations (forward references)
|
|
116
|
+
# The annotation could be "Span", "ops.Span", "mirascope.ops.Span", etc.
|
|
117
|
+
if isinstance(annotation, str):
|
|
118
|
+
return annotation == "Span" or annotation.endswith(".Span")
|
|
119
|
+
|
|
120
|
+
# Check by identity first
|
|
121
|
+
if annotation is Span:
|
|
122
|
+
return True
|
|
123
|
+
|
|
124
|
+
# Fallback: check by class name and module for robustness
|
|
125
|
+
# This handles cases where the same class might have different identities
|
|
126
|
+
# due to module reloading or import issues in test environments
|
|
127
|
+
if isinstance(annotation, type):
|
|
128
|
+
return (
|
|
129
|
+
annotation.__name__ == "Span"
|
|
130
|
+
and annotation.__module__ == "mirascope.ops._internal.spans"
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
return False
|
|
@@ -6,6 +6,7 @@ from dataclasses import dataclass, field
|
|
|
6
6
|
from typing import Any, Generic, TypeVar
|
|
7
7
|
from typing_extensions import TypeIs
|
|
8
8
|
|
|
9
|
+
from ..._utils import copy_function_metadata
|
|
9
10
|
from ...llm.calls import AsyncCall, AsyncContextCall, Call, ContextCall
|
|
10
11
|
from ...llm.context import Context, DepsT
|
|
11
12
|
from ...llm.formatting import FormattableT
|
|
@@ -22,8 +23,10 @@ from ...llm.responses import (
|
|
|
22
23
|
from ...llm.types import P
|
|
23
24
|
from .protocols import (
|
|
24
25
|
AsyncFunction,
|
|
26
|
+
AsyncSpanFunction,
|
|
25
27
|
R,
|
|
26
28
|
SyncFunction,
|
|
29
|
+
SyncSpanFunction,
|
|
27
30
|
)
|
|
28
31
|
from .traced_functions import (
|
|
29
32
|
AsyncTrace,
|
|
@@ -33,6 +36,7 @@ from .traced_functions import (
|
|
|
33
36
|
TracedContextFunction,
|
|
34
37
|
TracedFunction,
|
|
35
38
|
)
|
|
39
|
+
from .utils import get_original_fn
|
|
36
40
|
|
|
37
41
|
CallT = TypeVar(
|
|
38
42
|
"CallT",
|
|
@@ -47,6 +51,8 @@ def is_call_type(
|
|
|
47
51
|
fn: (
|
|
48
52
|
SyncFunction[P, R]
|
|
49
53
|
| AsyncFunction[P, R]
|
|
54
|
+
| SyncSpanFunction[P, R]
|
|
55
|
+
| AsyncSpanFunction[P, R]
|
|
50
56
|
| ContextCall[P, DepsT, FormattableT]
|
|
51
57
|
| AsyncContextCall[P, DepsT, FormattableT]
|
|
52
58
|
| Call[P, FormattableT]
|
|
@@ -102,6 +108,14 @@ class _BaseTracedCall(Generic[CallT]):
|
|
|
102
108
|
metadata: dict[str, str] = field(default_factory=dict)
|
|
103
109
|
"""Arbitrary key-value pairs for additional metadata."""
|
|
104
110
|
|
|
111
|
+
__name__: str = field(init=False, repr=False, default="")
|
|
112
|
+
"""The name of the underlying function (preserved for decorator stacking)."""
|
|
113
|
+
|
|
114
|
+
def __post_init__(self) -> None:
|
|
115
|
+
"""Preserve standard function attributes for decorator stacking."""
|
|
116
|
+
original_fn = get_original_fn(self._call.prompt.fn)
|
|
117
|
+
copy_function_metadata(self, original_fn)
|
|
118
|
+
|
|
105
119
|
|
|
106
120
|
@dataclass(kw_only=True)
|
|
107
121
|
class TracedCall(_BaseTracedCall[Call[P, FormattableT]]):
|
|
@@ -149,6 +163,7 @@ class TracedCall(_BaseTracedCall[Call[P, FormattableT]]):
|
|
|
149
163
|
|
|
150
164
|
def __post_init__(self) -> None:
|
|
151
165
|
"""Initialize TracedFunction wrappers for call and stream methods."""
|
|
166
|
+
super().__post_init__()
|
|
152
167
|
self.call = TracedFunction(
|
|
153
168
|
fn=self._call.call, tags=self.tags, metadata=self.metadata
|
|
154
169
|
)
|
|
@@ -209,6 +224,7 @@ class TracedAsyncCall(_BaseTracedCall[AsyncCall[P, FormattableT]]):
|
|
|
209
224
|
|
|
210
225
|
def __post_init__(self) -> None:
|
|
211
226
|
"""Initialize AsyncTracedFunction wrappers for call and stream methods."""
|
|
227
|
+
super().__post_init__()
|
|
212
228
|
self.call = AsyncTracedFunction(
|
|
213
229
|
fn=self._call.call, tags=self.tags, metadata=self.metadata
|
|
214
230
|
)
|
|
@@ -272,6 +288,7 @@ class TracedContextCall(_BaseTracedCall[ContextCall[P, DepsT, FormattableT]]):
|
|
|
272
288
|
|
|
273
289
|
def __post_init__(self) -> None:
|
|
274
290
|
"""Initialize TracedContextFunction wrappers for call and stream methods."""
|
|
291
|
+
super().__post_init__()
|
|
275
292
|
self.call = TracedContextFunction(
|
|
276
293
|
fn=self._call.call, tags=self.tags, metadata=self.metadata
|
|
277
294
|
)
|
|
@@ -340,6 +357,7 @@ class TracedAsyncContextCall(_BaseTracedCall[AsyncContextCall[P, DepsT, Formatta
|
|
|
340
357
|
|
|
341
358
|
def __post_init__(self) -> None:
|
|
342
359
|
"""Initialize AsyncTracedContextFunction wrappers for call and stream methods."""
|
|
360
|
+
super().__post_init__()
|
|
343
361
|
self.call = AsyncTracedContextFunction(
|
|
344
362
|
fn=self._call.call, tags=self.tags, metadata=self.metadata
|
|
345
363
|
)
|
|
@@ -6,27 +6,32 @@ from abc import ABC, abstractmethod
|
|
|
6
6
|
from collections.abc import Generator
|
|
7
7
|
from contextlib import contextmanager
|
|
8
8
|
from dataclasses import dataclass, field
|
|
9
|
-
from typing import
|
|
10
|
-
Any,
|
|
11
|
-
Generic,
|
|
12
|
-
Literal,
|
|
13
|
-
TypeVar,
|
|
14
|
-
)
|
|
9
|
+
from typing import Any, Generic, Literal, TypeVar
|
|
15
10
|
|
|
16
11
|
from opentelemetry.util.types import AttributeValue
|
|
17
12
|
|
|
13
|
+
from ..._utils import copy_function_metadata
|
|
18
14
|
from ...api.client import get_async_client, get_sync_client
|
|
19
15
|
from ...llm.context import Context, DepsT
|
|
20
16
|
from ...llm.responses.root_response import RootResponse
|
|
17
|
+
from .instrumentation.llm.serialize import attach_mirascope_response
|
|
21
18
|
from .protocols import (
|
|
22
19
|
AsyncContextFunction,
|
|
23
20
|
AsyncFunction,
|
|
21
|
+
AsyncSpanFunction,
|
|
24
22
|
SyncContextFunction,
|
|
25
23
|
SyncFunction,
|
|
24
|
+
SyncSpanFunction,
|
|
26
25
|
)
|
|
27
26
|
from .spans import Span
|
|
28
27
|
from .types import Jsonable, P, R
|
|
29
|
-
from .utils import
|
|
28
|
+
from .utils import (
|
|
29
|
+
PrimitiveType,
|
|
30
|
+
extract_arguments,
|
|
31
|
+
get_original_fn,
|
|
32
|
+
get_qualified_name,
|
|
33
|
+
json_dumps,
|
|
34
|
+
)
|
|
30
35
|
|
|
31
36
|
FunctionT = TypeVar(
|
|
32
37
|
"FunctionT",
|
|
@@ -47,6 +52,7 @@ def record_result_to_span(span: Span, result: object) -> None:
|
|
|
47
52
|
output: str | int | float | bool = result
|
|
48
53
|
elif isinstance(result, RootResponse):
|
|
49
54
|
output = result.pretty()
|
|
55
|
+
attach_mirascope_response(span, result)
|
|
50
56
|
else:
|
|
51
57
|
try:
|
|
52
58
|
output = json_dumps(result)
|
|
@@ -170,10 +176,15 @@ class _BaseFunction(Generic[P, R, FunctionT], ABC):
|
|
|
170
176
|
_is_async: bool = field(init=False)
|
|
171
177
|
"""Whether the wrapped function is asynchronous."""
|
|
172
178
|
|
|
179
|
+
__name__: str = field(init=False, repr=False, default="")
|
|
180
|
+
"""The name of the underlying function (preserved for decorator stacking)."""
|
|
181
|
+
|
|
173
182
|
def __post_init__(self) -> None:
|
|
174
183
|
"""Initialize additional attributes after dataclass init."""
|
|
175
184
|
self._qualified_name = get_qualified_name(self.fn)
|
|
176
|
-
|
|
185
|
+
original_fn = get_original_fn(self.fn)
|
|
186
|
+
self._module_name = getattr(original_fn, "__module__", "")
|
|
187
|
+
copy_function_metadata(self, original_fn)
|
|
177
188
|
|
|
178
189
|
|
|
179
190
|
@dataclass(kw_only=True)
|
|
@@ -194,7 +205,7 @@ class _BaseTracedFunction(_BaseFunction[P, R, FunctionT]):
|
|
|
194
205
|
"mirascope.trace.arg_values": json_dumps(arg_values),
|
|
195
206
|
}
|
|
196
207
|
if self.tags:
|
|
197
|
-
attributes["mirascope.trace.tags"] = self.tags
|
|
208
|
+
attributes["mirascope.trace.tags"] = list(self.tags)
|
|
198
209
|
if self.metadata:
|
|
199
210
|
attributes["mirascope.trace.metadata"] = json_dumps(self.metadata)
|
|
200
211
|
span.set(**attributes)
|
|
@@ -308,7 +319,7 @@ class _BaseTracedContextFunction(
|
|
|
308
319
|
"mirascope.trace.arg_values": json_dumps(arg_values),
|
|
309
320
|
}
|
|
310
321
|
if self.tags:
|
|
311
|
-
attributes["mirascope.trace.tags"] = self.tags
|
|
322
|
+
attributes["mirascope.trace.tags"] = list(self.tags)
|
|
312
323
|
if self.metadata:
|
|
313
324
|
attributes["mirascope.trace.metadata"] = json_dumps(self.metadata)
|
|
314
325
|
span.set(**attributes)
|
|
@@ -411,3 +422,107 @@ class AsyncTracedContextFunction(BaseTracedAsyncContextFunction[P, DepsT, R]):
|
|
|
411
422
|
result = await self.fn(ctx, *args, **kwargs)
|
|
412
423
|
record_result_to_span(span, result)
|
|
413
424
|
return AsyncTrace(result=result, span=span)
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
@dataclass(kw_only=True)
|
|
428
|
+
class BaseSyncTracedSpanFunction(_BaseTracedFunction[P, R, SyncSpanFunction[P, R]]):
|
|
429
|
+
"""Abstract base class for synchronous traced span function wrappers."""
|
|
430
|
+
|
|
431
|
+
_is_async: bool = field(default=False, init=False)
|
|
432
|
+
"""Whether the wrapped function is asynchronous."""
|
|
433
|
+
|
|
434
|
+
@abstractmethod
|
|
435
|
+
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
|
|
436
|
+
"""Returns the result of the traced function directly."""
|
|
437
|
+
...
|
|
438
|
+
|
|
439
|
+
@abstractmethod
|
|
440
|
+
def wrapped(self, *args: P.args, **kwargs: P.kwargs) -> Trace[R]:
|
|
441
|
+
"""Returns the trace containing the function result and span info."""
|
|
442
|
+
...
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
@dataclass(kw_only=True)
|
|
446
|
+
class TracedSpanFunction(BaseSyncTracedSpanFunction[P, R]):
|
|
447
|
+
"""Wrapper for synchronous functions that receive Span as first parameter.
|
|
448
|
+
|
|
449
|
+
The external interface does NOT include `trace_ctx` - it is injected
|
|
450
|
+
automatically by the decorator when calling the inner function.
|
|
451
|
+
|
|
452
|
+
Example:
|
|
453
|
+
```python
|
|
454
|
+
@ops.trace
|
|
455
|
+
def my_fn(trace_ctx: Span, arg: str) -> str:
|
|
456
|
+
trace_ctx.info(f"Processing: {arg}")
|
|
457
|
+
return arg.upper()
|
|
458
|
+
|
|
459
|
+
# Call without trace_ctx - it's injected
|
|
460
|
+
result = my_fn("hello") # Returns "HELLO"
|
|
461
|
+
```
|
|
462
|
+
"""
|
|
463
|
+
|
|
464
|
+
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
|
|
465
|
+
"""Returns the result of the traced function directly."""
|
|
466
|
+
with self._span(*args, **kwargs) as span:
|
|
467
|
+
result = self.fn(span, *args, **kwargs)
|
|
468
|
+
record_result_to_span(span, result)
|
|
469
|
+
return result
|
|
470
|
+
|
|
471
|
+
def wrapped(self, *args: P.args, **kwargs: P.kwargs) -> Trace[R]:
|
|
472
|
+
"""Returns the trace containing the function result and span info."""
|
|
473
|
+
with self._span(*args, **kwargs) as span:
|
|
474
|
+
result = self.fn(span, *args, **kwargs)
|
|
475
|
+
record_result_to_span(span, result)
|
|
476
|
+
return Trace(result=result, span=span)
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
@dataclass(kw_only=True)
|
|
480
|
+
class BaseAsyncTracedSpanFunction(_BaseTracedFunction[P, R, AsyncSpanFunction[P, R]]):
|
|
481
|
+
"""Abstract base class for asynchronous traced span function wrappers."""
|
|
482
|
+
|
|
483
|
+
_is_async: bool = field(default=True, init=False)
|
|
484
|
+
"""Whether the wrapped function is asynchronous."""
|
|
485
|
+
|
|
486
|
+
@abstractmethod
|
|
487
|
+
async def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
|
|
488
|
+
"""Returns the result of the traced function directly."""
|
|
489
|
+
...
|
|
490
|
+
|
|
491
|
+
@abstractmethod
|
|
492
|
+
async def wrapped(self, *args: P.args, **kwargs: P.kwargs) -> AsyncTrace[R]:
|
|
493
|
+
"""Returns the trace containing the function result and span info."""
|
|
494
|
+
...
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
@dataclass(kw_only=True)
|
|
498
|
+
class AsyncTracedSpanFunction(BaseAsyncTracedSpanFunction[P, R]):
|
|
499
|
+
"""Wrapper for asynchronous functions that receive Span as first parameter.
|
|
500
|
+
|
|
501
|
+
The external interface does NOT include `trace_ctx` - it is injected
|
|
502
|
+
automatically by the decorator when calling the inner function.
|
|
503
|
+
|
|
504
|
+
Example:
|
|
505
|
+
```python
|
|
506
|
+
@ops.trace
|
|
507
|
+
async def my_fn(trace_ctx: Span, arg: str) -> str:
|
|
508
|
+
trace_ctx.info(f"Processing: {arg}")
|
|
509
|
+
return arg.upper()
|
|
510
|
+
|
|
511
|
+
# Call without trace_ctx - it's injected
|
|
512
|
+
result = await my_fn("hello") # Returns "HELLO"
|
|
513
|
+
```
|
|
514
|
+
"""
|
|
515
|
+
|
|
516
|
+
async def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
|
|
517
|
+
"""Returns the result of the traced function directly."""
|
|
518
|
+
with self._span(*args, **kwargs) as span:
|
|
519
|
+
result = await self.fn(span, *args, **kwargs)
|
|
520
|
+
record_result_to_span(span, result)
|
|
521
|
+
return result
|
|
522
|
+
|
|
523
|
+
async def wrapped(self, *args: P.args, **kwargs: P.kwargs) -> AsyncTrace[R]:
|
|
524
|
+
"""Returns the trace containing the function result and span info."""
|
|
525
|
+
with self._span(*args, **kwargs) as span:
|
|
526
|
+
result = await self.fn(span, *args, **kwargs)
|
|
527
|
+
record_result_to_span(span, result)
|
|
528
|
+
return AsyncTrace(result=result, span=span)
|