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,368 @@
1
+ # Copyright (c) 2025 Lakshya A Agrawal and the GEPA contributors
2
+ # https://github.com/gepa-ai/gepa
3
+
4
+ from typing import Any
5
+
6
+ from mantisdk.algorithm.gepa.lib.adapters.generic_rag_adapter.vector_store_interface import VectorStoreInterface
7
+
8
+
9
+ class QdrantVectorStore(VectorStoreInterface):
10
+ """
11
+ Qdrant implementation of the VectorStoreInterface.
12
+
13
+ Qdrant is an open-source vector database with excellent filtering capabilities
14
+ and support for both REST and gRPC APIs. It excels at handling complex metadata
15
+ filtering alongside vector similarity search.
16
+ """
17
+
18
+ def __init__(self, client, collection_name: str, embedding_function=None):
19
+ """
20
+ Initialize QdrantVectorStore.
21
+
22
+ Args:
23
+ client: QdrantClient instance
24
+ collection_name: Name of the collection to use
25
+ embedding_function: Optional function to compute embeddings for queries
26
+ """
27
+ import importlib.util
28
+
29
+ if importlib.util.find_spec("qdrant_client") is None:
30
+ raise ImportError(
31
+ "Qdrant client is required for QdrantVectorStore. Install with: pip install litellm qdrant-client"
32
+ )
33
+
34
+ from qdrant_client.http import models
35
+
36
+ self.client = client
37
+ self.collection_name = collection_name
38
+ self.embedding_function = embedding_function
39
+ self.models = models
40
+
41
+ # Verify collection exists
42
+ try:
43
+ self.client.get_collection(collection_name)
44
+ except Exception as e:
45
+ raise ValueError(
46
+ f"Collection '{collection_name}' not found. Please create the collection first. Error: {e!s}"
47
+ ) from e
48
+
49
+ def similarity_search(
50
+ self,
51
+ query: str,
52
+ k: int = 5,
53
+ filters: dict[str, Any] | None = None,
54
+ ) -> list[dict[str, Any]]:
55
+ """Search for documents similar to the query text using embeddings."""
56
+ if self.embedding_function is None:
57
+ raise ValueError("No embedding function provided for similarity search")
58
+
59
+ # Compute embeddings for the query
60
+ try:
61
+ query_vector = self.embedding_function(query)
62
+ if hasattr(query_vector, "tolist"):
63
+ query_vector = query_vector.tolist()
64
+ except Exception as e:
65
+ raise RuntimeError(f"Failed to compute embeddings for query: {e!s}") from e
66
+
67
+ # Use vector search with computed embeddings
68
+ return self.vector_search(query_vector, k, filters)
69
+
70
+ def vector_search(
71
+ self,
72
+ query_vector: list[float],
73
+ k: int = 5,
74
+ filters: dict[str, Any] | None = None,
75
+ ) -> list[dict[str, Any]]:
76
+ """Search using a pre-computed query vector."""
77
+ qdrant_filter = self._convert_filters(filters) if filters else None
78
+
79
+ try:
80
+ # Use modern query_points API
81
+ results = self.client.query_points(
82
+ collection_name=self.collection_name,
83
+ query=query_vector,
84
+ query_filter=qdrant_filter,
85
+ limit=k,
86
+ with_payload=True,
87
+ with_vectors=False,
88
+ score_threshold=None,
89
+ )
90
+
91
+ return self._format_results(results)
92
+
93
+ except Exception as e:
94
+ raise RuntimeError(f"Qdrant vector search failed: {e!s}") from e
95
+
96
+ def add_documents(
97
+ self,
98
+ documents: list[dict[str, Any]],
99
+ embeddings: list[list[float]],
100
+ ids: list[str] | None = None,
101
+ ) -> list[str]:
102
+ """Add documents with their embeddings to the collection."""
103
+ if len(documents) != len(embeddings):
104
+ raise ValueError("Number of documents must match number of embeddings")
105
+
106
+ # Generate IDs if not provided - use integers for Qdrant compatibility
107
+ if ids is None:
108
+ ids = list(range(len(documents)))
109
+ string_ids = [f"doc_{i}" for i in range(len(documents))]
110
+ else:
111
+ if len(ids) != len(documents):
112
+ raise ValueError("Number of IDs must match number of documents")
113
+ # Convert string IDs to integers for Qdrant, but keep original strings in payload
114
+ string_ids = ids
115
+ ids = list(range(len(documents)))
116
+
117
+ # Create Qdrant points
118
+ points = []
119
+ for i, (doc, embedding) in enumerate(zip(documents, embeddings, strict=False)):
120
+ # Add the original string ID to the payload for retrieval
121
+ payload = dict(doc)
122
+ payload["original_id"] = string_ids[i]
123
+
124
+ point = self.models.PointStruct(
125
+ id=ids[i], # Use integer ID for Qdrant
126
+ vector=embedding,
127
+ payload=payload,
128
+ )
129
+ points.append(point)
130
+
131
+ try:
132
+ # Upsert points to collection
133
+ result = self.client.upsert(
134
+ collection_name=self.collection_name,
135
+ points=points,
136
+ wait=True, # Wait for operation to complete
137
+ )
138
+
139
+ if result.status.name != "COMPLETED":
140
+ raise RuntimeError(f"Upsert operation failed: {result.status}")
141
+
142
+ return string_ids # Return original string IDs
143
+
144
+ except Exception as e:
145
+ raise RuntimeError(f"Failed to add documents to Qdrant: {e!s}") from e
146
+
147
+ def delete_documents(self, ids: list[str]) -> bool:
148
+ """Delete documents by their IDs."""
149
+ try:
150
+ # Create filter to match documents by original_id
151
+ delete_filter = self.models.Filter(
152
+ must=[self.models.FieldCondition(key="original_id", match=self.models.MatchAny(any=ids))]
153
+ )
154
+
155
+ result = self.client.delete(
156
+ collection_name=self.collection_name,
157
+ points_selector=self.models.FilterSelector(filter=delete_filter),
158
+ wait=True,
159
+ )
160
+
161
+ return result.status.name == "COMPLETED"
162
+
163
+ except Exception as e:
164
+ raise RuntimeError(f"Failed to delete documents from Qdrant: {e!s}") from e
165
+
166
+ def get_collection_info(self) -> dict[str, Any]:
167
+ """Get metadata about the Qdrant collection."""
168
+ try:
169
+ # Get collection info
170
+ info = self.client.get_collection(self.collection_name)
171
+
172
+ # Get collection statistics
173
+ points_count = info.points_count or 0
174
+ vectors_config = info.config.params.vectors
175
+
176
+ # Handle both named and unnamed vector configs
177
+ if isinstance(vectors_config, dict):
178
+ # Named vectors
179
+ vector_info = next(iter(vectors_config.values())) if vectors_config else None
180
+ dimension = vector_info.size if vector_info else 0
181
+ distance = vector_info.distance.name if vector_info else "UNKNOWN"
182
+ else:
183
+ # Single vector config
184
+ dimension = vectors_config.size if vectors_config else 0
185
+ distance = vectors_config.distance.name if vectors_config else "UNKNOWN"
186
+
187
+ return {
188
+ "name": self.collection_name,
189
+ "document_count": points_count,
190
+ "dimension": dimension,
191
+ "vector_store_type": "qdrant",
192
+ "distance_metric": distance.lower(),
193
+ "status": info.status.name,
194
+ }
195
+
196
+ except Exception as e:
197
+ # Fallback info if detailed info fails
198
+ return {
199
+ "name": self.collection_name,
200
+ "document_count": 0,
201
+ "dimension": 0,
202
+ "vector_store_type": "qdrant",
203
+ "error": str(e),
204
+ }
205
+
206
+ def supports_hybrid_search(self) -> bool:
207
+ """Qdrant supports hybrid search through payload filtering."""
208
+ return True
209
+
210
+ def hybrid_search(
211
+ self,
212
+ query: str,
213
+ k: int = 5,
214
+ alpha: float = 0.5,
215
+ filters: dict[str, Any] | None = None,
216
+ ) -> list[dict[str, Any]]:
217
+ """
218
+ Hybrid search combining vector similarity and keyword matching.
219
+
220
+ Note: Qdrant doesn't have built-in hybrid search like some other databases,
221
+ but we can combine vector search with payload filtering for similar functionality.
222
+ """
223
+ # For now, implement as filtered vector search
224
+ # In the future, this could be enhanced with text search capabilities
225
+ return self.similarity_search(query, k, filters)
226
+
227
+ def _format_results(self, results) -> list[dict[str, Any]]:
228
+ """Convert Qdrant results to standardized format."""
229
+ documents = []
230
+
231
+ # Handle both direct points list and QueryResponse
232
+ points = results.points if hasattr(results, "points") else results
233
+
234
+ if not points:
235
+ return documents
236
+
237
+ for point in points:
238
+ # Extract content from payload
239
+ payload = point.payload or {}
240
+
241
+ # Get content - try different field names
242
+ content = ""
243
+ content_fields = ["content", "text", "document", "body", "description"]
244
+ for field in content_fields:
245
+ if field in payload:
246
+ content = payload[field]
247
+ break
248
+
249
+ # If no content field found, use all text properties
250
+ if not content:
251
+ text_properties = []
252
+ for key, value in payload.items():
253
+ if isinstance(value, str) and value.strip():
254
+ text_properties.append(f"{key}: {value}")
255
+ content = " | ".join(text_properties)
256
+
257
+ # Create metadata (all payload except content field)
258
+ metadata = {k: v for k, v in payload.items() if k not in content_fields and k != "original_id"}
259
+ metadata["doc_id"] = payload.get("original_id", str(point.id))
260
+
261
+ # Convert score (Qdrant returns similarity score, higher is better)
262
+ score = float(point.score) if hasattr(point, "score") and point.score is not None else 0.0
263
+
264
+ documents.append(
265
+ {
266
+ "content": content,
267
+ "metadata": metadata,
268
+ "score": score,
269
+ }
270
+ )
271
+
272
+ return documents
273
+
274
+ def _convert_filters(self, filters: dict[str, Any]) -> Any:
275
+ """Convert generic filters to Qdrant filter format."""
276
+ if not filters:
277
+ return None
278
+
279
+ must_conditions = []
280
+
281
+ for key, value in filters.items():
282
+ if isinstance(value, str):
283
+ # Exact string match
284
+ condition = self.models.FieldCondition(key=key, match=self.models.MatchValue(value=value))
285
+ elif isinstance(value, int | float):
286
+ # Exact numeric match
287
+ condition = self.models.FieldCondition(key=key, match=self.models.MatchValue(value=value))
288
+ elif isinstance(value, list):
289
+ # Match any of the values
290
+ condition = self.models.FieldCondition(key=key, match=self.models.MatchAny(any=value))
291
+ elif isinstance(value, dict):
292
+ # Range queries or complex conditions
293
+ if "gte" in value or "gt" in value or "lte" in value or "lt" in value:
294
+ condition = self.models.FieldCondition(
295
+ key=key,
296
+ range=self.models.Range(
297
+ gte=value.get("gte"), gt=value.get("gt"), lte=value.get("lte"), lt=value.get("lt")
298
+ ),
299
+ )
300
+ else:
301
+ # Skip unsupported filter format
302
+ continue
303
+ else:
304
+ # Skip unsupported filter type
305
+ continue
306
+
307
+ must_conditions.append(condition)
308
+
309
+ if must_conditions:
310
+ return self.models.Filter(must=must_conditions)
311
+
312
+ return None
313
+
314
+ @classmethod
315
+ def create_local(
316
+ cls, collection_name: str, embedding_function=None, path: str = ":memory:", vector_size: int = 384
317
+ ):
318
+ """Create a local Qdrant vector store (useful for testing)."""
319
+ try:
320
+ from qdrant_client import QdrantClient
321
+ from qdrant_client.http import models
322
+ except ImportError as e:
323
+ raise ImportError("Qdrant client is required. Install with: pip install litellm qdrant-client") from e
324
+
325
+ client = QdrantClient(path=path)
326
+
327
+ # Create collection if it doesn't exist
328
+ try:
329
+ client.get_collection(collection_name)
330
+ except Exception:
331
+ # Collection doesn't exist, create it
332
+ client.create_collection(
333
+ collection_name=collection_name,
334
+ vectors_config=models.VectorParams(size=vector_size, distance=models.Distance.COSINE),
335
+ )
336
+
337
+ return cls(client, collection_name, embedding_function)
338
+
339
+ @classmethod
340
+ def create_remote(
341
+ cls,
342
+ collection_name: str,
343
+ embedding_function=None,
344
+ host: str = "localhost",
345
+ port: int = 6333,
346
+ api_key: str | None = None,
347
+ vector_size: int = 384,
348
+ ):
349
+ """Create a remote Qdrant vector store."""
350
+ try:
351
+ from qdrant_client import QdrantClient
352
+ from qdrant_client.http import models
353
+ except ImportError as e:
354
+ raise ImportError("Qdrant client is required. Install with: pip install litellm qdrant-client") from e
355
+
356
+ client = QdrantClient(host=host, port=port, api_key=api_key)
357
+
358
+ # Create collection if it doesn't exist
359
+ try:
360
+ client.get_collection(collection_name)
361
+ except Exception:
362
+ # Collection doesn't exist, create it
363
+ client.create_collection(
364
+ collection_name=collection_name,
365
+ vectors_config=models.VectorParams(size=vector_size, distance=models.Distance.COSINE),
366
+ )
367
+
368
+ return cls(client, collection_name, embedding_function)