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,409 @@
|
|
|
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 MilvusVectorStore(VectorStoreInterface):
|
|
10
|
+
"""
|
|
11
|
+
Milvus implementation of the VectorStoreInterface.
|
|
12
|
+
|
|
13
|
+
Milvus is a cloud-native vector database built for scalable similarity search
|
|
14
|
+
and AI applications. It provides excellent performance for large-scale deployments
|
|
15
|
+
and supports various index types and distance metrics.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, client, collection_name: str, embedding_function=None):
|
|
19
|
+
"""
|
|
20
|
+
Initialize MilvusVectorStore.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
client: MilvusClient 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("pymilvus") is None:
|
|
30
|
+
raise ImportError(
|
|
31
|
+
"Milvus client is required for MilvusVectorStore. Install with: pip install litellm pymilvus"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
self.client = client
|
|
35
|
+
self.collection_name = collection_name
|
|
36
|
+
self.embedding_function = embedding_function
|
|
37
|
+
|
|
38
|
+
# Verify collection exists
|
|
39
|
+
if not self.client.has_collection(collection_name):
|
|
40
|
+
raise ValueError(f"Collection '{collection_name}' not found. Please create the collection first.")
|
|
41
|
+
|
|
42
|
+
# Load collection into memory for search operations
|
|
43
|
+
try:
|
|
44
|
+
self.client.load_collection(collection_name)
|
|
45
|
+
except Exception:
|
|
46
|
+
# Collection might already be loaded, which is fine
|
|
47
|
+
pass
|
|
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
|
+
try:
|
|
78
|
+
# Convert filters to Milvus expression format
|
|
79
|
+
filter_expr = self._convert_filters(filters) if filters else None
|
|
80
|
+
|
|
81
|
+
# Perform vector search
|
|
82
|
+
results = self.client.search(
|
|
83
|
+
collection_name=self.collection_name,
|
|
84
|
+
data=[query_vector], # Milvus expects list of vectors
|
|
85
|
+
limit=k,
|
|
86
|
+
filter=filter_expr,
|
|
87
|
+
output_fields=["*"], # Return all fields
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
return self._format_results(results)
|
|
91
|
+
|
|
92
|
+
except Exception as e:
|
|
93
|
+
raise RuntimeError(f"Milvus vector search failed: {e!s}") from e
|
|
94
|
+
|
|
95
|
+
def add_documents(
|
|
96
|
+
self,
|
|
97
|
+
documents: list[dict[str, Any]],
|
|
98
|
+
embeddings: list[list[float]],
|
|
99
|
+
ids: list[str] | None = None,
|
|
100
|
+
) -> list[str]:
|
|
101
|
+
"""Add documents with their embeddings to the collection."""
|
|
102
|
+
if len(documents) != len(embeddings):
|
|
103
|
+
raise ValueError("Number of documents must match number of embeddings")
|
|
104
|
+
|
|
105
|
+
# Generate IDs if not provided
|
|
106
|
+
if ids is None:
|
|
107
|
+
ids = [f"doc_{i}" for i in range(len(documents))]
|
|
108
|
+
elif len(ids) != len(documents):
|
|
109
|
+
raise ValueError("Number of IDs must match number of documents")
|
|
110
|
+
|
|
111
|
+
# Prepare data for insertion
|
|
112
|
+
data_to_insert = []
|
|
113
|
+
for doc_id, doc, embedding in zip(ids, documents, embeddings, strict=False):
|
|
114
|
+
# Milvus requires consistent field structure
|
|
115
|
+
record = {
|
|
116
|
+
"id": doc_id,
|
|
117
|
+
"vector": embedding,
|
|
118
|
+
**doc, # Include all document fields
|
|
119
|
+
}
|
|
120
|
+
data_to_insert.append(record)
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
# Insert data into collection
|
|
124
|
+
result = self.client.insert(collection_name=self.collection_name, data=data_to_insert)
|
|
125
|
+
|
|
126
|
+
# Check if insertion was successful
|
|
127
|
+
if "insert_count" in result and result["insert_count"] == len(data_to_insert):
|
|
128
|
+
return ids
|
|
129
|
+
else:
|
|
130
|
+
raise RuntimeError(
|
|
131
|
+
f"Insertion failed. Expected {len(data_to_insert)}, got {result.get('insert_count', 0)}"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
except Exception as e:
|
|
135
|
+
raise RuntimeError(f"Failed to add documents to Milvus: {e!s}") from e
|
|
136
|
+
|
|
137
|
+
def delete_documents(self, ids: list[str]) -> bool:
|
|
138
|
+
"""Delete documents by their IDs."""
|
|
139
|
+
try:
|
|
140
|
+
# Use parameterized ID-based deletion to avoid injection
|
|
141
|
+
result = self.client.delete(collection_name=self.collection_name, ids=ids)
|
|
142
|
+
|
|
143
|
+
return "delete_count" in result and result["delete_count"] > 0
|
|
144
|
+
|
|
145
|
+
except Exception as e:
|
|
146
|
+
raise RuntimeError(f"Failed to delete documents from Milvus: {e!s}") from e
|
|
147
|
+
|
|
148
|
+
def get_collection_info(self) -> dict[str, Any]:
|
|
149
|
+
"""Get metadata about the Milvus collection."""
|
|
150
|
+
try:
|
|
151
|
+
# Get collection description
|
|
152
|
+
description = self.client.describe_collection(self.collection_name)
|
|
153
|
+
|
|
154
|
+
# Get collection statistics
|
|
155
|
+
stats = self.client.get_collection_stats(self.collection_name)
|
|
156
|
+
|
|
157
|
+
# Extract vector field information
|
|
158
|
+
vector_field = None
|
|
159
|
+
dimension = 0
|
|
160
|
+
for field in description.get("fields", []):
|
|
161
|
+
if field.get("type") == "FloatVector":
|
|
162
|
+
vector_field = field.get("name", "vector")
|
|
163
|
+
dimension = field.get("params", {}).get("dim", 0)
|
|
164
|
+
break
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
"name": self.collection_name,
|
|
168
|
+
"document_count": stats.get("row_count", 0),
|
|
169
|
+
"dimension": dimension,
|
|
170
|
+
"vector_store_type": "milvus",
|
|
171
|
+
"vector_field": vector_field,
|
|
172
|
+
"schema": description,
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
except Exception as e:
|
|
176
|
+
# Fallback info if detailed info fails
|
|
177
|
+
return {
|
|
178
|
+
"name": self.collection_name,
|
|
179
|
+
"document_count": 0,
|
|
180
|
+
"dimension": 0,
|
|
181
|
+
"vector_store_type": "milvus",
|
|
182
|
+
"error": str(e),
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
def supports_hybrid_search(self) -> bool:
|
|
186
|
+
"""Milvus supports hybrid search through dense + sparse vectors."""
|
|
187
|
+
return True
|
|
188
|
+
|
|
189
|
+
def hybrid_search(
|
|
190
|
+
self,
|
|
191
|
+
query: str,
|
|
192
|
+
k: int = 5,
|
|
193
|
+
alpha: float = 0.5,
|
|
194
|
+
filters: dict[str, Any] | None = None,
|
|
195
|
+
) -> list[dict[str, Any]]:
|
|
196
|
+
"""
|
|
197
|
+
Hybrid search combining vector similarity and other search methods.
|
|
198
|
+
|
|
199
|
+
Note: This implementation focuses on vector search with filtering.
|
|
200
|
+
Full hybrid search with sparse vectors would require additional setup.
|
|
201
|
+
"""
|
|
202
|
+
# For now, implement as filtered vector search
|
|
203
|
+
# Future enhancement could include sparse vector search for text matching
|
|
204
|
+
return self.similarity_search(query, k, filters)
|
|
205
|
+
|
|
206
|
+
def _format_results(self, results) -> list[dict[str, Any]]:
|
|
207
|
+
"""Convert Milvus results to standardized format."""
|
|
208
|
+
documents = []
|
|
209
|
+
|
|
210
|
+
# Milvus returns nested list: results[0] contains hits for first query vector
|
|
211
|
+
if not results or not results[0]:
|
|
212
|
+
return documents
|
|
213
|
+
|
|
214
|
+
hits = results[0] # Get hits for our single query vector
|
|
215
|
+
|
|
216
|
+
for hit in hits:
|
|
217
|
+
# Extract fields from hit
|
|
218
|
+
hit_id = hit.get("id", "")
|
|
219
|
+
distance = hit.get("distance", 0.0)
|
|
220
|
+
|
|
221
|
+
# Extract content - try different field names
|
|
222
|
+
content = ""
|
|
223
|
+
content_fields = ["content", "text", "document", "body", "description"]
|
|
224
|
+
for field in content_fields:
|
|
225
|
+
if field in hit:
|
|
226
|
+
content = hit[field]
|
|
227
|
+
break
|
|
228
|
+
|
|
229
|
+
# If no content field found, combine text fields
|
|
230
|
+
if not content:
|
|
231
|
+
text_properties = []
|
|
232
|
+
for key, value in hit.items():
|
|
233
|
+
if isinstance(value, str) and value.strip() and key not in ["id", "distance"]:
|
|
234
|
+
text_properties.append(f"{key}: {value}")
|
|
235
|
+
content = " | ".join(text_properties)
|
|
236
|
+
|
|
237
|
+
# Create metadata (all fields except content and system fields)
|
|
238
|
+
metadata = {}
|
|
239
|
+
system_fields = ["id", "distance", "vector"] + content_fields
|
|
240
|
+
for key, value in hit.items():
|
|
241
|
+
if key not in system_fields:
|
|
242
|
+
metadata[key] = value
|
|
243
|
+
metadata["doc_id"] = str(hit_id)
|
|
244
|
+
|
|
245
|
+
# Convert distance to similarity score (Milvus returns distance, lower is better)
|
|
246
|
+
# For cosine distance: similarity = 1 - distance
|
|
247
|
+
# For L2 distance: similarity = 1 / (1 + distance)
|
|
248
|
+
score = max(0.0, 1.0 - distance) if distance <= 1.0 else 1.0 / (1.0 + distance)
|
|
249
|
+
|
|
250
|
+
documents.append(
|
|
251
|
+
{
|
|
252
|
+
"content": content,
|
|
253
|
+
"metadata": metadata,
|
|
254
|
+
"score": score,
|
|
255
|
+
}
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
return documents
|
|
259
|
+
|
|
260
|
+
def _convert_filters(self, filters: dict[str, Any]) -> str:
|
|
261
|
+
"""Convert generic filters to Milvus expression format."""
|
|
262
|
+
if not filters:
|
|
263
|
+
return None
|
|
264
|
+
|
|
265
|
+
expressions = []
|
|
266
|
+
|
|
267
|
+
for key, value in filters.items():
|
|
268
|
+
if isinstance(value, str):
|
|
269
|
+
# String exact match
|
|
270
|
+
expressions.append(f'{key} == "{value}"')
|
|
271
|
+
elif isinstance(value, int | float):
|
|
272
|
+
# Numeric exact match
|
|
273
|
+
expressions.append(f"{key} == {value}")
|
|
274
|
+
elif isinstance(value, list):
|
|
275
|
+
# IN clause for multiple values
|
|
276
|
+
if all(isinstance(v, str) for v in value):
|
|
277
|
+
values_str = '", "'.join(value)
|
|
278
|
+
expressions.append(f'{key} in ["{values_str}"]')
|
|
279
|
+
else:
|
|
280
|
+
values_str = ", ".join(str(v) for v in value)
|
|
281
|
+
expressions.append(f"{key} in [{values_str}]")
|
|
282
|
+
elif isinstance(value, dict):
|
|
283
|
+
# Range queries
|
|
284
|
+
range_conditions = []
|
|
285
|
+
if "gte" in value:
|
|
286
|
+
range_conditions.append(f"{key} >= {value['gte']}")
|
|
287
|
+
if "gt" in value:
|
|
288
|
+
range_conditions.append(f"{key} > {value['gt']}")
|
|
289
|
+
if "lte" in value:
|
|
290
|
+
range_conditions.append(f"{key} <= {value['lte']}")
|
|
291
|
+
if "lt" in value:
|
|
292
|
+
range_conditions.append(f"{key} < {value['lt']}")
|
|
293
|
+
|
|
294
|
+
if range_conditions:
|
|
295
|
+
expressions.append(" and ".join(range_conditions))
|
|
296
|
+
|
|
297
|
+
if expressions:
|
|
298
|
+
return " and ".join(expressions)
|
|
299
|
+
|
|
300
|
+
return None
|
|
301
|
+
|
|
302
|
+
@classmethod
|
|
303
|
+
def create_local(
|
|
304
|
+
cls, collection_name: str, embedding_function=None, vector_size: int = 384, uri: str = "./milvus_demo.db"
|
|
305
|
+
):
|
|
306
|
+
"""Create a local Milvus vector store (Milvus Lite)."""
|
|
307
|
+
try:
|
|
308
|
+
from pymilvus import DataType, MilvusClient
|
|
309
|
+
except ImportError as e:
|
|
310
|
+
raise ImportError("Milvus client is required. Install with: pip install litellm pymilvus") from e
|
|
311
|
+
|
|
312
|
+
client = MilvusClient(uri=uri)
|
|
313
|
+
|
|
314
|
+
# Create collection if it doesn't exist
|
|
315
|
+
if not client.has_collection(collection_name):
|
|
316
|
+
# Create collection with explicit schema to avoid max_length issues
|
|
317
|
+
schema = client.create_schema(auto_id=False, enable_dynamic_field=True)
|
|
318
|
+
|
|
319
|
+
# Add ID field (string)
|
|
320
|
+
schema.add_field(
|
|
321
|
+
field_name="id",
|
|
322
|
+
datatype=DataType.VARCHAR,
|
|
323
|
+
is_primary=True,
|
|
324
|
+
max_length=512, # Maximum length for ID strings
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
# Add vector field
|
|
328
|
+
schema.add_field(field_name="vector", datatype=DataType.FLOAT_VECTOR, dim=vector_size)
|
|
329
|
+
|
|
330
|
+
# Add content field
|
|
331
|
+
schema.add_field(
|
|
332
|
+
field_name="content",
|
|
333
|
+
datatype=DataType.VARCHAR,
|
|
334
|
+
max_length=65535, # Large max length for content
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
# Create collection with schema
|
|
338
|
+
client.create_collection(
|
|
339
|
+
collection_name=collection_name,
|
|
340
|
+
schema=schema,
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
# Create index for vector field
|
|
344
|
+
index_params = client.prepare_index_params()
|
|
345
|
+
index_params.add_index(field_name="vector", metric_type="COSINE")
|
|
346
|
+
client.create_index(collection_name=collection_name, index_params=index_params)
|
|
347
|
+
|
|
348
|
+
return cls(client, collection_name, embedding_function)
|
|
349
|
+
|
|
350
|
+
@classmethod
|
|
351
|
+
def create_remote(
|
|
352
|
+
cls,
|
|
353
|
+
collection_name: str,
|
|
354
|
+
embedding_function=None,
|
|
355
|
+
uri: str = "http://localhost:19530",
|
|
356
|
+
user: str = "",
|
|
357
|
+
password: str = "",
|
|
358
|
+
token: str = "",
|
|
359
|
+
vector_size: int = 384,
|
|
360
|
+
):
|
|
361
|
+
"""Create a remote Milvus vector store."""
|
|
362
|
+
try:
|
|
363
|
+
from pymilvus import DataType, MilvusClient
|
|
364
|
+
except ImportError as e:
|
|
365
|
+
raise ImportError("Milvus client is required. Install with: pip install litellm pymilvus") from e
|
|
366
|
+
|
|
367
|
+
# Connect to remote Milvus
|
|
368
|
+
client = MilvusClient(
|
|
369
|
+
uri=uri,
|
|
370
|
+
user=user if user else None,
|
|
371
|
+
password=password if password else None,
|
|
372
|
+
token=token if token else None,
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
# Create collection if it doesn't exist
|
|
376
|
+
if not client.has_collection(collection_name):
|
|
377
|
+
# Create collection with explicit schema to avoid max_length issues
|
|
378
|
+
schema = client.create_schema(auto_id=False, enable_dynamic_field=True)
|
|
379
|
+
|
|
380
|
+
# Add ID field (string)
|
|
381
|
+
schema.add_field(
|
|
382
|
+
field_name="id",
|
|
383
|
+
datatype=DataType.VARCHAR,
|
|
384
|
+
is_primary=True,
|
|
385
|
+
max_length=512, # Maximum length for ID strings
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
# Add vector field
|
|
389
|
+
schema.add_field(field_name="vector", datatype=DataType.FLOAT_VECTOR, dim=vector_size)
|
|
390
|
+
|
|
391
|
+
# Add content field
|
|
392
|
+
schema.add_field(
|
|
393
|
+
field_name="content",
|
|
394
|
+
datatype=DataType.VARCHAR,
|
|
395
|
+
max_length=65535, # Large max length for content
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
# Create collection with schema
|
|
399
|
+
client.create_collection(
|
|
400
|
+
collection_name=collection_name,
|
|
401
|
+
schema=schema,
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
# Create index for vector field
|
|
405
|
+
index_params = client.prepare_index_params()
|
|
406
|
+
index_params.add_index(field_name="vector", metric_type="COSINE")
|
|
407
|
+
client.create_index(collection_name=collection_name, index_params=index_params)
|
|
408
|
+
|
|
409
|
+
return cls(client, collection_name, embedding_function)
|