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/__init__.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from .auto import AutoAgent, AutoConfig, AutoProgram, AutoRetriever
|
|
2
|
+
from .observability import Trackable, configure, track, track_modaic_obj
|
|
3
|
+
from .precompiled import Indexer, PrecompiledAgent, PrecompiledConfig, PrecompiledProgram, Retriever
|
|
4
|
+
from .programs import Predict, PredictConfig # noqa: F401
|
|
5
|
+
from .serializers import SerializableLM, SerializableSignature
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
# New preferred names
|
|
9
|
+
"AutoProgram",
|
|
10
|
+
"PrecompiledProgram",
|
|
11
|
+
# Deprecated names (kept for backward compatibility)
|
|
12
|
+
"AutoAgent",
|
|
13
|
+
"PrecompiledAgent",
|
|
14
|
+
# Other exports
|
|
15
|
+
"AutoConfig",
|
|
16
|
+
"AutoRetriever",
|
|
17
|
+
"Retriever",
|
|
18
|
+
"Indexer",
|
|
19
|
+
"PrecompiledConfig",
|
|
20
|
+
"configure",
|
|
21
|
+
"track",
|
|
22
|
+
"Trackable",
|
|
23
|
+
"track_modaic_obj",
|
|
24
|
+
"SerializableSignature",
|
|
25
|
+
"SerializableLM",
|
|
26
|
+
]
|
|
27
|
+
_configured = False
|
modaic/auto.py
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import json
|
|
3
|
+
import sys
|
|
4
|
+
import warnings
|
|
5
|
+
from functools import lru_cache
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Callable, Literal, Optional, Type, TypedDict
|
|
8
|
+
|
|
9
|
+
from .constants import MODAIC_HUB_CACHE
|
|
10
|
+
from .hub import load_repo
|
|
11
|
+
from .precompiled import PrecompiledConfig, PrecompiledProgram, Retriever, _get_state_with_secrets, is_local_path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RegisteredRepo(TypedDict, total=False):
|
|
15
|
+
AutoConfig: Type[PrecompiledConfig]
|
|
16
|
+
AutoProgram: Type[PrecompiledProgram]
|
|
17
|
+
AutoRetriever: Type[Retriever]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
_REGISTRY: dict[str, RegisteredRepo] = {}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def register(
|
|
24
|
+
name: str,
|
|
25
|
+
auto_type: Literal["AutoConfig", "AutoProgram", "AutoRetriever"],
|
|
26
|
+
cls: Type[PrecompiledConfig | PrecompiledProgram | Retriever],
|
|
27
|
+
):
|
|
28
|
+
if name in _REGISTRY:
|
|
29
|
+
_REGISTRY[name][auto_type] = cls
|
|
30
|
+
else:
|
|
31
|
+
_REGISTRY[name] = {auto_type: cls}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# TODO: Cleanup code still using parent_mdoule
|
|
35
|
+
@lru_cache
|
|
36
|
+
def _load_dynamic_class(
|
|
37
|
+
repo_dir: Path, class_path: str, hub_path: str = None, rev: str = "main"
|
|
38
|
+
) -> Type[PrecompiledConfig | PrecompiledProgram | Retriever]:
|
|
39
|
+
"""
|
|
40
|
+
Load a class from a given repository directory and fully qualified class path.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
repo_dir: Absolute path to a local repository directory containing the code.
|
|
44
|
+
class_path: Dotted path to the target class (e.g., "pkg.program.Class").
|
|
45
|
+
hub_path: The path to the repo on modaic hub (if its a hub repo) *Must be specified if its a hub repo*
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
The resolved class object.
|
|
49
|
+
"""
|
|
50
|
+
if class_path == "modaic.PrecompiledConfig":
|
|
51
|
+
return PrecompiledConfig
|
|
52
|
+
if hub_path is None:
|
|
53
|
+
# Local folder case
|
|
54
|
+
repo_dir_str = str(repo_dir)
|
|
55
|
+
if repo_dir_str not in sys.path:
|
|
56
|
+
sys.path.insert(0, repo_dir_str)
|
|
57
|
+
full_path = f"{class_path}"
|
|
58
|
+
else:
|
|
59
|
+
# loaded hub repo case
|
|
60
|
+
modaic_hub_cache_str = str(MODAIC_HUB_CACHE.parent)
|
|
61
|
+
modaic_hub = MODAIC_HUB_CACHE.name
|
|
62
|
+
if modaic_hub_cache_str not in sys.path:
|
|
63
|
+
sys.path.insert(0, modaic_hub_cache_str)
|
|
64
|
+
parent_module = hub_path.replace("/", ".")
|
|
65
|
+
parent_module = f"{parent_module}.{rev}"
|
|
66
|
+
full_path = f"{modaic_hub}.{parent_module}.{class_path}"
|
|
67
|
+
|
|
68
|
+
module_name, _, attr = full_path.rpartition(".")
|
|
69
|
+
module = importlib.import_module(module_name)
|
|
70
|
+
return getattr(module, attr)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class AutoConfig:
|
|
74
|
+
"""
|
|
75
|
+
Config loader for precompiled programs and retrievers.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
def from_precompiled(repo_path: str, rev: str = "main", **kwargs) -> PrecompiledConfig:
|
|
80
|
+
local = is_local_path(repo_path)
|
|
81
|
+
repo_dir, _ = load_repo(repo_path, is_local=local, rev=rev)
|
|
82
|
+
return AutoConfig._from_precompiled(repo_dir, hub_path=repo_path if not local else None, **kwargs)
|
|
83
|
+
|
|
84
|
+
@staticmethod
|
|
85
|
+
def _from_precompiled(repo_dir: Path, hub_path: str = None, rev: str = "main", **kwargs) -> PrecompiledConfig:
|
|
86
|
+
"""
|
|
87
|
+
Load a config for an program or retriever from a precompiled repo.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
repo_dir: The path to the repo directory. the loaded local repository directory.
|
|
91
|
+
hub_path: The path to the repo on modaic hub (if its a hub repo) *Must be specified if its a hub repo*
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
A config object constructed via the resolved config class.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
cfg_path = repo_dir / "config.json"
|
|
98
|
+
if not cfg_path.exists():
|
|
99
|
+
raise FileNotFoundError(f"Failed to load AutoConfig, config.json not found in {hub_path or str(repo_dir)}")
|
|
100
|
+
with open(cfg_path, "r") as fp:
|
|
101
|
+
cfg = json.load(fp)
|
|
102
|
+
|
|
103
|
+
ConfigClass = _load_auto_class(repo_dir, "AutoConfig", hub_path=hub_path, rev=rev) # noqa: N806
|
|
104
|
+
return ConfigClass.from_dict(cfg, **kwargs)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class AutoProgram:
|
|
108
|
+
"""
|
|
109
|
+
Dynamic loader for precompiled programs hosted on a hub or local path.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
@staticmethod
|
|
113
|
+
def from_precompiled(
|
|
114
|
+
repo_path: str,
|
|
115
|
+
*,
|
|
116
|
+
access_token: Optional[str] = None,
|
|
117
|
+
config: Optional[dict] = None,
|
|
118
|
+
rev: str = "main",
|
|
119
|
+
**kw,
|
|
120
|
+
) -> PrecompiledProgram:
|
|
121
|
+
"""
|
|
122
|
+
Load a compiled program from the given identifier.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
repo_path: Hub path ("user/repo") or local directory.
|
|
126
|
+
**kw: Additional keyword arguments forwarded to the Program constructor.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
An instantiated Program subclass.
|
|
130
|
+
"""
|
|
131
|
+
# TODO: fast lookups via registry
|
|
132
|
+
local = is_local_path(repo_path)
|
|
133
|
+
repo_dir, source_commit = load_repo(repo_path, access_token=access_token, is_local=local, rev=rev)
|
|
134
|
+
hub_path = repo_path if not local else None
|
|
135
|
+
|
|
136
|
+
# Support new (AutoProgram) and legacy (AutoAgent) naming in auto_classes.json
|
|
137
|
+
try:
|
|
138
|
+
ProgramClass = _load_auto_class(repo_dir, "AutoProgram", hub_path=hub_path, rev=rev) # noqa: N806
|
|
139
|
+
except KeyError:
|
|
140
|
+
# Fall back to legacy AutoAgent for backward compatibility
|
|
141
|
+
ProgramClass = _load_auto_class(repo_dir, "AutoAgent", hub_path=hub_path, rev=rev) # noqa: N806
|
|
142
|
+
|
|
143
|
+
# automatically configure repo and project from repo_path if not provided
|
|
144
|
+
# TODO: redundant checks in if statement. Investigate removing.
|
|
145
|
+
api_key = kw.get("api_key")
|
|
146
|
+
hf_token = kw.get("hf_token")
|
|
147
|
+
program = ProgramClass.from_precompiled(repo_dir, config=config, api_key=api_key, hf_token=hf_token, **kw)
|
|
148
|
+
|
|
149
|
+
program._source = repo_dir
|
|
150
|
+
program._source_commit = source_commit
|
|
151
|
+
program._from_auto = True
|
|
152
|
+
return program
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class AutoRetriever:
|
|
156
|
+
"""
|
|
157
|
+
Dynamic loader for precompiled retrievers hosted on a hub or local path.
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
@staticmethod
|
|
161
|
+
def from_precompiled(
|
|
162
|
+
repo_path: str,
|
|
163
|
+
*,
|
|
164
|
+
config: Optional[dict] = None,
|
|
165
|
+
rev: str = "main",
|
|
166
|
+
**kw,
|
|
167
|
+
) -> Retriever:
|
|
168
|
+
"""
|
|
169
|
+
Load a compiled retriever from the given identifier.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
repo_path: hub path ("user/repo"), or local directory.
|
|
173
|
+
project: Optional project name. If not provided and repo_path is a hub path, defaults to the repo name.
|
|
174
|
+
**kw: Additional keyword arguments forwarded to the Retriever constructor.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
An instantiated Retriever subclass.
|
|
178
|
+
"""
|
|
179
|
+
local = is_local_path(repo_path)
|
|
180
|
+
repo_dir, source_commit = load_repo(repo_path, is_local=local, rev=rev)
|
|
181
|
+
hub_path = repo_path if not local else None
|
|
182
|
+
|
|
183
|
+
if config is None:
|
|
184
|
+
config = {}
|
|
185
|
+
|
|
186
|
+
cfg = AutoConfig._from_precompiled(repo_dir, hub_path=hub_path, rev=rev, **config)
|
|
187
|
+
RetrieverClass = _load_auto_class(repo_dir, "AutoRetriever", hub_path=hub_path, rev=rev) # noqa: N806
|
|
188
|
+
|
|
189
|
+
retriever = RetrieverClass(config=cfg, **kw)
|
|
190
|
+
retriever._source = repo_dir
|
|
191
|
+
retriever._source_commit = source_commit
|
|
192
|
+
retriever._from_auto = True
|
|
193
|
+
# automatically configure repo and project from repo_path if not provided
|
|
194
|
+
return retriever
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _load_auto_class(
|
|
198
|
+
repo_dir: Path,
|
|
199
|
+
auto_name: Literal["AutoConfig", "AutoProgram", "AutoAgent", "AutoRetriever"],
|
|
200
|
+
hub_path: str = None,
|
|
201
|
+
rev: str = "main",
|
|
202
|
+
) -> Type[PrecompiledConfig | PrecompiledProgram | Retriever]:
|
|
203
|
+
"""
|
|
204
|
+
Load a class from the auto_classes.json file.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
repo_dir: The path to the repo directory. the loaded local repository directory.
|
|
208
|
+
auto_name: The name of the auto class to load. (AutoConfig, AutoProgram, AutoAgent (deprecated), AutoRetriever)
|
|
209
|
+
hub_path: The path to the repo on modaic hub (if its a hub repo) *Must be specified if its a hub repo*
|
|
210
|
+
"""
|
|
211
|
+
# determine if the repo was loaded from local or hub
|
|
212
|
+
local = hub_path is None
|
|
213
|
+
auto_classes_path = repo_dir / "auto_classes.json"
|
|
214
|
+
|
|
215
|
+
if not auto_classes_path.exists():
|
|
216
|
+
raise FileNotFoundError(
|
|
217
|
+
f"Failed to load {auto_name}, auto_classes.json not found in {hub_path or str(repo_dir)}, if this is your repo, make sure you push_to_hub() with `with_code=True`"
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
with open(auto_classes_path, "r") as fp:
|
|
221
|
+
auto_classes = json.load(fp)
|
|
222
|
+
|
|
223
|
+
if not (auto_class_path := auto_classes.get(auto_name)):
|
|
224
|
+
raise KeyError(
|
|
225
|
+
f"{auto_name} not found in {hub_path or str(repo_dir)}/auto_classes.json. Please check that the auto_classes.json file is correct."
|
|
226
|
+
) from None
|
|
227
|
+
|
|
228
|
+
repo_dir = repo_dir.parent.parent if not local else repo_dir
|
|
229
|
+
LoadedClass = _load_dynamic_class(repo_dir, auto_class_path, hub_path=hub_path, rev=rev) # noqa: N806
|
|
230
|
+
return LoadedClass
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def builtin_program(name: str) -> Callable[[Type], Type]:
|
|
234
|
+
"""Decorator to register a builtin program."""
|
|
235
|
+
|
|
236
|
+
def _wrap(cls: Type) -> Type:
|
|
237
|
+
register(name, "AutoProgram", cls)
|
|
238
|
+
return cls
|
|
239
|
+
|
|
240
|
+
return _wrap
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def builtin_agent(name: str) -> Callable[[Type], Type]:
|
|
244
|
+
"""Deprecated: Use builtin_program instead."""
|
|
245
|
+
warnings.warn(
|
|
246
|
+
"builtin_agent is deprecated and will be removed in a future version. "
|
|
247
|
+
"Please use builtin_program instead for better parity with DSPy.",
|
|
248
|
+
DeprecationWarning,
|
|
249
|
+
stacklevel=2,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
def _wrap(cls: Type) -> Type:
|
|
253
|
+
register(name, "AutoProgram", cls)
|
|
254
|
+
return cls
|
|
255
|
+
|
|
256
|
+
return _wrap
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def builtin_indexer(name: str) -> Callable[[Type], Type]:
|
|
260
|
+
def _wrap(cls: Type) -> Type:
|
|
261
|
+
register(name, "AutoRetriever", cls)
|
|
262
|
+
return cls
|
|
263
|
+
|
|
264
|
+
return _wrap
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def builtin_config(name: str) -> Callable[[Type], Type]:
|
|
268
|
+
def _wrap(cls: Type) -> Type:
|
|
269
|
+
register(name, "AutoConfig", cls)
|
|
270
|
+
return cls
|
|
271
|
+
|
|
272
|
+
return _wrap
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
# Deprecated alias for backward compatibility
|
|
276
|
+
class AutoAgent(AutoProgram):
|
|
277
|
+
"""
|
|
278
|
+
Deprecated: Use AutoProgram instead.
|
|
279
|
+
|
|
280
|
+
Dynamic loader for precompiled programs hosted on a hub or local path.
|
|
281
|
+
"""
|
|
282
|
+
|
|
283
|
+
@staticmethod
|
|
284
|
+
def from_precompiled(
|
|
285
|
+
repo_path: str,
|
|
286
|
+
*,
|
|
287
|
+
config: Optional[dict] = None,
|
|
288
|
+
**kw,
|
|
289
|
+
) -> PrecompiledProgram:
|
|
290
|
+
"""Load a compiled program from the given identifier.
|
|
291
|
+
|
|
292
|
+
.. deprecated::
|
|
293
|
+
Use :class:`AutoProgram` instead. AutoAgent is deprecated for better parity with DSPy.
|
|
294
|
+
"""
|
|
295
|
+
warnings.warn(
|
|
296
|
+
"AutoAgent is deprecated and will be removed in a future version. "
|
|
297
|
+
"Please use AutoProgram instead for better parity with DSPy.",
|
|
298
|
+
DeprecationWarning,
|
|
299
|
+
stacklevel=2,
|
|
300
|
+
)
|
|
301
|
+
return AutoProgram.from_precompiled(repo_path, config=config, **kw)
|
modaic/constants.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from .utils import compute_cache_dir
|
|
5
|
+
|
|
6
|
+
MODAIC_CACHE = compute_cache_dir()
|
|
7
|
+
MODAIC_HUB_CACHE = Path(MODAIC_CACHE) / "modaic_hub" / "modaic_hub"
|
|
8
|
+
EDITABLE_MODE = os.getenv("EDITABLE_MODE", "false").lower() == "true"
|
|
9
|
+
STAGING_DIR = Path(MODAIC_CACHE) / "staging"
|
|
10
|
+
SYNC_DIR = Path(MODAIC_CACHE) / "sync"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
MODAIC_TOKEN = os.getenv("MODAIC_TOKEN")
|
|
14
|
+
MODAIC_GIT_URL = os.getenv("MODAIC_GIT_URL", "https://git.modaic.dev").rstrip("/")
|
|
15
|
+
|
|
16
|
+
USE_GITHUB = "github.com" in MODAIC_GIT_URL
|
|
17
|
+
|
|
18
|
+
MODAIC_API_URL = os.getenv("MODAIC_API_URL", "https://api.modaic.dev").rstrip("/")
|
modaic/datasets.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import dspy
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Dataset:
|
|
5
|
+
def __init__(self, data: list):
|
|
6
|
+
self.data = data
|
|
7
|
+
|
|
8
|
+
def to_dspy(self) -> list:
|
|
9
|
+
return dspy.Dataset(self.data)
|
|
10
|
+
|
|
11
|
+
@classmethod
|
|
12
|
+
def from_csv(cls, file_path: str) -> "Dataset":
|
|
13
|
+
with open(file_path, "r") as file:
|
|
14
|
+
data = file.read()
|
|
15
|
+
return cls(data)
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
def from_hub(cls, dataset_name: str) -> "Dataset":
|
|
19
|
+
from datasets import load_dataset
|
|
20
|
+
|
|
21
|
+
data = load_dataset(dataset_name)
|
|
22
|
+
return cls(data)
|
modaic/exceptions.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
class ModaicError(Exception):
|
|
2
|
+
pass
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ModaicHubError(ModaicError):
|
|
6
|
+
"""Base class for all hub-related errors."""
|
|
7
|
+
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RevisionNotFoundError(ModaicHubError):
|
|
12
|
+
"""Raised when revision is not found"""
|
|
13
|
+
|
|
14
|
+
message: str
|
|
15
|
+
rev: str
|
|
16
|
+
|
|
17
|
+
def __init__(self, message: str, rev: str):
|
|
18
|
+
self.message = message
|
|
19
|
+
self.rev = rev
|
|
20
|
+
super().__init__(message)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class RepositoryExistsError(ModaicHubError):
|
|
24
|
+
"""Raised when repository already exists"""
|
|
25
|
+
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class AuthenticationError(ModaicHubError):
|
|
30
|
+
"""Raised when authentication fails"""
|
|
31
|
+
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class RepositoryNotFoundError(ModaicHubError):
|
|
36
|
+
"""Raised when repository does not exist"""
|
|
37
|
+
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class SchemaError(ModaicError):
|
|
42
|
+
"""Raised when a schema is invalid"""
|
|
43
|
+
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class BackendCompatibilityError(ModaicError):
|
|
48
|
+
"""Raised when a feature is not supported by a backend"""
|
|
49
|
+
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class MissingSecretError(AuthenticationError):
|
|
54
|
+
"""Raised when a secret is missing"""
|
|
55
|
+
|
|
56
|
+
def __init__(self, message: str, secret_name: str):
|
|
57
|
+
self.message = message
|
|
58
|
+
self.secret_name = secret_name
|
|
59
|
+
super().__init__(message)
|