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,238 @@
1
+ # Copyright (c) 2025 Lakshya A Agrawal and the GEPA contributors
2
+ # https://github.com/gepa-ai/gepa
3
+
4
+ from typing import Any, Callable
5
+
6
+ from mantisdk.algorithm.gepa.lib.adapters.generic_rag_adapter.vector_store_interface import VectorStoreInterface
7
+
8
+
9
+ class RAGPipeline:
10
+ """
11
+ Generic RAG pipeline that works with any vector store.
12
+
13
+ This pipeline orchestrates the full RAG process:
14
+ 1. Query reformulation (optional)
15
+ 2. Document retrieval
16
+ 3. Document reranking (optional)
17
+ 4. Context synthesis
18
+ 5. Answer generation
19
+ """
20
+
21
+ def __init__(
22
+ self,
23
+ vector_store: VectorStoreInterface,
24
+ llm_client,
25
+ embedding_model: str = "text-embedding-3-small",
26
+ embedding_function: Callable[[str], list[float]] | None = None,
27
+ ):
28
+ """
29
+ Initialize the RAG pipeline.
30
+
31
+ Args:
32
+ vector_store: Vector store interface implementation
33
+ llm_client: LLM client for generation (should have a callable interface)
34
+ embedding_model: Model name for embeddings (if using default embedding function)
35
+ embedding_function: Optional custom embedding function
36
+ """
37
+ self.vector_store = vector_store
38
+ self.llm_client = llm_client
39
+ self.embedding_model = embedding_model
40
+ self.embedding_function = embedding_function
41
+
42
+ # Initialize default embedding function if none provided
43
+ if self.embedding_function is None:
44
+ self.embedding_function = self._default_embedding_function
45
+
46
+ def execute_rag(
47
+ self,
48
+ query: str,
49
+ prompts: dict[str, str],
50
+ config: dict[str, Any],
51
+ ) -> dict[str, Any]:
52
+ """
53
+ Execute the full RAG pipeline with given prompts and configuration.
54
+
55
+ Args:
56
+ query: User query
57
+ prompts: Dictionary of prompt templates for different stages
58
+ config: Configuration parameters for retrieval and generation
59
+
60
+ Returns:
61
+ Dictionary containing all pipeline outputs and metadata
62
+ """
63
+ # Step 1: Query reformulation (if enabled)
64
+ reformulated_query = query
65
+ if "query_reformulation" in prompts and prompts["query_reformulation"].strip():
66
+ reformulated_query = self._reformulate_query(query, prompts["query_reformulation"])
67
+
68
+ # Step 2: Retrieval
69
+ retrieved_docs = self._retrieve_documents(reformulated_query, config)
70
+
71
+ # Step 3: Reranking (if enabled)
72
+ if "reranking_criteria" in prompts and prompts["reranking_criteria"].strip():
73
+ retrieved_docs = self._rerank_documents(retrieved_docs, query, prompts["reranking_criteria"], config)
74
+
75
+ # Step 4: Context synthesis
76
+ context = self._synthesize_context(retrieved_docs, query, prompts.get("context_synthesis", ""))
77
+
78
+ # Step 5: Answer generation
79
+ answer = self._generate_answer(query, context, prompts.get("answer_generation", ""))
80
+
81
+ return {
82
+ "original_query": query,
83
+ "reformulated_query": reformulated_query,
84
+ "retrieved_docs": retrieved_docs,
85
+ "synthesized_context": context,
86
+ "generated_answer": answer,
87
+ "metadata": {
88
+ "retrieval_count": len(retrieved_docs),
89
+ "total_tokens": self._estimate_token_count(context + answer),
90
+ "vector_store_type": self.vector_store.get_collection_info().get("vector_store_type", "unknown"),
91
+ },
92
+ }
93
+
94
+ def _reformulate_query(self, query: str, reformulation_prompt: str) -> str:
95
+ """Reformulate the user query using the provided prompt."""
96
+ messages = [
97
+ {"role": "system", "content": reformulation_prompt},
98
+ {"role": "user", "content": f"Original query: {query}"},
99
+ ]
100
+
101
+ try:
102
+ if callable(self.llm_client):
103
+ response = self.llm_client(messages)
104
+ else:
105
+ # Assume it's a litellm-style client
106
+ response = self.llm_client.completion(messages=messages).choices[0].message.content
107
+
108
+ return response.strip() if response else query
109
+ except Exception:
110
+ # Fallback to original query if reformulation fails
111
+ return query
112
+
113
+ def _retrieve_documents(self, query: str, config: dict[str, Any]) -> list[dict[str, Any]]:
114
+ """Retrieve documents using the configured strategy."""
115
+ retrieval_strategy = config.get("retrieval_strategy", "similarity")
116
+ k = config.get("top_k", 5)
117
+ filters = config.get("filters", None)
118
+
119
+ if retrieval_strategy == "similarity":
120
+ return self.vector_store.similarity_search(query, k=k, filters=filters)
121
+ elif retrieval_strategy == "hybrid":
122
+ if self.vector_store.supports_hybrid_search():
123
+ alpha = config.get("hybrid_alpha", 0.5)
124
+ return self.vector_store.hybrid_search(query, k=k, alpha=alpha, filters=filters)
125
+ else:
126
+ # Fallback to similarity search
127
+ return self.vector_store.similarity_search(query, k=k, filters=filters)
128
+ elif retrieval_strategy == "vector":
129
+ # Use pre-computed embedding
130
+ query_vector = self.embedding_function(query)
131
+ return self.vector_store.vector_search(query_vector, k=k, filters=filters)
132
+ else:
133
+ raise ValueError(f"Unknown retrieval strategy: {retrieval_strategy}")
134
+
135
+ def _rerank_documents(
136
+ self, documents: list[dict[str, Any]], query: str, reranking_prompt: str, config: dict[str, Any]
137
+ ) -> list[dict[str, Any]]:
138
+ """Rerank documents based on relevance criteria."""
139
+ if not documents:
140
+ return documents
141
+
142
+ # For simplicity, we'll use a prompt-based reranking approach
143
+ # In production, you might use dedicated reranking models
144
+ try:
145
+ doc_texts = [f"Document {i + 1}: {doc['content']}" for i, doc in enumerate(documents)]
146
+ doc_context = "\n\n".join(doc_texts)
147
+
148
+ messages = [
149
+ {"role": "system", "content": reranking_prompt},
150
+ {
151
+ "role": "user",
152
+ "content": f"Query: {query}\n\nDocuments:\n{doc_context}\n\nPlease rank these documents by relevance (return document numbers in order, e.g., '3,1,4,2,5'):",
153
+ },
154
+ ]
155
+
156
+ if callable(self.llm_client):
157
+ response = self.llm_client(messages)
158
+ else:
159
+ response = self.llm_client.completion(messages=messages).choices[0].message.content
160
+
161
+ # Parse the ranking response
162
+ ranking_str = response.strip()
163
+ rankings = [int(x.strip()) - 1 for x in ranking_str.split(",") if x.strip().isdigit()]
164
+
165
+ # Reorder documents based on ranking
166
+ if len(rankings) == len(documents):
167
+ return [documents[i] for i in rankings if 0 <= i < len(documents)]
168
+ except Exception:
169
+ pass
170
+
171
+ # Return original order if reranking fails
172
+ return documents
173
+
174
+ def _synthesize_context(self, documents: list[dict[str, Any]], query: str, synthesis_prompt: str) -> str:
175
+ """Synthesize retrieved documents into coherent context."""
176
+ if not documents:
177
+ return ""
178
+
179
+ if not synthesis_prompt.strip():
180
+ # Default: simple concatenation
181
+ contexts = []
182
+ for i, doc in enumerate(documents):
183
+ contexts.append(f"[Document {i + 1}] {doc['content']}")
184
+ return "\n\n".join(contexts)
185
+
186
+ # Use LLM for context synthesis
187
+ doc_texts = [doc["content"] for doc in documents]
188
+ doc_context = "\n\n".join(f"Document {i + 1}: {text}" for i, text in enumerate(doc_texts))
189
+
190
+ messages = [
191
+ {"role": "system", "content": synthesis_prompt},
192
+ {"role": "user", "content": f"Query: {query}\n\nRetrieved Documents:\n{doc_context}"},
193
+ ]
194
+
195
+ try:
196
+ if callable(self.llm_client):
197
+ response = self.llm_client(messages)
198
+ else:
199
+ response = self.llm_client.completion(messages=messages).choices[0].message.content
200
+
201
+ return response.strip() if response else doc_context
202
+ except Exception:
203
+ # Fallback to simple concatenation
204
+ return doc_context
205
+
206
+ def _generate_answer(self, query: str, context: str, generation_prompt: str) -> str:
207
+ """Generate the final answer using the query and synthesized context."""
208
+ if not generation_prompt.strip():
209
+ generation_prompt = "You are a helpful assistant. Answer the user's question based on the provided context."
210
+
211
+ messages = [
212
+ {"role": "system", "content": generation_prompt},
213
+ {"role": "user", "content": f"Context:\n{context}\n\nQuestion: {query}"},
214
+ ]
215
+
216
+ try:
217
+ if callable(self.llm_client):
218
+ response = self.llm_client(messages)
219
+ else:
220
+ response = self.llm_client.completion(messages=messages).choices[0].message.content
221
+
222
+ return response.strip() if response else "I couldn't generate an answer based on the provided context."
223
+ except Exception as e:
224
+ return f"Error generating answer: {e!s}"
225
+
226
+ def _default_embedding_function(self, text: str) -> list[float]:
227
+ """Default embedding function using litellm."""
228
+ try:
229
+ import litellm
230
+
231
+ response = litellm.embedding(model=self.embedding_model, input=text)
232
+ return response.data[0].embedding
233
+ except Exception as e:
234
+ raise RuntimeError(f"Failed to generate embeddings: {e!s}") from e
235
+
236
+ def _estimate_token_count(self, text: str) -> int:
237
+ """Rough estimate of token count (4 chars per token approximation)."""
238
+ return len(text) // 4
@@ -0,0 +1,212 @@
1
+ # Copyright (c) 2025 Lakshya A Agrawal and the GEPA contributors
2
+ # https://github.com/gepa-ai/gepa
3
+
4
+ from abc import ABC, abstractmethod
5
+ from typing import Any
6
+
7
+
8
+ class VectorStoreInterface(ABC):
9
+ """
10
+ Abstract interface for vector store operations in RAG systems.
11
+
12
+ This interface defines the core operations needed for retrieval-augmented generation,
13
+ enabling GEPA to work with any vector store implementation (ChromaDB, Weaviate, Qdrant,
14
+ Pinecone, Milvus, etc.) through a unified API.
15
+
16
+ The interface supports:
17
+ - Semantic similarity search
18
+ - Vector-based search with pre-computed embeddings
19
+ - Hybrid search (semantic + keyword, where supported)
20
+ - Metadata filtering and collection introspection
21
+
22
+ Implementing this interface allows your vector store to be used with GEPA's
23
+ evolutionary prompt optimization for RAG systems.
24
+
25
+ Example:
26
+ .. code-block:: python
27
+
28
+ class MyVectorStore(VectorStoreInterface):
29
+ def similarity_search(self, query, k=5, filters=None):
30
+ # Your implementation
31
+ return documents
32
+
33
+ vector_store = MyVectorStore()
34
+ adapter = GenericRAGAdapter(vector_store=vector_store)
35
+ """
36
+
37
+ @abstractmethod
38
+ def similarity_search(
39
+ self,
40
+ query: str,
41
+ k: int = 5,
42
+ filters: dict[str, Any] | None = None,
43
+ ) -> list[dict[str, Any]]:
44
+ """
45
+ Search for documents semantically similar to the query text.
46
+
47
+ This method performs semantic similarity search using the vector store's
48
+ default embedding model or configured vectorizer.
49
+
50
+ Args:
51
+ query: Text query to search for similar documents
52
+ k: Maximum number of documents to return (default: 5)
53
+ filters: Optional metadata filters to constrain search.
54
+ Format: {"key": "value"} or {"key": {"$op": value}}
55
+
56
+ Returns:
57
+ List of documents ordered by similarity score (highest first).
58
+ Each document is a dictionary with:
59
+ - "content" (str): The document text content
60
+ - "metadata" (dict): Document metadata including any doc_id
61
+ - "score" (float): Similarity score between 0.0 and 1.0 (higher = more similar)
62
+
63
+ Raises:
64
+ NotImplementedError: Must be implemented by concrete vector store classes
65
+
66
+ Example:
67
+ .. code-block:: python
68
+
69
+ results = vector_store.similarity_search(
70
+ query="machine learning algorithms",
71
+ k=3,
72
+ filters={"category": "AI"}
73
+ )
74
+ print(results[0]["content"]) # Most similar document
75
+ """
76
+ pass
77
+
78
+ @abstractmethod
79
+ def vector_search(
80
+ self,
81
+ query_vector: list[float],
82
+ k: int = 5,
83
+ filters: dict[str, Any] | None = None,
84
+ ) -> list[dict[str, Any]]:
85
+ """
86
+ Search using a pre-computed query embedding vector.
87
+
88
+ This method allows direct vector similarity search when you already have
89
+ the query embedding, avoiding the need for additional embedding computation.
90
+
91
+ Args:
92
+ query_vector: Pre-computed embedding vector for the query.
93
+ Must match the dimensionality of vectors in the collection.
94
+ k: Maximum number of documents to return (default: 5)
95
+ filters: Optional metadata filters to constrain search
96
+
97
+ Returns:
98
+ List of documents ordered by vector similarity (highest first).
99
+ Same format as similarity_search().
100
+
101
+ Raises:
102
+ NotImplementedError: Must be implemented by concrete vector store classes
103
+ ValueError: If query_vector dimensions don't match collection
104
+
105
+ Example:
106
+ .. code-block:: python
107
+
108
+ import numpy as np
109
+ query_vector = embedding_model.encode("machine learning")
110
+ results = vector_store.vector_search(query_vector.tolist(), k=5)
111
+ """
112
+ pass
113
+
114
+ def hybrid_search(
115
+ self,
116
+ query: str,
117
+ k: int = 5,
118
+ alpha: float = 0.5,
119
+ filters: dict[str, Any] | None = None,
120
+ ) -> list[dict[str, Any]]:
121
+ """
122
+ Hybrid semantic + keyword search combining vector and text-based matching.
123
+
124
+ This method combines semantic similarity (vector search) with keyword-based
125
+ search (like BM25) to leverage both approaches. The alpha parameter controls
126
+ the balance between the two search methods.
127
+
128
+ Args:
129
+ query: Text query to search for
130
+ k: Maximum number of documents to return (default: 5)
131
+ alpha: Weight for semantic vs keyword search (default: 0.5)
132
+ - 0.0 = pure keyword/BM25 search
133
+ - 1.0 = pure semantic/vector search
134
+ - 0.5 = balanced hybrid search
135
+ filters: Optional metadata filters to constrain search
136
+
137
+ Returns:
138
+ List of documents ordered by hybrid similarity score (highest first).
139
+ Same format as similarity_search().
140
+
141
+ Note:
142
+ If hybrid search is not supported by the vector store implementation,
143
+ this method falls back to similarity_search() with a warning.
144
+ Use supports_hybrid_search() to check availability.
145
+
146
+ Example:
147
+ .. code-block:: python
148
+
149
+ # Balanced hybrid search
150
+ results = vector_store.hybrid_search("AI algorithms", alpha=0.5)
151
+
152
+ # More semantic-focused
153
+ results = vector_store.hybrid_search("AI algorithms", alpha=0.8)
154
+ """
155
+ # Default fallback implementation
156
+ return self.similarity_search(query, k, filters)
157
+
158
+ @abstractmethod
159
+ def get_collection_info(self) -> dict[str, Any]:
160
+ """
161
+ Get metadata and statistics about the vector store collection.
162
+
163
+ This method provides introspection capabilities for the collection,
164
+ returning key information about its configuration and contents.
165
+
166
+ Returns:
167
+ Dictionary containing collection metadata with keys:
168
+ - "name" (str): Collection/index name
169
+ - "document_count" (int): Total number of documents
170
+ - "dimension" (int): Vector embedding dimension (0 if unknown)
171
+ - "vector_store_type" (str): Type of vector store (e.g., "chromadb", "weaviate")
172
+ - Additional store-specific metadata as available
173
+
174
+ Raises:
175
+ NotImplementedError: Must be implemented by concrete vector store classes
176
+
177
+ Example:
178
+ .. code-block:: python
179
+
180
+ info = vector_store.get_collection_info()
181
+ print(f"Collection {info['name']} has {info['document_count']} documents")
182
+ print(f"Vector dimension: {info['dimension']}")
183
+ """
184
+ pass
185
+
186
+ def get_embedding_dimension(self) -> int:
187
+ """
188
+ Get the embedding dimension of the vector store.
189
+
190
+ Returns:
191
+ Dimension of the vectors in the collection
192
+ """
193
+ info = self.get_collection_info()
194
+ return info.get("dimension", 0)
195
+
196
+ def supports_hybrid_search(self) -> bool:
197
+ """
198
+ Check if the vector store supports hybrid search.
199
+
200
+ Returns:
201
+ True if hybrid search is supported, False otherwise
202
+ """
203
+ return False
204
+
205
+ def supports_metadata_filtering(self) -> bool:
206
+ """
207
+ Check if the vector store supports metadata filtering.
208
+
209
+ Returns:
210
+ True if metadata filtering is supported, False otherwise
211
+ """
212
+ return True
@@ -0,0 +1,2 @@
1
+ # Copyright (c) 2025 Lakshya A Agrawal and the GEPA contributors
2
+ # https://github.com/gepa-ai/gepa
@@ -0,0 +1,196 @@
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 ChromaVectorStore(VectorStoreInterface):
10
+ """
11
+ ChromaDB implementation of the VectorStoreInterface.
12
+
13
+ ChromaDB is an open-source embedding database that's easy to use
14
+ and perfect for local development and prototyping.
15
+ """
16
+
17
+ def __init__(self, client, collection_name: str, embedding_function=None):
18
+ """
19
+ Initialize ChromaVectorStore.
20
+
21
+ Args:
22
+ client: ChromaDB client instance
23
+ collection_name: Name of the collection to use
24
+ embedding_function: Optional embedding function for text queries
25
+ """
26
+ import importlib.util
27
+
28
+ if importlib.util.find_spec("chromadb") is None:
29
+ raise ImportError("ChromaDB is required for ChromaVectorStore. Install with: pip install litellm chromadb")
30
+
31
+ self.client = client
32
+ self.collection_name = collection_name
33
+ self.embedding_function = embedding_function
34
+
35
+ # Get or create the collection
36
+ try:
37
+ self.collection = self.client.get_collection(name=collection_name, embedding_function=embedding_function)
38
+ except Exception:
39
+ # Collection doesn't exist, create it
40
+ self.collection = self.client.create_collection(name=collection_name, embedding_function=embedding_function)
41
+
42
+ def similarity_search(
43
+ self,
44
+ query: str,
45
+ k: int = 5,
46
+ filters: dict[str, Any] | None = None,
47
+ ) -> list[dict[str, Any]]:
48
+ """Search for documents similar to the query text."""
49
+ # Convert filters to ChromaDB format if provided
50
+ where_clause = self._convert_filters(filters) if filters else None
51
+
52
+ results = self.collection.query(
53
+ query_texts=[query], n_results=k, where=where_clause, include=["documents", "metadatas", "distances"]
54
+ )
55
+
56
+ return self._format_results(results)
57
+
58
+ def vector_search(
59
+ self,
60
+ query_vector: list[float],
61
+ k: int = 5,
62
+ filters: dict[str, Any] | None = None,
63
+ ) -> list[dict[str, Any]]:
64
+ """Search using a pre-computed query vector."""
65
+ where_clause = self._convert_filters(filters) if filters else None
66
+
67
+ results = self.collection.query(
68
+ query_embeddings=[query_vector],
69
+ n_results=k,
70
+ where=where_clause,
71
+ include=["documents", "metadatas", "distances"],
72
+ )
73
+
74
+ return self._format_results(results)
75
+
76
+ def get_collection_info(self) -> dict[str, Any]:
77
+ """Get metadata about the ChromaDB collection."""
78
+ count = self.collection.count()
79
+
80
+ # Try to get a sample document to determine embedding dimension
81
+ dimension = 0
82
+ if count > 0:
83
+ sample = self.collection.peek(limit=1)
84
+ embeddings = sample.get("embeddings")
85
+ if embeddings is not None and len(embeddings) > 0:
86
+ dimension = len(embeddings[0])
87
+
88
+ return {
89
+ "name": self.collection_name,
90
+ "document_count": count,
91
+ "dimension": dimension,
92
+ "vector_store_type": "chromadb",
93
+ }
94
+
95
+ def supports_metadata_filtering(self) -> bool:
96
+ """ChromaDB supports metadata filtering."""
97
+ return True
98
+
99
+ def _convert_filters(self, filters: dict[str, Any]) -> dict[str, Any]:
100
+ """
101
+ Convert generic filters to ChromaDB where clause format.
102
+
103
+ Generic format: {"key": "value", "key2": {"$gt": 5}}
104
+ ChromaDB format: {"key": {"$eq": "value"}, "key2": {"$gt": 5}}
105
+ """
106
+ chroma_filters = {}
107
+
108
+ for key, value in filters.items():
109
+ if isinstance(value, dict):
110
+ # Already in operator format
111
+ chroma_filters[key] = value
112
+ else:
113
+ # Convert to equality operator
114
+ chroma_filters[key] = {"$eq": value}
115
+
116
+ return chroma_filters
117
+
118
+ def _format_results(self, results) -> list[dict[str, Any]]:
119
+ """Convert ChromaDB results to standardized format."""
120
+ documents = []
121
+
122
+ if not results["documents"] or not results["documents"][0]:
123
+ return documents
124
+
125
+ docs = results["documents"][0]
126
+ metadatas = results.get("metadatas", [None] * len(docs))[0] or [{}] * len(docs)
127
+ distances = results.get("distances", [0.0] * len(docs))[0]
128
+
129
+ for doc, metadata, distance in zip(docs, metadatas, distances, strict=False):
130
+ # Convert distance to similarity score (higher is better)
131
+ # ChromaDB uses cosine distance, so similarity = 1 - distance
132
+ distance_val = self._extract_distance_value(distance)
133
+ similarity_score = max(0.0, 1.0 - distance_val)
134
+
135
+ documents.append({"content": doc, "metadata": metadata or {}, "score": similarity_score})
136
+
137
+ return documents
138
+
139
+ def _extract_distance_value(self, distance) -> float:
140
+ """
141
+ Helper to extract a scalar float from a distance value returned by ChromaDB.
142
+ Handles numpy scalars, single-element lists/arrays, and plain floats.
143
+ """
144
+ try:
145
+ if hasattr(distance, "item"): # numpy scalar
146
+ return distance.item()
147
+ elif hasattr(distance, "__len__") and not isinstance(distance, str | bytes) and len(distance) == 1:
148
+ return float(distance[0])
149
+ else:
150
+ return float(distance)
151
+ except (TypeError, ValueError, IndexError) as e:
152
+ import logging
153
+
154
+ logging.warning(f"Unexpected distance format: {type(distance)}, value: {distance}, error: {e}")
155
+ return 0.0 # Default fallback
156
+
157
+ @classmethod
158
+ def create_local(cls, persist_directory: str, collection_name: str, embedding_function=None) -> "ChromaVectorStore":
159
+ """
160
+ Create a ChromaVectorStore with local persistence.
161
+
162
+ Args:
163
+ persist_directory: Directory to persist the database
164
+ collection_name: Name of the collection
165
+ embedding_function: Optional embedding function
166
+
167
+ Returns:
168
+ ChromaVectorStore instance
169
+ """
170
+ try:
171
+ import chromadb
172
+ except ImportError as e:
173
+ raise ImportError("ChromaDB is required. Install with: pip install litellm chromadb") from e
174
+
175
+ client = chromadb.PersistentClient(path=persist_directory)
176
+ return cls(client, collection_name, embedding_function)
177
+
178
+ @classmethod
179
+ def create_memory(cls, collection_name: str, embedding_function=None) -> "ChromaVectorStore":
180
+ """
181
+ Create a ChromaVectorStore in memory (for testing).
182
+
183
+ Args:
184
+ collection_name: Name of the collection
185
+ embedding_function: Optional embedding function
186
+
187
+ Returns:
188
+ ChromaVectorStore instance
189
+ """
190
+ try:
191
+ import chromadb
192
+ except ImportError as e:
193
+ raise ImportError("ChromaDB is required. Install with: pip install litellm chromadb") from e
194
+
195
+ client = chromadb.Client()
196
+ return cls(client, collection_name, embedding_function)