mirascope 2.0.0a2__py3-none-any.whl → 2.0.0a4__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 (252) 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 +141 -0
  5. mirascope/api/_generated/client.py +163 -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 +17 -0
  25. mirascope/api/_generated/errors/bad_request_error.py +15 -0
  26. mirascope/api/_generated/errors/conflict_error.py +15 -0
  27. mirascope/api/_generated/errors/forbidden_error.py +15 -0
  28. mirascope/api/_generated/errors/internal_server_error.py +15 -0
  29. mirascope/api/_generated/errors/not_found_error.py +15 -0
  30. mirascope/api/_generated/health/__init__.py +7 -0
  31. mirascope/api/_generated/health/client.py +96 -0
  32. mirascope/api/_generated/health/raw_client.py +129 -0
  33. mirascope/api/_generated/health/types/__init__.py +8 -0
  34. mirascope/api/_generated/health/types/health_check_response.py +24 -0
  35. mirascope/api/_generated/health/types/health_check_response_status.py +5 -0
  36. mirascope/api/_generated/organizations/__init__.py +25 -0
  37. mirascope/api/_generated/organizations/client.py +380 -0
  38. mirascope/api/_generated/organizations/raw_client.py +876 -0
  39. mirascope/api/_generated/organizations/types/__init__.py +23 -0
  40. mirascope/api/_generated/organizations/types/organizations_create_response.py +24 -0
  41. mirascope/api/_generated/organizations/types/organizations_create_response_role.py +7 -0
  42. mirascope/api/_generated/organizations/types/organizations_get_response.py +24 -0
  43. mirascope/api/_generated/organizations/types/organizations_get_response_role.py +7 -0
  44. mirascope/api/_generated/organizations/types/organizations_list_response_item.py +24 -0
  45. mirascope/api/_generated/organizations/types/organizations_list_response_item_role.py +7 -0
  46. mirascope/api/_generated/organizations/types/organizations_update_response.py +24 -0
  47. mirascope/api/_generated/organizations/types/organizations_update_response_role.py +7 -0
  48. mirascope/api/_generated/projects/__init__.py +17 -0
  49. mirascope/api/_generated/projects/client.py +458 -0
  50. mirascope/api/_generated/projects/raw_client.py +1016 -0
  51. mirascope/api/_generated/projects/types/__init__.py +15 -0
  52. mirascope/api/_generated/projects/types/projects_create_response.py +30 -0
  53. mirascope/api/_generated/projects/types/projects_get_response.py +30 -0
  54. mirascope/api/_generated/projects/types/projects_list_response_item.py +30 -0
  55. mirascope/api/_generated/projects/types/projects_update_response.py +30 -0
  56. mirascope/api/_generated/reference.md +753 -0
  57. mirascope/api/_generated/traces/__init__.py +55 -0
  58. mirascope/api/_generated/traces/client.py +162 -0
  59. mirascope/api/_generated/traces/raw_client.py +168 -0
  60. mirascope/api/_generated/traces/types/__init__.py +95 -0
  61. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item.py +36 -0
  62. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource.py +31 -0
  63. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item.py +25 -0
  64. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value.py +54 -0
  65. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_array_value.py +23 -0
  66. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_kvlist_value.py +28 -0
  67. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_kvlist_value_values_item.py +24 -0
  68. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item.py +35 -0
  69. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope.py +35 -0
  70. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item.py +27 -0
  71. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value.py +54 -0
  72. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_array_value.py +23 -0
  73. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_kvlist_value.py +28 -0
  74. 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
  75. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item.py +60 -0
  76. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item.py +29 -0
  77. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value.py +54 -0
  78. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_array_value.py +23 -0
  79. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_kvlist_value.py +28 -0
  80. 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
  81. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_status.py +24 -0
  82. mirascope/api/_generated/traces/types/traces_create_response.py +27 -0
  83. mirascope/api/_generated/traces/types/traces_create_response_partial_success.py +28 -0
  84. mirascope/api/_generated/types/__init__.py +37 -0
  85. mirascope/api/_generated/types/already_exists_error.py +24 -0
  86. mirascope/api/_generated/types/already_exists_error_tag.py +5 -0
  87. mirascope/api/_generated/types/database_error.py +24 -0
  88. mirascope/api/_generated/types/database_error_tag.py +5 -0
  89. mirascope/api/_generated/types/http_api_decode_error.py +29 -0
  90. mirascope/api/_generated/types/http_api_decode_error_tag.py +5 -0
  91. mirascope/api/_generated/types/issue.py +40 -0
  92. mirascope/api/_generated/types/issue_tag.py +17 -0
  93. mirascope/api/_generated/types/not_found_error_body.py +24 -0
  94. mirascope/api/_generated/types/not_found_error_tag.py +5 -0
  95. mirascope/api/_generated/types/permission_denied_error.py +24 -0
  96. mirascope/api/_generated/types/permission_denied_error_tag.py +7 -0
  97. mirascope/api/_generated/types/property_key.py +7 -0
  98. mirascope/api/_generated/types/property_key_key.py +27 -0
  99. mirascope/api/_generated/types/property_key_key_tag.py +5 -0
  100. mirascope/api/client.py +255 -0
  101. mirascope/api/settings.py +81 -0
  102. mirascope/llm/__init__.py +45 -11
  103. mirascope/llm/calls/calls.py +81 -57
  104. mirascope/llm/calls/decorator.py +121 -115
  105. mirascope/llm/content/__init__.py +3 -2
  106. mirascope/llm/context/_utils.py +19 -6
  107. mirascope/llm/exceptions.py +30 -16
  108. mirascope/llm/formatting/_utils.py +9 -5
  109. mirascope/llm/formatting/format.py +2 -2
  110. mirascope/llm/formatting/from_call_args.py +2 -2
  111. mirascope/llm/messages/message.py +13 -5
  112. mirascope/llm/models/__init__.py +2 -2
  113. mirascope/llm/models/models.py +189 -81
  114. mirascope/llm/prompts/__init__.py +13 -12
  115. mirascope/llm/prompts/_utils.py +27 -24
  116. mirascope/llm/prompts/decorator.py +133 -204
  117. mirascope/llm/prompts/prompts.py +424 -0
  118. mirascope/llm/prompts/protocols.py +25 -59
  119. mirascope/llm/providers/__init__.py +44 -0
  120. mirascope/llm/{clients → providers}/_missing_import_stubs.py +8 -6
  121. mirascope/llm/providers/anthropic/__init__.py +29 -0
  122. mirascope/llm/providers/anthropic/_utils/__init__.py +23 -0
  123. mirascope/llm/providers/anthropic/_utils/beta_decode.py +271 -0
  124. mirascope/llm/providers/anthropic/_utils/beta_encode.py +216 -0
  125. mirascope/llm/{clients → providers}/anthropic/_utils/decode.py +44 -11
  126. mirascope/llm/providers/anthropic/_utils/encode.py +356 -0
  127. mirascope/llm/providers/anthropic/beta_provider.py +322 -0
  128. mirascope/llm/providers/anthropic/model_id.py +23 -0
  129. mirascope/llm/providers/anthropic/model_info.py +87 -0
  130. mirascope/llm/providers/anthropic/provider.py +416 -0
  131. mirascope/llm/{clients → providers}/base/__init__.py +3 -3
  132. mirascope/llm/{clients → providers}/base/_utils.py +25 -8
  133. mirascope/llm/{clients/base/client.py → providers/base/base_provider.py} +255 -126
  134. mirascope/llm/providers/google/__init__.py +21 -0
  135. mirascope/llm/{clients → providers}/google/_utils/decode.py +61 -7
  136. mirascope/llm/{clients → providers}/google/_utils/encode.py +44 -30
  137. mirascope/llm/providers/google/model_id.py +22 -0
  138. mirascope/llm/providers/google/model_info.py +62 -0
  139. mirascope/llm/providers/google/provider.py +442 -0
  140. mirascope/llm/providers/load_provider.py +54 -0
  141. mirascope/llm/providers/mlx/__init__.py +24 -0
  142. mirascope/llm/providers/mlx/_utils.py +129 -0
  143. mirascope/llm/providers/mlx/encoding/__init__.py +8 -0
  144. mirascope/llm/providers/mlx/encoding/base.py +69 -0
  145. mirascope/llm/providers/mlx/encoding/transformers.py +147 -0
  146. mirascope/llm/providers/mlx/mlx.py +237 -0
  147. mirascope/llm/providers/mlx/model_id.py +17 -0
  148. mirascope/llm/providers/mlx/provider.py +415 -0
  149. mirascope/llm/providers/model_id.py +16 -0
  150. mirascope/llm/providers/ollama/__init__.py +19 -0
  151. mirascope/llm/providers/ollama/provider.py +71 -0
  152. mirascope/llm/providers/openai/__init__.py +6 -0
  153. mirascope/llm/providers/openai/completions/__init__.py +25 -0
  154. mirascope/llm/{clients → providers}/openai/completions/_utils/__init__.py +2 -0
  155. mirascope/llm/{clients → providers}/openai/completions/_utils/decode.py +60 -6
  156. mirascope/llm/{clients → providers}/openai/completions/_utils/encode.py +37 -26
  157. mirascope/llm/providers/openai/completions/base_provider.py +513 -0
  158. mirascope/llm/providers/openai/completions/provider.py +22 -0
  159. mirascope/llm/providers/openai/model_id.py +31 -0
  160. mirascope/llm/providers/openai/model_info.py +303 -0
  161. mirascope/llm/providers/openai/provider.py +398 -0
  162. mirascope/llm/providers/openai/responses/__init__.py +21 -0
  163. mirascope/llm/{clients → providers}/openai/responses/_utils/decode.py +59 -6
  164. mirascope/llm/{clients → providers}/openai/responses/_utils/encode.py +34 -23
  165. mirascope/llm/providers/openai/responses/provider.py +469 -0
  166. mirascope/llm/providers/provider_id.py +23 -0
  167. mirascope/llm/providers/provider_registry.py +169 -0
  168. mirascope/llm/providers/together/__init__.py +19 -0
  169. mirascope/llm/providers/together/provider.py +40 -0
  170. mirascope/llm/responses/__init__.py +3 -0
  171. mirascope/llm/responses/base_response.py +14 -5
  172. mirascope/llm/responses/base_stream_response.py +35 -6
  173. mirascope/llm/responses/finish_reason.py +1 -0
  174. mirascope/llm/responses/response.py +33 -13
  175. mirascope/llm/responses/root_response.py +12 -13
  176. mirascope/llm/responses/stream_response.py +35 -23
  177. mirascope/llm/responses/usage.py +95 -0
  178. mirascope/llm/tools/__init__.py +9 -2
  179. mirascope/llm/tools/_utils.py +12 -3
  180. mirascope/llm/tools/protocols.py +4 -4
  181. mirascope/llm/tools/tool_schema.py +44 -9
  182. mirascope/llm/tools/tools.py +10 -9
  183. mirascope/ops/__init__.py +156 -0
  184. mirascope/ops/_internal/__init__.py +5 -0
  185. mirascope/ops/_internal/closure.py +1118 -0
  186. mirascope/ops/_internal/configuration.py +126 -0
  187. mirascope/ops/_internal/context.py +76 -0
  188. mirascope/ops/_internal/exporters/__init__.py +26 -0
  189. mirascope/ops/_internal/exporters/exporters.py +342 -0
  190. mirascope/ops/_internal/exporters/processors.py +104 -0
  191. mirascope/ops/_internal/exporters/types.py +165 -0
  192. mirascope/ops/_internal/exporters/utils.py +29 -0
  193. mirascope/ops/_internal/instrumentation/__init__.py +8 -0
  194. mirascope/ops/_internal/instrumentation/llm/__init__.py +8 -0
  195. mirascope/ops/_internal/instrumentation/llm/encode.py +238 -0
  196. mirascope/ops/_internal/instrumentation/llm/gen_ai_types/__init__.py +38 -0
  197. mirascope/ops/_internal/instrumentation/llm/gen_ai_types/gen_ai_input_messages.py +31 -0
  198. mirascope/ops/_internal/instrumentation/llm/gen_ai_types/gen_ai_output_messages.py +38 -0
  199. mirascope/ops/_internal/instrumentation/llm/gen_ai_types/gen_ai_system_instructions.py +18 -0
  200. mirascope/ops/_internal/instrumentation/llm/gen_ai_types/shared.py +100 -0
  201. mirascope/ops/_internal/instrumentation/llm/llm.py +1288 -0
  202. mirascope/ops/_internal/propagation.py +198 -0
  203. mirascope/ops/_internal/protocols.py +51 -0
  204. mirascope/ops/_internal/session.py +139 -0
  205. mirascope/ops/_internal/spans.py +232 -0
  206. mirascope/ops/_internal/traced_calls.py +371 -0
  207. mirascope/ops/_internal/traced_functions.py +394 -0
  208. mirascope/ops/_internal/tracing.py +276 -0
  209. mirascope/ops/_internal/types.py +13 -0
  210. mirascope/ops/_internal/utils.py +75 -0
  211. mirascope/ops/_internal/versioned_calls.py +512 -0
  212. mirascope/ops/_internal/versioned_functions.py +346 -0
  213. mirascope/ops/_internal/versioning.py +303 -0
  214. mirascope/ops/exceptions.py +21 -0
  215. {mirascope-2.0.0a2.dist-info → mirascope-2.0.0a4.dist-info}/METADATA +78 -3
  216. mirascope-2.0.0a4.dist-info/RECORD +247 -0
  217. {mirascope-2.0.0a2.dist-info → mirascope-2.0.0a4.dist-info}/WHEEL +1 -1
  218. mirascope/graphs/__init__.py +0 -22
  219. mirascope/graphs/finite_state_machine.py +0 -625
  220. mirascope/llm/agents/__init__.py +0 -15
  221. mirascope/llm/agents/agent.py +0 -97
  222. mirascope/llm/agents/agent_template.py +0 -45
  223. mirascope/llm/agents/decorator.py +0 -176
  224. mirascope/llm/calls/base_call.py +0 -33
  225. mirascope/llm/clients/__init__.py +0 -34
  226. mirascope/llm/clients/anthropic/__init__.py +0 -25
  227. mirascope/llm/clients/anthropic/_utils/encode.py +0 -243
  228. mirascope/llm/clients/anthropic/clients.py +0 -819
  229. mirascope/llm/clients/anthropic/model_ids.py +0 -8
  230. mirascope/llm/clients/google/__init__.py +0 -20
  231. mirascope/llm/clients/google/clients.py +0 -853
  232. mirascope/llm/clients/google/model_ids.py +0 -15
  233. mirascope/llm/clients/openai/__init__.py +0 -25
  234. mirascope/llm/clients/openai/completions/__init__.py +0 -28
  235. mirascope/llm/clients/openai/completions/_utils/model_features.py +0 -81
  236. mirascope/llm/clients/openai/completions/clients.py +0 -833
  237. mirascope/llm/clients/openai/completions/model_ids.py +0 -8
  238. mirascope/llm/clients/openai/responses/__init__.py +0 -26
  239. mirascope/llm/clients/openai/responses/_utils/__init__.py +0 -13
  240. mirascope/llm/clients/openai/responses/_utils/model_features.py +0 -87
  241. mirascope/llm/clients/openai/responses/clients.py +0 -832
  242. mirascope/llm/clients/openai/responses/model_ids.py +0 -8
  243. mirascope/llm/clients/openai/shared/__init__.py +0 -7
  244. mirascope/llm/clients/openai/shared/_utils.py +0 -55
  245. mirascope/llm/clients/providers.py +0 -175
  246. mirascope-2.0.0a2.dist-info/RECORD +0 -102
  247. /mirascope/llm/{clients → providers}/base/kwargs.py +0 -0
  248. /mirascope/llm/{clients → providers}/base/params.py +0 -0
  249. /mirascope/llm/{clients/anthropic → providers/google}/_utils/__init__.py +0 -0
  250. /mirascope/llm/{clients → providers}/google/message.py +0 -0
  251. /mirascope/llm/{clients/google → providers/openai/responses}/_utils/__init__.py +0 -0
  252. {mirascope-2.0.0a2.dist-info → mirascope-2.0.0a4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,346 @@
1
+ """Versioned function implementations for Mirascope ops."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from collections.abc import Generator, Mapping
7
+ from contextlib import contextmanager
8
+ from dataclasses import dataclass, field
9
+ from functools import cached_property, lru_cache
10
+ from typing import Any, NewType
11
+
12
+ from ..exceptions import ClosureComputationError
13
+ from .closure import Closure
14
+ from .spans import Span
15
+ from .traced_functions import (
16
+ AsyncTrace,
17
+ BaseAsyncTracedFunction,
18
+ BaseSyncTracedFunction,
19
+ Trace,
20
+ _BaseTracedFunction,
21
+ record_result_to_span,
22
+ )
23
+ from .types import P, R
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ VersionId = NewType("VersionId", str)
29
+ """Unique identifier for a specific version."""
30
+
31
+ VersionRef = NewType("VersionRef", str)
32
+ """Reference to a version (can be tag, hash, or semantic version)."""
33
+
34
+
35
+ # NOTE: the `.get_version` methods will need to do some type-hint magic to get the
36
+ # correct type-hints for the desired version (i.e. the input args and return type) since
37
+ # those are not necessarily the same as the current version. This is what we did in v0,
38
+ # so we'll just need to replicate that functionality here when we get there.
39
+
40
+
41
+ @dataclass(kw_only=True, frozen=True)
42
+ class VersionedResult(Trace[R]):
43
+ """Per-call handle returned by `.wrapped()` methods for versioned functions.
44
+
45
+ Provides access to the result and per-call operations for annotation,
46
+ tagging, and assignment within a specific trace span context.
47
+ """
48
+
49
+ function_uuid: str | None = None
50
+
51
+
52
+ @dataclass(kw_only=True, frozen=True)
53
+ class AsyncVersionedResult(AsyncTrace[R]):
54
+ """Per-call handle returned by async `.wrapped()` methods for versioned functions.
55
+
56
+ Provides access to the result and per-call operations for annotation,
57
+ tagging, and assignment within a specific trace span context.
58
+ """
59
+
60
+ function_uuid: str | None = None
61
+
62
+
63
+ @dataclass(kw_only=True, frozen=True)
64
+ class VersionInfo:
65
+ """Static version metadata for a versioned function.
66
+
67
+ Contains all information needed to identify and describe a specific version
68
+ of a function, including its computed version number and hashes.
69
+ """
70
+
71
+ uuid: str | None
72
+ """Server-assigned unique identifier for this version (None if not registered)."""
73
+
74
+ hash: str
75
+ """SHA256 hash of the complete closure code."""
76
+
77
+ signature_hash: str
78
+ """SHA256 hash of the function signature."""
79
+
80
+ name: str
81
+ """Display name for the versioned function."""
82
+
83
+ description: str | None
84
+ """Human-readable description of the versioned function."""
85
+
86
+ version: str
87
+ """Auto-computed semantic version in X.Y format."""
88
+
89
+ tags: tuple[str, ...]
90
+ """Tags associated with this version for filtering/classification."""
91
+
92
+ metadata: Mapping[str, str]
93
+ """Arbitrary key-value pairs for additional metadata."""
94
+
95
+ def __post_init__(self) -> None:
96
+ """Clean up tags and initialize frozen metadata after dataclass init."""
97
+ object.__setattr__(self, "tags", tuple(sorted(set(self.tags or []))))
98
+ object.__setattr__(self, "metadata", dict(self.metadata))
99
+
100
+
101
+ @dataclass(kw_only=True)
102
+ class _BaseVersionedFunction(_BaseTracedFunction[P, R, Any]):
103
+ """Base class for versioned functions."""
104
+
105
+ name: str | None = None
106
+ """Optional custom name for the versioned function (overrides function name)."""
107
+
108
+ metadata: dict[str, str] = field(default_factory=dict)
109
+ """Arbitrary key-value pairs for additional metadata."""
110
+
111
+ closure: Closure | None = field(init=False, default=None)
112
+
113
+ def __post_init__(self) -> None:
114
+ super().__post_init__()
115
+ try:
116
+ self.closure = Closure.from_fn(self.fn)
117
+ except ClosureComputationError as e:
118
+ logger.warning(
119
+ "Failed to build closure for %s; continuing without version registration: %s",
120
+ e.qualified_name,
121
+ e,
122
+ )
123
+
124
+ @classmethod
125
+ @lru_cache(maxsize=128)
126
+ def _compute_version(cls, hash: str) -> str:
127
+ """Computes the version string from the closure hash.
128
+
129
+ For new functions without server history, returns "1.0" as the initial version.
130
+
131
+ TODO: When API client is available, query the server for existing versions:
132
+ 1. Check if a function with matching hash exists -> use its version
133
+ 2. If no matches, return "1.0" as initial version
134
+
135
+ Args:
136
+ hash: SHA256 hash of the complete closure code.
137
+
138
+ Returns:
139
+ A version string.
140
+ """
141
+ return "1.0"
142
+
143
+ @cached_property
144
+ def version_info(self) -> VersionInfo | None:
145
+ """Returns static version metadata for this versioned function.
146
+
147
+ Lazily constructs and caches the VersionInfo from the closure and
148
+ decorator arguments. Returns None if the closure could not be computed.
149
+
150
+ Returns:
151
+ VersionInfo containing hashes, version string, and metadata,
152
+ or None if closure computation failed.
153
+ """
154
+ if self.closure is None:
155
+ return None
156
+
157
+ return VersionInfo(
158
+ uuid=None,
159
+ hash=self.closure.hash,
160
+ signature_hash=self.closure.signature_hash,
161
+ name=self.name or self.closure.name,
162
+ description=self.closure.docstring,
163
+ version=self._compute_version(self.closure.hash),
164
+ tags=self.tags,
165
+ metadata=self.metadata,
166
+ )
167
+
168
+ @contextmanager
169
+ def _versioned_span(
170
+ self, function_uuid: str | None, *args: P.args, **kwargs: P.kwargs
171
+ ) -> Generator[Span, None, None]:
172
+ with super()._span(*args, **kwargs) as span:
173
+ if self.closure is not None:
174
+ span.set(
175
+ **{
176
+ "mirascope.version.hash": self.closure.hash,
177
+ "mirascope.version.signature_hash": self.closure.signature_hash,
178
+ }
179
+ )
180
+ if self.closure.docstring:
181
+ span.set(
182
+ **{"mirascope.version.description": self.closure.docstring}
183
+ )
184
+
185
+ if function_uuid:
186
+ span.set(**{"mirascope.version.uuid": function_uuid})
187
+
188
+ version_info = self.version_info
189
+ if version_info is not None:
190
+ span.set(**{"mirascope.version.version": version_info.version})
191
+ if self.name:
192
+ span.set(**{"mirascope.version.name": self.name})
193
+ if self.tags:
194
+ span.set(**{"mirascope.version.tags": self.tags})
195
+ if self.metadata:
196
+ for key, value in self.metadata.items():
197
+ span.set(**{f"mirascope.version.meta.{key}": value})
198
+ yield span
199
+
200
+
201
+ @dataclass(kw_only=True)
202
+ class VersionedFunction(_BaseVersionedFunction[P, R], BaseSyncTracedFunction[P, R]):
203
+ """Wrapper for synchronous functions with versioning capabilities."""
204
+
205
+ def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
206
+ """Returns the result of the versioned function directly.
207
+
208
+ A new version will be created if none yet exists for the specific version of
209
+ this function that's being run.
210
+ """
211
+ function_uuid = self._ensure_registration()
212
+ with self._versioned_span(function_uuid, *args, **kwargs) as span:
213
+ result = self.fn(*args, **kwargs)
214
+ record_result_to_span(span, result)
215
+ return result
216
+
217
+ def wrapped(self, *args: P.args, **kwargs: P.kwargs) -> VersionedResult[R]:
218
+ """Return a wrapper around the executed function's result for trace utilities.
219
+
220
+ Args:
221
+ *args: Positional arguments for the wrapped function.
222
+ **kwargs: Keyword arguments including 'version' for version reference.
223
+
224
+ Returns:
225
+ A VersionedResult containing the function result and trace context.
226
+ """
227
+ function_uuid = self._ensure_registration()
228
+ with self._versioned_span(function_uuid, *args, **kwargs) as span:
229
+ result = self.fn(*args, **kwargs)
230
+ record_result_to_span(span, result)
231
+ return VersionedResult(
232
+ result=result,
233
+ span=span,
234
+ function_uuid=function_uuid,
235
+ )
236
+
237
+ def get_version(self, version: VersionId) -> VersionedFunction[P, R]:
238
+ """Returns the specific version of this function requested."""
239
+ raise NotImplementedError("VersionedFunction.get_version not yet implemented")
240
+
241
+ def _ensure_registration(self) -> str | None:
242
+ """Returns function UUID after ensuring registration with API.
243
+
244
+ TODO: Implement API client integration to:
245
+ 1. Get sync client via `get_sync_client()`
246
+ 2. Check if function exists by hash: `client.functions.get_function_by_hash(self.closure.hash)`
247
+ 3. If not found, create new version: `client.functions.create_a_new_function_version(...)`
248
+ 4. Return the function UUID
249
+
250
+ Example implementation (from lilypad):
251
+ ```python
252
+ if self.closure is None:
253
+ return None
254
+ try:
255
+ client = get_sync_client()
256
+ except Exception as e:
257
+ logger.warning(f"Failed to get client for function registration: {e}")
258
+ return None
259
+
260
+ try:
261
+ existing = client.functions.get_function_by_hash(self.closure.hash)
262
+ return existing.uuid_
263
+ except NotFoundError:
264
+ response = client.functions.create_a_new_function_version(
265
+ code=self.closure.code,
266
+ hash=self.closure.hash,
267
+ name=self.closure.name,
268
+ signature=self.closure.signature,
269
+ dependencies=self.closure.dependencies,
270
+ )
271
+ return response.uuid_
272
+ ```
273
+ """
274
+ # TODO: Implement when API client is available
275
+ return None
276
+
277
+
278
+ @dataclass(kw_only=True)
279
+ class AsyncVersionedFunction(
280
+ _BaseVersionedFunction[P, R], BaseAsyncTracedFunction[P, R]
281
+ ):
282
+ """Wrapper for asynchronous functions with versioning capabilities."""
283
+
284
+ async def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
285
+ """Returns the result of the versioned function directly."""
286
+ function_uuid = await self._ensure_registration()
287
+ with self._versioned_span(function_uuid, *args, **kwargs) as span:
288
+ result = await self.fn(*args, **kwargs)
289
+ record_result_to_span(span, result)
290
+ return result
291
+
292
+ async def wrapped(
293
+ self, *args: P.args, **kwargs: P.kwargs
294
+ ) -> AsyncVersionedResult[R]:
295
+ """Returns a wrapper around the traced function's result for trace utilities."""
296
+ function_uuid = await self._ensure_registration()
297
+ with self._versioned_span(function_uuid, *args, **kwargs) as span:
298
+ result = await self.fn(*args, **kwargs)
299
+ record_result_to_span(span, result)
300
+ return AsyncVersionedResult(
301
+ result=result,
302
+ span=span,
303
+ function_uuid=function_uuid,
304
+ )
305
+
306
+ async def get_version(self, version: VersionId) -> VersionedFunction[P, R]:
307
+ """Returns the specific version of this function using an `AsyncLilypad` client."""
308
+ raise NotImplementedError(
309
+ "AsyncVersionedFunction.get_version not yet implemented"
310
+ )
311
+
312
+ async def _ensure_registration(self) -> str | None:
313
+ """Returns function UUID after ensuring registration with API.
314
+
315
+ TODO: Implement API client integration to:
316
+ 1. Get async client via `get_async_client()`
317
+ 2. Check if function exists by hash: `await client.functions.get_function_by_hash(self.closure.hash)`
318
+ 3. If not found, create new version: `await client.functions.create_a_new_function_version(...)`
319
+ 4. Return the function UUID
320
+
321
+ Example implementation (from lilypad):
322
+ ```python
323
+ if self.closure is None:
324
+ return None
325
+ try:
326
+ client = get_async_client()
327
+ except Exception as e:
328
+ logger.warning(f"Failed to get client for function registration: {e}")
329
+ return None
330
+
331
+ try:
332
+ existing = await client.functions.get_function_by_hash(self.closure.hash)
333
+ return existing.uuid_
334
+ except NotFoundError:
335
+ response = await client.functions.create_a_new_function_version(
336
+ code=self.closure.code,
337
+ hash=self.closure.hash,
338
+ name=self.closure.name,
339
+ signature=self.closure.signature,
340
+ dependencies=self.closure.dependencies,
341
+ )
342
+ return response.uuid_
343
+ ```
344
+ """
345
+ # TODO: Implement when API client is available
346
+ return None
@@ -0,0 +1,303 @@
1
+ """Version decorator for Mirascope ops."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Sequence
6
+ from dataclasses import dataclass, field
7
+ from typing import TYPE_CHECKING, overload
8
+
9
+ from ...llm.calls import AsyncCall, AsyncContextCall, Call, ContextCall
10
+ from ...llm.context import DepsT
11
+ from .protocols import AsyncFunction, SyncFunction, fn_is_async
12
+ from .types import P, R
13
+ from .versioned_calls import (
14
+ VersionedAsyncCall,
15
+ VersionedAsyncContextCall,
16
+ VersionedCall,
17
+ VersionedContextCall,
18
+ is_version_call_type,
19
+ wrap_version_call,
20
+ )
21
+ from .versioned_functions import AsyncVersionedFunction, VersionedFunction
22
+
23
+ if TYPE_CHECKING:
24
+ from ...llm.formatting import FormattableT
25
+
26
+
27
+ @dataclass(kw_only=True)
28
+ class VersionDecorator:
29
+ """Decorator implementation for adding versioning capabilities to functions."""
30
+
31
+ tags: tuple[str, ...] = ()
32
+ """Tags to be associated with versioned function calls."""
33
+
34
+ name: str | None = None
35
+ """Optional custom name for the versioned function."""
36
+
37
+ metadata: dict[str, str] = field(default_factory=dict)
38
+ """Arbitrary key-value pairs for additional metadata."""
39
+
40
+ @overload
41
+ def __call__( # pyright: ignore[reportOverlappingOverload]
42
+ self,
43
+ fn: AsyncContextCall[P, DepsT, FormattableT],
44
+ ) -> VersionedAsyncContextCall[P, DepsT, FormattableT]:
45
+ """Overload for applying decorator to an AsyncContextCall."""
46
+ ...
47
+
48
+ @overload
49
+ def __call__(
50
+ self,
51
+ fn: ContextCall[P, DepsT, FormattableT],
52
+ ) -> VersionedContextCall[P, DepsT, FormattableT]:
53
+ """Overload for applying decorator to a ContextCall."""
54
+ ...
55
+
56
+ @overload
57
+ def __call__(
58
+ self,
59
+ fn: AsyncCall[P, FormattableT],
60
+ ) -> VersionedAsyncCall[P, FormattableT]:
61
+ """Overload for applying decorator to an AsyncCall."""
62
+ ...
63
+
64
+ @overload
65
+ def __call__(
66
+ self,
67
+ fn: Call[P, FormattableT],
68
+ ) -> VersionedCall[P, FormattableT]:
69
+ """Overload for applying decorator to a Call."""
70
+ ...
71
+
72
+ @overload
73
+ def __call__(
74
+ self,
75
+ fn: AsyncFunction[P, R],
76
+ ) -> AsyncVersionedFunction[P, R]:
77
+ """Overload for applying decorator to an async function."""
78
+ ...
79
+
80
+ @overload
81
+ def __call__(
82
+ self,
83
+ fn: SyncFunction[P, R],
84
+ ) -> VersionedFunction[P, R]:
85
+ """Overload for applying decorator to a sync function."""
86
+ ...
87
+
88
+ def __call__( # pyright: ignore[reportGeneralTypeIssues]
89
+ self,
90
+ fn: (
91
+ AsyncContextCall[P, DepsT, FormattableT]
92
+ | ContextCall[P, DepsT, FormattableT]
93
+ | AsyncCall[P, FormattableT]
94
+ | Call[P, FormattableT]
95
+ | AsyncFunction[P, R]
96
+ | SyncFunction[P, R]
97
+ ),
98
+ ) -> (
99
+ VersionedAsyncContextCall[P, DepsT, FormattableT]
100
+ | VersionedContextCall[P, DepsT, FormattableT]
101
+ | VersionedAsyncCall[P, FormattableT]
102
+ | VersionedCall[P, FormattableT]
103
+ | AsyncVersionedFunction[P, R]
104
+ | VersionedFunction[P, R]
105
+ ):
106
+ """Applies the decorator to the given function or Call object."""
107
+ if is_version_call_type(fn):
108
+ return wrap_version_call(
109
+ fn=fn,
110
+ tags=self.tags,
111
+ name=self.name,
112
+ metadata=self.metadata or {},
113
+ )
114
+ elif fn_is_async(fn):
115
+ return AsyncVersionedFunction(
116
+ fn=fn,
117
+ tags=self.tags,
118
+ name=self.name,
119
+ metadata=self.metadata,
120
+ )
121
+ else:
122
+ return VersionedFunction(
123
+ fn=fn,
124
+ tags=self.tags,
125
+ name=self.name,
126
+ metadata=self.metadata,
127
+ )
128
+
129
+
130
+ @overload
131
+ def version(
132
+ __fn: None = None,
133
+ *,
134
+ tags: Sequence[str] | None = None,
135
+ name: str | None = None,
136
+ metadata: dict[str, str] | None = None,
137
+ ) -> VersionDecorator:
138
+ """Overload for providing kwargs before decorating (e.g. tags)."""
139
+ ...
140
+
141
+
142
+ @overload
143
+ def version( # pyright: ignore[reportOverlappingOverload]
144
+ __fn: AsyncContextCall[P, DepsT, FormattableT],
145
+ *,
146
+ tags: None = None,
147
+ name: None = None,
148
+ metadata: None = None,
149
+ ) -> VersionedAsyncContextCall[P, DepsT, FormattableT]:
150
+ """Overload for directly (no argument) decorating an AsyncContextCall."""
151
+ ...
152
+
153
+
154
+ @overload
155
+ def version(
156
+ __fn: ContextCall[P, DepsT, FormattableT],
157
+ *,
158
+ tags: None = None,
159
+ name: None = None,
160
+ metadata: None = None,
161
+ ) -> VersionedContextCall[P, DepsT, FormattableT]:
162
+ """Overload for directly (no argument) decorating a ContextCall."""
163
+ ...
164
+
165
+
166
+ @overload
167
+ def version(
168
+ __fn: AsyncCall[P, FormattableT],
169
+ *,
170
+ tags: None = None,
171
+ name: None = None,
172
+ metadata: None = None,
173
+ ) -> VersionedAsyncCall[P, FormattableT]:
174
+ """Overload for directly (no argument) decorating an AsyncCall."""
175
+ ...
176
+
177
+
178
+ @overload
179
+ def version(
180
+ __fn: Call[P, FormattableT],
181
+ *,
182
+ tags: None = None,
183
+ name: None = None,
184
+ metadata: None = None,
185
+ ) -> VersionedCall[P, FormattableT]:
186
+ """Overload for directly (no argument) decorating a Call."""
187
+ ...
188
+
189
+
190
+ @overload
191
+ def version(
192
+ __fn: AsyncFunction[P, R],
193
+ *,
194
+ tags: None = None,
195
+ name: None = None,
196
+ metadata: None = None,
197
+ ) -> AsyncVersionedFunction[P, R]:
198
+ """Overload for directly (no argument) decorating an asynchronous function"""
199
+ ...
200
+
201
+
202
+ @overload
203
+ def version(
204
+ __fn: SyncFunction[P, R],
205
+ *,
206
+ tags: None = None,
207
+ name: None = None,
208
+ metadata: None = None,
209
+ ) -> VersionedFunction[P, R]:
210
+ """Overload for directly (no argument) decorating a synchronous function"""
211
+ ...
212
+
213
+
214
+ def version( # pyright: ignore[reportGeneralTypeIssues]
215
+ __fn: (
216
+ AsyncContextCall[P, DepsT, FormattableT]
217
+ | ContextCall[P, DepsT, FormattableT]
218
+ | AsyncCall[P, FormattableT]
219
+ | Call[P, FormattableT]
220
+ | AsyncFunction[P, R]
221
+ | SyncFunction[P, R]
222
+ | None
223
+ ) = None,
224
+ *,
225
+ tags: Sequence[str] | None = None,
226
+ name: str | None = None,
227
+ metadata: dict[str, str] | None = None,
228
+ ) -> (
229
+ VersionDecorator
230
+ | VersionedAsyncContextCall[P, DepsT, FormattableT]
231
+ | VersionedContextCall[P, DepsT, FormattableT]
232
+ | VersionedAsyncCall[P, FormattableT]
233
+ | VersionedCall[P, FormattableT]
234
+ | AsyncVersionedFunction[P, R]
235
+ | VersionedFunction[P, R]
236
+ ):
237
+ """Add versioning capability to a callable function.
238
+
239
+ Enables version management for functions, allowing execution of specific
240
+ versions and version introspection. Can be composed with @trace and @remote.
241
+
242
+ Args:
243
+ __fn: The function to version (when used without parentheses).
244
+ tags: Optional version tags for this function.
245
+ name: Optional custom name for display (overrides function name).
246
+ metadata: Arbitrary key-value pairs for additional metadata.
247
+
248
+ Returns:
249
+ A versioned callable or a decorator function.
250
+
251
+ Examples:
252
+ ```python
253
+ @version()
254
+ def compute(x: int) -> int:
255
+ return x * 2
256
+ ```
257
+
258
+ ```python
259
+ @version(tags=["v1.0"])
260
+ async def process() -> str:
261
+ return "processed"
262
+ ```
263
+
264
+ ```python
265
+ @version(
266
+ name="book_recommender",
267
+ tags=["production"],
268
+ metadata={"owner": "team-ml", "ticket": "ENG-1234"},
269
+ )
270
+ def recommend_book(genre: str) -> str:
271
+ return f"Recommend a {genre} book"
272
+ ```
273
+ """
274
+ tags = tuple(sorted(set(tags or [])))
275
+ metadata = metadata or {}
276
+ if __fn is None:
277
+ return VersionDecorator(
278
+ tags=tags,
279
+ name=name,
280
+ metadata=metadata,
281
+ )
282
+
283
+ if is_version_call_type(__fn):
284
+ return wrap_version_call(
285
+ fn=__fn,
286
+ tags=tags,
287
+ name=name,
288
+ metadata=metadata,
289
+ )
290
+ elif fn_is_async(__fn):
291
+ return AsyncVersionedFunction(
292
+ fn=__fn,
293
+ tags=tags,
294
+ name=name,
295
+ metadata=metadata,
296
+ )
297
+ else:
298
+ return VersionedFunction(
299
+ fn=__fn,
300
+ tags=tags,
301
+ name=name,
302
+ metadata=metadata,
303
+ )
@@ -0,0 +1,21 @@
1
+ """Mirascope Ops exception hierarchy for unified error handling across providers."""
2
+
3
+
4
+ class MirascopeOpsError(Exception):
5
+ """Base exception for all Mirascope Ops errors."""
6
+
7
+ original_exception: Exception | None
8
+
9
+
10
+ class ConfigurationError(MirascopeOpsError):
11
+ """Raised when Ops configuration is invalid."""
12
+
13
+
14
+ class ClosureComputationError(MirascopeOpsError):
15
+ """Raised when the closure for a function cannot be computed properly."""
16
+
17
+ qualified_name: str
18
+
19
+ def __init__(self, qualified_name: str) -> None:
20
+ """Initializes an instance of `ClosureComputationError`."""
21
+ self.qualified_name = qualified_name