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 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)