modaic 0.1.0__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.

Potentially problematic release.


This version of modaic might be problematic. Click here for more details.

Files changed (39) hide show
  1. modaic/__init__.py +25 -0
  2. modaic/agents/rag_agent.py +33 -0
  3. modaic/agents/registry.py +84 -0
  4. modaic/auto_agent.py +228 -0
  5. modaic/context/__init__.py +34 -0
  6. modaic/context/base.py +1064 -0
  7. modaic/context/dtype_mapping.py +25 -0
  8. modaic/context/table.py +585 -0
  9. modaic/context/text.py +94 -0
  10. modaic/databases/__init__.py +35 -0
  11. modaic/databases/graph_database.py +269 -0
  12. modaic/databases/sql_database.py +355 -0
  13. modaic/databases/vector_database/__init__.py +12 -0
  14. modaic/databases/vector_database/benchmarks/baseline.py +123 -0
  15. modaic/databases/vector_database/benchmarks/common.py +48 -0
  16. modaic/databases/vector_database/benchmarks/fork.py +132 -0
  17. modaic/databases/vector_database/benchmarks/threaded.py +119 -0
  18. modaic/databases/vector_database/vector_database.py +722 -0
  19. modaic/databases/vector_database/vendors/milvus.py +408 -0
  20. modaic/databases/vector_database/vendors/mongodb.py +0 -0
  21. modaic/databases/vector_database/vendors/pinecone.py +0 -0
  22. modaic/databases/vector_database/vendors/qdrant.py +1 -0
  23. modaic/exceptions.py +38 -0
  24. modaic/hub.py +305 -0
  25. modaic/indexing.py +127 -0
  26. modaic/module_utils.py +341 -0
  27. modaic/observability.py +275 -0
  28. modaic/precompiled.py +429 -0
  29. modaic/query_language.py +321 -0
  30. modaic/storage/__init__.py +3 -0
  31. modaic/storage/file_store.py +239 -0
  32. modaic/storage/pickle_store.py +25 -0
  33. modaic/types.py +287 -0
  34. modaic/utils.py +21 -0
  35. modaic-0.1.0.dist-info/METADATA +281 -0
  36. modaic-0.1.0.dist-info/RECORD +39 -0
  37. modaic-0.1.0.dist-info/WHEEL +5 -0
  38. modaic-0.1.0.dist-info/licenses/LICENSE +31 -0
  39. modaic-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,321 @@
1
+ from types import NoneType
2
+ from typing import Any, Literal, Optional, Type, TypeAlias, Union
3
+
4
+ from langchain_core.structured_query import Comparator, Comparison, Operation, Operator, StructuredQuery, Visitor
5
+
6
+ ValueType: TypeAlias = Union[int, str, float, bool, NoneType, list, "Value"]
7
+
8
+ value_types = (int, str, float, bool, NoneType, list)
9
+
10
+ comparator_to_python = {
11
+ Comparator.EQ: "==",
12
+ Comparator.LT: "<",
13
+ Comparator.LTE: "<=",
14
+ Comparator.GT: ">",
15
+ Comparator.GTE: ">=",
16
+ Comparator.NE: "!=",
17
+ Comparator.CONTAIN: "contains",
18
+ Comparator.IN: "in_",
19
+ Comparator.NIN: "not_in",
20
+ }
21
+
22
+ allowed_types = {
23
+ Comparator.EQ: [int, str, list, dict, bool, NoneType],
24
+ Comparator.LT: [int, float],
25
+ Comparator.LTE: [int, float],
26
+ Comparator.GT: [int, float],
27
+ Comparator.GTE: [int, float],
28
+ Comparator.NE: [int, str, list, dict, bool, NoneType],
29
+ Comparator.CONTAIN: [int, str, bool, NoneType],
30
+ Comparator.IN: [list, dict],
31
+ Comparator.NIN: [list, dict],
32
+ }
33
+
34
+
35
+ # def _print_return(func): # noqa: ANN001
36
+ # def wrapper(*args, **kwargs):
37
+ # result = func(*args, **kwargs)
38
+ # if isinstance(op := args[1], str) and op[0] == "$":
39
+ # if kwargs.get("recursed", False):
40
+ # print( # noqa: T201
41
+ # f"{repr(args[0])} ({mql_operator_to_python[op]}) {repr(args[2])} ->:",
42
+ # result,
43
+ # )
44
+ # else:
45
+ # print( # noqa: T201
46
+ # f"{repr(args[0])} {mql_operator_to_python[op]} {repr(args[2])} ->:",
47
+ # result,
48
+ # )
49
+ # else:
50
+ # if func.__name__ == "__and__":
51
+ # print(f"{repr(args[0])} & {repr(args[1])} ->:", result) # noqa: T201
52
+ # elif func.__name__ == "__rand__":
53
+ # print(f"{repr(args[1])} & {repr(args[0])} ->:", result) # noqa: T201
54
+ # return result
55
+
56
+ # return wrapper
57
+
58
+
59
+ class Condition:
60
+ """
61
+ Modaic Query Language Property class.
62
+ """
63
+
64
+ is_operation: bool = False
65
+ condition: Comparison | Operation = None
66
+
67
+ def __init__(self, condition: Comparison | Operation):
68
+ if isinstance(condition, Comparison):
69
+ self.is_operation = False
70
+ else:
71
+ self.is_operation = True
72
+ self.condition = condition
73
+
74
+ def __repr__(self):
75
+ return f"Condition({repr(self.condition)})"
76
+
77
+ def __str__(self):
78
+ return (
79
+ f"Prop({self.condition.attribute}) {comparator_to_python[self.condition.comparator]} {self.condition.value}"
80
+ )
81
+
82
+ def __contains__(self, other: str):
83
+ raise ValueError("Modaic Filters do not support `in` use Prop.in_()/Prop.not_in() instead")
84
+
85
+ def __bool__(self):
86
+ raise ValueError(
87
+ "Attempted to evaluate Modaic condition as boolean. Please make sure you wrap ALL expresions with ()"
88
+ )
89
+
90
+ def __and__(self, other: Union["Condition", ValueType]):
91
+ if not isinstance(other, Condition):
92
+ other = Value(other)
93
+ return AND(self, other)
94
+
95
+ # @_print_return
96
+ def __or__(self, other: Union["Condition", ValueType]):
97
+ if not isinstance(other, Condition):
98
+ other = Value(other)
99
+ return OR(self, other)
100
+
101
+ # @_print_return
102
+ def __rand__(self, other: Union[int, str, list]):
103
+ if not isinstance(other, Condition):
104
+ other = Value(other)
105
+ return AND(other, self)
106
+
107
+ # @_print_return
108
+ def __ror__(self, other: Union[int, str, list]):
109
+ if not isinstance(other, Condition):
110
+ other = Value(other)
111
+ return OR(other, self)
112
+
113
+ def __invert__(self):
114
+ # TODO: implement , use nor
115
+ raise NotImplementedError("Modaic queires do not support ! yet")
116
+
117
+
118
+ def _enforce_types(
119
+ other: ValueType,
120
+ op: str,
121
+ allowed_types: list[Type],
122
+ ):
123
+ other_type = _get_type(other)
124
+ if other_type not in allowed_types:
125
+ raise ValueError(f"Value must be one of {allowed_types}, got {other_type} for {op}")
126
+
127
+
128
+ def _get_type(value: ValueType):
129
+ if isinstance(value, Value):
130
+ return type(value.value)
131
+ elif isinstance(value, value_types):
132
+ return type(value)
133
+ elif isinstance(value, Condition):
134
+ return bool
135
+ elif isinstance(value, Prop):
136
+ raise NotImplementedError("Modaic queries do not support comparisions between properties yet.")
137
+ else:
138
+ raise ValueError(f"Unssuported value type in Modaic condition, got {type(value)}")
139
+
140
+
141
+ class Prop:
142
+ """
143
+ Modaic Query Language Property class.
144
+ """
145
+
146
+ def __init__(
147
+ self,
148
+ name: str,
149
+ ):
150
+ super().__init__()
151
+ self.name = name
152
+
153
+ def __getitem__(self, key: str):
154
+ raise NotImplementedError("Modaic queries do not support nested properties yet")
155
+
156
+ def in_(self, other: Union["Value", list]) -> "Condition":
157
+ value = Value(other)
158
+ return self.comparison("in", Comparator.IN, value)
159
+
160
+ def not_in(self, other: Union["Value", list]) -> "Condition":
161
+ value = Value(other)
162
+ return self.comparison("not_in", Comparator.NIN, value)
163
+
164
+ def __eq__(self, other: Optional[Union[ValueType]]) -> "Condition":
165
+ value = Value(other)
166
+ return self.comparison("==", Comparator.EQ, value)
167
+
168
+ def __lt__(self, other: Union[ValueType]) -> "Condition":
169
+ value = Value(other)
170
+ return self.comparison("<", Comparator.LT, value)
171
+
172
+ def __le__(self, other: Union[ValueType]) -> "Condition":
173
+ value = Value(other)
174
+ return self.comparison("<=", Comparator.LTE, value)
175
+
176
+ def __gt__(self, other: Union[ValueType]) -> "Condition":
177
+ value = Value(other)
178
+ return self.comparison(">", Comparator.GT, value)
179
+
180
+ def __ge__(self, other: Union[ValueType]) -> "Condition":
181
+ value = Value(other)
182
+ return self.comparison(">=", Comparator.GTE, value)
183
+
184
+ def __ne__(self, other: Optional[Union[ValueType]]) -> "Condition":
185
+ value = Value(other)
186
+ return self.comparison("!=", Comparator.NE, value)
187
+
188
+ def contains(self, other: Union[ValueType]) -> "Condition":
189
+ value = Value(other)
190
+ return self.comparison("contains", Comparator.CONTAIN, value)
191
+
192
+ def exists(self) -> "Condition":
193
+ # TODO: implement
194
+ raise NotImplementedError("Prop does not support exists")
195
+
196
+ def not_exists(self) -> "Condition":
197
+ # TODO: implement
198
+ raise NotImplementedError("Prop does not support not_exists")
199
+
200
+ def comparison(self, op_expr: str, comparator: Comparator, other: "Value") -> "Condition":
201
+ _enforce_types(other, op_expr, allowed_types[comparator])
202
+ comparison = Comparison(
203
+ comparator=comparator,
204
+ attribute=self.name,
205
+ value=other.value,
206
+ )
207
+ return Condition(comparison)
208
+
209
+ def __len__(self):
210
+ # TODO: implement
211
+ raise NotImplementedError("Prop does not support __len__")
212
+
213
+ def all(self, other): # noqa: ANN001
214
+ # TODO: implement
215
+ raise NotImplementedError("Prop does not support all")
216
+
217
+ def any(self, other): # noqa: ANN001
218
+ # TODO: implement
219
+ raise NotImplementedError("Prop does not support any")
220
+
221
+
222
+ class Value:
223
+ """
224
+ Modaic Query Language Value class.
225
+ """
226
+
227
+ def __init__(self, value: Union[int, str, list, dict, bool, "Value", None]):
228
+ super().__init__()
229
+ if not isinstance(value, ValueType):
230
+ raise ValueError(
231
+ f"Value must be one of {value_types}, got {type(value)}. Please wrap your expressions with ()"
232
+ )
233
+ if isinstance(value, Value):
234
+ value = value.value
235
+ self.value = value
236
+
237
+
238
+ class AND(Condition):
239
+ """
240
+ Modaic Query Language AND class.
241
+ """
242
+
243
+ is_operation: bool = True
244
+ condition: Operation
245
+
246
+ def __init__(self, left: "Condition", right: "Condition"):
247
+ self.left = left
248
+ self.right = right
249
+ if isinstance(self.left, AND) and isinstance(self.right, AND):
250
+ arguments = self.left.condition.arguments + self.right.condition.arguments
251
+ elif and_other := _get_and_other(self.left, self.right):
252
+ arguments = and_other[0].condition.arguments + [and_other[1].condition]
253
+ else:
254
+ arguments = [self.left.condition, self.right.condition]
255
+ super().__init__(Operation(operator=Operator.AND, arguments=arguments))
256
+
257
+ def __repr__(self):
258
+ return f"AND({self.left}, {self.right})"
259
+
260
+ def __str__(self):
261
+ return f"({str(self.left)}) & ({str(self.right)})"
262
+
263
+
264
+ class OR(Condition):
265
+ """
266
+ Modaic Query Language OR class.
267
+ """
268
+
269
+ is_operation: bool = True
270
+ condition: Operation
271
+
272
+ def __init__(self, left: "Condition", right: "Condition", complete: bool = False):
273
+ self.left = left
274
+ self.right = right
275
+ if isinstance(self.left, OR) and isinstance(self.right, OR):
276
+ arguments = self.left.condition.arguments + self.right.condition.arguments
277
+ elif or_other := _get_or_other(self.left, self.right):
278
+ arguments = or_other[0].condition.arguments + [or_other[1].condition]
279
+ else:
280
+ arguments = [self.left.condition, self.right.condition]
281
+ super().__init__(Operation(operator=Operator.OR, arguments=arguments))
282
+
283
+ def __repr__(self):
284
+ return f"OR({self.left}, {self.right})"
285
+
286
+ def __str__(self):
287
+ return f"({str(self.left)}) | ({str(self.right)})"
288
+
289
+
290
+ def _get_and_or(left: "Condition", right: "Condition"):
291
+ if isinstance(left, AND) and isinstance(right, OR):
292
+ return left, right
293
+ elif isinstance(right, AND) and isinstance(left, OR):
294
+ return right, left
295
+ else:
296
+ return None
297
+
298
+
299
+ def _get_and_other(left: "Condition", right: "Condition"):
300
+ if isinstance(left, AND) and type(right) is Condition:
301
+ return left, right
302
+ elif isinstance(right, AND) and type(left) is Condition:
303
+ return right, left
304
+ else:
305
+ return None
306
+
307
+
308
+ def _get_or_other(left: "Condition", right: "Condition"):
309
+ if isinstance(left, OR) and right is Condition:
310
+ return left, right
311
+ elif isinstance(right, OR) and left is Condition:
312
+ return right, left
313
+ else:
314
+ return None
315
+
316
+
317
+ def parse_modaic_filter(translator: Visitor, condition: Condition) -> dict: # noqa: N802
318
+ if condition.is_operation:
319
+ return translator.visit_operation(condition.condition)
320
+ else:
321
+ return translator.visit_comparison(condition.condition)
@@ -0,0 +1,3 @@
1
+ from .file_store import FileStore, InPlaceFileStore, LocalFileStore
2
+
3
+ __all__ = ["FileStore", "InPlaceFileStore", "LocalFileStore"]
@@ -0,0 +1,239 @@
1
+ import shutil
2
+ import uuid
3
+ from abc import ABC, abstractmethod
4
+ from pathlib import Path
5
+ from typing import (
6
+ IO,
7
+ Iterator,
8
+ NamedTuple,
9
+ Optional,
10
+ Tuple,
11
+ )
12
+
13
+ import immutables
14
+
15
+
16
+ class FileResult(NamedTuple):
17
+ """
18
+ Return type for FileStore.get()
19
+ Args:
20
+ file: The file object
21
+ type: The type of the file without the leading dot (e.g. "csv", "txt", "xlsx", etc.). Usually the extension of the file but can be used for other purposes. (e.g. "google_doc")
22
+ name: The name of the file
23
+ metadata: A map of metadata for the file
24
+ """
25
+
26
+ file: Path | IO
27
+ type: str
28
+ name: Optional[str] = None
29
+ metadata: immutables.Map = immutables.Map()
30
+
31
+
32
+ class FileStore(ABC):
33
+ """
34
+ Base class for file stores. File stores are used to store files that hydrate Context objects. The files can be stored locally or remotely.
35
+ """
36
+
37
+ @abstractmethod
38
+ def get(self, reference: str) -> FileResult:
39
+ """
40
+ Get a file from the file store by reference (like an id or path).
41
+
42
+ Args:
43
+ reference: The id or path of the file in the FileStore.
44
+
45
+ Raises:
46
+ FileNotFoundError: If the file is not found.
47
+
48
+ Returns:
49
+ A Path object or a file-like object.
50
+ """
51
+
52
+ @abstractmethod
53
+ def contains(self, reference: str) -> bool:
54
+ """
55
+ Checks if the file store contains a given reference.
56
+
57
+ Args:
58
+ reference: The id or path of the file in the FileStore.
59
+
60
+ Returns:
61
+ True if the reference is found in the file store, False otherwise.
62
+ """
63
+ pass
64
+
65
+ @abstractmethod
66
+ def keys(self, folder: Optional[str] = None) -> Iterator[str]:
67
+ """
68
+ Iterate over all keys in the file store.
69
+ """
70
+ pass
71
+
72
+ def values(self, folder: Optional[str] = None) -> Iterator[FileResult]:
73
+ """
74
+ Iterate over all files in the file store.
75
+ """
76
+ for ref in self.keys(folder):
77
+ yield self.get(ref)
78
+
79
+ def items(self, folder: Optional[str] = None) -> Iterator[Tuple[str, FileResult]]:
80
+ """
81
+ Iterate over all keys and files in the file store.
82
+ """
83
+ for ref in self.keys(folder):
84
+ yield ref, self.get(ref)
85
+
86
+ def __iter__(self) -> Iterator[str]:
87
+ """
88
+ Iterate over all references in the file store.
89
+ """
90
+ return self.keys()
91
+
92
+ def __contains__(self, reference: str) -> bool:
93
+ return self.contains(reference)
94
+
95
+ def __len__(self) -> int:
96
+ """
97
+ Get the number of files in the file store.
98
+ """
99
+ return len(list(self.keys()))
100
+
101
+
102
+ class MutableFileStore(FileStore):
103
+ """
104
+ A FileStore that supports adding and removing files.
105
+ """
106
+
107
+ @abstractmethod
108
+ def add(self, file: str | Path | IO, type: Optional[str] = None) -> str:
109
+ """
110
+ Add a file to the file store.
111
+
112
+ Args:
113
+ file: The file to add to the file store.
114
+
115
+ Returns:
116
+ The reference to the file in the FileStore.
117
+ """
118
+
119
+ @abstractmethod
120
+ def update(self, reference: str, file: str | Path | IO, type: Optional[str] = None) -> None:
121
+ """
122
+ Update a file in the file store.
123
+
124
+ Args:
125
+ reference: The id or path of the file in the FileStore.
126
+ file: The file to update in the file store.
127
+
128
+ Raises:
129
+ FileNotFoundError: If the file is not found.
130
+ """
131
+ pass
132
+
133
+ @abstractmethod
134
+ def remove(self, reference: str) -> None:
135
+ """
136
+ Remove a file from the file store.
137
+
138
+ Raises:
139
+ FileNotFoundError: If the file is not found.
140
+
141
+ Args:
142
+ reference: The id or path of the file in the FileStore.
143
+ """
144
+
145
+
146
+ class InPlaceFileStore(FileStore):
147
+ def __init__(
148
+ self,
149
+ directory: str | Path,
150
+ ):
151
+ self.directory = Path(directory)
152
+ if not (self.directory).exists():
153
+ raise FileNotFoundError(f"File store directory {self.directory} is not a valid directory")
154
+
155
+ def get(self, reference: str) -> FileResult:
156
+ """
157
+ Get a file from the file store by path relative to self.directory.
158
+
159
+ Args:
160
+ reference: The local path of the file relative to self.directory.
161
+
162
+ Raises:
163
+ FileNotFoundError: If the file is not found.
164
+
165
+ Returns:
166
+ A Path object.
167
+
168
+ """
169
+ path = self.directory / reference
170
+ if not path.exists():
171
+ raise FileNotFoundError(f"File {reference} not found in {self.directory}")
172
+ return FileResult(file=path, type=path.suffix.lstrip("."), name=path.name)
173
+
174
+ def contains(self, reference: str) -> bool:
175
+ return (self.directory / reference).exists()
176
+
177
+ def keys(self, folder: Optional[str] = None) -> Iterator[str]:
178
+ """
179
+ Iterate over all files in the file store.
180
+ """
181
+ folder: Path = self.directory if folder is None else self.directory / folder
182
+ return (str(path.relative_to(self.directory)) for path in folder.iterdir() if path.is_file())
183
+
184
+
185
+ class LocalFileStore(MutableFileStore):
186
+ def __init__(self, directory: str | Path):
187
+ self.directory = Path(directory)
188
+ (self.directory / ".modaic").mkdir(parents=True, exist_ok=True)
189
+
190
+ def get(self, reference: str) -> FileResult:
191
+ """
192
+ Get a file from the file store by path relative to self.directory.
193
+ """
194
+ path = self.directory / reference
195
+ if not path.exists():
196
+ raise FileNotFoundError(f"File {reference} not found in {self.directory}")
197
+ return FileResult(file=path, type=path.suffix.lstrip("."), name=path.name)
198
+
199
+ def contains(self, reference: str) -> bool:
200
+ return (self.directory / reference).exists()
201
+
202
+ def add(self, file: str | Path | IO) -> str:
203
+ default_filename = str(uuid.uuid4())
204
+ if isinstance(file, (Path, str)):
205
+ original_path = Path(file)
206
+ if is_in_dir(original_path, self.directory):
207
+ path = original_path
208
+ else:
209
+ path = self.directory / ".modaic" / default_filename
210
+ shutil.copy2(original_path, path)
211
+ else:
212
+ path = self.directory / ".modaic" / default_filename
213
+ with open(path, "wb") as f:
214
+ shutil.copyfileobj(file, f)
215
+ return str(path.relative_to(self.directory))
216
+
217
+ def update(self, reference: str, file: str | Path | IO) -> None:
218
+ if isinstance(file, (Path, str)):
219
+ original_path = Path(file)
220
+ shutil.copy2(original_path, self.directory / reference)
221
+ else:
222
+ shutil.copyfileobj(file, self.directory / reference)
223
+
224
+ def remove(self, reference: str) -> None:
225
+ (self.directory / reference).unlink()
226
+
227
+ def keys(self, folder: Optional[str] = None) -> Iterator[str]:
228
+ base: Path = self.directory if folder is None else self.directory / folder
229
+ return (str(path.relative_to(self.directory)) for path in base.iterdir() if path.is_file())
230
+
231
+
232
+ def is_in_dir(path: str | Path, directory: str | Path) -> bool:
233
+ path = Path(path).resolve() # follows symlinks
234
+ directory = Path(directory).resolve()
235
+ try:
236
+ path.relative_to(directory)
237
+ return True
238
+ except ValueError:
239
+ return False
@@ -0,0 +1,25 @@
1
+ import os
2
+ import pickle
3
+ import uuid
4
+ from typing import List
5
+
6
+ from ..context.base import Context
7
+
8
+
9
+ class ContextPickleStore:
10
+ def __init__(self, directory: str):
11
+ self.directory = directory
12
+ os.makedirs(self.directory, exist_ok=True)
13
+
14
+ def add(self, contexts: List[Context], **kwargs) -> List[Context]:
15
+ for context in contexts:
16
+ context_id = uuid.uuid4()
17
+ file_name = f"{context_id}.pkl"
18
+ with open(os.path.join(self.directory, file_name), "wb") as f:
19
+ pickle.dump(context, f)
20
+ context.metadata["context_id"] = context_id
21
+ return contexts
22
+
23
+ def get(self, source: str) -> Context:
24
+ with open(os.path.join(self.directory, source), "rb") as f:
25
+ return pickle.load(f)