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.
- mantisdk/__init__.py +22 -0
- mantisdk/adapter/__init__.py +15 -0
- mantisdk/adapter/base.py +94 -0
- mantisdk/adapter/messages.py +270 -0
- mantisdk/adapter/triplet.py +1028 -0
- mantisdk/algorithm/__init__.py +39 -0
- mantisdk/algorithm/apo/__init__.py +5 -0
- mantisdk/algorithm/apo/apo.py +889 -0
- mantisdk/algorithm/apo/prompts/apply_edit_variant01.poml +22 -0
- mantisdk/algorithm/apo/prompts/apply_edit_variant02.poml +18 -0
- mantisdk/algorithm/apo/prompts/text_gradient_variant01.poml +18 -0
- mantisdk/algorithm/apo/prompts/text_gradient_variant02.poml +16 -0
- mantisdk/algorithm/apo/prompts/text_gradient_variant03.poml +107 -0
- mantisdk/algorithm/base.py +162 -0
- mantisdk/algorithm/decorator.py +264 -0
- mantisdk/algorithm/fast.py +250 -0
- mantisdk/algorithm/gepa/__init__.py +59 -0
- mantisdk/algorithm/gepa/adapter.py +459 -0
- mantisdk/algorithm/gepa/gepa.py +364 -0
- mantisdk/algorithm/gepa/lib/__init__.py +18 -0
- mantisdk/algorithm/gepa/lib/adapters/README.md +12 -0
- mantisdk/algorithm/gepa/lib/adapters/__init__.py +0 -0
- mantisdk/algorithm/gepa/lib/adapters/anymaths_adapter/README.md +341 -0
- mantisdk/algorithm/gepa/lib/adapters/anymaths_adapter/__init__.py +1 -0
- mantisdk/algorithm/gepa/lib/adapters/anymaths_adapter/anymaths_adapter.py +174 -0
- mantisdk/algorithm/gepa/lib/adapters/anymaths_adapter/requirements.txt +1 -0
- mantisdk/algorithm/gepa/lib/adapters/default_adapter/README.md +0 -0
- mantisdk/algorithm/gepa/lib/adapters/default_adapter/__init__.py +0 -0
- mantisdk/algorithm/gepa/lib/adapters/default_adapter/default_adapter.py +209 -0
- mantisdk/algorithm/gepa/lib/adapters/dspy_adapter/README.md +7 -0
- mantisdk/algorithm/gepa/lib/adapters/dspy_adapter/__init__.py +0 -0
- mantisdk/algorithm/gepa/lib/adapters/dspy_adapter/dspy_adapter.py +307 -0
- mantisdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/README.md +99 -0
- mantisdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/dspy_program_proposal_signature.py +137 -0
- mantisdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/full_program_adapter.py +266 -0
- mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/GEPA_RAG.md +621 -0
- mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/__init__.py +56 -0
- mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/evaluation_metrics.py +226 -0
- mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/generic_rag_adapter.py +496 -0
- mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/rag_pipeline.py +238 -0
- mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_store_interface.py +212 -0
- mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/__init__.py +2 -0
- mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/chroma_store.py +196 -0
- mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/lancedb_store.py +422 -0
- mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/milvus_store.py +409 -0
- mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/qdrant_store.py +368 -0
- mantisdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/weaviate_store.py +418 -0
- mantisdk/algorithm/gepa/lib/adapters/mcp_adapter/README.md +552 -0
- mantisdk/algorithm/gepa/lib/adapters/mcp_adapter/__init__.py +37 -0
- mantisdk/algorithm/gepa/lib/adapters/mcp_adapter/mcp_adapter.py +705 -0
- mantisdk/algorithm/gepa/lib/adapters/mcp_adapter/mcp_client.py +364 -0
- mantisdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/README.md +9 -0
- mantisdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/__init__.py +0 -0
- mantisdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/terminal_bench_adapter.py +217 -0
- mantisdk/algorithm/gepa/lib/api.py +375 -0
- mantisdk/algorithm/gepa/lib/core/__init__.py +0 -0
- mantisdk/algorithm/gepa/lib/core/adapter.py +180 -0
- mantisdk/algorithm/gepa/lib/core/data_loader.py +74 -0
- mantisdk/algorithm/gepa/lib/core/engine.py +356 -0
- mantisdk/algorithm/gepa/lib/core/result.py +233 -0
- mantisdk/algorithm/gepa/lib/core/state.py +636 -0
- mantisdk/algorithm/gepa/lib/examples/__init__.py +0 -0
- mantisdk/algorithm/gepa/lib/examples/aime.py +24 -0
- mantisdk/algorithm/gepa/lib/examples/anymaths-bench/eval_default.py +111 -0
- mantisdk/algorithm/gepa/lib/examples/anymaths-bench/prompt-templates/instruction_prompt.txt +9 -0
- mantisdk/algorithm/gepa/lib/examples/anymaths-bench/prompt-templates/optimal_prompt.txt +24 -0
- mantisdk/algorithm/gepa/lib/examples/anymaths-bench/train_anymaths.py +177 -0
- mantisdk/algorithm/gepa/lib/examples/dspy_full_program_evolution/arc_agi.ipynb +25705 -0
- mantisdk/algorithm/gepa/lib/examples/dspy_full_program_evolution/example.ipynb +348 -0
- mantisdk/algorithm/gepa/lib/examples/mcp_adapter/__init__.py +4 -0
- mantisdk/algorithm/gepa/lib/examples/mcp_adapter/mcp_optimization_example.py +455 -0
- mantisdk/algorithm/gepa/lib/examples/rag_adapter/RAG_GUIDE.md +613 -0
- mantisdk/algorithm/gepa/lib/examples/rag_adapter/__init__.py +9 -0
- mantisdk/algorithm/gepa/lib/examples/rag_adapter/rag_optimization.py +824 -0
- mantisdk/algorithm/gepa/lib/examples/rag_adapter/requirements-rag.txt +29 -0
- mantisdk/algorithm/gepa/lib/examples/terminal-bench/prompt-templates/instruction_prompt.txt +16 -0
- mantisdk/algorithm/gepa/lib/examples/terminal-bench/prompt-templates/terminus.txt +9 -0
- mantisdk/algorithm/gepa/lib/examples/terminal-bench/train_terminus.py +161 -0
- mantisdk/algorithm/gepa/lib/gepa_utils.py +117 -0
- mantisdk/algorithm/gepa/lib/logging/__init__.py +0 -0
- mantisdk/algorithm/gepa/lib/logging/experiment_tracker.py +187 -0
- mantisdk/algorithm/gepa/lib/logging/logger.py +75 -0
- mantisdk/algorithm/gepa/lib/logging/utils.py +103 -0
- mantisdk/algorithm/gepa/lib/proposer/__init__.py +0 -0
- mantisdk/algorithm/gepa/lib/proposer/base.py +31 -0
- mantisdk/algorithm/gepa/lib/proposer/merge.py +357 -0
- mantisdk/algorithm/gepa/lib/proposer/reflective_mutation/__init__.py +0 -0
- mantisdk/algorithm/gepa/lib/proposer/reflective_mutation/base.py +49 -0
- mantisdk/algorithm/gepa/lib/proposer/reflective_mutation/reflective_mutation.py +176 -0
- mantisdk/algorithm/gepa/lib/py.typed +0 -0
- mantisdk/algorithm/gepa/lib/strategies/__init__.py +0 -0
- mantisdk/algorithm/gepa/lib/strategies/batch_sampler.py +77 -0
- mantisdk/algorithm/gepa/lib/strategies/candidate_selector.py +50 -0
- mantisdk/algorithm/gepa/lib/strategies/component_selector.py +36 -0
- mantisdk/algorithm/gepa/lib/strategies/eval_policy.py +64 -0
- mantisdk/algorithm/gepa/lib/strategies/instruction_proposal.py +127 -0
- mantisdk/algorithm/gepa/lib/utils/__init__.py +10 -0
- mantisdk/algorithm/gepa/lib/utils/stop_condition.py +196 -0
- mantisdk/algorithm/gepa/tracing.py +105 -0
- mantisdk/algorithm/utils.py +177 -0
- mantisdk/algorithm/verl/__init__.py +5 -0
- mantisdk/algorithm/verl/interface.py +202 -0
- mantisdk/cli/__init__.py +56 -0
- mantisdk/cli/prometheus.py +115 -0
- mantisdk/cli/store.py +131 -0
- mantisdk/cli/vllm.py +29 -0
- mantisdk/client.py +408 -0
- mantisdk/config.py +348 -0
- mantisdk/emitter/__init__.py +43 -0
- mantisdk/emitter/annotation.py +370 -0
- mantisdk/emitter/exception.py +54 -0
- mantisdk/emitter/message.py +61 -0
- mantisdk/emitter/object.py +117 -0
- mantisdk/emitter/reward.py +320 -0
- mantisdk/env_var.py +156 -0
- mantisdk/execution/__init__.py +15 -0
- mantisdk/execution/base.py +64 -0
- mantisdk/execution/client_server.py +443 -0
- mantisdk/execution/events.py +69 -0
- mantisdk/execution/inter_process.py +16 -0
- mantisdk/execution/shared_memory.py +282 -0
- mantisdk/instrumentation/__init__.py +119 -0
- mantisdk/instrumentation/agentops.py +314 -0
- mantisdk/instrumentation/agentops_langchain.py +45 -0
- mantisdk/instrumentation/litellm.py +83 -0
- mantisdk/instrumentation/vllm.py +81 -0
- mantisdk/instrumentation/weave.py +500 -0
- mantisdk/litagent/__init__.py +11 -0
- mantisdk/litagent/decorator.py +536 -0
- mantisdk/litagent/litagent.py +252 -0
- mantisdk/llm_proxy.py +1890 -0
- mantisdk/logging.py +370 -0
- mantisdk/reward.py +7 -0
- mantisdk/runner/__init__.py +11 -0
- mantisdk/runner/agent.py +845 -0
- mantisdk/runner/base.py +182 -0
- mantisdk/runner/legacy.py +309 -0
- mantisdk/semconv.py +170 -0
- mantisdk/server.py +401 -0
- mantisdk/store/__init__.py +23 -0
- mantisdk/store/base.py +897 -0
- mantisdk/store/client_server.py +2092 -0
- mantisdk/store/collection/__init__.py +30 -0
- mantisdk/store/collection/base.py +587 -0
- mantisdk/store/collection/memory.py +970 -0
- mantisdk/store/collection/mongo.py +1412 -0
- mantisdk/store/collection_based.py +1823 -0
- mantisdk/store/insight.py +648 -0
- mantisdk/store/listener.py +58 -0
- mantisdk/store/memory.py +396 -0
- mantisdk/store/mongo.py +165 -0
- mantisdk/store/sqlite.py +3 -0
- mantisdk/store/threading.py +357 -0
- mantisdk/store/utils.py +142 -0
- mantisdk/tracer/__init__.py +16 -0
- mantisdk/tracer/agentops.py +242 -0
- mantisdk/tracer/base.py +287 -0
- mantisdk/tracer/dummy.py +106 -0
- mantisdk/tracer/otel.py +555 -0
- mantisdk/tracer/weave.py +677 -0
- mantisdk/trainer/__init__.py +6 -0
- mantisdk/trainer/init_utils.py +263 -0
- mantisdk/trainer/legacy.py +367 -0
- mantisdk/trainer/registry.py +12 -0
- mantisdk/trainer/trainer.py +618 -0
- mantisdk/types/__init__.py +6 -0
- mantisdk/types/core.py +553 -0
- mantisdk/types/resources.py +204 -0
- mantisdk/types/tracer.py +515 -0
- mantisdk/types/tracing.py +218 -0
- mantisdk/utils/__init__.py +1 -0
- mantisdk/utils/id.py +18 -0
- mantisdk/utils/metrics.py +1025 -0
- mantisdk/utils/otel.py +578 -0
- mantisdk/utils/otlp.py +536 -0
- mantisdk/utils/server_launcher.py +1045 -0
- mantisdk/utils/system_snapshot.py +81 -0
- mantisdk/verl/__init__.py +8 -0
- mantisdk/verl/__main__.py +6 -0
- mantisdk/verl/async_server.py +46 -0
- mantisdk/verl/config.yaml +27 -0
- mantisdk/verl/daemon.py +1154 -0
- mantisdk/verl/dataset.py +44 -0
- mantisdk/verl/entrypoint.py +248 -0
- mantisdk/verl/trainer.py +549 -0
- mantisdk-0.1.0.dist-info/METADATA +119 -0
- mantisdk-0.1.0.dist-info/RECORD +190 -0
- mantisdk-0.1.0.dist-info/WHEEL +4 -0
- mantisdk-0.1.0.dist-info/entry_points.txt +2 -0
- 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)
|