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.
- package/.github/workflows/adapters-ci.yml +142 -0
- package/.github/workflows/ci.yml +2 -5
- package/.github/workflows/pypi-publish.yml +102 -0
- package/AGENT_COUNCIL_FINDINGS.md +123 -98
- package/CAMPAIGN_SUMMARY.md +87 -0
- package/CHANGELOG.md +7 -1
- package/ENTERPRISE_INTEGRATIONS.md +94 -0
- package/INTEGRATION_PROGRESS.md +96 -0
- package/OPPORTUNITIES_100.md +277 -0
- package/POPULARITY_BOOSTERS.md +1 -1
- package/README.md +137 -142
- package/README_ja.md +17 -21
- package/README_zh.md +13 -17
- package/TODO_BROWSER_AUTOMATION.md +87 -0
- package/a3m-integrations-summary.md +73 -0
- package/adapters/a3m_adapter/__init__.py +39 -3
- package/adapters/a3m_adapter/adapter/__init__.py +16 -1
- package/adapters/a3m_adapter/adapter/autogen.py +169 -0
- package/adapters/a3m_adapter/adapter/haystack.py +197 -0
- package/adapters/a3m_adapter/adapter/langgraph.py +196 -0
- package/adapters/a3m_adapter/adapter/pinecone.py +217 -0
- package/adapters/a3m_adapter/adapter/vercel.py +188 -0
- package/adapters/a3m_adapter/tests/test_adapters.py +29 -31
- package/adapters/a3m_adapter/tests/test_integration.py +80 -0
- package/adapters/requirements-dev.txt +6 -0
- package/adapters/requirements.txt +4 -0
- package/apps/cost-calculator/README.md +72 -0
- package/apps/cost-calculator/calculator.css +280 -0
- package/apps/cost-calculator/calculator.js +150 -0
- package/apps/cost-calculator/index.html +321 -0
- package/apps/cost-calculator/package.json +13 -0
- package/articles/ANNOUNCEMENT_reddit_ml.md +76 -0
- package/articles/ANNOUNCEMENT_vc/347/244/276/345/214/272.md +71 -0
- package/articles/ANNOUNCEMENT_vercel.md +85 -0
- package/articles/CHINESE_DIRECTORIES.md +2 -2
- package/articles/CHINESE_SUBMISSIONS_READY.md +5 -5
- package/articles/HN_POST_READY.md +16 -20
- package/articles/INDIEHACKERS_READY.md +19 -34
- package/demo.py +251 -0
- package/dist/analytics/costAnalytics.d.ts +0 -1
- package/dist/benchmark/reproducible.d.ts +0 -1
- package/dist/cache/semanticCache.d.ts +0 -1
- package/dist/cost/budgetEnforcer.d.ts +0 -1
- package/dist/cost/costTracker.d.ts +0 -1
- package/dist/ensemble/multiRoundDialog.d.ts +0 -1
- package/dist/ensemble/shapleyValue.d.ts +0 -1
- package/dist/ensemble.d.ts +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/integrations/oauth.d.ts +0 -1
- package/dist/integrations/scienceAdapter.d.ts +0 -1
- package/dist/memory/autoFetch.d.ts +0 -1
- package/dist/memory/hybridMemory.d.ts +0 -1
- package/dist/memory/memoryTree.d.ts +0 -1
- package/dist/memory/obsidianVault.d.ts +0 -1
- package/dist/memory/reasoningBank.d.ts +0 -1
- package/dist/observability/changeWatch.d.ts +0 -1
- package/dist/observability/fatigueDetector.d.ts +0 -1
- package/dist/observability/index.d.ts +0 -1
- package/dist/observability/metrics.d.ts +0 -1
- package/dist/observability/middleware.d.ts +0 -1
- package/dist/observability/tracer.d.ts +0 -1
- package/dist/observability/types.d.ts +0 -1
- package/dist/providers/providerConfig.d.ts +5 -2
- package/dist/providers/providerConfig.js +1006 -1
- package/dist/providers/providerConfig.js.map +1 -1
- package/dist/routing/advancedRouter.d.ts +1 -2
- package/dist/routing/advancedRouter.js +84 -0
- package/dist/routing/advancedRouter.js.map +1 -1
- package/dist/routing/crossModelValidation.d.ts +0 -1
- package/dist/routing/providerHealth.d.ts +77 -2
- package/dist/routing/providerHealth.js +190 -4
- package/dist/routing/providerHealth.js.map +1 -1
- package/dist/routing/providerRetry.d.ts +0 -1
- package/dist/routing/shadowSampler.d.ts +187 -0
- package/dist/routing/shadowSampler.js +306 -0
- package/dist/routing/shadowSampler.js.map +1 -0
- package/dist/sdk.d.ts +0 -1
- package/dist/security/guardrails.d.ts +0 -1
- package/dist/server/dashboard.d.ts +0 -1
- package/dist/server/handlers/chatHandler.d.ts +0 -1
- package/dist/server/handlers/completionsHandler.d.ts +0 -1
- package/dist/server/handlers/embeddingsHandler.d.ts +0 -1
- package/dist/server/handlers/healthHandler.d.ts +0 -1
- package/dist/server/handlers/metricsHandler.d.ts +0 -1
- package/dist/server/handlers/modelsHandler.d.ts +0 -1
- package/dist/server/metrics.d.ts +0 -1
- package/dist/server/modelMapper.d.ts +0 -1
- package/dist/server/proxyServer.d.ts +0 -1
- package/dist/server/router.d.ts +0 -1
- package/dist/server/state.d.ts +0 -1
- package/dist/tui/dashboard.d.ts +0 -1
- package/dist/utils/costUtils.d.ts +0 -1
- package/dist/utils/sorting.d.ts +0 -1
- package/dist/utils/tokenUtils.d.ts +0 -1
- package/docker-compose.yml +84 -60
- package/docs/comparison.md +2 -2
- package/docs/llms-full.txt +89 -0
- package/docs/llms.txt +25 -67
- package/docs-site/index.html +9 -9
- package/integrations/browser-use/README.md +155 -0
- package/integrations/browser-use/examples/form-filling.ts +179 -0
- package/integrations/browser-use/index.ts +145 -0
- package/integrations/browser-use/package.json +34 -0
- package/integrations/browser-use/tsconfig.json +17 -0
- package/integrations/mcp-browser/README.md +177 -0
- package/integrations/mcp-browser/package.json +38 -0
- package/integrations/mcp-browser/src/index.ts +297 -0
- package/integrations/sota-browser/README.md +191 -0
- package/integrations/sota-browser/package.json +34 -0
- package/llms-full.txt +6 -6
- package/llms.txt +25 -67
- package/package.json +34 -5
- package/packages/a3m-vercel-ai/README.md +161 -0
- package/packages/a3m-vercel-ai/dist/a3m-language-model.d.ts +12 -0
- package/packages/a3m-vercel-ai/dist/a3m-language-model.d.ts.map +1 -0
- package/packages/a3m-vercel-ai/dist/a3m-language-model.js +289 -0
- package/packages/a3m-vercel-ai/dist/a3m-language-model.js.map +1 -0
- package/packages/a3m-vercel-ai/dist/index.d.ts +82 -0
- package/packages/a3m-vercel-ai/dist/index.d.ts.map +1 -0
- package/packages/a3m-vercel-ai/dist/index.js +79 -0
- package/packages/a3m-vercel-ai/dist/index.js.map +1 -0
- package/packages/a3m-vercel-ai/dist/types.d.ts +97 -0
- package/packages/a3m-vercel-ai/dist/types.d.ts.map +1 -0
- package/packages/a3m-vercel-ai/dist/types.js +5 -0
- package/packages/a3m-vercel-ai/dist/types.js.map +1 -0
- package/packages/a3m-vercel-ai/package-lock.json +969 -0
- package/packages/a3m-vercel-ai/package.json +46 -0
- package/packages/a3m-vercel-ai/src/a3m-language-model.ts +381 -0
- package/packages/a3m-vercel-ai/src/index.ts +104 -0
- package/packages/a3m-vercel-ai/src/types.ts +116 -0
- package/packages/a3m-vercel-ai/tsconfig.json +20 -0
- 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 +92 -65
- 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/a3m_router.egg-info/PKG-INFO +172 -0
- package/python/a3m_router.egg-info/SOURCES.txt +17 -0
- package/python/a3m_router.egg-info/dependency_links.txt +1 -0
- package/python/a3m_router.egg-info/requires.txt +24 -0
- package/python/a3m_router.egg-info/top_level.txt +1 -0
- package/python/build_verify.sh +32 -0
- package/python/dist/a3m_router-2.2.1-py3-none-any.whl +0 -0
- package/python/dist/a3m_router-2.2.1.tar.gz +0 -0
- package/python/dist/a3m_router-2.2.2-py3-none-any.whl +0 -0
- package/python/dist/a3m_router-2.2.2.tar.gz +0 -0
- package/python/mcp-server/README.md +172 -0
- package/python/mcp-server/a3m_mcp/__init__.py +15 -0
- package/python/mcp-server/a3m_mcp/__main__.py +15 -0
- package/python/mcp-server/a3m_mcp/server.py +339 -0
- package/python/mcp-server/pyproject.toml +24 -0
- package/python/pyproject.toml +56 -5
- package/python/setup.py +3 -28
- package/src/providers/providerConfig.ts +1053 -1
- package/summary.txt +38 -0
- package/tsconfig.build.json +1 -2
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -94
- package/.github/ISSUE_TEMPLATE/config.yml +0 -17
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -71
- package/.well-known/ai-plugin.json +0 -16
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A3M Router Adapter for LangGraph (Microsoft's agent framework).
|
|
3
|
+
|
|
4
|
+
Drop-in replacement for LangGraph's stateful agent that routes through A3M Router
|
|
5
|
+
for intelligent, cost-optimized multi-step conversations.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
from langgraph.prebuilt import create_react_agent
|
|
9
|
+
from a3m_adapter import A3MLangGraphAdapter
|
|
10
|
+
|
|
11
|
+
adapter = A3MLangGraphAdapter(model='auto', parallel_ensemble=2)
|
|
12
|
+
|
|
13
|
+
agent = create_react_agent(adapter, tools=[...])
|
|
14
|
+
|
|
15
|
+
result = agent.invoke({"messages": [{"role": "user", "content": "Hello"]})
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import logging
|
|
21
|
+
from typing import Any, Dict, List, TypedDict
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
LANGGRAPH_AVAILABLE = False
|
|
26
|
+
try:
|
|
27
|
+
import langgraph
|
|
28
|
+
from langgraph.prebuilt import create_react_agent
|
|
29
|
+
from langchain_core.messages import BaseMessage, AIMessage, HumanMessage
|
|
30
|
+
LANGGRAPH_AVAILABLE = True
|
|
31
|
+
except ImportError:
|
|
32
|
+
logger.warning("LangGraph not installed. Install with: pip install langgraph")
|
|
33
|
+
|
|
34
|
+
A3M_AVAILABLE = False
|
|
35
|
+
try:
|
|
36
|
+
from a3m.router import A3MRouter, RouteResponse
|
|
37
|
+
A3M_AVAILABLE = True
|
|
38
|
+
except ImportError:
|
|
39
|
+
logger.warning(
|
|
40
|
+
"A3M Router not installed. Install with: pip install adaptive-memory-multi-model-router"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class A3MLangGraphAdapter:
|
|
45
|
+
"""
|
|
46
|
+
A3M Router adapter for LangGraph's prebuilt agents.
|
|
47
|
+
|
|
48
|
+
Enables LangGraph agents to use A3M Router for automatic model selection
|
|
49
|
+
across 47+ providers with cost optimization and stateful conversations.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
model: str = "auto",
|
|
55
|
+
temperature: float = 0.7,
|
|
56
|
+
max_tokens: int = 4096,
|
|
57
|
+
parallel_ensemble: int = 1,
|
|
58
|
+
api_key: Optional[str] = None,
|
|
59
|
+
**kwargs: Any,
|
|
60
|
+
) -> None:
|
|
61
|
+
"""
|
|
62
|
+
Initialize A3M Router adapter for LangGraph.
|
|
63
|
+
"""
|
|
64
|
+
self.model = model
|
|
65
|
+
self.temperature = temperature
|
|
66
|
+
self.max_tokens = max_tokens
|
|
67
|
+
self.parallel_ensemble = parallel_ensemble
|
|
68
|
+
self.api_key = api_key
|
|
69
|
+
self._a3m_router = None
|
|
70
|
+
self._initialized = False
|
|
71
|
+
self._kwargs = kwargs
|
|
72
|
+
|
|
73
|
+
def _ensure_router(self) -> None:
|
|
74
|
+
"""Lazily initialize the A3M router."""
|
|
75
|
+
if self._initialized:
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
if not A3M_AVAILABLE:
|
|
79
|
+
raise ImportError(
|
|
80
|
+
"A3M Router is not installed. "
|
|
81
|
+
"Install with: pip install adaptive-memory-multi-model-router"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
self._a3m_router = A3MRouter(
|
|
85
|
+
model=self.model,
|
|
86
|
+
temperature=self.temperature,
|
|
87
|
+
parallel_ensemble=self.parallel_ensemble,
|
|
88
|
+
)
|
|
89
|
+
self._initialized = True
|
|
90
|
+
logger.info(
|
|
91
|
+
"A3M Router initialized for LangGraph: model=%s, ensemble=%d",
|
|
92
|
+
self.model,
|
|
93
|
+
self.parallel_ensemble,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def get_model(self):
|
|
97
|
+
"""
|
|
98
|
+
Get the underlying model for LangGraph.
|
|
99
|
+
|
|
100
|
+
Returns an object compatible with LangGraph's prebuilt agents.
|
|
101
|
+
"""
|
|
102
|
+
self._ensure_router()
|
|
103
|
+
return self
|
|
104
|
+
|
|
105
|
+
def __call__(
|
|
106
|
+
self,
|
|
107
|
+
state: Dict[str, Any],
|
|
108
|
+
**kwargs: Any,
|
|
109
|
+
) -> Dict[str, Any]:
|
|
110
|
+
"""
|
|
111
|
+
LangGraph-compatible callable for node execution.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
state: LangGraph state dict with 'messages' key
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Updated state dict
|
|
118
|
+
"""
|
|
119
|
+
self._ensure_router()
|
|
120
|
+
|
|
121
|
+
messages = state.get("messages", [])
|
|
122
|
+
|
|
123
|
+
# Convert LangGraph messages to A3M format
|
|
124
|
+
a3m_messages = self._convert_messages(messages)
|
|
125
|
+
|
|
126
|
+
import asyncio
|
|
127
|
+
loop = asyncio.get_event_loop()
|
|
128
|
+
route_result = loop.run_in_executor(
|
|
129
|
+
None,
|
|
130
|
+
lambda: self._a3m_router.route(
|
|
131
|
+
messages=a3m_messages,
|
|
132
|
+
temperature=kwargs.get("temperature", self.temperature),
|
|
133
|
+
max_tokens=kwargs.get("max_tokens", self.max_tokens),
|
|
134
|
+
**kwargs,
|
|
135
|
+
),
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# Add response to messages
|
|
139
|
+
new_messages = messages + [
|
|
140
|
+
AIMessage(content=route_result.content)
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
**state,
|
|
145
|
+
"messages": new_messages,
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
def _convert_messages(
|
|
149
|
+
self,
|
|
150
|
+
messages: List[BaseMessage],
|
|
151
|
+
) -> List[Dict[str, Any]]:
|
|
152
|
+
"""Convert LangGraph messages to A3M format."""
|
|
153
|
+
a3m_messages = []
|
|
154
|
+
for msg in messages:
|
|
155
|
+
if isinstance(msg, HumanMessage):
|
|
156
|
+
a3m_messages.append({"role": "user", "content": msg.content})
|
|
157
|
+
elif isinstance(msg, AIMessage):
|
|
158
|
+
a3m_messages.append({"role": "assistant", "content": msg.content})
|
|
159
|
+
else:
|
|
160
|
+
a3m_messages.append({"role": "user", "content": str(msg)})
|
|
161
|
+
return a3m_messages
|
|
162
|
+
|
|
163
|
+
async def ainvoke(
|
|
164
|
+
self,
|
|
165
|
+
state: Dict[str, Any],
|
|
166
|
+
**kwargs: Any,
|
|
167
|
+
) -> Dict[str, Any]:
|
|
168
|
+
"""Async version of __call__."""
|
|
169
|
+
self._ensure_router()
|
|
170
|
+
|
|
171
|
+
messages = state.get("messages", [])
|
|
172
|
+
a3m_messages = self._convert_messages(messages)
|
|
173
|
+
|
|
174
|
+
route_result = await self._a3m_router.aroute(
|
|
175
|
+
messages=a3m_messages,
|
|
176
|
+
temperature=kwargs.get("temperature", self.temperature),
|
|
177
|
+
max_tokens=kwargs.get("max_tokens", self.max_tokens),
|
|
178
|
+
**kwargs,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
new_messages = messages + [
|
|
182
|
+
AIMessage(content=route_result.content)
|
|
183
|
+
]
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
**state,
|
|
187
|
+
"messages": new_messages,
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
def __repr__(self) -> str:
|
|
191
|
+
return (
|
|
192
|
+
f"A3MLangGraphAdapter("
|
|
193
|
+
f"model={self.model!r}, "
|
|
194
|
+
f"temperature={self.temperature}, "
|
|
195
|
+
f"ensemble={self.parallel_ensemble})"
|
|
196
|
+
)
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A3M Router Adapter for Pinecone Vector Database.
|
|
3
|
+
|
|
4
|
+
Enables Pinecone's managed vector database to use A3M Router for
|
|
5
|
+
intelligent query routing and cost-optimized embeddings.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
from pinecone import Pinecone
|
|
9
|
+
from a3m_adapter import A3MPineconeAdapter
|
|
10
|
+
|
|
11
|
+
# Create A3M-powered embeddings
|
|
12
|
+
embed_adapter = A3MPineconeAdapter(model="auto")
|
|
13
|
+
|
|
14
|
+
# Generate embeddings
|
|
15
|
+
embedding = embed_adapter.embed("What is quantum computing?")
|
|
16
|
+
|
|
17
|
+
# Query Pinecone
|
|
18
|
+
results = index.query(
|
|
19
|
+
vector=embedding,
|
|
20
|
+
top_k=5,
|
|
21
|
+
)
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import logging
|
|
27
|
+
from typing import Any, Dict, List, Optional
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
A3M_AVAILABLE = False
|
|
32
|
+
try:
|
|
33
|
+
from a3m.router import A3MRouter
|
|
34
|
+
A3M_AVAILABLE = True
|
|
35
|
+
except ImportError:
|
|
36
|
+
logger.warning(
|
|
37
|
+
"A3M Router not installed. Install with: pip install adaptive-memory-multi-model-router"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class A3MPineconeAdapter:
|
|
42
|
+
"""
|
|
43
|
+
A3M Router adapter for Pinecone embeddings.
|
|
44
|
+
|
|
45
|
+
Provides intelligent embedding generation through A3M Router
|
|
46
|
+
with automatic model selection for cost optimization.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
model: str = "auto",
|
|
52
|
+
embed_model: str = "auto",
|
|
53
|
+
parallel_ensemble: int = 1,
|
|
54
|
+
api_key: Optional[str] = None,
|
|
55
|
+
**kwargs: Any,
|
|
56
|
+
) -> None:
|
|
57
|
+
"""
|
|
58
|
+
Initialize A3M Router adapter for Pinecone.
|
|
59
|
+
"""
|
|
60
|
+
self.model = model
|
|
61
|
+
self.embed_model = embed_model or "auto"
|
|
62
|
+
self.parallel_ensemble = parallel_ensemble
|
|
63
|
+
self.api_key = api_key
|
|
64
|
+
self._a3m_router = None
|
|
65
|
+
self._initialized = False
|
|
66
|
+
self._kwargs = kwargs
|
|
67
|
+
|
|
68
|
+
def _ensure_router(self) -> None:
|
|
69
|
+
"""Lazily initialize the A3M router."""
|
|
70
|
+
if self._initialized:
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
if not A3M_AVAILABLE:
|
|
74
|
+
raise ImportError(
|
|
75
|
+
"A3M Router is not installed. "
|
|
76
|
+
"Install with: pip install adaptive-memory-multi-model-router"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
self._a3m_router = A3MRouter(
|
|
80
|
+
model=self.model,
|
|
81
|
+
parallel_ensemble=self.parallel_ensemble,
|
|
82
|
+
)
|
|
83
|
+
self._initialized = True
|
|
84
|
+
logger.info(
|
|
85
|
+
"A3M Router initialized for Pinecone: embed_model=%s",
|
|
86
|
+
self.embed_model,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def embed(
|
|
90
|
+
self,
|
|
91
|
+
texts: List[str],
|
|
92
|
+
**kwargs: Any,
|
|
93
|
+
) -> List[List[float]]:
|
|
94
|
+
"""
|
|
95
|
+
Generate embeddings for texts using A3M Router.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
texts: List of text strings to embed
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
List of embedding vectors
|
|
102
|
+
"""
|
|
103
|
+
self._ensure_router()
|
|
104
|
+
|
|
105
|
+
import asyncio
|
|
106
|
+
loop = asyncio.get_event_loop()
|
|
107
|
+
|
|
108
|
+
# For embeddings, we typically call the router with a special embedding mode
|
|
109
|
+
# Since A3M supports /v1/embeddings endpoint
|
|
110
|
+
results = loop.run_in_executor(
|
|
111
|
+
None,
|
|
112
|
+
lambda: self._a3m_router.embed(
|
|
113
|
+
texts=texts,
|
|
114
|
+
model=self.embed_model,
|
|
115
|
+
**kwargs,
|
|
116
|
+
),
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
return results
|
|
120
|
+
|
|
121
|
+
def embed_query(
|
|
122
|
+
self,
|
|
123
|
+
text: str,
|
|
124
|
+
**kwargs: Any,
|
|
125
|
+
) -> List[float]:
|
|
126
|
+
"""
|
|
127
|
+
Generate embedding for a single query.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
text: Text to embed
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Embedding vector
|
|
134
|
+
"""
|
|
135
|
+
embeddings = self.embed([text], **kwargs)
|
|
136
|
+
return embeddings[0] if embeddings else []
|
|
137
|
+
|
|
138
|
+
async def aembed(
|
|
139
|
+
self,
|
|
140
|
+
texts: List[str],
|
|
141
|
+
**kwargs: Any,
|
|
142
|
+
) -> List[List[float]]:
|
|
143
|
+
"""Async version of embed."""
|
|
144
|
+
self._ensure_router()
|
|
145
|
+
|
|
146
|
+
results = await self._a3m_router.aembed(
|
|
147
|
+
texts=texts,
|
|
148
|
+
model=self.embed_model,
|
|
149
|
+
**kwargs,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
return results
|
|
153
|
+
|
|
154
|
+
def rag_query(
|
|
155
|
+
self,
|
|
156
|
+
query: str,
|
|
157
|
+
index,
|
|
158
|
+
top_k: int = 5,
|
|
159
|
+
**kwargs: Any,
|
|
160
|
+
) -> Dict[str, Any]:
|
|
161
|
+
"""
|
|
162
|
+
Perform RAG query: embed + Pinecone search + context.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
query: The search query
|
|
166
|
+
index: Pinecone index to query
|
|
167
|
+
top_k: Number of results to retrieve
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
Dict with 'results', 'context', 'provider', 'cost'
|
|
171
|
+
"""
|
|
172
|
+
# 1. Embed query
|
|
173
|
+
query_embedding = self.embed_query(query)
|
|
174
|
+
|
|
175
|
+
# 2. Search Pinecone
|
|
176
|
+
search_results = index.query(
|
|
177
|
+
vector=query_embedding,
|
|
178
|
+
top_k=top_k,
|
|
179
|
+
include_metadata=True,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# 3. Build context from results
|
|
183
|
+
context = "\n\n".join([
|
|
184
|
+
match.get('metadata', {}).get('text', str(match.get('id', '')))
|
|
185
|
+
for match in search_results.get('matches', [])[:3]
|
|
186
|
+
])
|
|
187
|
+
|
|
188
|
+
# 4. Route the full query through A3M
|
|
189
|
+
import asyncio
|
|
190
|
+
loop = asyncio.get_event_loop()
|
|
191
|
+
route_result = loop.run_in_executor(
|
|
192
|
+
None,
|
|
193
|
+
lambda: self._a3m_router.route(
|
|
194
|
+
messages=[{
|
|
195
|
+
"role": "user",
|
|
196
|
+
"content": f"Context:\n{context}\n\nQuestion: {query}"
|
|
197
|
+
}],
|
|
198
|
+
temperature=self._kwargs.get("temperature", 0.7),
|
|
199
|
+
**kwargs,
|
|
200
|
+
),
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
"results": search_results.get('matches', []),
|
|
205
|
+
"context": context,
|
|
206
|
+
"answer": route_result.content,
|
|
207
|
+
"provider": getattr(route_result, 'provider', 'a3m'),
|
|
208
|
+
"cost": getattr(route_result, 'cost', 0.0),
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
def __repr__(self) -> str:
|
|
212
|
+
return (
|
|
213
|
+
f"A3MPineconeAdapter("
|
|
214
|
+
f"model={self.model!r}, "
|
|
215
|
+
f"embed_model={self.embed_model!r}, "
|
|
216
|
+
f"ensemble={self.parallel_ensemble})"
|
|
217
|
+
)
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A3M Router Adapter for Vercel AI SDK.
|
|
3
|
+
|
|
4
|
+
Drop-in replacement for Vercel AI SDK's AI function that routes through A3M Router
|
|
5
|
+
for intelligent, cost-optimized responses in Next.js and other JavaScript environments.
|
|
6
|
+
|
|
7
|
+
Usage (JavaScript):
|
|
8
|
+
import { generateText } from 'ai';
|
|
9
|
+
import { createA3MProvider } from 'a3m-adapter/vercel';
|
|
10
|
+
|
|
11
|
+
const result = await generateText({
|
|
12
|
+
model: createA3MProvider({ model: 'auto', parallel_ensemble: 2 }),
|
|
13
|
+
prompt: 'What is the meaning of life?',
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
Usage (Python):
|
|
17
|
+
from a3m_adapter import A3MVercelAdapter
|
|
18
|
+
|
|
19
|
+
adapter = A3MVercelAdapter(model='auto', temperature=0.7)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import logging
|
|
25
|
+
from typing import Any, Dict, List, Optional
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
A3M_AVAILABLE = False
|
|
30
|
+
try:
|
|
31
|
+
from a3m.router import A3MRouter, RouteResponse
|
|
32
|
+
A3M_AVAILABLE = True
|
|
33
|
+
except ImportError:
|
|
34
|
+
logger.warning(
|
|
35
|
+
"A3M Router not installed. Install with: pip install adaptive-memory-multi-model-router"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class A3MVercelAdapter:
|
|
40
|
+
"""
|
|
41
|
+
A3M Router adapter for Vercel AI SDK compatibility.
|
|
42
|
+
|
|
43
|
+
Provides a drop-in replacement that routes through A3M Router
|
|
44
|
+
instead of calling OpenAI/Anthropic directly.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
model: str = "auto",
|
|
50
|
+
temperature: float = 0.7,
|
|
51
|
+
max_tokens: int = 4096,
|
|
52
|
+
parallel_ensemble: int = 1,
|
|
53
|
+
api_key: Optional[str] = None,
|
|
54
|
+
**kwargs: Any,
|
|
55
|
+
) -> None:
|
|
56
|
+
"""
|
|
57
|
+
Initialize A3M Router adapter for Vercel AI SDK.
|
|
58
|
+
"""
|
|
59
|
+
self.model = model
|
|
60
|
+
self.temperature = temperature
|
|
61
|
+
self.max_tokens = max_tokens
|
|
62
|
+
self.parallel_ensemble = parallel_ensemble
|
|
63
|
+
self.api_key = api_key
|
|
64
|
+
self._a3m_router = None
|
|
65
|
+
self._initialized = False
|
|
66
|
+
self._kwargs = kwargs
|
|
67
|
+
|
|
68
|
+
def _ensure_router(self) -> None:
|
|
69
|
+
"""Lazily initialize the A3M router."""
|
|
70
|
+
if self._initialized:
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
if not A3M_AVAILABLE:
|
|
74
|
+
raise ImportError(
|
|
75
|
+
"A3M Router is not installed. "
|
|
76
|
+
"Install with: pip install adaptive-memory-multi-model-router"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
self._a3m_router = A3MRouter(
|
|
80
|
+
model=self.model,
|
|
81
|
+
temperature=self.temperature,
|
|
82
|
+
parallel_ensemble=self.parallel_ensemble,
|
|
83
|
+
)
|
|
84
|
+
self._initialized = True
|
|
85
|
+
logger.info(
|
|
86
|
+
"A3M Router initialized for Vercel AI SDK: model=%s",
|
|
87
|
+
self.model,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def __call__(
|
|
91
|
+
self,
|
|
92
|
+
prompt: str,
|
|
93
|
+
**kwargs: Any,
|
|
94
|
+
) -> Dict[str, Any]:
|
|
95
|
+
"""
|
|
96
|
+
Generate text from prompt (Vercel AI SDK compatible interface).
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
prompt: The prompt string
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Dict with 'text', 'provider', 'usage', 'finishReason'
|
|
103
|
+
"""
|
|
104
|
+
self._ensure_router()
|
|
105
|
+
|
|
106
|
+
messages = [{"role": "user", "content": prompt}]
|
|
107
|
+
|
|
108
|
+
import asyncio
|
|
109
|
+
loop = asyncio.get_event_loop()
|
|
110
|
+
route_result = loop.run_in_executor(
|
|
111
|
+
None,
|
|
112
|
+
lambda: self._a3m_router.route(
|
|
113
|
+
messages=messages,
|
|
114
|
+
temperature=kwargs.get("temperature", self.temperature),
|
|
115
|
+
max_tokens=kwargs.get("max_tokens", self.max_tokens),
|
|
116
|
+
**kwargs,
|
|
117
|
+
),
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
"text": route_result.content,
|
|
122
|
+
"provider": getattr(route_result, 'provider', 'a3m'),
|
|
123
|
+
"finishReason": getattr(route_result, 'finish_reason', 'stop'),
|
|
124
|
+
"usage": {
|
|
125
|
+
"promptTokens": getattr(route_result, 'prompt_tokens', 0),
|
|
126
|
+
"completionTokens": getattr(route_result, 'completion_tokens', 0),
|
|
127
|
+
"totalTokens": getattr(route_result, 'total_tokens', 0),
|
|
128
|
+
},
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async def generate(
|
|
132
|
+
self,
|
|
133
|
+
prompt: str,
|
|
134
|
+
**kwargs: Any,
|
|
135
|
+
) -> Dict[str, Any]:
|
|
136
|
+
"""Async generate for Vercel AI SDK."""
|
|
137
|
+
self._ensure_router()
|
|
138
|
+
|
|
139
|
+
messages = [{"role": "user", "content": prompt}]
|
|
140
|
+
|
|
141
|
+
route_result = await self._a3m_router.aroute(
|
|
142
|
+
messages=messages,
|
|
143
|
+
temperature=kwargs.get("temperature", self.temperature),
|
|
144
|
+
max_tokens=kwargs.get("max_tokens", self.max_tokens),
|
|
145
|
+
**kwargs,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
"text": route_result.content,
|
|
150
|
+
"provider": getattr(route_result, 'provider', 'a3m'),
|
|
151
|
+
"finishReason": getattr(route_result, 'finish_reason', 'stop'),
|
|
152
|
+
"usage": {
|
|
153
|
+
"promptTokens": getattr(route_result, 'prompt_tokens', 0),
|
|
154
|
+
"completionTokens": getattr(route_result, 'completion_tokens', 0),
|
|
155
|
+
"totalTokens": getattr(route_result, 'total_tokens', 0),
|
|
156
|
+
},
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
def __repr__(self) -> str:
|
|
160
|
+
return (
|
|
161
|
+
f"A3MVercelAdapter("
|
|
162
|
+
f"model={self.model!r}, "
|
|
163
|
+
f"temperature={self.temperature}, "
|
|
164
|
+
f"max_tokens={self.max_tokens})"
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# JavaScript-compatible factory function
|
|
169
|
+
def createA3MProvider(config: Dict[str, Any]) -> A3MVercelAdapter:
|
|
170
|
+
"""
|
|
171
|
+
Create an A3M Provider for Vercel AI SDK (JavaScript usage).
|
|
172
|
+
|
|
173
|
+
Usage:
|
|
174
|
+
import { generateText } from 'ai';
|
|
175
|
+
import { createA3MProvider } from 'a3m-adapter/vercel';
|
|
176
|
+
|
|
177
|
+
const result = await generateText({
|
|
178
|
+
model: createA3MProvider({ model: 'auto', parallel_ensemble: 2 }),
|
|
179
|
+
prompt: 'What is 2+2?',
|
|
180
|
+
});
|
|
181
|
+
"""
|
|
182
|
+
return A3MVercelAdapter(
|
|
183
|
+
model=config.get("model", "auto"),
|
|
184
|
+
temperature=config.get("temperature", 0.7),
|
|
185
|
+
max_tokens=config.get("max_tokens", 4096),
|
|
186
|
+
parallel_ensemble=config.get("parallel_ensemble", 1),
|
|
187
|
+
api_key=config.get("api_key"),
|
|
188
|
+
)
|