adaptive-memory-multi-model-router 2.16.0 → 2.16.2
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.
- package/.github/workflows/adapters-ci.yml +142 -0
- package/.github/workflows/auto-submit-sitemap.yml +41 -0
- package/.github/workflows/ci.yml +2 -5
- package/.github/workflows/mcp-pypi-publish.yml +34 -0
- package/.github/workflows/pypi-publish.yml +146 -0
- package/.github/workflows/tmlpd-publish.yml +23 -0
- package/README.md +245 -148
- package/RELEASE_v2.16.0.md +149 -0
- package/TECHNICAL_README.md +253 -0
- package/adapters/README.md +36 -0
- package/adapters/__init__.py +25 -0
- package/adapters/a3m_adapter/__init__.py +51 -0
- package/adapters/a3m_adapter/adapter/__init__.py +22 -0
- package/adapters/a3m_adapter/adapter/autogen.py +169 -0
- package/adapters/a3m_adapter/adapter/config.py +100 -0
- package/adapters/a3m_adapter/adapter/haystack.py +197 -0
- package/adapters/a3m_adapter/adapter/langchain.py +155 -0
- package/adapters/a3m_adapter/adapter/langgraph.py +196 -0
- package/adapters/a3m_adapter/adapter/llamaindex.py +162 -0
- package/adapters/a3m_adapter/adapter/pinecone.py +217 -0
- package/adapters/a3m_adapter/adapter/vercel.py +188 -0
- package/adapters/a3m_adapter/tests/__init__.py +1 -0
- package/adapters/a3m_adapter/tests/test_adapters.py +118 -0
- package/adapters/a3m_adapter/tests/test_integration.py +80 -0
- package/adapters/requirements-dev.txt +6 -0
- package/adapters/requirements.txt +4 -0
- package/adapters/setup.py +23 -0
- package/demo.py +251 -0
- package/discoverability-diagnosis.md +280 -0
- package/dist/analytics/costAnalytics.d.ts.map +1 -1
- package/dist/benchmark/reproducible.d.ts.map +1 -1
- package/dist/cache/semanticCache.d.ts.map +1 -1
- package/dist/cli/setupWizard.d.ts +257 -50
- package/dist/cli/setupWizard.d.ts.map +1 -1
- package/dist/cli/setupWizard.js +419 -109
- package/dist/cli/setupWizard.js.map +1 -1
- package/dist/cli/tui.d.ts +6 -0
- package/dist/cli/tui.js +96 -67
- package/dist/cli/tui.js.map +1 -0
- package/dist/cli.js +9 -0
- package/dist/cost/budgetEnforcer.d.ts.map +1 -1
- package/dist/cost/costTracker.d.ts.map +1 -1
- package/dist/ensemble/multiRoundDialog.d.ts.map +1 -1
- package/dist/ensemble/shapleyValue.d.ts.map +1 -1
- package/dist/ensemble.d.ts +1 -1
- package/dist/ensemble.js +141 -0
- package/dist/integrations/langchainAdapter.d.ts.map +1 -1
- package/dist/integrations/langchainAdapter.js +3 -3
- package/dist/integrations/langchainAdapter.js.map +1 -1
- package/dist/integrations/oauth.d.ts.map +1 -1
- package/dist/integrations/scienceAdapter.d.ts.map +1 -1
- package/dist/memory/autoFetch.d.ts.map +1 -1
- package/dist/memory/hybridMemory.d.ts.map +1 -1
- package/dist/memory/memoryTree.d.ts.map +1 -1
- package/dist/memory/obsidianVault.d.ts.map +1 -1
- package/dist/memory/reasoningBank.d.ts.map +1 -1
- package/dist/observability/metrics.d.ts.map +1 -1
- package/dist/observability/tracer.d.ts.map +1 -1
- package/dist/providers/providerConfig.d.ts.map +1 -1
- package/dist/providers/providerConfig.js +32 -17
- package/dist/providers/providerConfig.js.map +1 -1
- package/dist/routing/advancedRouter.d.ts.map +1 -1
- package/dist/routing/advancedRouter.js +106 -14
- package/dist/routing/advancedRouter.js.map +1 -1
- package/dist/routing/providerHealth.d.ts.map +1 -1
- package/dist/routing/providerRetry.d.ts.map +1 -1
- package/dist/routing/shadowSampler.d.ts.map +1 -0
- package/dist/routing/shadowSampler.js.map +1 -1
- package/dist/security/guardrails.d.ts.map +1 -1
- package/dist/server/handlers/chatHandler.d.ts.map +1 -1
- package/dist/server/handlers/completionsHandler.d.ts.map +1 -1
- package/dist/server/handlers/embeddingsHandler.d.ts.map +1 -1
- package/dist/server/handlers/healthHandler.d.ts.map +1 -1
- package/dist/server/handlers/metricsHandler.d.ts.map +1 -1
- package/dist/server/handlers/modelsHandler.d.ts.map +1 -1
- package/dist/server/metrics.d.ts.map +1 -1
- package/dist/server/proxyServer.d.ts.map +1 -1
- package/dist/server/router.d.ts.map +1 -1
- package/dist/server/state.d.ts.map +1 -1
- package/dist/skills/__tests__/skill_manager.test.js +5 -265
- package/dist/skills/__tests__/skill_manager.test.js.map +1 -1
- package/dist/utils/tokenUtils.d.ts.map +1 -1
- package/docker-compose.yml +84 -60
- package/docs/ARTICLE_Biology_Inspired_Routing.md +208 -0
- package/docs/ARTICLE_Master.md +78 -0
- package/docs/ARTICLE_Master_CN.md +78 -0
- package/docs/ARTICLE_OpenRouter_Stripe.md +140 -0
- package/docs/DEVPTO_ARTICLE.md +84 -0
- package/docs/HUMAN_STYLE_GUIDE.md +75 -0
- package/docs/IMPRINT_PLAN.md +88 -0
- package/docs/OPENROUTER_ALTERNATIVE.md +184 -0
- package/docs/SOCIAL_CAMPAIGN.md +316 -0
- package/docs/anthropic.html +45 -0
- package/docs/best-llm-routers-2025.html +157 -0
- package/docs/cerebras.html +43 -0
- package/docs/cli-cheatsheet.md +286 -212
- package/docs/comparison.md +2 -2
- package/docs/deepseek.html +44 -0
- package/docs/google.html +47 -0
- package/docs/groq.html +44 -0
- package/docs/llms-full.txt +360 -138
- package/docs/llms.txt +70 -71
- package/docs/mistral.html +43 -0
- package/docs/ollama.html +50 -0
- package/docs/openai.html +57 -0
- package/docs/sitemap.xml +69 -57
- package/docs-site/blog/best-llm-routers-2025.html +157 -0
- package/docs-site/index.html +68 -9
- package/docs-site/providers/anthropic.html +45 -0
- package/docs-site/providers/cerebras.html +43 -0
- package/docs-site/providers/deepseek.html +44 -0
- package/docs-site/providers/google.html +47 -0
- package/docs-site/providers/groq.html +44 -0
- package/docs-site/providers/index.html +41 -0
- package/docs-site/providers/mistral.html +43 -0
- package/docs-site/providers/ollama.html +50 -0
- package/docs-site/providers/openai.html +57 -0
- package/docs-site/sitemap.xml +69 -0
- package/llms.txt +70 -62
- package/package.json +44 -182
- package/packages/agentkit-adapter/LICENSE +21 -0
- package/packages/agentkit-adapter/README.md +126 -0
- package/packages/agentkit-adapter/examples/agentkit-example.ts +139 -0
- package/packages/agentkit-adapter/package.json +57 -0
- package/packages/agentkit-adapter/src/adapter.ts +381 -0
- package/packages/agentkit-adapter/src/index.ts +36 -0
- package/packages/agentkit-adapter/src/types.ts +105 -0
- package/packages/agentkit-adapter/src/util.ts +13 -0
- package/packages/agentkit-adapter/tsconfig.json +22 -0
- package/prometheus.yml +8 -0
- package/python/README.md +35 -81
- package/python/a3m/__init__.py +32 -3
- package/python/a3m/adapters/__init__.py +21 -0
- package/python/a3m/adapters/langchain.py +190 -0
- package/python/a3m/adapters/llamaindex.py +249 -0
- package/python/a3m/adapters/qdrant.py +240 -0
- package/python/a3m/adapters/weaviate.py +263 -0
- package/python/a3m/client.py +5 -0
- package/python/build_verify.sh +32 -0
- package/python/mcp-server/README.md +172 -0
- package/python/mcp-server/a3m_mcp/__init__.py +25 -0
- package/python/mcp-server/a3m_mcp/__main__.py +15 -0
- package/python/mcp-server/a3m_mcp/server.py +205 -0
- package/python/mcp-server/pyproject.toml +24 -0
- package/python/pyproject.toml +60 -6
- package/python/setup.py +3 -28
- package/scripts/submit-sitemap.sh +52 -0
- package/src/__types__/registry.d.ts +14 -0
- package/src/cli/setupWizard.ts +443 -112
- package/src/cli/tui.ts +159 -0
- package/src/ensemble.ts +154 -1
- package/src/integrations/langchainAdapter.ts +2 -2
- package/src/providers/providerConfig.ts +32 -17
- package/src/providers/registry.js +27 -0
- package/src/routing/advancedRouter.ts +99 -14
- package/src/routing/shadowSampler.ts +1 -1
- package/test-install/package.json +12 -0
- package/tests/tsconfig.json +0 -1
- package/tmlpd-pi-extension/README.md +105 -44
- package/tmlpd-pi-extension/docs/demo.svg +33 -0
- package/tmlpd-pi-extension/package.json +35 -106
- package/tmlpd-pi-extension/src/tokenOptimization/contextStratifier.ts +163 -0
- package/tmlpd-pi-extension/src/tokenOptimization/fetchOnceLocal.ts +136 -0
- package/tmlpd-pi-extension/src/tokenOptimization/index.ts +197 -0
- package/tmlpd-pi-extension/src/tokenOptimization/interAgentCompression.ts +157 -0
- package/tmlpd-pi-extension/src/tokenOptimization/schemaContract.ts +101 -0
- package/tmlpd-pi-extension/src/tokenOptimization/semanticCache.ts +248 -0
- package/tmlpd-pi-extension/src/tokenOptimization/tokenAwareFallback.ts +192 -0
- package/tmlpd-pi-extension/test/verify.js +21 -0
- package/tsconfig.build.json +3 -2
- package/packages/a3m-vercel-ai/dist/a3m-language-model.d.ts +0 -12
- package/packages/a3m-vercel-ai/dist/a3m-language-model.d.ts.map +0 -1
- package/packages/a3m-vercel-ai/dist/a3m-language-model.js +0 -289
- package/packages/a3m-vercel-ai/dist/a3m-language-model.js.map +0 -1
- package/packages/a3m-vercel-ai/dist/index.d.ts +0 -82
- package/packages/a3m-vercel-ai/dist/index.d.ts.map +0 -1
- package/packages/a3m-vercel-ai/dist/index.js +0 -79
- package/packages/a3m-vercel-ai/dist/index.js.map +0 -1
- package/packages/a3m-vercel-ai/dist/types.d.ts +0 -97
- package/packages/a3m-vercel-ai/dist/types.d.ts.map +0 -1
- package/packages/a3m-vercel-ai/dist/types.js +0 -5
- package/packages/a3m-vercel-ai/dist/types.js.map +0 -1
- package/src/skills/__tests__/skill_manager.test.ts +0 -328
- package/tmlpd-pi-extension/dist/cache/prefixCache.d.ts +0 -114
- package/tmlpd-pi-extension/dist/cache/prefixCache.d.ts.map +0 -1
- package/tmlpd-pi-extension/dist/cache/prefixCache.js +0 -285
- package/tmlpd-pi-extension/dist/cache/prefixCache.js.map +0 -1
- package/tmlpd-pi-extension/dist/cache/responseCache.d.ts +0 -58
- package/tmlpd-pi-extension/dist/cache/responseCache.d.ts.map +0 -1
- package/tmlpd-pi-extension/dist/cache/responseCache.js +0 -153
- package/tmlpd-pi-extension/dist/cache/responseCache.js.map +0 -1
- package/tmlpd-pi-extension/dist/cli.js +0 -59
- package/tmlpd-pi-extension/dist/cost/costTracker.d.ts +0 -95
- package/tmlpd-pi-extension/dist/cost/costTracker.d.ts.map +0 -1
- package/tmlpd-pi-extension/dist/cost/costTracker.js +0 -240
- package/tmlpd-pi-extension/dist/cost/costTracker.js.map +0 -1
- package/tmlpd-pi-extension/dist/index.d.ts +0 -723
- package/tmlpd-pi-extension/dist/index.d.ts.map +0 -1
- package/tmlpd-pi-extension/dist/index.js +0 -239
- package/tmlpd-pi-extension/dist/index.js.map +0 -1
- package/tmlpd-pi-extension/dist/memory/episodicMemory.d.ts +0 -82
- package/tmlpd-pi-extension/dist/memory/episodicMemory.d.ts.map +0 -1
- package/tmlpd-pi-extension/dist/memory/episodicMemory.js +0 -145
- package/tmlpd-pi-extension/dist/memory/episodicMemory.js.map +0 -1
- package/tmlpd-pi-extension/dist/orchestration/haloOrchestrator.d.ts +0 -102
- package/tmlpd-pi-extension/dist/orchestration/haloOrchestrator.d.ts.map +0 -1
- package/tmlpd-pi-extension/dist/orchestration/haloOrchestrator.js +0 -207
- package/tmlpd-pi-extension/dist/orchestration/haloOrchestrator.js.map +0 -1
- package/tmlpd-pi-extension/dist/orchestration/mctsWorkflow.d.ts +0 -85
- package/tmlpd-pi-extension/dist/orchestration/mctsWorkflow.d.ts.map +0 -1
- package/tmlpd-pi-extension/dist/orchestration/mctsWorkflow.js +0 -210
- package/tmlpd-pi-extension/dist/orchestration/mctsWorkflow.js.map +0 -1
- package/tmlpd-pi-extension/dist/providers/localProvider.d.ts +0 -102
- package/tmlpd-pi-extension/dist/providers/localProvider.d.ts.map +0 -1
- package/tmlpd-pi-extension/dist/providers/localProvider.js +0 -338
- package/tmlpd-pi-extension/dist/providers/localProvider.js.map +0 -1
- package/tmlpd-pi-extension/dist/providers/registry.d.ts +0 -55
- package/tmlpd-pi-extension/dist/providers/registry.d.ts.map +0 -1
- package/tmlpd-pi-extension/dist/providers/registry.js +0 -138
- package/tmlpd-pi-extension/dist/providers/registry.js.map +0 -1
- package/tmlpd-pi-extension/dist/routing/advancedRouter.d.ts +0 -68
- package/tmlpd-pi-extension/dist/routing/advancedRouter.d.ts.map +0 -1
- package/tmlpd-pi-extension/dist/routing/advancedRouter.js +0 -332
- package/tmlpd-pi-extension/dist/routing/advancedRouter.js.map +0 -1
- package/tmlpd-pi-extension/dist/tools/tmlpdTools.d.ts +0 -101
- package/tmlpd-pi-extension/dist/tools/tmlpdTools.d.ts.map +0 -1
- package/tmlpd-pi-extension/dist/tools/tmlpdTools.js +0 -368
- package/tmlpd-pi-extension/dist/tools/tmlpdTools.js.map +0 -1
- package/tmlpd-pi-extension/dist/utils/batchProcessor.d.ts +0 -96
- package/tmlpd-pi-extension/dist/utils/batchProcessor.d.ts.map +0 -1
- package/tmlpd-pi-extension/dist/utils/batchProcessor.js +0 -170
- package/tmlpd-pi-extension/dist/utils/batchProcessor.js.map +0 -1
- package/tmlpd-pi-extension/dist/utils/compression.d.ts +0 -61
- package/tmlpd-pi-extension/dist/utils/compression.d.ts.map +0 -1
- package/tmlpd-pi-extension/dist/utils/compression.js +0 -281
- package/tmlpd-pi-extension/dist/utils/compression.js.map +0 -1
- package/tmlpd-pi-extension/dist/utils/reliability.d.ts +0 -74
- package/tmlpd-pi-extension/dist/utils/reliability.d.ts.map +0 -1
- package/tmlpd-pi-extension/dist/utils/reliability.js +0 -177
- package/tmlpd-pi-extension/dist/utils/reliability.js.map +0 -1
- package/tmlpd-pi-extension/dist/utils/speculativeDecoding.d.ts +0 -117
- package/tmlpd-pi-extension/dist/utils/speculativeDecoding.d.ts.map +0 -1
- package/tmlpd-pi-extension/dist/utils/speculativeDecoding.js +0 -246
- package/tmlpd-pi-extension/dist/utils/speculativeDecoding.js.map +0 -1
- package/tmlpd-pi-extension/dist/utils/tokenUtils.d.ts +0 -50
- package/tmlpd-pi-extension/dist/utils/tokenUtils.d.ts.map +0 -1
- package/tmlpd-pi-extension/dist/utils/tokenUtils.js +0 -124
- package/tmlpd-pi-extension/dist/utils/tokenUtils.js.map +0 -1
|
@@ -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
|
+
)
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A3M Router Weaviate adapter.
|
|
3
|
+
|
|
4
|
+
Enables A3M Router as the embedding layer for Weaviate vector search.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
import weaviate
|
|
8
|
+
from a3m.adapters import WeaviateAdapter
|
|
9
|
+
|
|
10
|
+
# Create A3M-powered Weaviate client
|
|
11
|
+
client = WeaviateAdapter(
|
|
12
|
+
a3m_base_url="http://localhost:8787",
|
|
13
|
+
weaviate_url="http://localhost:8080",
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
# Generate embeddings via A3M
|
|
17
|
+
query_embedding = client.embed_query("What is AI?")
|
|
18
|
+
|
|
19
|
+
# Search Weaviate
|
|
20
|
+
results = client.query.get("Article", ["title", "content"]).with_near_vector({
|
|
21
|
+
"vector": query_embedding
|
|
22
|
+
}).with_limit(5).do()
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import logging
|
|
28
|
+
from typing import Any, Dict, List, Optional
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
WEAVIATE_AVAILABLE = False
|
|
33
|
+
try:
|
|
34
|
+
import weaviate
|
|
35
|
+
from weaviate import Client as WeaviateClient
|
|
36
|
+
from weaviate.types import NUMBERS
|
|
37
|
+
WEAVIATE_AVAILABLE = True
|
|
38
|
+
except ImportError:
|
|
39
|
+
logger.warning("Weaviate client not installed. pip install weaviate-client")
|
|
40
|
+
|
|
41
|
+
from a3m.client import A3MRouter, A3MRouterError
|
|
42
|
+
|
|
43
|
+
if WEAVIATE_AVAILABLE:
|
|
44
|
+
class WeaviateAdapter(WeaviateClient):
|
|
45
|
+
"""
|
|
46
|
+
Weaviate client wrapper that uses A3M Router for embeddings.
|
|
47
|
+
|
|
48
|
+
Provides intelligent embedding generation for Weaviate's
|
|
49
|
+
vector search with automatic model selection.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
a3m_base_url: A3M Router server URL (for embeddings).
|
|
53
|
+
embed_model: Embedding model ("auto" for A3M selection).
|
|
54
|
+
weaviate_url: Weaviate server URL.
|
|
55
|
+
weaviate_client: Existing Weaviate client (for hybrid use).
|
|
56
|
+
**kwargs: Additional WeaviateClient options.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
a3m_base_url: str = "http://localhost:8787",
|
|
62
|
+
embed_model: str = "auto",
|
|
63
|
+
weaviate_url: Optional[str] = None,
|
|
64
|
+
weaviate_client: Optional[WeaviateClient] = None,
|
|
65
|
+
**kwargs: Any,
|
|
66
|
+
) -> None:
|
|
67
|
+
# Initialize Weaviate client if URL provided
|
|
68
|
+
if weaviate_client:
|
|
69
|
+
# Copy internal state from provided client
|
|
70
|
+
self._connection = weaviate_client._connection
|
|
71
|
+
self._batch = weaviate_client._batch
|
|
72
|
+
self._schema = weaviate_client._schema
|
|
73
|
+
elif weaviate_url:
|
|
74
|
+
super().__init__(url=weaviate_url, **kwargs)
|
|
75
|
+
else:
|
|
76
|
+
# Initialize with empty client for embedding-only use
|
|
77
|
+
self._connection = None
|
|
78
|
+
self._batch = None
|
|
79
|
+
self._schema = None
|
|
80
|
+
|
|
81
|
+
self._a3m_router = A3MRouter(base_url=a3m_base_url, default_model=embed_model)
|
|
82
|
+
self._embed_model = embed_model
|
|
83
|
+
|
|
84
|
+
def embed_query(
|
|
85
|
+
self,
|
|
86
|
+
query: str,
|
|
87
|
+
**kwargs: Any,
|
|
88
|
+
) -> List[float]:
|
|
89
|
+
"""
|
|
90
|
+
Generate embedding for a query string.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
query: Text to embed.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Embedding vector (list of floats).
|
|
97
|
+
"""
|
|
98
|
+
try:
|
|
99
|
+
response = self._a3m_router.embed(texts=[query], model=self._embed_model)
|
|
100
|
+
return response.embedding
|
|
101
|
+
except A3MRouterError as e:
|
|
102
|
+
logger.error(f"A3M Router embedding error: {e}")
|
|
103
|
+
raise
|
|
104
|
+
|
|
105
|
+
def embed_texts(
|
|
106
|
+
self,
|
|
107
|
+
texts: List[str],
|
|
108
|
+
**kwargs: Any,
|
|
109
|
+
) -> List[List[float]]:
|
|
110
|
+
"""
|
|
111
|
+
Generate embeddings for multiple texts.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
texts: List of texts to embed.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
List of embedding vectors.
|
|
118
|
+
"""
|
|
119
|
+
try:
|
|
120
|
+
response = self._a3m_router.embed(texts=texts, model=self._embed_model)
|
|
121
|
+
if isinstance(response, list):
|
|
122
|
+
return [r.embedding for r in response]
|
|
123
|
+
return [response.embedding]
|
|
124
|
+
except A3MRouterError as e:
|
|
125
|
+
logger.error(f"A3M Router embedding error: {e}")
|
|
126
|
+
raise
|
|
127
|
+
|
|
128
|
+
def with_near_text(
|
|
129
|
+
self,
|
|
130
|
+
query: str,
|
|
131
|
+
**kwargs: Any,
|
|
132
|
+
) -> "WeaviateNearText":
|
|
133
|
+
"""
|
|
134
|
+
Create a near-text search using A3M embeddings.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
query: Text to search for.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
WeaviateNearText object ready for .do()
|
|
141
|
+
"""
|
|
142
|
+
embedding = self.embed_query(query)
|
|
143
|
+
return WeaviateNearText(
|
|
144
|
+
client=self,
|
|
145
|
+
text=query,
|
|
146
|
+
embedding=embedding,
|
|
147
|
+
**kwargs,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
def rag_search(
|
|
151
|
+
self,
|
|
152
|
+
query: str,
|
|
153
|
+
class_name: str,
|
|
154
|
+
properties: List[str],
|
|
155
|
+
limit: int = 5,
|
|
156
|
+
**kwargs: Any,
|
|
157
|
+
) -> Dict[str, Any]:
|
|
158
|
+
"""
|
|
159
|
+
Perform RAG search: embed query + Weaviate search + context build.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
query: User query string.
|
|
163
|
+
class_name: Weaviate class to search.
|
|
164
|
+
properties: Properties to return.
|
|
165
|
+
limit: Number of results.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
Dict with 'chunks', 'context', 'answer', 'provider'.
|
|
169
|
+
"""
|
|
170
|
+
# 1. Embed query via A3M
|
|
171
|
+
query_embedding = self.embed_query(query)
|
|
172
|
+
|
|
173
|
+
# 2. Search Weaviate
|
|
174
|
+
try:
|
|
175
|
+
results = (
|
|
176
|
+
self.query
|
|
177
|
+
.get(class_name, properties)
|
|
178
|
+
.with_near_vector({"vector": query_embedding})
|
|
179
|
+
.with_limit(limit)
|
|
180
|
+
.do()
|
|
181
|
+
)
|
|
182
|
+
except Exception as e:
|
|
183
|
+
logger.warning(f"Weaviate search error: {e}")
|
|
184
|
+
results = {"data": {"Get": {class_name: []}}}
|
|
185
|
+
|
|
186
|
+
# 3. Extract chunks
|
|
187
|
+
data = results.get("data", {}).get("Get", {}).get(class_name, [])
|
|
188
|
+
chunks = []
|
|
189
|
+
for item in data:
|
|
190
|
+
text = " ".join([str(item.get(p, "")) for p in properties if p in item])
|
|
191
|
+
chunks.append(text)
|
|
192
|
+
|
|
193
|
+
context = "\n\n".join(chunks)
|
|
194
|
+
|
|
195
|
+
# 4. Generate answer via A3M
|
|
196
|
+
answer = None
|
|
197
|
+
provider = None
|
|
198
|
+
if chunks:
|
|
199
|
+
try:
|
|
200
|
+
response = self._a3m_router.chat(
|
|
201
|
+
messages=[{
|
|
202
|
+
"role": "user",
|
|
203
|
+
"content": f"Context:\n{context}\n\nQuestion: {query}\n\nAnswer concisely:"
|
|
204
|
+
}],
|
|
205
|
+
model=self._embed_model,
|
|
206
|
+
)
|
|
207
|
+
answer = response.content
|
|
208
|
+
provider = response.provider
|
|
209
|
+
except A3MRouterError:
|
|
210
|
+
answer = chunks[0] if chunks else ""
|
|
211
|
+
provider = "weaviate"
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
"chunks": chunks,
|
|
215
|
+
"context": context,
|
|
216
|
+
"answer": answer,
|
|
217
|
+
"provider": provider,
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
class WeaviateNearText:
|
|
221
|
+
"""Helper class for near-text search with A3M embeddings."""
|
|
222
|
+
|
|
223
|
+
def __init__(
|
|
224
|
+
self,
|
|
225
|
+
client: WeaviateAdapter,
|
|
226
|
+
text: str,
|
|
227
|
+
embedding: List[float],
|
|
228
|
+
certainty: Optional[float] = None,
|
|
229
|
+
distance: Optional[float] = None,
|
|
230
|
+
) -> None:
|
|
231
|
+
self._client = client
|
|
232
|
+
self._text = text
|
|
233
|
+
self._embedding = embedding
|
|
234
|
+
self._certainty = certainty
|
|
235
|
+
self._distance = distance
|
|
236
|
+
|
|
237
|
+
def with_limit(self, limit: int) -> "WeaviateNearText":
|
|
238
|
+
"""Set result limit."""
|
|
239
|
+
self._limit = limit
|
|
240
|
+
return self
|
|
241
|
+
|
|
242
|
+
def do(self) -> Dict[str, Any]:
|
|
243
|
+
"""Execute the search."""
|
|
244
|
+
near_vector = {"vector": self._embedding}
|
|
245
|
+
if self._certainty is not None:
|
|
246
|
+
near_vector["certainty"] = self._certainty
|
|
247
|
+
if self._distance is not None:
|
|
248
|
+
near_vector["distance"] = self._distance
|
|
249
|
+
|
|
250
|
+
return self._client.query.get(
|
|
251
|
+
self._class_name,
|
|
252
|
+
self._properties
|
|
253
|
+
).with_near_vector(near_vector).with_limit(
|
|
254
|
+
getattr(self, "_limit", 10)
|
|
255
|
+
).do()
|
|
256
|
+
|
|
257
|
+
else:
|
|
258
|
+
class WeaviateAdapter:
|
|
259
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
260
|
+
raise ImportError(
|
|
261
|
+
"Weaviate client not installed. "
|
|
262
|
+
"Install with: pip install weaviate-client"
|
|
263
|
+
)
|
package/python/a3m/client.py
CHANGED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Build and verify Python SDK
|
|
3
|
+
set -e
|
|
4
|
+
|
|
5
|
+
echo "=== Building A3M Router Python SDK v2.2.0 ==="
|
|
6
|
+
cd "$(dirname "$0")"
|
|
7
|
+
|
|
8
|
+
echo "1. Installing build dependencies..."
|
|
9
|
+
pip install build twine 2>/dev/null || pip3 install build twine
|
|
10
|
+
|
|
11
|
+
echo "2. Cleaning old builds..."
|
|
12
|
+
rm -rf dist/ build/ *.egg-info
|
|
13
|
+
|
|
14
|
+
echo "3. Building package..."
|
|
15
|
+
python -m build
|
|
16
|
+
|
|
17
|
+
echo "4. Checking package..."
|
|
18
|
+
python -m twine check dist/*
|
|
19
|
+
|
|
20
|
+
echo "5. Listing built files..."
|
|
21
|
+
ls -lh dist/
|
|
22
|
+
|
|
23
|
+
echo ""
|
|
24
|
+
echo "=== Build complete! ==="
|
|
25
|
+
echo "To publish to Test PyPI:"
|
|
26
|
+
echo " cd python && python -m twine upload --repository testpypi dist/*"
|
|
27
|
+
echo ""
|
|
28
|
+
echo "To publish to PyPI:"
|
|
29
|
+
echo " cd python && python -m twine upload dist/*"
|
|
30
|
+
echo ""
|
|
31
|
+
echo "To install locally:"
|
|
32
|
+
echo " pip install dist/a3m_router-*.whl"
|