adaptive-memory-multi-model-router 2.15.4 → 2.15.5

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.
Files changed (173) hide show
  1. package/.github/workflows/adapters-ci.yml +142 -0
  2. package/.github/workflows/ci.yml +2 -5
  3. package/.github/workflows/pypi-publish.yml +102 -0
  4. package/AGENT_COUNCIL_FINDINGS.md +123 -98
  5. package/CAMPAIGN_SUMMARY.md +87 -0
  6. package/CHANGELOG.md +7 -1
  7. package/ENTERPRISE_INTEGRATIONS.md +94 -0
  8. package/INTEGRATION_PROGRESS.md +96 -0
  9. package/OPPORTUNITIES_100.md +277 -0
  10. package/POPULARITY_BOOSTERS.md +1 -1
  11. package/README.md +137 -142
  12. package/README_ja.md +17 -21
  13. package/README_zh.md +13 -17
  14. package/TODO_BROWSER_AUTOMATION.md +87 -0
  15. package/a3m-integrations-summary.md +73 -0
  16. package/adapters/a3m_adapter/__init__.py +39 -3
  17. package/adapters/a3m_adapter/adapter/__init__.py +16 -1
  18. package/adapters/a3m_adapter/adapter/autogen.py +169 -0
  19. package/adapters/a3m_adapter/adapter/haystack.py +197 -0
  20. package/adapters/a3m_adapter/adapter/langgraph.py +196 -0
  21. package/adapters/a3m_adapter/adapter/pinecone.py +217 -0
  22. package/adapters/a3m_adapter/adapter/vercel.py +188 -0
  23. package/adapters/a3m_adapter/tests/test_adapters.py +29 -31
  24. package/adapters/a3m_adapter/tests/test_integration.py +80 -0
  25. package/adapters/requirements-dev.txt +6 -0
  26. package/adapters/requirements.txt +4 -0
  27. package/apps/cost-calculator/README.md +72 -0
  28. package/apps/cost-calculator/calculator.css +280 -0
  29. package/apps/cost-calculator/calculator.js +150 -0
  30. package/apps/cost-calculator/index.html +321 -0
  31. package/apps/cost-calculator/package.json +13 -0
  32. package/articles/ANNOUNCEMENT_reddit_ml.md +76 -0
  33. package/articles/ANNOUNCEMENT_vc/347/244/276/345/214/272.md +71 -0
  34. package/articles/ANNOUNCEMENT_vercel.md +85 -0
  35. package/articles/CHINESE_DIRECTORIES.md +2 -2
  36. package/articles/CHINESE_SUBMISSIONS_READY.md +5 -5
  37. package/articles/HN_POST_READY.md +16 -20
  38. package/articles/INDIEHACKERS_READY.md +19 -34
  39. package/demo.py +251 -0
  40. package/dist/analytics/costAnalytics.d.ts +0 -1
  41. package/dist/benchmark/reproducible.d.ts +0 -1
  42. package/dist/cache/semanticCache.d.ts +0 -1
  43. package/dist/cost/budgetEnforcer.d.ts +0 -1
  44. package/dist/cost/costTracker.d.ts +0 -1
  45. package/dist/ensemble/multiRoundDialog.d.ts +0 -1
  46. package/dist/ensemble/shapleyValue.d.ts +0 -1
  47. package/dist/ensemble.d.ts +0 -1
  48. package/dist/index.d.ts +0 -1
  49. package/dist/integrations/oauth.d.ts +0 -1
  50. package/dist/integrations/scienceAdapter.d.ts +0 -1
  51. package/dist/memory/autoFetch.d.ts +0 -1
  52. package/dist/memory/hybridMemory.d.ts +0 -1
  53. package/dist/memory/memoryTree.d.ts +0 -1
  54. package/dist/memory/obsidianVault.d.ts +0 -1
  55. package/dist/memory/reasoningBank.d.ts +0 -1
  56. package/dist/observability/changeWatch.d.ts +0 -1
  57. package/dist/observability/fatigueDetector.d.ts +0 -1
  58. package/dist/observability/index.d.ts +0 -1
  59. package/dist/observability/metrics.d.ts +0 -1
  60. package/dist/observability/middleware.d.ts +0 -1
  61. package/dist/observability/tracer.d.ts +0 -1
  62. package/dist/observability/types.d.ts +0 -1
  63. package/dist/providers/providerConfig.d.ts +5 -2
  64. package/dist/providers/providerConfig.js +1006 -1
  65. package/dist/providers/providerConfig.js.map +1 -1
  66. package/dist/routing/advancedRouter.d.ts +1 -2
  67. package/dist/routing/advancedRouter.js +84 -0
  68. package/dist/routing/advancedRouter.js.map +1 -1
  69. package/dist/routing/crossModelValidation.d.ts +0 -1
  70. package/dist/routing/providerHealth.d.ts +77 -2
  71. package/dist/routing/providerHealth.js +190 -4
  72. package/dist/routing/providerHealth.js.map +1 -1
  73. package/dist/routing/providerRetry.d.ts +0 -1
  74. package/dist/routing/shadowSampler.d.ts +187 -0
  75. package/dist/routing/shadowSampler.js +306 -0
  76. package/dist/routing/shadowSampler.js.map +1 -0
  77. package/dist/sdk.d.ts +0 -1
  78. package/dist/security/guardrails.d.ts +0 -1
  79. package/dist/server/dashboard.d.ts +0 -1
  80. package/dist/server/handlers/chatHandler.d.ts +0 -1
  81. package/dist/server/handlers/completionsHandler.d.ts +0 -1
  82. package/dist/server/handlers/embeddingsHandler.d.ts +0 -1
  83. package/dist/server/handlers/healthHandler.d.ts +0 -1
  84. package/dist/server/handlers/metricsHandler.d.ts +0 -1
  85. package/dist/server/handlers/modelsHandler.d.ts +0 -1
  86. package/dist/server/metrics.d.ts +0 -1
  87. package/dist/server/modelMapper.d.ts +0 -1
  88. package/dist/server/proxyServer.d.ts +0 -1
  89. package/dist/server/router.d.ts +0 -1
  90. package/dist/server/state.d.ts +0 -1
  91. package/dist/tui/dashboard.d.ts +0 -1
  92. package/dist/utils/costUtils.d.ts +0 -1
  93. package/dist/utils/sorting.d.ts +0 -1
  94. package/dist/utils/tokenUtils.d.ts +0 -1
  95. package/docker-compose.yml +84 -60
  96. package/docs/comparison.md +2 -2
  97. package/docs/llms-full.txt +89 -0
  98. package/docs/llms.txt +25 -67
  99. package/docs-site/index.html +9 -9
  100. package/integrations/browser-use/README.md +155 -0
  101. package/integrations/browser-use/examples/form-filling.ts +179 -0
  102. package/integrations/browser-use/index.ts +145 -0
  103. package/integrations/browser-use/package.json +34 -0
  104. package/integrations/browser-use/tsconfig.json +17 -0
  105. package/integrations/mcp-browser/README.md +177 -0
  106. package/integrations/mcp-browser/package.json +38 -0
  107. package/integrations/mcp-browser/src/index.ts +297 -0
  108. package/integrations/sota-browser/README.md +191 -0
  109. package/integrations/sota-browser/package.json +34 -0
  110. package/llms-full.txt +6 -6
  111. package/llms.txt +25 -67
  112. package/package.json +34 -5
  113. package/packages/a3m-vercel-ai/README.md +161 -0
  114. package/packages/a3m-vercel-ai/dist/a3m-language-model.d.ts +12 -0
  115. package/packages/a3m-vercel-ai/dist/a3m-language-model.d.ts.map +1 -0
  116. package/packages/a3m-vercel-ai/dist/a3m-language-model.js +289 -0
  117. package/packages/a3m-vercel-ai/dist/a3m-language-model.js.map +1 -0
  118. package/packages/a3m-vercel-ai/dist/index.d.ts +82 -0
  119. package/packages/a3m-vercel-ai/dist/index.d.ts.map +1 -0
  120. package/packages/a3m-vercel-ai/dist/index.js +79 -0
  121. package/packages/a3m-vercel-ai/dist/index.js.map +1 -0
  122. package/packages/a3m-vercel-ai/dist/types.d.ts +97 -0
  123. package/packages/a3m-vercel-ai/dist/types.d.ts.map +1 -0
  124. package/packages/a3m-vercel-ai/dist/types.js +5 -0
  125. package/packages/a3m-vercel-ai/dist/types.js.map +1 -0
  126. package/packages/a3m-vercel-ai/package-lock.json +969 -0
  127. package/packages/a3m-vercel-ai/package.json +46 -0
  128. package/packages/a3m-vercel-ai/src/a3m-language-model.ts +381 -0
  129. package/packages/a3m-vercel-ai/src/index.ts +104 -0
  130. package/packages/a3m-vercel-ai/src/types.ts +116 -0
  131. package/packages/a3m-vercel-ai/tsconfig.json +20 -0
  132. package/packages/agentkit-adapter/LICENSE +21 -0
  133. package/packages/agentkit-adapter/README.md +126 -0
  134. package/packages/agentkit-adapter/examples/agentkit-example.ts +139 -0
  135. package/packages/agentkit-adapter/package.json +57 -0
  136. package/packages/agentkit-adapter/src/adapter.ts +381 -0
  137. package/packages/agentkit-adapter/src/index.ts +36 -0
  138. package/packages/agentkit-adapter/src/types.ts +105 -0
  139. package/packages/agentkit-adapter/src/util.ts +13 -0
  140. package/packages/agentkit-adapter/tsconfig.json +22 -0
  141. package/prometheus.yml +8 -0
  142. package/python/README.md +92 -65
  143. package/python/a3m/__init__.py +32 -3
  144. package/python/a3m/adapters/__init__.py +21 -0
  145. package/python/a3m/adapters/langchain.py +190 -0
  146. package/python/a3m/adapters/llamaindex.py +249 -0
  147. package/python/a3m/adapters/qdrant.py +240 -0
  148. package/python/a3m/adapters/weaviate.py +263 -0
  149. package/python/a3m/client.py +5 -0
  150. package/python/a3m_router.egg-info/PKG-INFO +172 -0
  151. package/python/a3m_router.egg-info/SOURCES.txt +17 -0
  152. package/python/a3m_router.egg-info/dependency_links.txt +1 -0
  153. package/python/a3m_router.egg-info/requires.txt +24 -0
  154. package/python/a3m_router.egg-info/top_level.txt +1 -0
  155. package/python/build_verify.sh +32 -0
  156. package/python/dist/a3m_router-2.2.1-py3-none-any.whl +0 -0
  157. package/python/dist/a3m_router-2.2.1.tar.gz +0 -0
  158. package/python/dist/a3m_router-2.2.2-py3-none-any.whl +0 -0
  159. package/python/dist/a3m_router-2.2.2.tar.gz +0 -0
  160. package/python/mcp-server/README.md +172 -0
  161. package/python/mcp-server/a3m_mcp/__init__.py +15 -0
  162. package/python/mcp-server/a3m_mcp/__main__.py +15 -0
  163. package/python/mcp-server/a3m_mcp/server.py +339 -0
  164. package/python/mcp-server/pyproject.toml +24 -0
  165. package/python/pyproject.toml +56 -5
  166. package/python/setup.py +3 -28
  167. package/src/providers/providerConfig.ts +1053 -1
  168. package/summary.txt +38 -0
  169. package/tsconfig.build.json +1 -2
  170. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -94
  171. package/.github/ISSUE_TEMPLATE/config.yml +0 -17
  172. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -71
  173. package/.well-known/ai-plugin.json +0 -16
@@ -0,0 +1,249 @@
1
+ """
2
+ A3M Router LlamaIndex adapter.
3
+
4
+ Drop-in LLM for LlamaIndex that routes through A3M Router.
5
+
6
+ Usage:
7
+ from llama_index import VectorStoreIndex, SimpleWebPageReader
8
+ from a3m.adapters import LlamaIndexAdapter
9
+
10
+ llm = LlamaIndexAdapter(
11
+ base_url="http://localhost:8787",
12
+ model="auto",
13
+ temperature=0.7,
14
+ )
15
+
16
+ index = VectorStoreIndex.from_documents(
17
+ documents,
18
+ llm=llm, # Use A3M Router as the LLM
19
+ )
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import logging
25
+ from typing import Any, Awaitable, List, Optional
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+ LLAMAINDEX_AVAILABLE = False
30
+ try:
31
+ from llama_index.llms import BaseLLM
32
+ from llama_index.llms.custom import CustomLLM
33
+ from llama_index.types import ModelType
34
+ from llama_index.output_parsers.base import BaseOutputParser
35
+ LLAMAINDEX_AVAILABLE = True
36
+ except ImportError:
37
+ logger.warning("LlamaIndex not installed. pip install llama-index")
38
+
39
+ from a3m.client import A3MRouter, A3MRouterError
40
+
41
+ if LLAMAINDEX_AVAILABLE:
42
+ class LlamaIndexAdapter(BaseLLM):
43
+ """
44
+ LlamaIndex LLM that routes through A3M Router.
45
+
46
+ Drop-in replacement for OpenAI/GPT LLMs in LlamaIndex pipelines.
47
+
48
+ Args:
49
+ base_url: A3M Router server URL.
50
+ model: Model name or "auto" for intelligent routing.
51
+ temperature: Sampling temperature.
52
+ max_tokens: Max tokens to generate.
53
+ parallel_ensemble: Number of providers for ensemble calls.
54
+ """
55
+
56
+ base_url: str = "http://localhost:8787"
57
+ model: str = "auto"
58
+ temperature: float = 0.7
59
+ max_tokens: Optional[int] = 4096
60
+ parallel_ensemble: int = 1
61
+ api_key: Optional[str] = None
62
+
63
+ def __init__(self, **kwargs: Any) -> None:
64
+ super().__init__(**kwargs)
65
+ self._router: Optional[A3MRouter] = None
66
+
67
+ def _get_router(self) -> A3MRouter:
68
+ """Get or create A3M Router client."""
69
+ if self._router is None:
70
+ self._router = A3MRouter(
71
+ base_url=self.base_url,
72
+ api_key=self.api_key,
73
+ default_model=self.model,
74
+ default_temperature=self.temperature,
75
+ default_max_tokens=self.max_tokens,
76
+ parallel_ensemble=self.parallel_ensemble,
77
+ )
78
+ return self._router
79
+
80
+ @property
81
+ def model_type(self) -> ModelType:
82
+ return ModelType.LLM
83
+
84
+ @property
85
+ def class_name(self) -> str:
86
+ return "A3MRouter"
87
+
88
+ def complete(
89
+ self,
90
+ prompt: str,
91
+ formatted: bool = False,
92
+ **kwargs: Any,
93
+ ) -> Any:
94
+ """
95
+ Synchronous completion.
96
+
97
+ LlamaIndex calls this for text completion.
98
+ """
99
+ router = self._get_router()
100
+
101
+ try:
102
+ response = router.chat(
103
+ messages=[{"role": "user", "content": prompt}],
104
+ model=self.model,
105
+ temperature=self.temperature,
106
+ max_tokens=self.max_tokens,
107
+ parallel_ensemble=self.parallel_ensemble,
108
+ **kwargs,
109
+ )
110
+ return response.content
111
+ except A3MRouterError as e:
112
+ logger.error(f"A3M Router error: {e}")
113
+ raise
114
+
115
+ async def acomplete(
116
+ self,
117
+ prompt: str,
118
+ formatted: bool = False,
119
+ **kwargs: Any,
120
+ ) -> Any:
121
+ """Async completion."""
122
+ router = self._get_router()
123
+
124
+ try:
125
+ response = await router.achat(
126
+ messages=[{"role": "user", "content": prompt}],
127
+ model=self.model,
128
+ temperature=self.temperature,
129
+ max_tokens=self.max_tokens,
130
+ parallel_ensemble=self.parallel_ensemble,
131
+ **kwargs,
132
+ )
133
+ return response.content
134
+ except A3MRouterError as e:
135
+ logger.error(f"A3M Router error: {e}")
136
+ raise
137
+
138
+ def stream_complete(
139
+ self,
140
+ prompt: str,
141
+ **kwargs: Any,
142
+ ) -> Any:
143
+ """
144
+ Streaming completion.
145
+
146
+ Returns a generator that yields response chunks.
147
+ """
148
+ router = self._get_router()
149
+
150
+ try:
151
+ chunks = router.stream_chat(
152
+ messages=[{"role": "user", "content": prompt}],
153
+ model=self.model,
154
+ temperature=self.temperature,
155
+ max_tokens=self.max_tokens,
156
+ **kwargs,
157
+ )
158
+ for chunk in chunks:
159
+ yield chunk.content
160
+ except A3MRouterError as e:
161
+ logger.error(f"A3M Router error: {e}")
162
+ raise
163
+
164
+ async def astream_complete(
165
+ self,
166
+ prompt: str,
167
+ **kwargs: Any,
168
+ ) -> Awaitable[Any]:
169
+ """Async streaming completion."""
170
+ router = self._get_router()
171
+
172
+ async def gen():
173
+ async for chunk in router.astream_chat(
174
+ messages=[{"role": "user", "content": prompt}],
175
+ model=self.model,
176
+ temperature=self.temperature,
177
+ max_tokens=self.max_tokens,
178
+ **kwargs,
179
+ ):
180
+ yield chunk.content
181
+
182
+ return gen()
183
+
184
+ def chat(self, messages: List[Any], **kwargs: Any) -> Any:
185
+ """
186
+ Synchronous chat.
187
+
188
+ Converts messages to a single prompt.
189
+ """
190
+ router = self._get_router()
191
+
192
+ # Convert message objects to content strings
193
+ if hasattr(messages[0], "content"):
194
+ content = "\n".join([getattr(m, "content", str(m)) for m in messages])
195
+ else:
196
+ content = str(messages[0])
197
+
198
+ try:
199
+ response = router.chat(
200
+ messages=[{"role": "user", "content": content}],
201
+ model=self.model,
202
+ temperature=self.temperature,
203
+ max_tokens=self.max_tokens,
204
+ **kwargs,
205
+ )
206
+ # Return a simple object that LlamaIndex expects
207
+ return ChatMessage(content=response.content)
208
+ except A3MRouterError as e:
209
+ logger.error(f"A3M Router error: {e}")
210
+ raise
211
+
212
+ async def achat(self, messages: List[Any], **kwargs: Any) -> Any:
213
+ """Async chat."""
214
+ router = self._get_router()
215
+
216
+ if hasattr(messages[0], "content"):
217
+ content = "\n".join([getattr(m, "content", str(m)) for m in messages])
218
+ else:
219
+ content = str(messages[0])
220
+
221
+ try:
222
+ response = await router.achat(
223
+ messages=[{"role": "user", "content": content}],
224
+ model=self.model,
225
+ temperature=self.temperature,
226
+ max_tokens=self.max_tokens,
227
+ **kwargs,
228
+ )
229
+ return ChatMessage(content=response.content)
230
+ except A3MRouterError as e:
231
+ logger.error(f"A3M Router error: {e}")
232
+ raise
233
+
234
+ else:
235
+ # Stub class when LlamaIndex is not installed
236
+ class LlamaIndexAdapter:
237
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
238
+ raise ImportError(
239
+ "LlamaIndex is not installed. "
240
+ "Install with: pip install llama-index"
241
+ )
242
+
243
+
244
+ # Simple message class for LlamaIndex compatibility
245
+ class ChatMessage:
246
+ """Simple chat message for LlamaIndex compatibility."""
247
+ def __init__(self, content: str) -> None:
248
+ self.content = content
249
+ self.raw = {}
@@ -0,0 +1,240 @@
1
+ """
2
+ A3M Router Qdrant adapter.
3
+
4
+ Enables A3M Router as the embedding + reranking layer for Qdrant vector search.
5
+
6
+ Usage:
7
+ from qdrant_client import QdrantClient
8
+ from a3m.adapters import QdrantAdapter
9
+
10
+ # Create A3M-powered Qdrant client
11
+ client = QdrantAdapter(
12
+ base_url="http://localhost:8787",
13
+ collection_name="my_docs",
14
+ )
15
+
16
+ # Generate embeddings via A3M
17
+ query_embedding = client.embed_query("What is machine learning?")
18
+
19
+ # Search Qdrant
20
+ results = client.search(query_vector=query_embedding, limit=5)
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import logging
26
+ from typing import Any, Dict, List, Optional, Union
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+ QDRANT_AVAILABLE = False
31
+ try:
32
+ from qdrant_client import QdrantClient
33
+ from qdrant_client.http.exceptions import UnexpectedResponse
34
+ QDRANT_AVAILABLE = True
35
+ except ImportError:
36
+ logger.warning("Qdrant client not installed. pip install qdrant-client")
37
+
38
+ from a3m.client import A3MRouter, A3MRouterError
39
+
40
+ if QDRANT_AVAILABLE:
41
+ class QdrantAdapter(QdrantClient):
42
+ """
43
+ Qdrant client wrapper that uses A3M Router for embeddings.
44
+
45
+ Provides intelligent embedding generation and can be used as a
46
+ drop-in replacement for QdrantClient in most use cases.
47
+
48
+ Args:
49
+ base_url: A3M Router server URL (for embeddings).
50
+ collection_name: Default collection to search.
51
+ embed_model: Embedding model ("auto" for A3M selection).
52
+ host: Qdrant server host.
53
+ port: Qdrant server port.
54
+ **kwargs: Additional QdrantClient options.
55
+ """
56
+
57
+ def __init__(
58
+ self,
59
+ base_url: str = "http://localhost:8787",
60
+ collection_name: Optional[str] = None,
61
+ embed_model: str = "auto",
62
+ host: Optional[str] = None,
63
+ port: Optional[int] = None,
64
+ **kwargs: Any,
65
+ ) -> None:
66
+ # Initialize QdrantClient
67
+ if host and port:
68
+ super().__init__(host=host, port=port, **kwargs)
69
+ else:
70
+ super().__init__(**kwargs)
71
+
72
+ self._a3m_router = A3MRouter(base_url=base_url, default_model=embed_model)
73
+ self._collection_name = collection_name
74
+ self._embed_model = embed_model
75
+
76
+ def embed_query(
77
+ self,
78
+ query: str,
79
+ **kwargs: Any,
80
+ ) -> List[float]:
81
+ """
82
+ Generate embedding for a query string.
83
+
84
+ Args:
85
+ query: Text to embed.
86
+
87
+ Returns:
88
+ Embedding vector (list of floats).
89
+ """
90
+ try:
91
+ response = self._a3m_router.embed(texts=[query], model=self._embed_model)
92
+ return response.embedding
93
+ except A3MRouterError as e:
94
+ logger.error(f"A3M Router embedding error: {e}")
95
+ raise
96
+
97
+ def embedTexts(
98
+ self,
99
+ texts: List[str],
100
+ **kwargs: Any,
101
+ ) -> List[List[float]]:
102
+ """
103
+ Generate embeddings for multiple texts.
104
+
105
+ Args:
106
+ texts: List of texts to embed.
107
+
108
+ Returns:
109
+ List of embedding vectors.
110
+ """
111
+ try:
112
+ response = self._a3m_router.embed(texts=texts, model=self._embed_model)
113
+ if isinstance(response, list):
114
+ return [r.embedding for r in response]
115
+ return [response.embedding]
116
+ except A3MRouterError as e:
117
+ logger.error(f"A3M Router embedding error: {e}")
118
+ raise
119
+
120
+ def search(
121
+ self,
122
+ collection_name: Optional[str] = None,
123
+ query_vector: Optional[List[float]] = None,
124
+ query_filter: Optional[Any] = None,
125
+ limit: int = 10,
126
+ offset: Optional[int] = None,
127
+ with_vectors: bool = False,
128
+ with_payload: bool = True,
129
+ **kwargs: Any,
130
+ ) -> List[Any]:
131
+ """
132
+ Search Qdrant with A3M-generated query embedding.
133
+
134
+ Args:
135
+ collection_name: Collection to search. Uses default if None.
136
+ query_vector: Query embedding. If None, uses A3M to embed a query.
137
+ query_filter: Qdrant filter condition.
138
+ limit: Number of results to return.
139
+ offset: Pagination offset.
140
+ with_vectors: Include vectors in results.
141
+ with_payload: Include payload in results.
142
+
143
+ Returns:
144
+ List of search results.
145
+ """
146
+ collection = collection_name or self._collection_name
147
+ if not collection:
148
+ raise ValueError("collection_name is required")
149
+
150
+ if query_vector is None:
151
+ raise ValueError("query_vector is required")
152
+
153
+ return super().search(
154
+ collection_name=collection,
155
+ query_vector=query_vector,
156
+ query_filter=query_filter,
157
+ limit=limit,
158
+ offset=offset,
159
+ with_vectors=with_vectors,
160
+ with_payload=with_payload,
161
+ **kwargs,
162
+ )
163
+
164
+ def rag_search(
165
+ self,
166
+ query: str,
167
+ collection_name: Optional[str] = None,
168
+ limit: int = 5,
169
+ rerank: bool = True,
170
+ **kwargs: Any,
171
+ ) -> Dict[str, Any]:
172
+ """
173
+ Perform RAG search: embed query + Qdrant search + context build.
174
+
175
+ Args:
176
+ query: User query string.
177
+ collection_name: Collection to search.
178
+ limit: Number of chunks to retrieve.
179
+ rerank: Whether to rerank results using A3M.
180
+
181
+ Returns:
182
+ Dict with 'chunks', 'context', 'answer', 'provider'.
183
+ """
184
+ collection = collection_name or self._collection_name
185
+ if not collection:
186
+ raise ValueError("collection_name is required")
187
+
188
+ # 1. Embed query via A3M
189
+ query_embedding = self.embed_query(query)
190
+
191
+ # 2. Search Qdrant
192
+ results = self.search(
193
+ collection_name=collection,
194
+ query_vector=query_embedding,
195
+ limit=limit,
196
+ with_payload=True,
197
+ )
198
+
199
+ # 3. Build context from results
200
+ chunks = []
201
+ for r in results:
202
+ payload = r.payload or {}
203
+ text = payload.get("text", payload.get("content", str(r.id)))
204
+ chunks.append({"id": r.id, "text": text, "score": r.score})
205
+
206
+ context = "\n\n".join([c["text"] for c in chunks])
207
+
208
+ # 4. Generate answer via A3M if reranking
209
+ answer = None
210
+ provider = None
211
+ if rerank and chunks:
212
+ try:
213
+ response = self._a3m_router.chat(
214
+ messages=[{
215
+ "role": "user",
216
+ "content": f"Context:\n{context}\n\nQuestion: {query}\n\nAnswer concisely:"
217
+ }],
218
+ model=self._embed_model,
219
+ )
220
+ answer = response.content
221
+ provider = response.provider
222
+ except A3MRouterError:
223
+ answer = chunks[0]["text"] if chunks else ""
224
+ provider = "qdrant"
225
+
226
+ return {
227
+ "chunks": chunks,
228
+ "context": context,
229
+ "answer": answer,
230
+ "provider": provider,
231
+ "query_embedding": query_embedding[:10], # First 10 dims for debug
232
+ }
233
+
234
+ else:
235
+ class QdrantAdapter:
236
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
237
+ raise ImportError(
238
+ "Qdrant client not installed. "
239
+ "Install with: pip install qdrant-client"
240
+ )