mantisdk 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 mantisdk might be problematic. Click here for more details.

Files changed (190) hide show
  1. mantisdk/__init__.py +22 -0
  2. mantisdk/adapter/__init__.py +15 -0
  3. mantisdk/adapter/base.py +94 -0
  4. mantisdk/adapter/messages.py +270 -0
  5. mantisdk/adapter/triplet.py +1028 -0
  6. mantisdk/algorithm/__init__.py +39 -0
  7. mantisdk/algorithm/apo/__init__.py +5 -0
  8. mantisdk/algorithm/apo/apo.py +889 -0
  9. mantisdk/algorithm/apo/prompts/apply_edit_variant01.poml +22 -0
  10. mantisdk/algorithm/apo/prompts/apply_edit_variant02.poml +18 -0
  11. mantisdk/algorithm/apo/prompts/text_gradient_variant01.poml +18 -0
  12. mantisdk/algorithm/apo/prompts/text_gradient_variant02.poml +16 -0
  13. mantisdk/algorithm/apo/prompts/text_gradient_variant03.poml +107 -0
  14. mantisdk/algorithm/base.py +162 -0
  15. mantisdk/algorithm/decorator.py +264 -0
  16. mantisdk/algorithm/fast.py +250 -0
  17. mantisdk/algorithm/gepa/__init__.py +59 -0
  18. mantisdk/algorithm/gepa/adapter.py +459 -0
  19. mantisdk/algorithm/gepa/gepa.py +364 -0
  20. mantisdk/algorithm/gepa/lib/__init__.py +18 -0
  21. mantisdk/algorithm/gepa/lib/adapters/README.md +12 -0
  22. mantisdk/algorithm/gepa/lib/adapters/__init__.py +0 -0
  23. mantisdk/algorithm/gepa/lib/adapters/anymaths_adapter/README.md +341 -0
  24. mantisdk/algorithm/gepa/lib/adapters/anymaths_adapter/__init__.py +1 -0
  25. mantisdk/algorithm/gepa/lib/adapters/anymaths_adapter/anymaths_adapter.py +174 -0
  26. mantisdk/algorithm/gepa/lib/adapters/anymaths_adapter/requirements.txt +1 -0
  27. mantisdk/algorithm/gepa/lib/adapters/default_adapter/README.md +0 -0
  28. mantisdk/algorithm/gepa/lib/adapters/default_adapter/__init__.py +0 -0
  29. mantisdk/algorithm/gepa/lib/adapters/default_adapter/default_adapter.py +209 -0
  30. mantisdk/algorithm/gepa/lib/adapters/dspy_adapter/README.md +7 -0
  31. mantisdk/algorithm/gepa/lib/adapters/dspy_adapter/__init__.py +0 -0
  32. mantisdk/algorithm/gepa/lib/adapters/dspy_adapter/dspy_adapter.py +307 -0
  33. mantisdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/README.md +99 -0
  34. mantisdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/dspy_program_proposal_signature.py +137 -0
  35. mantisdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/full_program_adapter.py +266 -0
  36. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/GEPA_RAG.md +621 -0
  37. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/__init__.py +56 -0
  38. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/evaluation_metrics.py +226 -0
  39. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/generic_rag_adapter.py +496 -0
  40. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/rag_pipeline.py +238 -0
  41. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_store_interface.py +212 -0
  42. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/__init__.py +2 -0
  43. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/chroma_store.py +196 -0
  44. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/lancedb_store.py +422 -0
  45. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/milvus_store.py +409 -0
  46. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/qdrant_store.py +368 -0
  47. mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/weaviate_store.py +418 -0
  48. mantisdk/algorithm/gepa/lib/adapters/mcp_adapter/README.md +552 -0
  49. mantisdk/algorithm/gepa/lib/adapters/mcp_adapter/__init__.py +37 -0
  50. mantisdk/algorithm/gepa/lib/adapters/mcp_adapter/mcp_adapter.py +705 -0
  51. mantisdk/algorithm/gepa/lib/adapters/mcp_adapter/mcp_client.py +364 -0
  52. mantisdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/README.md +9 -0
  53. mantisdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/__init__.py +0 -0
  54. mantisdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/terminal_bench_adapter.py +217 -0
  55. mantisdk/algorithm/gepa/lib/api.py +375 -0
  56. mantisdk/algorithm/gepa/lib/core/__init__.py +0 -0
  57. mantisdk/algorithm/gepa/lib/core/adapter.py +180 -0
  58. mantisdk/algorithm/gepa/lib/core/data_loader.py +74 -0
  59. mantisdk/algorithm/gepa/lib/core/engine.py +356 -0
  60. mantisdk/algorithm/gepa/lib/core/result.py +233 -0
  61. mantisdk/algorithm/gepa/lib/core/state.py +636 -0
  62. mantisdk/algorithm/gepa/lib/examples/__init__.py +0 -0
  63. mantisdk/algorithm/gepa/lib/examples/aime.py +24 -0
  64. mantisdk/algorithm/gepa/lib/examples/anymaths-bench/eval_default.py +111 -0
  65. mantisdk/algorithm/gepa/lib/examples/anymaths-bench/prompt-templates/instruction_prompt.txt +9 -0
  66. mantisdk/algorithm/gepa/lib/examples/anymaths-bench/prompt-templates/optimal_prompt.txt +24 -0
  67. mantisdk/algorithm/gepa/lib/examples/anymaths-bench/train_anymaths.py +177 -0
  68. mantisdk/algorithm/gepa/lib/examples/dspy_full_program_evolution/arc_agi.ipynb +25705 -0
  69. mantisdk/algorithm/gepa/lib/examples/dspy_full_program_evolution/example.ipynb +348 -0
  70. mantisdk/algorithm/gepa/lib/examples/mcp_adapter/__init__.py +4 -0
  71. mantisdk/algorithm/gepa/lib/examples/mcp_adapter/mcp_optimization_example.py +455 -0
  72. mantisdk/algorithm/gepa/lib/examples/rag_adapter/RAG_GUIDE.md +613 -0
  73. mantisdk/algorithm/gepa/lib/examples/rag_adapter/__init__.py +9 -0
  74. mantisdk/algorithm/gepa/lib/examples/rag_adapter/rag_optimization.py +824 -0
  75. mantisdk/algorithm/gepa/lib/examples/rag_adapter/requirements-rag.txt +29 -0
  76. mantisdk/algorithm/gepa/lib/examples/terminal-bench/prompt-templates/instruction_prompt.txt +16 -0
  77. mantisdk/algorithm/gepa/lib/examples/terminal-bench/prompt-templates/terminus.txt +9 -0
  78. mantisdk/algorithm/gepa/lib/examples/terminal-bench/train_terminus.py +161 -0
  79. mantisdk/algorithm/gepa/lib/gepa_utils.py +117 -0
  80. mantisdk/algorithm/gepa/lib/logging/__init__.py +0 -0
  81. mantisdk/algorithm/gepa/lib/logging/experiment_tracker.py +187 -0
  82. mantisdk/algorithm/gepa/lib/logging/logger.py +75 -0
  83. mantisdk/algorithm/gepa/lib/logging/utils.py +103 -0
  84. mantisdk/algorithm/gepa/lib/proposer/__init__.py +0 -0
  85. mantisdk/algorithm/gepa/lib/proposer/base.py +31 -0
  86. mantisdk/algorithm/gepa/lib/proposer/merge.py +357 -0
  87. mantisdk/algorithm/gepa/lib/proposer/reflective_mutation/__init__.py +0 -0
  88. mantisdk/algorithm/gepa/lib/proposer/reflective_mutation/base.py +49 -0
  89. mantisdk/algorithm/gepa/lib/proposer/reflective_mutation/reflective_mutation.py +176 -0
  90. mantisdk/algorithm/gepa/lib/py.typed +0 -0
  91. mantisdk/algorithm/gepa/lib/strategies/__init__.py +0 -0
  92. mantisdk/algorithm/gepa/lib/strategies/batch_sampler.py +77 -0
  93. mantisdk/algorithm/gepa/lib/strategies/candidate_selector.py +50 -0
  94. mantisdk/algorithm/gepa/lib/strategies/component_selector.py +36 -0
  95. mantisdk/algorithm/gepa/lib/strategies/eval_policy.py +64 -0
  96. mantisdk/algorithm/gepa/lib/strategies/instruction_proposal.py +127 -0
  97. mantisdk/algorithm/gepa/lib/utils/__init__.py +10 -0
  98. mantisdk/algorithm/gepa/lib/utils/stop_condition.py +196 -0
  99. mantisdk/algorithm/gepa/tracing.py +105 -0
  100. mantisdk/algorithm/utils.py +177 -0
  101. mantisdk/algorithm/verl/__init__.py +5 -0
  102. mantisdk/algorithm/verl/interface.py +202 -0
  103. mantisdk/cli/__init__.py +56 -0
  104. mantisdk/cli/prometheus.py +115 -0
  105. mantisdk/cli/store.py +131 -0
  106. mantisdk/cli/vllm.py +29 -0
  107. mantisdk/client.py +408 -0
  108. mantisdk/config.py +348 -0
  109. mantisdk/emitter/__init__.py +43 -0
  110. mantisdk/emitter/annotation.py +370 -0
  111. mantisdk/emitter/exception.py +54 -0
  112. mantisdk/emitter/message.py +61 -0
  113. mantisdk/emitter/object.py +117 -0
  114. mantisdk/emitter/reward.py +320 -0
  115. mantisdk/env_var.py +156 -0
  116. mantisdk/execution/__init__.py +15 -0
  117. mantisdk/execution/base.py +64 -0
  118. mantisdk/execution/client_server.py +443 -0
  119. mantisdk/execution/events.py +69 -0
  120. mantisdk/execution/inter_process.py +16 -0
  121. mantisdk/execution/shared_memory.py +282 -0
  122. mantisdk/instrumentation/__init__.py +119 -0
  123. mantisdk/instrumentation/agentops.py +314 -0
  124. mantisdk/instrumentation/agentops_langchain.py +45 -0
  125. mantisdk/instrumentation/litellm.py +83 -0
  126. mantisdk/instrumentation/vllm.py +81 -0
  127. mantisdk/instrumentation/weave.py +500 -0
  128. mantisdk/litagent/__init__.py +11 -0
  129. mantisdk/litagent/decorator.py +536 -0
  130. mantisdk/litagent/litagent.py +252 -0
  131. mantisdk/llm_proxy.py +1890 -0
  132. mantisdk/logging.py +370 -0
  133. mantisdk/reward.py +7 -0
  134. mantisdk/runner/__init__.py +11 -0
  135. mantisdk/runner/agent.py +845 -0
  136. mantisdk/runner/base.py +182 -0
  137. mantisdk/runner/legacy.py +309 -0
  138. mantisdk/semconv.py +170 -0
  139. mantisdk/server.py +401 -0
  140. mantisdk/store/__init__.py +23 -0
  141. mantisdk/store/base.py +897 -0
  142. mantisdk/store/client_server.py +2092 -0
  143. mantisdk/store/collection/__init__.py +30 -0
  144. mantisdk/store/collection/base.py +587 -0
  145. mantisdk/store/collection/memory.py +970 -0
  146. mantisdk/store/collection/mongo.py +1412 -0
  147. mantisdk/store/collection_based.py +1823 -0
  148. mantisdk/store/insight.py +648 -0
  149. mantisdk/store/listener.py +58 -0
  150. mantisdk/store/memory.py +396 -0
  151. mantisdk/store/mongo.py +165 -0
  152. mantisdk/store/sqlite.py +3 -0
  153. mantisdk/store/threading.py +357 -0
  154. mantisdk/store/utils.py +142 -0
  155. mantisdk/tracer/__init__.py +16 -0
  156. mantisdk/tracer/agentops.py +242 -0
  157. mantisdk/tracer/base.py +287 -0
  158. mantisdk/tracer/dummy.py +106 -0
  159. mantisdk/tracer/otel.py +555 -0
  160. mantisdk/tracer/weave.py +677 -0
  161. mantisdk/trainer/__init__.py +6 -0
  162. mantisdk/trainer/init_utils.py +263 -0
  163. mantisdk/trainer/legacy.py +367 -0
  164. mantisdk/trainer/registry.py +12 -0
  165. mantisdk/trainer/trainer.py +618 -0
  166. mantisdk/types/__init__.py +6 -0
  167. mantisdk/types/core.py +553 -0
  168. mantisdk/types/resources.py +204 -0
  169. mantisdk/types/tracer.py +515 -0
  170. mantisdk/types/tracing.py +218 -0
  171. mantisdk/utils/__init__.py +1 -0
  172. mantisdk/utils/id.py +18 -0
  173. mantisdk/utils/metrics.py +1025 -0
  174. mantisdk/utils/otel.py +578 -0
  175. mantisdk/utils/otlp.py +536 -0
  176. mantisdk/utils/server_launcher.py +1045 -0
  177. mantisdk/utils/system_snapshot.py +81 -0
  178. mantisdk/verl/__init__.py +8 -0
  179. mantisdk/verl/__main__.py +6 -0
  180. mantisdk/verl/async_server.py +46 -0
  181. mantisdk/verl/config.yaml +27 -0
  182. mantisdk/verl/daemon.py +1154 -0
  183. mantisdk/verl/dataset.py +44 -0
  184. mantisdk/verl/entrypoint.py +248 -0
  185. mantisdk/verl/trainer.py +549 -0
  186. mantisdk-0.1.0.dist-info/METADATA +119 -0
  187. mantisdk-0.1.0.dist-info/RECORD +190 -0
  188. mantisdk-0.1.0.dist-info/WHEEL +4 -0
  189. mantisdk-0.1.0.dist-info/entry_points.txt +2 -0
  190. mantisdk-0.1.0.dist-info/licenses/LICENSE +19 -0
@@ -0,0 +1,30 @@
1
+ # Copyright (c) Microsoft. All rights reserved.
2
+
3
+ from .base import (
4
+ AtomicLabels,
5
+ AtomicMode,
6
+ Collection,
7
+ FilterOptions,
8
+ KeyValue,
9
+ LightningCollections,
10
+ PaginatedResult,
11
+ Queue,
12
+ SortOptions,
13
+ )
14
+ from .memory import DequeBasedQueue, DictBasedKeyValue, InMemoryLightningCollections, ListBasedCollection
15
+
16
+ __all__ = [
17
+ "AtomicLabels",
18
+ "AtomicMode",
19
+ "Collection",
20
+ "Queue",
21
+ "KeyValue",
22
+ "FilterOptions",
23
+ "SortOptions",
24
+ "PaginatedResult",
25
+ "LightningCollections",
26
+ "ListBasedCollection",
27
+ "DequeBasedQueue",
28
+ "DictBasedKeyValue",
29
+ "InMemoryLightningCollections",
30
+ ]
@@ -0,0 +1,587 @@
1
+ # Copyright (c) Microsoft. All rights reserved.
2
+
3
+ from __future__ import annotations
4
+
5
+ import functools
6
+ import time
7
+ from contextlib import asynccontextmanager
8
+ from numbers import Real
9
+ from typing import (
10
+ TYPE_CHECKING,
11
+ Any,
12
+ AsyncContextManager,
13
+ Awaitable,
14
+ Callable,
15
+ Dict,
16
+ Generic,
17
+ List,
18
+ Literal,
19
+ Mapping,
20
+ MutableMapping,
21
+ Optional,
22
+ Sequence,
23
+ Tuple,
24
+ Type,
25
+ TypeGuard,
26
+ TypeVar,
27
+ cast,
28
+ )
29
+
30
+ from mantisdk.store.utils import LATENCY_BUCKETS
31
+ from mantisdk.utils.metrics import MetricsBackend
32
+
33
+ if TYPE_CHECKING:
34
+ from typing import Self
35
+
36
+ from mantisdk.types import (
37
+ Attempt,
38
+ FilterField,
39
+ FilterOptions,
40
+ PaginatedResult,
41
+ ResourcesUpdate,
42
+ Rollout,
43
+ SortOptions,
44
+ Span,
45
+ Worker,
46
+ )
47
+
48
+ T = TypeVar("T") # Recommended to be a BaseModel
49
+ K = TypeVar("K")
50
+ V = TypeVar("V")
51
+ T_callable = TypeVar("T_callable", bound=Callable[..., Any])
52
+
53
+ AtomicMode = Literal["r", "w", "rw"]
54
+ """What is expected within the atomic context. Can be "read", "write", or "read-write"."""
55
+
56
+ AtomicLabels = Literal[
57
+ "rollouts", "attempts", "spans", "resources", "workers", "rollout_queue", "span_sequence_ids", "generic"
58
+ ]
59
+ """Labels for atomic operations.
60
+
61
+ These labels are used to identify the collections that are affected by the atomic operation.
62
+
63
+ The `generic` label is used to identify atomic operations that are not associated with any specific collection.
64
+ """
65
+
66
+
67
+ def resolve_error_type(exc: BaseException | None) -> str:
68
+ if exc is None:
69
+ return "N/A"
70
+
71
+ try:
72
+ from .mongo import resolve_mongo_error_type
73
+
74
+ error_type = resolve_mongo_error_type(exc)
75
+ if error_type is not None:
76
+ return error_type
77
+ except ImportError:
78
+ # If the mongo backend is not available, fall back to using the exception's class name.
79
+ pass
80
+
81
+ return exc.__class__.__name__
82
+
83
+
84
+ def tracked(operation: str):
85
+ """Decorator to track the execution of the decorated method."""
86
+
87
+ def decorator(func: T_callable) -> T_callable:
88
+
89
+ @functools.wraps(func)
90
+ async def wrapper(self: TrackedCollection, *args: Any, **kwargs: Any) -> Any:
91
+ async with self.tracking_context(operation, self.collection_name):
92
+ return await func(self, *args, **kwargs)
93
+
94
+ return cast(T_callable, wrapper)
95
+
96
+ return decorator
97
+
98
+
99
+ def ensure_numeric(value: Any, *, description: str) -> TypeGuard[Real]:
100
+ """Validate that *value* behaves like a real number.
101
+
102
+ Returns true or crashes.
103
+ """
104
+
105
+ if isinstance(value, bool):
106
+ raise TypeError(f"{description} must be numeric; got bool")
107
+ if not isinstance(value, Real):
108
+ raise TypeError(f"{description} must be numeric; got {type(value).__name__}")
109
+ return True
110
+
111
+
112
+ class DuplicatedPrimaryKeyError(ValueError):
113
+ """Error raised when a duplicate key is encountered."""
114
+
115
+ pass
116
+
117
+
118
+ class TrackedCollection:
119
+ """An object that can be tracked by the metrics backend."""
120
+
121
+ def __init__(self, tracker: MetricsBackend | None = None):
122
+ self._tracker = tracker
123
+
124
+ @property
125
+ def tracker(self) -> MetricsBackend | None:
126
+ return self._tracker
127
+
128
+ @property
129
+ def collection_name(self) -> str:
130
+ """The identifier of the collection."""
131
+ raise NotImplementedError()
132
+
133
+ @property
134
+ def extra_tracking_labels(self) -> Mapping[str, Any]:
135
+ """Extra labels to add to the tracking context."""
136
+ return {}
137
+
138
+ @asynccontextmanager
139
+ async def tracking_context(self, operation: str, collection: str):
140
+ """Context manager to track the execution of the decorated method.
141
+
142
+ Args:
143
+ operation: The operation to track.
144
+ collection: The collection to track.
145
+ """
146
+ if self._tracker is None:
147
+ # no-op context manager
148
+ yield
149
+
150
+ else:
151
+ from mantisdk.store.collection_based import get_current_store_methods
152
+
153
+ # Enable tracking
154
+ start_time = time.perf_counter()
155
+ status: str = "OK"
156
+ public_store_method, private_store_method = get_current_store_methods()
157
+ try:
158
+ yield
159
+ except BaseException as exc:
160
+ status = resolve_error_type(exc)
161
+ raise
162
+ finally:
163
+ elapsed = time.perf_counter() - start_time
164
+ await self._tracker.inc_counter( # pyright: ignore[reportPrivateUsage]
165
+ "msk.collections.total",
166
+ labels={
167
+ "store_pubmeth": public_store_method,
168
+ "store_privmeth": private_store_method,
169
+ "operation": operation,
170
+ "collection": collection,
171
+ "status": status,
172
+ **self.extra_tracking_labels,
173
+ },
174
+ )
175
+ await self._tracker.observe_histogram( # pyright: ignore[reportPrivateUsage]
176
+ "msk.collections.latency",
177
+ value=elapsed,
178
+ labels={
179
+ "store_pubmeth": public_store_method,
180
+ "store_privmeth": private_store_method,
181
+ "operation": operation,
182
+ "collection": collection,
183
+ "status": status,
184
+ **self.extra_tracking_labels,
185
+ },
186
+ )
187
+
188
+
189
+ class Collection(TrackedCollection, Generic[T]):
190
+ """Standard collection interface. Behaves like a list of items. Supporting addition, updating, and deletion of items."""
191
+
192
+ def primary_keys(self) -> Sequence[str]:
193
+ """Get the primary keys of the collection."""
194
+ raise NotImplementedError()
195
+
196
+ def __repr__(self) -> str:
197
+ return f"<{self.__class__.__name__}[{self.item_type().__name__}]>"
198
+
199
+ def item_type(self) -> Type[T]:
200
+ """Get the type of the items in the collection."""
201
+ raise NotImplementedError()
202
+
203
+ async def size(self) -> int:
204
+ """Get the number of items in the collection."""
205
+ raise NotImplementedError()
206
+
207
+ async def query(
208
+ self,
209
+ filter: Optional[FilterOptions] = None,
210
+ sort: Optional[SortOptions] = None,
211
+ limit: int = -1,
212
+ offset: int = 0,
213
+ ) -> PaginatedResult[T]:
214
+ """Query the collection with the given filters, sort order, and pagination.
215
+
216
+ Args:
217
+ filter:
218
+ The filters to apply to the collection. See [`FilterOptions`][mantisdk.FilterOptions].
219
+
220
+ sort:
221
+ The options for sorting the collection. See [`SortOptions`][mantisdk.SortOptions].
222
+ The field must exist in the model. If field might contain null values, in which case the behavior is undefined
223
+ (i.e., depending on the implementation).
224
+
225
+ limit:
226
+ Max number of items to return. Use -1 for "no limit".
227
+
228
+ offset:
229
+ Number of items to skip from the start of the *matching* items.
230
+
231
+ Returns:
232
+ PaginatedResult with items, limit, offset, and total matched items.
233
+ """
234
+ raise NotImplementedError()
235
+
236
+ async def get(
237
+ self,
238
+ filter: Optional[FilterOptions] = None,
239
+ sort: Optional[SortOptions] = None,
240
+ ) -> Optional[T]:
241
+ """Get the first item that matches the given filters.
242
+
243
+ Args:
244
+ filter: The filters to apply to the collection.
245
+ See [`FilterOptions`][mantisdk.store.collection.FilterOptions].
246
+ sort: Sort options. See [`SortOptions`][mantisdk.store.collection.SortOptions].
247
+
248
+ Returns:
249
+ The first item that matches the given filters, or None if no item matches.
250
+ """
251
+ raise NotImplementedError()
252
+
253
+ async def insert(self, items: Sequence[T]) -> None:
254
+ """Add the given items to the collection.
255
+
256
+ Raises:
257
+ ValueError: If an item with the same primary key already exists.
258
+ """
259
+ raise NotImplementedError()
260
+
261
+ async def update(self, items: Sequence[T], update_fields: Sequence[str] | None = None) -> Sequence[T]:
262
+ """Update the given items in the collection.
263
+
264
+ Args:
265
+ items: The items to update in the collection.
266
+ update_fields: The fields to update. If not provided, all fields in the type will be updated.
267
+ Only applicable if the item type is a Pydantic BaseModel.
268
+
269
+ Raises:
270
+ ValueError: If an item with the primary keys does not exist.
271
+
272
+ Returns:
273
+ The items that were updated.
274
+ """
275
+ raise NotImplementedError()
276
+
277
+ async def upsert(self, items: Sequence[T], update_fields: Sequence[str] | None = None) -> Sequence[T]:
278
+ """Upsert the given items into the collection.
279
+
280
+ If the items with the same primary keys already exist, they will be updated.
281
+ Otherwise, they will be inserted.
282
+
283
+ The operation has three semantics configurable via `update_fields`:
284
+
285
+ - `update_or_insert` via `collection.upsert(items, update_fields=["status", "updated_at"])`.
286
+ If the item with the same primary keys already exists, only the specified fields will be updated.
287
+ Otherwise, the item will be inserted.
288
+ - `get_or_insert` via `collection.upsert(items, update_fields=[])`.
289
+ If the item with the same primary keys already exists, the item will be left unchanged.
290
+ Otherwise, the item will be inserted.
291
+ - `replace_ish` via `collection.upsert(items)`.
292
+ If the item with the same primary keys already exists, all fields from the item will be set.
293
+ Otherwise, the item will be inserted.
294
+
295
+ Returns:
296
+ The items that were upserted.
297
+ """
298
+ raise NotImplementedError()
299
+
300
+ async def delete(self, items: Sequence[T]) -> None:
301
+ """Delete the given items from the collection.
302
+
303
+ Args:
304
+ items: The items to delete from the collection.
305
+
306
+ Raises:
307
+ ValueError: If the items with the primary keys to be deleted do not exist.
308
+ """
309
+ raise NotImplementedError()
310
+
311
+
312
+ class Queue(TrackedCollection, Generic[T]):
313
+ """Behaves like a deque. Supporting appending items to the end and popping items from the front."""
314
+
315
+ def __repr__(self) -> str:
316
+ return f"<{self.__class__.__name__}[{self.item_type().__name__}]>"
317
+
318
+ def item_type(self) -> Type[T]:
319
+ """Get the type of the items in the queue."""
320
+ raise NotImplementedError()
321
+
322
+ async def has(self, item: T) -> bool:
323
+ """Check if the given item is in the queue."""
324
+ raise NotImplementedError()
325
+
326
+ async def enqueue(self, items: Sequence[T]) -> Sequence[T]:
327
+ """Append the given items to the end of the queue.
328
+
329
+ Args:
330
+ items: The items to append to the end of the queue.
331
+
332
+ Returns:
333
+ The items that were appended to the end of the queue.
334
+ """
335
+ raise NotImplementedError()
336
+
337
+ async def dequeue(self, limit: int = 1) -> Sequence[T]:
338
+ """Pop the given number of items from the front of the queue.
339
+
340
+ Args:
341
+ limit: The number of items to pop from the front of the queue.
342
+
343
+ Returns:
344
+ The items that were popped from the front of the queue.
345
+ If there are less than `limit` items in the queue, the remaining items will be returned.
346
+ """
347
+ raise NotImplementedError()
348
+
349
+ async def peek(self, limit: int = 1) -> Sequence[T]:
350
+ """Peek the given number of items from the front of the queue.
351
+
352
+ Args:
353
+ limit: The number of items to peek from the front of the queue.
354
+
355
+ Returns:
356
+ The items that were peeked from the front of the queue.
357
+ If there are less than `limit` items in the queue, the remaining items will be returned.
358
+ """
359
+ raise NotImplementedError()
360
+
361
+ async def size(self) -> int:
362
+ """Get the number of items in the queue."""
363
+ raise NotImplementedError()
364
+
365
+
366
+ class KeyValue(TrackedCollection, Generic[K, V]):
367
+ """Behaves like a dictionary. Supporting addition, updating, and deletion of items."""
368
+
369
+ def __repr__(self) -> str:
370
+ return f"<{self.__class__.__name__}>"
371
+
372
+ async def has(self, key: K) -> bool:
373
+ """Check if the given key is in the dictionary."""
374
+ raise NotImplementedError()
375
+
376
+ async def get(self, key: K, default: V | None = None) -> V | None:
377
+ """Get the value for the given key, or the default value if the key is not found."""
378
+ raise NotImplementedError()
379
+
380
+ async def set(self, key: K, value: V) -> None:
381
+ """Set the value for the given key."""
382
+ raise NotImplementedError()
383
+
384
+ async def inc(self, key: K, amount: V) -> V:
385
+ """Increase the numeric value for the given key by `amount` and return the new value.
386
+
387
+ Raises:
388
+ TypeError: If the existing value or `amount` is not numeric.
389
+ """
390
+ raise NotImplementedError()
391
+
392
+ async def chmax(self, key: K, value: V) -> V:
393
+ """Set the value for the given key to the maximum of the current and new value.
394
+
395
+ Raises:
396
+ TypeError: If the existing value or `value` is not numeric.
397
+ """
398
+ raise NotImplementedError()
399
+
400
+ async def pop(self, key: K, default: V | None = None) -> V | None:
401
+ """Pop the value for the given key, or the default value if the key is not found."""
402
+ raise NotImplementedError()
403
+
404
+ async def size(self) -> int:
405
+ """Get the number of items in the dictionary."""
406
+ raise NotImplementedError()
407
+
408
+
409
+ class LightningCollections(TrackedCollection):
410
+ """Collections of rollouts, attempts, spans, resources, and workers.
411
+
412
+ [LightningStore][mantisdk.LightningStore] implementations can use this as a storage base
413
+ to implement the store API.
414
+ """
415
+
416
+ def __init__(self, tracker: MetricsBackend | None = None, extra_labels: Optional[Sequence[str]] = None):
417
+ super().__init__(tracker=tracker)
418
+ self.register_collection_metrics(extra_labels)
419
+
420
+ def register_collection_metrics(self, extra_labels: Optional[Sequence[str]] = None) -> None:
421
+ if self._tracker is None:
422
+ return
423
+ labels = ["store_pubmeth", "operation", "collection", "store_privmeth", "status"]
424
+ if extra_labels is not None:
425
+ labels.extend(extra_labels)
426
+ self._tracker.register_histogram(
427
+ "msk.collections.latency",
428
+ labels,
429
+ buckets=LATENCY_BUCKETS,
430
+ group_level=2,
431
+ )
432
+ self._tracker.register_counter("msk.collections.total", labels, group_level=2)
433
+
434
+ @property
435
+ def tracker(self) -> MetricsBackend | None:
436
+ return self._tracker
437
+
438
+ @property
439
+ def rollouts(self) -> Collection[Rollout]:
440
+ """Collections of rollouts."""
441
+ raise NotImplementedError()
442
+
443
+ @property
444
+ def attempts(self) -> Collection[Attempt]:
445
+ """Collections of attempts."""
446
+ raise NotImplementedError()
447
+
448
+ @property
449
+ def spans(self) -> Collection[Span]:
450
+ """Collections of spans."""
451
+ raise NotImplementedError()
452
+
453
+ @property
454
+ def resources(self) -> Collection[ResourcesUpdate]:
455
+ """Collections of resources."""
456
+ raise NotImplementedError()
457
+
458
+ @property
459
+ def workers(self) -> Collection[Worker]:
460
+ """Collections of workers."""
461
+ raise NotImplementedError()
462
+
463
+ @property
464
+ def rollout_queue(self) -> Queue[str]:
465
+ """Queue of rollouts (tasks)."""
466
+ raise NotImplementedError()
467
+
468
+ @property
469
+ def span_sequence_ids(self) -> KeyValue[str, int]:
470
+ """Dictionary (counter) of span sequence IDs."""
471
+ raise NotImplementedError()
472
+
473
+ def atomic(
474
+ self,
475
+ *,
476
+ mode: AtomicMode = "rw",
477
+ snapshot: bool = False,
478
+ commit: bool = False,
479
+ labels: Optional[Sequence[AtomicLabels]] = None,
480
+ **kwargs: Any,
481
+ ) -> AsyncContextManager[Self]:
482
+ """Perform a atomic operation on the collections.
483
+
484
+ Subclass may use args and kwargs to support multiple levels of atomicity.
485
+ The arguments can be seen as tags. They only imply the behavior of the operation, not the implementation.
486
+
487
+ Args:
488
+ mode: The mode of atomicity. See [`AtomicMode`][mantisdk.store.collection.AtomicMode].
489
+ snapshot: Enable read snapshot for repeatable reads. Data consistency is guaranteed. The real behavior is implementation-dependent.
490
+ commit: Enable commitment for write operations. Unsuccessful operations will be rolled back depending on the implementation.
491
+ Recommend to use [`execute()`][mantisdk.store.collection.LightningCollections.execute] for this level to enable automatic retries.
492
+ Remember that the real behavior is implementation-dependent.
493
+ labels: Labels to add to the atomic operation (commonly used as lock names or collection names).
494
+ **kwargs: Keyword arguments to pass to the operation.
495
+ """
496
+ raise NotImplementedError()
497
+
498
+ async def execute(
499
+ self,
500
+ callback: Callable[[Self], Awaitable[T]],
501
+ *,
502
+ mode: AtomicMode = "rw",
503
+ snapshot: bool = False,
504
+ commit: bool = False,
505
+ labels: Optional[Sequence[AtomicLabels]] = None,
506
+ **kwargs: Any,
507
+ ) -> T:
508
+ """Execute the given callback within an atomic operation. Retry on transient errors is implied.
509
+
510
+ See [`atomic()`][mantisdk.store.collection.LightningCollections.atomic] for more details.
511
+ """
512
+ async with self.atomic(mode=mode, snapshot=snapshot, commit=commit, labels=labels, **kwargs) as collections:
513
+ return await callback(collections)
514
+
515
+
516
+ FilterMap = Mapping[str, FilterField]
517
+
518
+
519
+ def merge_must_filters(target: MutableMapping[str, FilterField], definition: Any) -> None:
520
+ """Normalize a `_must` filter group into the provided mapping.
521
+
522
+ Mainly for validation purposes.
523
+ """
524
+ if definition is None:
525
+ return
526
+
527
+ entries: List[Mapping[str, FilterField]] = []
528
+ if isinstance(definition, Mapping):
529
+ entries.append(cast(Mapping[str, FilterField], definition))
530
+ elif isinstance(definition, Sequence) and not isinstance(definition, (str, bytes)):
531
+ for entry in definition: # type: ignore
532
+ if not isinstance(entry, Mapping):
533
+ raise TypeError("Each `_must` entry must be a mapping of field names to operators")
534
+ entries.append(cast(Mapping[str, FilterField], entry))
535
+ else:
536
+ raise TypeError("`_must` filters must be provided as a mapping or sequence of mappings")
537
+
538
+ for entry in entries:
539
+ for field_name, ops in entry.items():
540
+ existing = target.get(field_name, {})
541
+ merged_ops: Dict[str, Any] = dict(existing)
542
+ for op_name, expected in ops.items():
543
+ if op_name in merged_ops:
544
+ raise ValueError(f"Duplicate operator '{op_name}' for field '{field_name}' in must filters")
545
+ merged_ops[op_name] = expected
546
+ target[field_name] = cast(FilterField, merged_ops)
547
+
548
+
549
+ def normalize_filter_options(
550
+ filter_options: Optional[FilterOptions],
551
+ ) -> Tuple[Optional[FilterMap], Optional[FilterMap], Literal["and", "or"]]:
552
+ """Convert FilterOptions to the internal structure and resolve aggregate logic."""
553
+ if not filter_options:
554
+ return None, None, "and"
555
+
556
+ aggregate = cast(Literal["and", "or"], filter_options.get("_aggregate", "and"))
557
+ if aggregate not in ("and", "or"):
558
+ raise ValueError(f"Unsupported filter aggregate '{aggregate}'")
559
+
560
+ # Extract normalized filters and must filters from the filter options.
561
+ normalized: Dict[str, FilterField] = {}
562
+ must_filters: Dict[str, FilterField] = {}
563
+ for field_name, ops in filter_options.items():
564
+ if field_name == "_aggregate":
565
+ continue
566
+ if field_name == "_must":
567
+ merge_must_filters(must_filters, ops)
568
+ continue
569
+ normalized[field_name] = cast(FilterField, dict(ops)) # type: ignore
570
+
571
+ return (normalized or None, must_filters or None, aggregate)
572
+
573
+
574
+ def resolve_sort_options(sort: Optional[SortOptions]) -> Tuple[Optional[str], Literal["asc", "desc"]]:
575
+ """Extract sort field/order from the caller-provided SortOptions."""
576
+ if not sort:
577
+ return None, "asc"
578
+
579
+ sort_name = sort.get("name")
580
+ if not sort_name:
581
+ raise ValueError("Sort options must include a 'name' field")
582
+
583
+ sort_order = sort.get("order", "asc")
584
+ if sort_order not in ("asc", "desc"):
585
+ raise ValueError(f"Unsupported sort order '{sort_order}'")
586
+
587
+ return sort_name, sort_order