modaic 0.10.4__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.
- modaic/__init__.py +27 -0
- modaic/auto.py +301 -0
- modaic/constants.py +18 -0
- modaic/datasets.py +22 -0
- modaic/exceptions.py +59 -0
- modaic/hub.py +671 -0
- modaic/module_utils.py +560 -0
- modaic/observability.py +259 -0
- modaic/precompiled.py +608 -0
- modaic/programs/__init__.py +1 -0
- modaic/programs/predict.py +51 -0
- modaic/programs/rag_program.py +35 -0
- modaic/programs/registry.py +104 -0
- modaic/serializers.py +222 -0
- modaic/utils.py +115 -0
- modaic-0.10.4.dist-info/METADATA +138 -0
- modaic-0.10.4.dist-info/RECORD +19 -0
- modaic-0.10.4.dist-info/WHEEL +4 -0
- modaic-0.10.4.dist-info/licenses/LICENSE +31 -0
modaic/observability.py
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from functools import wraps
|
|
5
|
+
from typing import Any, Callable, Dict, Optional, TypeVar, cast
|
|
6
|
+
|
|
7
|
+
import dspy
|
|
8
|
+
import opik
|
|
9
|
+
from opik import Opik, config
|
|
10
|
+
from opik.integrations.dspy.callback import OpikCallback
|
|
11
|
+
from typing_extensions import Concatenate, ParamSpec
|
|
12
|
+
|
|
13
|
+
from .utils import validate_project_name
|
|
14
|
+
|
|
15
|
+
P = ParamSpec("P") # params of the function
|
|
16
|
+
R = TypeVar("R") # return type of the function
|
|
17
|
+
T = TypeVar("T", bound="Trackable") # an instance of a class that inherits from Trackable
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class ModaicSettings:
|
|
22
|
+
"""Global settings for Modaic observability."""
|
|
23
|
+
|
|
24
|
+
tracing: bool = False
|
|
25
|
+
project: Optional[str] = None
|
|
26
|
+
base_url: str = "https://api.modaic.dev"
|
|
27
|
+
modaic_token: Optional[str] = None
|
|
28
|
+
default_tags: Dict[str, str] = field(default_factory=dict)
|
|
29
|
+
log_inputs: bool = True
|
|
30
|
+
log_outputs: bool = True
|
|
31
|
+
max_input_size: int = 10000
|
|
32
|
+
max_output_size: int = 10000
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# global settings instance
|
|
36
|
+
_settings = ModaicSettings()
|
|
37
|
+
_opik_client: Optional[Opik] = None
|
|
38
|
+
_configured = False
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def configure(
|
|
42
|
+
project: str,
|
|
43
|
+
tracing: bool = True,
|
|
44
|
+
base_url: str = "https://api.modaic.dev",
|
|
45
|
+
modaic_token: Optional[str] = None,
|
|
46
|
+
default_tags: Optional[Dict[str, str]] = None,
|
|
47
|
+
log_inputs: bool = True,
|
|
48
|
+
log_outputs: bool = True,
|
|
49
|
+
max_input_size: int = 10000,
|
|
50
|
+
max_output_size: int = 10000,
|
|
51
|
+
**opik_kwargs,
|
|
52
|
+
) -> None:
|
|
53
|
+
"""Configure Modaic observability settings globally.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
tracing: Whether observability is enabled
|
|
57
|
+
project: Default project name
|
|
58
|
+
base_url: Opik server URL
|
|
59
|
+
modaic_token: Authentication token for Opik
|
|
60
|
+
default_tags: Default tags to apply to all traces
|
|
61
|
+
log_inputs: Whether to log function inputs
|
|
62
|
+
log_outputs: Whether to log function outputs
|
|
63
|
+
max_input_size: Maximum size of logged inputs
|
|
64
|
+
max_output_size: Maximum size of logged outputs
|
|
65
|
+
**opik_kwargs: Additional arguments passed to opik.configure()
|
|
66
|
+
"""
|
|
67
|
+
global _settings, _opik_client, _configured
|
|
68
|
+
|
|
69
|
+
# update global settings
|
|
70
|
+
_settings.tracing = tracing
|
|
71
|
+
_settings.project = project
|
|
72
|
+
_settings.base_url = base_url
|
|
73
|
+
_settings.modaic_token = modaic_token
|
|
74
|
+
_settings.default_tags = default_tags or {}
|
|
75
|
+
_settings.log_inputs = log_inputs
|
|
76
|
+
_settings.log_outputs = log_outputs
|
|
77
|
+
_settings.max_input_size = max_input_size
|
|
78
|
+
_settings.max_output_size = max_output_size
|
|
79
|
+
|
|
80
|
+
if tracing:
|
|
81
|
+
# configure Opik
|
|
82
|
+
opik_config = {"use_local": True, "url": base_url, "force": True, "automatic_approvals": True, **opik_kwargs}
|
|
83
|
+
|
|
84
|
+
opik.configure(**opik_config)
|
|
85
|
+
|
|
86
|
+
_opik_client = Opik(host=base_url, project_name=project)
|
|
87
|
+
opik_callback = OpikCallback(project_name=project, log_graph=False)
|
|
88
|
+
dspy.configure(callbacks=[opik_callback])
|
|
89
|
+
|
|
90
|
+
config.update_session_config("track_disable", not tracing)
|
|
91
|
+
|
|
92
|
+
_configured = True
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _get_effective_settings(project: Optional[str] = None, tags: Optional[Dict[str, str]] = None) -> Dict[str, Any]:
|
|
96
|
+
"""Get effective settings by merging global and local parameters."""
|
|
97
|
+
effective_project = project if project else _settings.project
|
|
98
|
+
|
|
99
|
+
# validate project name if provided
|
|
100
|
+
if effective_project:
|
|
101
|
+
validate_project_name(effective_project)
|
|
102
|
+
|
|
103
|
+
# merge tags
|
|
104
|
+
effective_tags = {**_settings.default_tags}
|
|
105
|
+
if tags:
|
|
106
|
+
effective_tags.update(tags)
|
|
107
|
+
|
|
108
|
+
return {"project": effective_project, "tags": effective_tags}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _truncate_data(data: Any, max_size: int) -> Any:
|
|
112
|
+
"""Truncate data if it exceeds max_size when serialized."""
|
|
113
|
+
try:
|
|
114
|
+
import json
|
|
115
|
+
|
|
116
|
+
serialized = json.dumps(data, default=str)
|
|
117
|
+
if len(serialized) > max_size:
|
|
118
|
+
return f"<Data truncated: {len(serialized)} chars>"
|
|
119
|
+
return data
|
|
120
|
+
except Exception:
|
|
121
|
+
# if serialization fails, convert to string and truncate
|
|
122
|
+
str_data = str(data)
|
|
123
|
+
if len(str_data) > max_size:
|
|
124
|
+
return str_data[:max_size] + "..."
|
|
125
|
+
return str_data
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def track( # noqa: ANN201
|
|
129
|
+
name: Optional[str] = None,
|
|
130
|
+
project: Optional[str] = None,
|
|
131
|
+
tags: Optional[Dict[str, str]] = None,
|
|
132
|
+
span_type: str = "general",
|
|
133
|
+
capture_input: Optional[bool] = None,
|
|
134
|
+
capture_output: Optional[bool] = None,
|
|
135
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
136
|
+
**opik_kwargs,
|
|
137
|
+
) -> Callable[[Callable[P, R]], Callable[P, R]]:
|
|
138
|
+
"""Decorator to track function calls with Opik.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
name: Custom name for the tracked operation
|
|
142
|
+
project: Project name (overrides global setting)
|
|
143
|
+
tags: Additional tags for this operation
|
|
144
|
+
span_type: Type of span ('general', 'tool', 'llm', 'guardrail')
|
|
145
|
+
capture_input: Whether to capture input (overrides global setting)
|
|
146
|
+
capture_output: Whether to capture output (overrides global setting)
|
|
147
|
+
metadata: Additional metadata
|
|
148
|
+
**opik_kwargs: Additional arguments passed to opik.track
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
def decorator(func: Callable) -> Callable:
|
|
152
|
+
if not _settings.tracing:
|
|
153
|
+
return func
|
|
154
|
+
|
|
155
|
+
# get effective settings
|
|
156
|
+
settings = _get_effective_settings(project, tags)
|
|
157
|
+
|
|
158
|
+
# determine capture settings
|
|
159
|
+
should_capture_input = capture_input if capture_input is not None else _settings.log_inputs
|
|
160
|
+
should_capture_output = capture_output if capture_output is not None else _settings.log_outputs
|
|
161
|
+
|
|
162
|
+
# build opik.track arguments
|
|
163
|
+
track_args: Dict[str, Any] = {
|
|
164
|
+
"type": span_type,
|
|
165
|
+
"capture_input": should_capture_input,
|
|
166
|
+
"capture_output": should_capture_output,
|
|
167
|
+
**opik_kwargs,
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
# add project if available
|
|
171
|
+
if settings["project"]:
|
|
172
|
+
track_args["project_name"] = settings["project"]
|
|
173
|
+
|
|
174
|
+
if name:
|
|
175
|
+
track_args["name"] = name
|
|
176
|
+
|
|
177
|
+
# add tags and metadata
|
|
178
|
+
if settings["tags"] or metadata:
|
|
179
|
+
combined_metadata = {**(metadata or {})}
|
|
180
|
+
if settings["tags"]:
|
|
181
|
+
combined_metadata["tags"] = settings["tags"]
|
|
182
|
+
track_args["metadata"] = combined_metadata
|
|
183
|
+
|
|
184
|
+
# apply opik.track decorator
|
|
185
|
+
# Return function with type annotations persisted for static type checking
|
|
186
|
+
return cast(Callable[P, R], opik.track(**track_args)(func))
|
|
187
|
+
|
|
188
|
+
return decorator
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class Trackable:
|
|
192
|
+
"""Base class for objects that support automatic tracking.
|
|
193
|
+
|
|
194
|
+
Manages the attributes project, and commit for classes that subclass it.
|
|
195
|
+
All Modaic classes except PrecompiledProgram should inherit from this class.
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
def __init__(
|
|
199
|
+
self,
|
|
200
|
+
project: Optional[str] = None,
|
|
201
|
+
commit: Optional[str] = None,
|
|
202
|
+
trace: bool = False,
|
|
203
|
+
):
|
|
204
|
+
self.project = project
|
|
205
|
+
self.commit = commit
|
|
206
|
+
self.trace = trace
|
|
207
|
+
|
|
208
|
+
def set_project(self, project: Optional[str] = None, trace: bool = True):
|
|
209
|
+
"""Update the project for this trackable object."""
|
|
210
|
+
self.project = project
|
|
211
|
+
self.trace = trace
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
MethodDecorator = Callable[
|
|
215
|
+
[Callable[Concatenate[T, P], R]],
|
|
216
|
+
Callable[Concatenate[T, P], R],
|
|
217
|
+
]
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def track_modaic_obj(func: Callable[Concatenate[T, P], R]) -> Callable[Concatenate[T, P], R]:
|
|
221
|
+
"""Method decorator for Trackable objects to automatically track method calls.
|
|
222
|
+
|
|
223
|
+
Uses self.project to automatically set project
|
|
224
|
+
for modaic.track, then wraps the function with modaic.track.
|
|
225
|
+
|
|
226
|
+
Usage:
|
|
227
|
+
class Retriever(Trackable):
|
|
228
|
+
@track_modaic_obj
|
|
229
|
+
def retrieve(self, query: str):
|
|
230
|
+
...
|
|
231
|
+
"""
|
|
232
|
+
|
|
233
|
+
@wraps(func)
|
|
234
|
+
def wrapper(self: T, *args: P.args, **kwargs: P.kwargs) -> R:
|
|
235
|
+
# self should be a Trackable instance
|
|
236
|
+
# TODO: may want to get rid of this type check for hot paths
|
|
237
|
+
if not isinstance(self, Trackable):
|
|
238
|
+
raise ValueError("@track_modaic_obj can only be used on methods of Trackable subclasses")
|
|
239
|
+
|
|
240
|
+
# get project from self
|
|
241
|
+
project = getattr(self, "project", None)
|
|
242
|
+
|
|
243
|
+
# check if tracking is enabled both globally and for this object
|
|
244
|
+
if not _settings.tracing or not self.trace:
|
|
245
|
+
# binds the method to self so it can be called with args and kwars, also type cast's it to callable with type vars for static type checking
|
|
246
|
+
bound = cast(Callable[P, R], func.__get__(self, type(self)))
|
|
247
|
+
return bound(*args, **kwargs)
|
|
248
|
+
|
|
249
|
+
# create tracking decorator with automatic name generation
|
|
250
|
+
tracker = track(name=f"{self.__class__.__name__}.{func.__name__}", project=project, span_type="general")
|
|
251
|
+
|
|
252
|
+
# apply tracking and call method
|
|
253
|
+
# type casts the 'track' decorator static type checking
|
|
254
|
+
tracked_func = cast(MethodDecorator, tracker)(func)
|
|
255
|
+
# binds the method to self so it can be called with args and kwars, also type cast's it to callable with type vars for static type checking
|
|
256
|
+
bound_tracked = cast(Callable[P, R], tracked_func.__get__(self, type(self)))
|
|
257
|
+
return bound_tracked(*args, **kwargs)
|
|
258
|
+
|
|
259
|
+
return cast(Callable[Concatenate[T, P], R], wrapper)
|