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.
- mirascope/__init__.py +2 -2
- mirascope/api/__init__.py +6 -0
- mirascope/api/_generated/README.md +207 -0
- mirascope/api/_generated/__init__.py +85 -0
- mirascope/api/_generated/client.py +155 -0
- mirascope/api/_generated/core/__init__.py +52 -0
- mirascope/api/_generated/core/api_error.py +23 -0
- mirascope/api/_generated/core/client_wrapper.py +58 -0
- mirascope/api/_generated/core/datetime_utils.py +30 -0
- mirascope/api/_generated/core/file.py +70 -0
- mirascope/api/_generated/core/force_multipart.py +16 -0
- mirascope/api/_generated/core/http_client.py +619 -0
- mirascope/api/_generated/core/http_response.py +55 -0
- mirascope/api/_generated/core/jsonable_encoder.py +102 -0
- mirascope/api/_generated/core/pydantic_utilities.py +310 -0
- mirascope/api/_generated/core/query_encoder.py +60 -0
- mirascope/api/_generated/core/remove_none_from_dict.py +11 -0
- mirascope/api/_generated/core/request_options.py +35 -0
- mirascope/api/_generated/core/serialization.py +282 -0
- mirascope/api/_generated/docs/__init__.py +4 -0
- mirascope/api/_generated/docs/client.py +95 -0
- mirascope/api/_generated/docs/raw_client.py +132 -0
- mirascope/api/_generated/environment.py +9 -0
- mirascope/api/_generated/errors/__init__.py +7 -0
- mirascope/api/_generated/errors/bad_request_error.py +15 -0
- mirascope/api/_generated/health/__init__.py +7 -0
- mirascope/api/_generated/health/client.py +96 -0
- mirascope/api/_generated/health/raw_client.py +129 -0
- mirascope/api/_generated/health/types/__init__.py +8 -0
- mirascope/api/_generated/health/types/health_check_response.py +24 -0
- mirascope/api/_generated/health/types/health_check_response_status.py +5 -0
- mirascope/api/_generated/reference.md +167 -0
- mirascope/api/_generated/traces/__init__.py +55 -0
- mirascope/api/_generated/traces/client.py +162 -0
- mirascope/api/_generated/traces/raw_client.py +168 -0
- mirascope/api/_generated/traces/types/__init__.py +95 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item.py +36 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource.py +31 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item.py +25 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value.py +54 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_array_value.py +23 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_kvlist_value.py +28 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_kvlist_value_values_item.py +24 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item.py +35 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope.py +35 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item.py +27 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value.py +54 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_array_value.py +23 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_kvlist_value.py +28 -0
- 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
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item.py +60 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item.py +29 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value.py +54 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_array_value.py +23 -0
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_kvlist_value.py +28 -0
- 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
- mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_status.py +24 -0
- mirascope/api/_generated/traces/types/traces_create_response.py +27 -0
- mirascope/api/_generated/traces/types/traces_create_response_partial_success.py +28 -0
- mirascope/api/_generated/types/__init__.py +21 -0
- mirascope/api/_generated/types/http_api_decode_error.py +31 -0
- mirascope/api/_generated/types/http_api_decode_error_tag.py +5 -0
- mirascope/api/_generated/types/issue.py +44 -0
- mirascope/api/_generated/types/issue_tag.py +17 -0
- mirascope/api/_generated/types/property_key.py +7 -0
- mirascope/api/_generated/types/property_key_tag.py +29 -0
- mirascope/api/_generated/types/property_key_tag_tag.py +5 -0
- mirascope/api/client.py +255 -0
- mirascope/api/settings.py +81 -0
- mirascope/llm/__init__.py +41 -11
- mirascope/llm/calls/calls.py +81 -57
- mirascope/llm/calls/decorator.py +121 -115
- mirascope/llm/content/__init__.py +3 -2
- mirascope/llm/context/_utils.py +19 -6
- mirascope/llm/exceptions.py +30 -16
- mirascope/llm/formatting/_utils.py +9 -5
- mirascope/llm/formatting/format.py +2 -2
- mirascope/llm/formatting/from_call_args.py +2 -2
- mirascope/llm/messages/message.py +13 -5
- mirascope/llm/models/__init__.py +2 -2
- mirascope/llm/models/models.py +189 -81
- mirascope/llm/prompts/__init__.py +13 -12
- mirascope/llm/prompts/_utils.py +27 -24
- mirascope/llm/prompts/decorator.py +133 -204
- mirascope/llm/prompts/prompts.py +424 -0
- mirascope/llm/prompts/protocols.py +25 -59
- mirascope/llm/providers/__init__.py +38 -0
- mirascope/llm/{clients → providers}/_missing_import_stubs.py +8 -6
- mirascope/llm/providers/anthropic/__init__.py +24 -0
- mirascope/llm/{clients → providers}/anthropic/_utils/decode.py +5 -4
- mirascope/llm/{clients → providers}/anthropic/_utils/encode.py +31 -10
- mirascope/llm/providers/anthropic/model_id.py +40 -0
- mirascope/llm/{clients/anthropic/clients.py → providers/anthropic/provider.py} +33 -418
- mirascope/llm/{clients → providers}/base/__init__.py +3 -3
- mirascope/llm/{clients → providers}/base/_utils.py +10 -7
- mirascope/llm/{clients/base/client.py → providers/base/base_provider.py} +255 -126
- mirascope/llm/providers/google/__init__.py +21 -0
- mirascope/llm/{clients → providers}/google/_utils/decode.py +6 -4
- mirascope/llm/{clients → providers}/google/_utils/encode.py +30 -24
- mirascope/llm/providers/google/model_id.py +28 -0
- mirascope/llm/providers/google/provider.py +438 -0
- mirascope/llm/providers/load_provider.py +48 -0
- mirascope/llm/providers/mlx/__init__.py +24 -0
- mirascope/llm/providers/mlx/_utils.py +107 -0
- mirascope/llm/providers/mlx/encoding/__init__.py +8 -0
- mirascope/llm/providers/mlx/encoding/base.py +69 -0
- mirascope/llm/providers/mlx/encoding/transformers.py +131 -0
- mirascope/llm/providers/mlx/mlx.py +237 -0
- mirascope/llm/providers/mlx/model_id.py +17 -0
- mirascope/llm/providers/mlx/provider.py +411 -0
- mirascope/llm/providers/model_id.py +16 -0
- mirascope/llm/providers/openai/__init__.py +6 -0
- mirascope/llm/providers/openai/completions/__init__.py +20 -0
- mirascope/llm/{clients/openai/responses → providers/openai/completions}/_utils/__init__.py +2 -0
- mirascope/llm/{clients → providers}/openai/completions/_utils/decode.py +5 -3
- mirascope/llm/{clients → providers}/openai/completions/_utils/encode.py +33 -23
- mirascope/llm/providers/openai/completions/provider.py +456 -0
- mirascope/llm/providers/openai/model_id.py +31 -0
- mirascope/llm/providers/openai/model_info.py +246 -0
- mirascope/llm/providers/openai/provider.py +386 -0
- mirascope/llm/providers/openai/responses/__init__.py +21 -0
- mirascope/llm/{clients → providers}/openai/responses/_utils/decode.py +5 -3
- mirascope/llm/{clients → providers}/openai/responses/_utils/encode.py +28 -17
- mirascope/llm/providers/openai/responses/provider.py +470 -0
- mirascope/llm/{clients → providers}/openai/shared/_utils.py +7 -3
- mirascope/llm/providers/provider_id.py +13 -0
- mirascope/llm/providers/provider_registry.py +167 -0
- mirascope/llm/responses/base_response.py +10 -5
- mirascope/llm/responses/base_stream_response.py +10 -5
- mirascope/llm/responses/response.py +24 -13
- mirascope/llm/responses/root_response.py +7 -12
- mirascope/llm/responses/stream_response.py +35 -23
- mirascope/llm/tools/__init__.py +9 -2
- mirascope/llm/tools/_utils.py +12 -3
- mirascope/llm/tools/protocols.py +4 -4
- mirascope/llm/tools/tool_schema.py +44 -9
- mirascope/llm/tools/tools.py +10 -9
- mirascope/ops/__init__.py +156 -0
- mirascope/ops/_internal/__init__.py +5 -0
- mirascope/ops/_internal/closure.py +1118 -0
- mirascope/ops/_internal/configuration.py +126 -0
- mirascope/ops/_internal/context.py +76 -0
- mirascope/ops/_internal/exporters/__init__.py +26 -0
- mirascope/ops/_internal/exporters/exporters.py +342 -0
- mirascope/ops/_internal/exporters/processors.py +104 -0
- mirascope/ops/_internal/exporters/types.py +165 -0
- mirascope/ops/_internal/exporters/utils.py +29 -0
- mirascope/ops/_internal/instrumentation/__init__.py +8 -0
- mirascope/ops/_internal/instrumentation/llm/__init__.py +8 -0
- mirascope/ops/_internal/instrumentation/llm/encode.py +238 -0
- mirascope/ops/_internal/instrumentation/llm/gen_ai_types/__init__.py +38 -0
- mirascope/ops/_internal/instrumentation/llm/gen_ai_types/gen_ai_input_messages.py +31 -0
- mirascope/ops/_internal/instrumentation/llm/gen_ai_types/gen_ai_output_messages.py +38 -0
- mirascope/ops/_internal/instrumentation/llm/gen_ai_types/gen_ai_system_instructions.py +18 -0
- mirascope/ops/_internal/instrumentation/llm/gen_ai_types/shared.py +100 -0
- mirascope/ops/_internal/instrumentation/llm/llm.py +1288 -0
- mirascope/ops/_internal/propagation.py +198 -0
- mirascope/ops/_internal/protocols.py +51 -0
- mirascope/ops/_internal/session.py +139 -0
- mirascope/ops/_internal/spans.py +232 -0
- mirascope/ops/_internal/traced_calls.py +371 -0
- mirascope/ops/_internal/traced_functions.py +394 -0
- mirascope/ops/_internal/tracing.py +276 -0
- mirascope/ops/_internal/types.py +13 -0
- mirascope/ops/_internal/utils.py +75 -0
- mirascope/ops/_internal/versioned_calls.py +512 -0
- mirascope/ops/_internal/versioned_functions.py +346 -0
- mirascope/ops/_internal/versioning.py +303 -0
- mirascope/ops/exceptions.py +21 -0
- {mirascope-2.0.0a2.dist-info → mirascope-2.0.0a3.dist-info}/METADATA +76 -1
- mirascope-2.0.0a3.dist-info/RECORD +206 -0
- {mirascope-2.0.0a2.dist-info → mirascope-2.0.0a3.dist-info}/WHEEL +1 -1
- mirascope/graphs/__init__.py +0 -22
- mirascope/graphs/finite_state_machine.py +0 -625
- mirascope/llm/agents/__init__.py +0 -15
- mirascope/llm/agents/agent.py +0 -97
- mirascope/llm/agents/agent_template.py +0 -45
- mirascope/llm/agents/decorator.py +0 -176
- mirascope/llm/calls/base_call.py +0 -33
- mirascope/llm/clients/__init__.py +0 -34
- mirascope/llm/clients/anthropic/__init__.py +0 -25
- mirascope/llm/clients/anthropic/model_ids.py +0 -8
- mirascope/llm/clients/google/__init__.py +0 -20
- mirascope/llm/clients/google/clients.py +0 -853
- mirascope/llm/clients/google/model_ids.py +0 -15
- mirascope/llm/clients/openai/__init__.py +0 -25
- mirascope/llm/clients/openai/completions/__init__.py +0 -28
- mirascope/llm/clients/openai/completions/_utils/model_features.py +0 -81
- mirascope/llm/clients/openai/completions/clients.py +0 -833
- mirascope/llm/clients/openai/completions/model_ids.py +0 -8
- mirascope/llm/clients/openai/responses/__init__.py +0 -26
- mirascope/llm/clients/openai/responses/_utils/model_features.py +0 -87
- mirascope/llm/clients/openai/responses/clients.py +0 -832
- mirascope/llm/clients/openai/responses/model_ids.py +0 -8
- mirascope/llm/clients/providers.py +0 -175
- mirascope-2.0.0a2.dist-info/RECORD +0 -102
- /mirascope/llm/{clients → providers}/anthropic/_utils/__init__.py +0 -0
- /mirascope/llm/{clients → providers}/base/kwargs.py +0 -0
- /mirascope/llm/{clients → providers}/base/params.py +0 -0
- /mirascope/llm/{clients → providers}/google/_utils/__init__.py +0 -0
- /mirascope/llm/{clients → providers}/google/message.py +0 -0
- /mirascope/llm/{clients/openai/completions → providers/openai/responses}/_utils/__init__.py +0 -0
- /mirascope/llm/{clients → providers}/openai/shared/__init__.py +0 -0
- {mirascope-2.0.0a2.dist-info → mirascope-2.0.0a3.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
|