mirascope 2.0.0a2__py3-none-any.whl → 2.0.0a3__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.
Files changed (204) hide show
  1. mirascope/__init__.py +2 -2
  2. mirascope/api/__init__.py +6 -0
  3. mirascope/api/_generated/README.md +207 -0
  4. mirascope/api/_generated/__init__.py +85 -0
  5. mirascope/api/_generated/client.py +155 -0
  6. mirascope/api/_generated/core/__init__.py +52 -0
  7. mirascope/api/_generated/core/api_error.py +23 -0
  8. mirascope/api/_generated/core/client_wrapper.py +58 -0
  9. mirascope/api/_generated/core/datetime_utils.py +30 -0
  10. mirascope/api/_generated/core/file.py +70 -0
  11. mirascope/api/_generated/core/force_multipart.py +16 -0
  12. mirascope/api/_generated/core/http_client.py +619 -0
  13. mirascope/api/_generated/core/http_response.py +55 -0
  14. mirascope/api/_generated/core/jsonable_encoder.py +102 -0
  15. mirascope/api/_generated/core/pydantic_utilities.py +310 -0
  16. mirascope/api/_generated/core/query_encoder.py +60 -0
  17. mirascope/api/_generated/core/remove_none_from_dict.py +11 -0
  18. mirascope/api/_generated/core/request_options.py +35 -0
  19. mirascope/api/_generated/core/serialization.py +282 -0
  20. mirascope/api/_generated/docs/__init__.py +4 -0
  21. mirascope/api/_generated/docs/client.py +95 -0
  22. mirascope/api/_generated/docs/raw_client.py +132 -0
  23. mirascope/api/_generated/environment.py +9 -0
  24. mirascope/api/_generated/errors/__init__.py +7 -0
  25. mirascope/api/_generated/errors/bad_request_error.py +15 -0
  26. mirascope/api/_generated/health/__init__.py +7 -0
  27. mirascope/api/_generated/health/client.py +96 -0
  28. mirascope/api/_generated/health/raw_client.py +129 -0
  29. mirascope/api/_generated/health/types/__init__.py +8 -0
  30. mirascope/api/_generated/health/types/health_check_response.py +24 -0
  31. mirascope/api/_generated/health/types/health_check_response_status.py +5 -0
  32. mirascope/api/_generated/reference.md +167 -0
  33. mirascope/api/_generated/traces/__init__.py +55 -0
  34. mirascope/api/_generated/traces/client.py +162 -0
  35. mirascope/api/_generated/traces/raw_client.py +168 -0
  36. mirascope/api/_generated/traces/types/__init__.py +95 -0
  37. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item.py +36 -0
  38. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource.py +31 -0
  39. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item.py +25 -0
  40. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value.py +54 -0
  41. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_array_value.py +23 -0
  42. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_kvlist_value.py +28 -0
  43. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_kvlist_value_values_item.py +24 -0
  44. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item.py +35 -0
  45. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope.py +35 -0
  46. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item.py +27 -0
  47. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value.py +54 -0
  48. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_array_value.py +23 -0
  49. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_kvlist_value.py +28 -0
  50. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_kvlist_value_values_item.py +24 -0
  51. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item.py +60 -0
  52. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item.py +29 -0
  53. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value.py +54 -0
  54. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_array_value.py +23 -0
  55. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_kvlist_value.py +28 -0
  56. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_kvlist_value_values_item.py +24 -0
  57. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_status.py +24 -0
  58. mirascope/api/_generated/traces/types/traces_create_response.py +27 -0
  59. mirascope/api/_generated/traces/types/traces_create_response_partial_success.py +28 -0
  60. mirascope/api/_generated/types/__init__.py +21 -0
  61. mirascope/api/_generated/types/http_api_decode_error.py +31 -0
  62. mirascope/api/_generated/types/http_api_decode_error_tag.py +5 -0
  63. mirascope/api/_generated/types/issue.py +44 -0
  64. mirascope/api/_generated/types/issue_tag.py +17 -0
  65. mirascope/api/_generated/types/property_key.py +7 -0
  66. mirascope/api/_generated/types/property_key_tag.py +29 -0
  67. mirascope/api/_generated/types/property_key_tag_tag.py +5 -0
  68. mirascope/api/client.py +255 -0
  69. mirascope/api/settings.py +81 -0
  70. mirascope/llm/__init__.py +41 -11
  71. mirascope/llm/calls/calls.py +81 -57
  72. mirascope/llm/calls/decorator.py +121 -115
  73. mirascope/llm/content/__init__.py +3 -2
  74. mirascope/llm/context/_utils.py +19 -6
  75. mirascope/llm/exceptions.py +30 -16
  76. mirascope/llm/formatting/_utils.py +9 -5
  77. mirascope/llm/formatting/format.py +2 -2
  78. mirascope/llm/formatting/from_call_args.py +2 -2
  79. mirascope/llm/messages/message.py +13 -5
  80. mirascope/llm/models/__init__.py +2 -2
  81. mirascope/llm/models/models.py +189 -81
  82. mirascope/llm/prompts/__init__.py +13 -12
  83. mirascope/llm/prompts/_utils.py +27 -24
  84. mirascope/llm/prompts/decorator.py +133 -204
  85. mirascope/llm/prompts/prompts.py +424 -0
  86. mirascope/llm/prompts/protocols.py +25 -59
  87. mirascope/llm/providers/__init__.py +38 -0
  88. mirascope/llm/{clients → providers}/_missing_import_stubs.py +8 -6
  89. mirascope/llm/providers/anthropic/__init__.py +24 -0
  90. mirascope/llm/{clients → providers}/anthropic/_utils/decode.py +5 -4
  91. mirascope/llm/{clients → providers}/anthropic/_utils/encode.py +31 -10
  92. mirascope/llm/providers/anthropic/model_id.py +40 -0
  93. mirascope/llm/{clients/anthropic/clients.py → providers/anthropic/provider.py} +33 -418
  94. mirascope/llm/{clients → providers}/base/__init__.py +3 -3
  95. mirascope/llm/{clients → providers}/base/_utils.py +10 -7
  96. mirascope/llm/{clients/base/client.py → providers/base/base_provider.py} +255 -126
  97. mirascope/llm/providers/google/__init__.py +21 -0
  98. mirascope/llm/{clients → providers}/google/_utils/decode.py +6 -4
  99. mirascope/llm/{clients → providers}/google/_utils/encode.py +30 -24
  100. mirascope/llm/providers/google/model_id.py +28 -0
  101. mirascope/llm/providers/google/provider.py +438 -0
  102. mirascope/llm/providers/load_provider.py +48 -0
  103. mirascope/llm/providers/mlx/__init__.py +24 -0
  104. mirascope/llm/providers/mlx/_utils.py +107 -0
  105. mirascope/llm/providers/mlx/encoding/__init__.py +8 -0
  106. mirascope/llm/providers/mlx/encoding/base.py +69 -0
  107. mirascope/llm/providers/mlx/encoding/transformers.py +131 -0
  108. mirascope/llm/providers/mlx/mlx.py +237 -0
  109. mirascope/llm/providers/mlx/model_id.py +17 -0
  110. mirascope/llm/providers/mlx/provider.py +411 -0
  111. mirascope/llm/providers/model_id.py +16 -0
  112. mirascope/llm/providers/openai/__init__.py +6 -0
  113. mirascope/llm/providers/openai/completions/__init__.py +20 -0
  114. mirascope/llm/{clients/openai/responses → providers/openai/completions}/_utils/__init__.py +2 -0
  115. mirascope/llm/{clients → providers}/openai/completions/_utils/decode.py +5 -3
  116. mirascope/llm/{clients → providers}/openai/completions/_utils/encode.py +33 -23
  117. mirascope/llm/providers/openai/completions/provider.py +456 -0
  118. mirascope/llm/providers/openai/model_id.py +31 -0
  119. mirascope/llm/providers/openai/model_info.py +246 -0
  120. mirascope/llm/providers/openai/provider.py +386 -0
  121. mirascope/llm/providers/openai/responses/__init__.py +21 -0
  122. mirascope/llm/{clients → providers}/openai/responses/_utils/decode.py +5 -3
  123. mirascope/llm/{clients → providers}/openai/responses/_utils/encode.py +28 -17
  124. mirascope/llm/providers/openai/responses/provider.py +470 -0
  125. mirascope/llm/{clients → providers}/openai/shared/_utils.py +7 -3
  126. mirascope/llm/providers/provider_id.py +13 -0
  127. mirascope/llm/providers/provider_registry.py +167 -0
  128. mirascope/llm/responses/base_response.py +10 -5
  129. mirascope/llm/responses/base_stream_response.py +10 -5
  130. mirascope/llm/responses/response.py +24 -13
  131. mirascope/llm/responses/root_response.py +7 -12
  132. mirascope/llm/responses/stream_response.py +35 -23
  133. mirascope/llm/tools/__init__.py +9 -2
  134. mirascope/llm/tools/_utils.py +12 -3
  135. mirascope/llm/tools/protocols.py +4 -4
  136. mirascope/llm/tools/tool_schema.py +44 -9
  137. mirascope/llm/tools/tools.py +10 -9
  138. mirascope/ops/__init__.py +156 -0
  139. mirascope/ops/_internal/__init__.py +5 -0
  140. mirascope/ops/_internal/closure.py +1118 -0
  141. mirascope/ops/_internal/configuration.py +126 -0
  142. mirascope/ops/_internal/context.py +76 -0
  143. mirascope/ops/_internal/exporters/__init__.py +26 -0
  144. mirascope/ops/_internal/exporters/exporters.py +342 -0
  145. mirascope/ops/_internal/exporters/processors.py +104 -0
  146. mirascope/ops/_internal/exporters/types.py +165 -0
  147. mirascope/ops/_internal/exporters/utils.py +29 -0
  148. mirascope/ops/_internal/instrumentation/__init__.py +8 -0
  149. mirascope/ops/_internal/instrumentation/llm/__init__.py +8 -0
  150. mirascope/ops/_internal/instrumentation/llm/encode.py +238 -0
  151. mirascope/ops/_internal/instrumentation/llm/gen_ai_types/__init__.py +38 -0
  152. mirascope/ops/_internal/instrumentation/llm/gen_ai_types/gen_ai_input_messages.py +31 -0
  153. mirascope/ops/_internal/instrumentation/llm/gen_ai_types/gen_ai_output_messages.py +38 -0
  154. mirascope/ops/_internal/instrumentation/llm/gen_ai_types/gen_ai_system_instructions.py +18 -0
  155. mirascope/ops/_internal/instrumentation/llm/gen_ai_types/shared.py +100 -0
  156. mirascope/ops/_internal/instrumentation/llm/llm.py +1288 -0
  157. mirascope/ops/_internal/propagation.py +198 -0
  158. mirascope/ops/_internal/protocols.py +51 -0
  159. mirascope/ops/_internal/session.py +139 -0
  160. mirascope/ops/_internal/spans.py +232 -0
  161. mirascope/ops/_internal/traced_calls.py +371 -0
  162. mirascope/ops/_internal/traced_functions.py +394 -0
  163. mirascope/ops/_internal/tracing.py +276 -0
  164. mirascope/ops/_internal/types.py +13 -0
  165. mirascope/ops/_internal/utils.py +75 -0
  166. mirascope/ops/_internal/versioned_calls.py +512 -0
  167. mirascope/ops/_internal/versioned_functions.py +346 -0
  168. mirascope/ops/_internal/versioning.py +303 -0
  169. mirascope/ops/exceptions.py +21 -0
  170. {mirascope-2.0.0a2.dist-info → mirascope-2.0.0a3.dist-info}/METADATA +76 -1
  171. mirascope-2.0.0a3.dist-info/RECORD +206 -0
  172. {mirascope-2.0.0a2.dist-info → mirascope-2.0.0a3.dist-info}/WHEEL +1 -1
  173. mirascope/graphs/__init__.py +0 -22
  174. mirascope/graphs/finite_state_machine.py +0 -625
  175. mirascope/llm/agents/__init__.py +0 -15
  176. mirascope/llm/agents/agent.py +0 -97
  177. mirascope/llm/agents/agent_template.py +0 -45
  178. mirascope/llm/agents/decorator.py +0 -176
  179. mirascope/llm/calls/base_call.py +0 -33
  180. mirascope/llm/clients/__init__.py +0 -34
  181. mirascope/llm/clients/anthropic/__init__.py +0 -25
  182. mirascope/llm/clients/anthropic/model_ids.py +0 -8
  183. mirascope/llm/clients/google/__init__.py +0 -20
  184. mirascope/llm/clients/google/clients.py +0 -853
  185. mirascope/llm/clients/google/model_ids.py +0 -15
  186. mirascope/llm/clients/openai/__init__.py +0 -25
  187. mirascope/llm/clients/openai/completions/__init__.py +0 -28
  188. mirascope/llm/clients/openai/completions/_utils/model_features.py +0 -81
  189. mirascope/llm/clients/openai/completions/clients.py +0 -833
  190. mirascope/llm/clients/openai/completions/model_ids.py +0 -8
  191. mirascope/llm/clients/openai/responses/__init__.py +0 -26
  192. mirascope/llm/clients/openai/responses/_utils/model_features.py +0 -87
  193. mirascope/llm/clients/openai/responses/clients.py +0 -832
  194. mirascope/llm/clients/openai/responses/model_ids.py +0 -8
  195. mirascope/llm/clients/providers.py +0 -175
  196. mirascope-2.0.0a2.dist-info/RECORD +0 -102
  197. /mirascope/llm/{clients → providers}/anthropic/_utils/__init__.py +0 -0
  198. /mirascope/llm/{clients → providers}/base/kwargs.py +0 -0
  199. /mirascope/llm/{clients → providers}/base/params.py +0 -0
  200. /mirascope/llm/{clients → providers}/google/_utils/__init__.py +0 -0
  201. /mirascope/llm/{clients → providers}/google/message.py +0 -0
  202. /mirascope/llm/{clients/openai/completions → providers/openai/responses}/_utils/__init__.py +0 -0
  203. /mirascope/llm/{clients → providers}/openai/shared/__init__.py +0 -0
  204. {mirascope-2.0.0a2.dist-info → mirascope-2.0.0a3.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,394 @@
1
+ """Tracing decorators for `mirascope.ops`."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from abc import ABC, abstractmethod
6
+ from collections.abc import Generator
7
+ from contextlib import contextmanager
8
+ from dataclasses import dataclass, field
9
+ from typing import (
10
+ Any,
11
+ Generic,
12
+ Literal,
13
+ TypeVar,
14
+ )
15
+
16
+ from opentelemetry.util.types import AttributeValue
17
+
18
+ from ...llm.context import Context, DepsT
19
+ from ...llm.responses.root_response import RootResponse
20
+ from .protocols import (
21
+ AsyncContextFunction,
22
+ AsyncFunction,
23
+ SyncContextFunction,
24
+ SyncFunction,
25
+ )
26
+ from .spans import Span
27
+ from .types import Jsonable, P, R
28
+ from .utils import PrimitiveType, extract_arguments, get_qualified_name, json_dumps
29
+
30
+ FunctionT = TypeVar(
31
+ "FunctionT",
32
+ bound="SyncFunction[..., Any] | AsyncFunction[..., Any]",
33
+ covariant=True,
34
+ )
35
+
36
+
37
+ def record_result_to_span(span: Span, result: object) -> None:
38
+ """Records the function result in the given span.
39
+
40
+ This is a shared helper function used by all traced function classes
41
+ to record results consistently.
42
+ """
43
+ if result is None:
44
+ return # NOTE: we treat `None` valued results as such through omission.
45
+ elif isinstance(result, PrimitiveType):
46
+ output: str | int | float | bool = result
47
+ elif isinstance(result, RootResponse):
48
+ output = result.pretty()
49
+ else:
50
+ try:
51
+ output = json_dumps(result)
52
+ except (TypeError, ValueError):
53
+ output = repr(result)
54
+ span.set(**{"mirascope.trace.output": output})
55
+
56
+
57
+ @dataclass(kw_only=True, frozen=True)
58
+ class _BaseTrace(Generic[R]):
59
+ """Base class for trace results with shared functionality."""
60
+
61
+ result: R
62
+ """Return value produced by the traced call."""
63
+
64
+ span: Span
65
+ """Span associated with the traced call."""
66
+
67
+ @property
68
+ def span_id(self) -> str | None:
69
+ """Returns the ID of the span for the trace."""
70
+ return self.span.span_id
71
+
72
+ @property
73
+ def trace_id(self) -> str | None:
74
+ """Returns the ID of the trace."""
75
+ return self.span.trace_id
76
+
77
+
78
+ @dataclass(kw_only=True, frozen=True)
79
+ class Trace(_BaseTrace[R]):
80
+ """Result container returned by traced function calls.
81
+
82
+ Provides access to the function result and trace span metadata,
83
+ as well as per-call operations for annotation, tagging, and assignment.
84
+ """
85
+
86
+ def annotate(
87
+ self,
88
+ *,
89
+ label: Literal["pass", "fail"],
90
+ reasoning: str | None = None,
91
+ metadata: dict[str, Jsonable] | None = None,
92
+ ) -> None:
93
+ """Annotates the current trace span."""
94
+ raise NotImplementedError("Trace.annotate not yet implemented")
95
+
96
+ def tag(self, *tags: str) -> None:
97
+ """Adds given tags to the current trace span."""
98
+ raise NotImplementedError("Trace.tag not yet implemented")
99
+
100
+ def assign(self, *emails: str) -> None:
101
+ """Assigns the trace to users with the given emails."""
102
+ raise NotImplementedError("Trace.assign not yet implemented")
103
+
104
+
105
+ @dataclass(kw_only=True, frozen=True)
106
+ class AsyncTrace(_BaseTrace[R]):
107
+ """Result container returned by async traced function calls.
108
+
109
+ Provides access to the function result and trace span metadata,
110
+ as well as per-call operations for annotation, tagging, and assignment.
111
+ """
112
+
113
+ async def annotate(
114
+ self,
115
+ *,
116
+ label: str | None = None,
117
+ data: dict[str, Jsonable] | None = None,
118
+ reasoning: str | None = None,
119
+ ) -> None:
120
+ """Annotates the current trace span."""
121
+ raise NotImplementedError("AsyncTrace.annotate not yet implemented")
122
+
123
+ async def tag(self, *tags: str) -> None:
124
+ """Adds given tags to the current trace span."""
125
+ raise NotImplementedError("AsyncTrace.tag not yet implemented")
126
+
127
+ async def assign(self, *emails: str) -> None:
128
+ """Assigns the trace to users with the given emails."""
129
+ raise NotImplementedError("AsyncTrace.assign not yet implemented")
130
+
131
+
132
+ @dataclass(kw_only=True)
133
+ class _BaseFunction(Generic[P, R, FunctionT], ABC):
134
+ """Abstract base class for base functions to be traced."""
135
+
136
+ fn: FunctionT
137
+ """The function being traced."""
138
+
139
+ tags: tuple[str, ...]
140
+ """Tags to be associated with the trace span."""
141
+
142
+ metadata: dict[str, str] = field(default_factory=dict)
143
+ """Arbitrary key-value pairs for additional metadata."""
144
+
145
+ _qualified_name: str = field(init=False)
146
+ """Fully qualified name of the wrapped function."""
147
+
148
+ _module_name: str = field(init=False)
149
+ """Module name of the wrapped function."""
150
+
151
+ _is_async: bool = field(init=False)
152
+ """Whether the wrapped function is asynchronous."""
153
+
154
+ def __post_init__(self) -> None:
155
+ """Initialize additional attributes after dataclass init."""
156
+ self._qualified_name = get_qualified_name(self.fn)
157
+ self._module_name = getattr(self.fn, "__module__", "")
158
+
159
+
160
+ @dataclass(kw_only=True)
161
+ class _BaseTracedFunction(_BaseFunction[P, R, FunctionT]):
162
+ """Abstract base class for traced function wrappers."""
163
+
164
+ @contextmanager
165
+ def _span(self, *args: P.args, **kwargs: P.kwargs) -> Generator[Span, None, None]:
166
+ """Returns a span context manager populated with call attributes."""
167
+ arg_types, arg_values = extract_arguments(self.fn, *args, **kwargs)
168
+ with Span(self._qualified_name) as span:
169
+ attributes: dict[str, AttributeValue] = {
170
+ "mirascope.type": "trace",
171
+ "mirascope.fn.qualname": self._qualified_name,
172
+ "mirascope.fn.module": self._module_name,
173
+ "mirascope.fn.is_async": self._is_async,
174
+ "mirascope.trace.arg_types": json_dumps(arg_types),
175
+ "mirascope.trace.arg_values": json_dumps(arg_values),
176
+ }
177
+ if self.tags:
178
+ attributes["mirascope.trace.tags"] = self.tags
179
+ if self.metadata:
180
+ attributes["mirascope.trace.metadata"] = json_dumps(self.metadata)
181
+ span.set(**attributes)
182
+ yield span
183
+
184
+
185
+ @dataclass(kw_only=True)
186
+ class BaseSyncTracedFunction(_BaseTracedFunction[P, R, SyncFunction[P, R]]):
187
+ """Abstract base class for synchronous traced function wrappers."""
188
+
189
+ _is_async: bool = field(default=False, init=False)
190
+ """Whether the wrapped function is asynchronous."""
191
+
192
+ @abstractmethod
193
+ def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
194
+ """Returns the result of the traced function directly."""
195
+ ...
196
+
197
+ @abstractmethod
198
+ def wrapped(self, *args: P.args, **kwargs: P.kwargs) -> Trace[R]:
199
+ """Returns the trace containing the function result and span info."""
200
+ ...
201
+
202
+
203
+ @dataclass(kw_only=True)
204
+ class TracedFunction(BaseSyncTracedFunction[P, R]):
205
+ """Wrapper for synchronous functions with tracing capabilities.
206
+
207
+ Provides traced execution of synchronous functions, returning Trace
208
+ with access to span information.
209
+ """
210
+
211
+ def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
212
+ """Returns the result of the traced function directly."""
213
+ with self._span(*args, **kwargs) as span:
214
+ result = self.fn(*args, **kwargs)
215
+ record_result_to_span(span, result)
216
+ return result
217
+
218
+ def wrapped(self, *args: P.args, **kwargs: P.kwargs) -> Trace[R]:
219
+ """Returns the trace containing the function result and span info."""
220
+ with self._span(*args, **kwargs) as span:
221
+ result = self.fn(*args, **kwargs)
222
+ record_result_to_span(span, result)
223
+ return Trace(result=result, span=span)
224
+
225
+
226
+ @dataclass(kw_only=True)
227
+ class BaseAsyncTracedFunction(_BaseTracedFunction[P, R, AsyncFunction[P, R]]):
228
+ """Abstract base class for asynchronous traced function wrappers."""
229
+
230
+ _is_async: bool = field(default=True, init=False)
231
+ """Whether the wrapped function is asynchronous."""
232
+
233
+ @abstractmethod
234
+ async def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
235
+ """Returns the result of the traced function directly."""
236
+ ...
237
+
238
+ @abstractmethod
239
+ async def wrapped(self, *args: P.args, **kwargs: P.kwargs) -> AsyncTrace[R]:
240
+ """Returns the trace containing the function result and span info."""
241
+ ...
242
+
243
+
244
+ @dataclass(kw_only=True)
245
+ class AsyncTracedFunction(BaseAsyncTracedFunction[P, R]):
246
+ """Wrapper for asynchronous functions with tracing capabilities.
247
+
248
+ Provides traced execution of asynchronous functions, returning AsyncTrace
249
+ with access to span information.
250
+ """
251
+
252
+ async def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
253
+ """Returns the result of the traced function directly."""
254
+ with self._span(*args, **kwargs) as span:
255
+ result = await self.fn(*args, **kwargs)
256
+ record_result_to_span(span, result)
257
+ return result
258
+
259
+ async def wrapped(self, *args: P.args, **kwargs: P.kwargs) -> AsyncTrace[R]:
260
+ """Returns the trace containing the function result and span info."""
261
+ with self._span(*args, **kwargs) as span:
262
+ result = await self.fn(*args, **kwargs)
263
+ record_result_to_span(span, result)
264
+ return AsyncTrace(result=result, span=span)
265
+
266
+
267
+ @dataclass(kw_only=True)
268
+ class _BaseTracedContextFunction(
269
+ _BaseFunction[P, R, FunctionT], Generic[P, DepsT, R, FunctionT]
270
+ ):
271
+ """Abstract base class for traced function wrappers."""
272
+
273
+ _is_async: bool = field(default=False, init=False)
274
+ """Whether the wrapped function is asynchronous."""
275
+
276
+ @contextmanager
277
+ def _span(
278
+ self, ctx: Context[DepsT], *args: P.args, **kwargs: P.kwargs
279
+ ) -> Generator[Span, None, None]:
280
+ """Returns a span context manager populated with call attributes."""
281
+ arg_types, arg_values = extract_arguments(self.fn, ctx, *args, **kwargs)
282
+ with Span(name=self._qualified_name) as span:
283
+ attributes: dict[str, AttributeValue] = {
284
+ "mirascope.type": "trace",
285
+ "mirascope.fn.qualname": self._qualified_name,
286
+ "mirascope.fn.module": self.fn.__module__,
287
+ "mirascope.fn.is_async": self._is_async,
288
+ "mirascope.trace.arg_types": json_dumps(arg_types),
289
+ "mirascope.trace.arg_values": json_dumps(arg_values),
290
+ }
291
+ if self.tags:
292
+ attributes["mirascope.trace.tags"] = self.tags
293
+ if self.metadata:
294
+ attributes["mirascope.trace.metadata"] = json_dumps(self.metadata)
295
+ span.set(**attributes)
296
+ yield span
297
+
298
+
299
+ @dataclass(kw_only=True)
300
+ class BaseTracedSyncContextFunction(
301
+ _BaseTracedContextFunction[P, DepsT, R, SyncContextFunction[P, DepsT, R]]
302
+ ):
303
+ """Abstract base class for synchronous traced function wrappers."""
304
+
305
+ _is_async: bool = field(default=False, init=False)
306
+ """Whether the wrapped function is asynchronous."""
307
+
308
+ @abstractmethod
309
+ def __call__(self, ctx: Context[DepsT], *args: P.args, **kwargs: P.kwargs) -> R:
310
+ """Returns the result of the traced function directly."""
311
+ ...
312
+
313
+ @abstractmethod
314
+ def wrapped(
315
+ self, ctx: Context[DepsT], *args: P.args, **kwargs: P.kwargs
316
+ ) -> Trace[R]:
317
+ """Returns the trace containing the function result and span info."""
318
+ ...
319
+
320
+
321
+ @dataclass(kw_only=True)
322
+ class TracedContextFunction(BaseTracedSyncContextFunction[P, DepsT, R]):
323
+ """Wrapper for synchronous context functions with tracing capabilities.
324
+
325
+ Provides traced execution of synchronous functions that take a Context parameter,
326
+ returning Trace with access to span information.
327
+ """
328
+
329
+ def __call__(self, ctx: Context[DepsT], *args: P.args, **kwargs: P.kwargs) -> R:
330
+ """Returns the result of the traced function directly."""
331
+ with self._span(ctx, *args, **kwargs) as span:
332
+ result = self.fn(ctx, *args, **kwargs)
333
+ record_result_to_span(span, result)
334
+ return result
335
+
336
+ def wrapped(
337
+ self, ctx: Context[DepsT], *args: P.args, **kwargs: P.kwargs
338
+ ) -> Trace[R]:
339
+ """Returns the trace containing the function result and span info."""
340
+ with self._span(ctx, *args, **kwargs) as span:
341
+ result = self.fn(ctx, *args, **kwargs)
342
+ record_result_to_span(span, result)
343
+ return Trace(result=result, span=span)
344
+
345
+
346
+ @dataclass(kw_only=True)
347
+ class BaseTracedAsyncContextFunction(
348
+ _BaseTracedContextFunction[P, DepsT, R, AsyncContextFunction[P, DepsT, R]]
349
+ ):
350
+ """Abstract base class for synchronous traced function wrappers."""
351
+
352
+ _is_async: bool = field(default=True, init=False)
353
+ """Whether the wrapped function is asynchronous."""
354
+
355
+ @abstractmethod
356
+ async def __call__(
357
+ self, ctx: Context[DepsT], *args: P.args, **kwargs: P.kwargs
358
+ ) -> R:
359
+ """Returns the result of the traced function directly."""
360
+ ...
361
+
362
+ @abstractmethod
363
+ async def wrapped(
364
+ self, ctx: Context[DepsT], *args: P.args, **kwargs: P.kwargs
365
+ ) -> AsyncTrace[R]:
366
+ """Returns the trace containing the function result and span info."""
367
+ ...
368
+
369
+
370
+ @dataclass(kw_only=True)
371
+ class AsyncTracedContextFunction(BaseTracedAsyncContextFunction[P, DepsT, R]):
372
+ """Wrapper for asynchronous context functions with tracing capabilities.
373
+
374
+ Provides traced execution of asynchronous functions that take a Context parameter,
375
+ returning AsyncTrace with access to span information.
376
+ """
377
+
378
+ async def __call__(
379
+ self, ctx: Context[DepsT], *args: P.args, **kwargs: P.kwargs
380
+ ) -> R:
381
+ """Returns the result of the traced function directly."""
382
+ with self._span(ctx, *args, **kwargs) as span:
383
+ result = await self.fn(ctx, *args, **kwargs)
384
+ record_result_to_span(span, result)
385
+ return result
386
+
387
+ async def wrapped(
388
+ self, ctx: Context[DepsT], *args: P.args, **kwargs: P.kwargs
389
+ ) -> AsyncTrace[R]:
390
+ """Returns the trace containing the function result and span info."""
391
+ with self._span(ctx, *args, **kwargs) as span:
392
+ result = await self.fn(ctx, *args, **kwargs)
393
+ record_result_to_span(span, result)
394
+ return AsyncTrace(result=result, span=span)
@@ -0,0 +1,276 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Sequence
4
+ from dataclasses import dataclass, field
5
+ from typing import (
6
+ TYPE_CHECKING,
7
+ overload,
8
+ )
9
+
10
+ from ...llm.calls import AsyncCall, AsyncContextCall, Call, ContextCall
11
+ from ...llm.context import DepsT
12
+ from .protocols import (
13
+ AsyncFunction,
14
+ SyncFunction,
15
+ fn_is_async,
16
+ )
17
+ from .traced_calls import (
18
+ TracedAsyncCall,
19
+ TracedAsyncContextCall,
20
+ TracedCall,
21
+ TracedContextCall,
22
+ is_call_type,
23
+ wrap_call,
24
+ )
25
+ from .traced_functions import (
26
+ AsyncTracedFunction,
27
+ TracedFunction,
28
+ )
29
+ from .types import P, R
30
+
31
+ if TYPE_CHECKING:
32
+ from ...llm.formatting import FormattableT
33
+
34
+
35
+ @dataclass(kw_only=True)
36
+ class TraceDecorator:
37
+ """Decorator implementation for adding tracing capabilities to functions."""
38
+
39
+ tags: tuple[str, ...] = ()
40
+ """Tags to be associated with traced function calls."""
41
+
42
+ metadata: dict[str, str] = field(default_factory=dict)
43
+ """Arbitrary key-value pairs for additional metadata."""
44
+
45
+ # IMPORTANT: The order of these overloads matters for type inference.
46
+ # Call type overloads come first, then function overloads.
47
+ @overload
48
+ def __call__( # pyright: ignore[reportOverlappingOverload]
49
+ self,
50
+ fn: AsyncContextCall[P, DepsT, FormattableT],
51
+ ) -> TracedAsyncContextCall[P, DepsT, FormattableT]:
52
+ """Overload for applying decorator to an AsyncContextCall."""
53
+ ...
54
+
55
+ @overload
56
+ def __call__(
57
+ self,
58
+ fn: ContextCall[P, DepsT, FormattableT],
59
+ ) -> TracedContextCall[P, DepsT, FormattableT]:
60
+ """Overload for applying decorator to a ContextCall."""
61
+ ...
62
+
63
+ @overload
64
+ def __call__(
65
+ self,
66
+ fn: AsyncCall[P, FormattableT],
67
+ ) -> TracedAsyncCall[P, FormattableT]:
68
+ """Overload for applying decorator to an AsyncCall."""
69
+ ...
70
+
71
+ @overload
72
+ def __call__(
73
+ self,
74
+ fn: Call[P, FormattableT],
75
+ ) -> TracedCall[P, FormattableT]:
76
+ """Overload for applying decorator to a Call."""
77
+ ...
78
+
79
+ @overload
80
+ def __call__(
81
+ self,
82
+ fn: AsyncFunction[P, R],
83
+ ) -> AsyncTracedFunction[P, R]:
84
+ """Overload for applying decorator to an async function."""
85
+ ...
86
+
87
+ @overload
88
+ def __call__(
89
+ self,
90
+ fn: SyncFunction[P, R],
91
+ ) -> TracedFunction[P, R]:
92
+ """Overload for applying decorator to a sync function."""
93
+ ...
94
+
95
+ def __call__( # pyright: ignore[reportGeneralTypeIssues]
96
+ self,
97
+ fn: (
98
+ AsyncContextCall[P, DepsT, FormattableT]
99
+ | ContextCall[P, DepsT, FormattableT]
100
+ | AsyncCall[P, FormattableT]
101
+ | Call[P, FormattableT]
102
+ | AsyncFunction[P, R]
103
+ | SyncFunction[P, R]
104
+ ),
105
+ ) -> (
106
+ TracedAsyncContextCall[P, DepsT, FormattableT]
107
+ | TracedContextCall[P, DepsT, FormattableT]
108
+ | TracedAsyncCall[P, FormattableT]
109
+ | TracedCall[P, FormattableT]
110
+ | AsyncTracedFunction[P, R]
111
+ | TracedFunction[P, R]
112
+ ):
113
+ """Applies the decorator to the given function or Call object."""
114
+ if is_call_type(fn):
115
+ return wrap_call(fn=fn, tags=self.tags, metadata=self.metadata)
116
+ elif fn_is_async(fn):
117
+ return AsyncTracedFunction(fn=fn, tags=self.tags, metadata=self.metadata)
118
+ else:
119
+ return TracedFunction(fn=fn, tags=self.tags, metadata=self.metadata)
120
+
121
+
122
+ @overload
123
+ def trace(
124
+ __fn: None = None,
125
+ *,
126
+ tags: list[str] | None = None,
127
+ metadata: dict[str, str] | None = None,
128
+ ) -> TraceDecorator:
129
+ """Overload for providing kwargs before decorating (e.g. tags)."""
130
+ ...
131
+
132
+
133
+ @overload
134
+ def trace( # pyright: ignore[reportOverlappingOverload]
135
+ __fn: AsyncContextCall[P, DepsT, FormattableT],
136
+ *,
137
+ tags: None = None,
138
+ metadata: None = None,
139
+ ) -> TracedAsyncContextCall[P, DepsT, FormattableT]:
140
+ """Overload for directly decorating an AsyncContextCall."""
141
+ ...
142
+
143
+
144
+ @overload
145
+ def trace(
146
+ __fn: ContextCall[P, DepsT, FormattableT],
147
+ *,
148
+ tags: None = None,
149
+ metadata: None = None,
150
+ ) -> TracedContextCall[P, DepsT, FormattableT]:
151
+ """Overload for directly decorating a ContextCall."""
152
+ ...
153
+
154
+
155
+ @overload
156
+ def trace(
157
+ __fn: AsyncCall[P, FormattableT],
158
+ *,
159
+ tags: None = None,
160
+ metadata: None = None,
161
+ ) -> TracedAsyncCall[P, FormattableT]:
162
+ """Overload for directly decorating an AsyncCall."""
163
+ ...
164
+
165
+
166
+ @overload
167
+ def trace(
168
+ __fn: Call[P, FormattableT],
169
+ *,
170
+ tags: None = None,
171
+ metadata: None = None,
172
+ ) -> TracedCall[P, FormattableT]:
173
+ """Overload for directly decorating a Call."""
174
+ ...
175
+
176
+
177
+ @overload
178
+ def trace(
179
+ __fn: AsyncFunction[P, R],
180
+ *,
181
+ tags: None = None,
182
+ metadata: None = None,
183
+ ) -> AsyncTracedFunction[P, R]:
184
+ """Overload for directly (no argument) decorating an asynchronous function"""
185
+ ...
186
+
187
+
188
+ @overload
189
+ def trace(
190
+ __fn: SyncFunction[P, R],
191
+ *,
192
+ tags: None = None,
193
+ metadata: None = None,
194
+ ) -> TracedFunction[P, R]:
195
+ """Overload for directly (no argument) decorating a synchronous function"""
196
+ ...
197
+
198
+
199
+ def trace( # pyright: ignore[reportGeneralTypeIssues]
200
+ __fn: (
201
+ AsyncContextCall[P, DepsT, FormattableT]
202
+ | ContextCall[P, DepsT, FormattableT]
203
+ | AsyncCall[P, FormattableT]
204
+ | Call[P, FormattableT]
205
+ | AsyncFunction[P, R]
206
+ | SyncFunction[P, R]
207
+ | None
208
+ ) = None,
209
+ *,
210
+ tags: Sequence[str] | None = None,
211
+ metadata: dict[str, str] | None = None,
212
+ ) -> (
213
+ TracedAsyncContextCall[P, DepsT, FormattableT]
214
+ | TracedContextCall[P, DepsT, FormattableT]
215
+ | TracedAsyncCall[P, FormattableT]
216
+ | TracedCall[P, FormattableT]
217
+ | AsyncTracedFunction[P, R]
218
+ | TracedFunction[P, R]
219
+ | TraceDecorator
220
+ ):
221
+ """Decorator for adding tracing capabilities to functions and LLM calls.
222
+
223
+ Creates a wrapper that enables distributed tracing, performance monitoring,
224
+ and execution tracking for decorated functions. When called, the decorated
225
+ function returns a Trace containing both the result and span info.
226
+
227
+ When decorating an @llm.call function, returns a TracedCall that wraps both
228
+ the call and stream methods with tracing capabilities.
229
+
230
+ Args:
231
+ __fn: The function or Call object to decorate.
232
+ tags: Optional list of string tags to associate with traced executions.
233
+ metadata: Arbitrary key-value pairs for additional metadata.
234
+
235
+ Returns:
236
+ A decorator that wraps functions with tracing capabilities.
237
+
238
+ Example:
239
+ ```python
240
+ @ops.trace
241
+ def process_data(data: dict) -> dict:
242
+ return {"processed": data}
243
+
244
+ traced_result = process_data({"key": "value"})
245
+ print(traced_result.result) # {"processed": {"key": "value"}}
246
+ print(traced_result.span_id) # Access span ID
247
+ ```
248
+
249
+ Example:
250
+ ```python
251
+ @ops.trace
252
+ @llm.call("gpt-4o-mini")
253
+ def recommend_book(genre: str):
254
+ return f"Recommend a {genre} book"
255
+
256
+ # Returns Response directly (execution is still traced)
257
+ response = recommend_book("fantasy")
258
+ print(response.content)
259
+
260
+ # Use .wrapped() to get Trace[Response] with span info
261
+ trace = recommend_book.wrapped("fantasy")
262
+ print(trace.result.content)
263
+ print(trace.span_id)
264
+ ```
265
+ """
266
+ tags = tuple(sorted(set(tags or [])))
267
+ metadata = metadata or {}
268
+ if __fn is None:
269
+ return TraceDecorator(tags=tags, metadata=metadata)
270
+
271
+ if is_call_type(__fn):
272
+ return wrap_call(fn=__fn, tags=tags, metadata=metadata)
273
+ elif fn_is_async(__fn):
274
+ return AsyncTracedFunction(fn=__fn, tags=tags, metadata=metadata)
275
+ else:
276
+ return TracedFunction(fn=__fn, tags=tags, metadata=metadata)
@@ -0,0 +1,13 @@
1
+ """Type definitions for Mirascope tracing and capabilities."""
2
+
3
+ from collections.abc import Mapping, Sequence
4
+ from typing import ParamSpec, TypeAlias
5
+ from typing_extensions import TypeVar
6
+
7
+ P = ParamSpec("P")
8
+ R = TypeVar("R", infer_variance=True)
9
+
10
+ Jsonable: TypeAlias = (
11
+ None | str | int | float | bool | Sequence["Jsonable"] | Mapping[str, "Jsonable"]
12
+ )
13
+ """Simple type alias for JSON-serializable types."""