adaptive-memory-multi-model-router 2.15.3 → 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/CHANGELOG.md +7 -1
- package/README.md +260 -137
- package/README_ja.md +2 -2
- package/README_zh.md +1 -1
- 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/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/demo.py +251 -0
- package/dist/providers/providerConfig.d.ts +5 -1
- package/dist/providers/providerConfig.js +1006 -1
- package/dist/providers/providerConfig.js.map +1 -1
- package/docker-compose.yml +84 -60
- package/docs/comparison.md +2 -2
- package/docs/llms-full.txt +360 -138
- package/docs/llms.txt +70 -71
- package/docs-site/index.html +9 -9
- package/llms.txt +70 -62
- package/package.json +41 -91
- 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
|
@@ -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,162 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A3M Router Adapter for LlamaIndex.
|
|
3
|
+
|
|
4
|
+
Drop-in replacement for LlamaIndex's BaseLLM that routes through A3M Router
|
|
5
|
+
for intelligent, cost-optimized model selection.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from typing import Any, Dict, List, Optional, Sequence
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
# Check availability
|
|
16
|
+
LLAMAINDEX_AVAILABLE = False
|
|
17
|
+
_llama_metadata_class = None
|
|
18
|
+
try:
|
|
19
|
+
from llama_index.core.base.llms.base import BaseLLM, CompletionResponse
|
|
20
|
+
from llama_index.core.base.llms.types import ChatMessage
|
|
21
|
+
LLAMAINDEX_AVAILABLE = True
|
|
22
|
+
try:
|
|
23
|
+
from llama_index.core.base.llms.base import LLMMetadata
|
|
24
|
+
_llama_metadata_class = LLMMetadata
|
|
25
|
+
except ImportError:
|
|
26
|
+
pass
|
|
27
|
+
except ImportError:
|
|
28
|
+
logger.warning("LlamaIndex not installed. Install with: pip install llama-index")
|
|
29
|
+
|
|
30
|
+
A3M_AVAILABLE = False
|
|
31
|
+
try:
|
|
32
|
+
from a3m.router import A3MRouter, RouteResponse
|
|
33
|
+
A3M_AVAILABLE = True
|
|
34
|
+
except ImportError:
|
|
35
|
+
logger.warning("A3M Router not installed. Install with: pip install adaptive-memory-multi-model-router")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class A3MLlamaIndexAdapter:
|
|
39
|
+
"""
|
|
40
|
+
A3M Router adapter for LlamaIndex's BaseLLM interface.
|
|
41
|
+
|
|
42
|
+
Routes prompts through A3M Router to automatically select the cheapest
|
|
43
|
+
capable model across 47+ LLM providers.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
model: str = "auto",
|
|
49
|
+
temperature: float = 0.0,
|
|
50
|
+
max_tokens: Optional[int] = 4096,
|
|
51
|
+
parallel_ensemble: int = 1,
|
|
52
|
+
api_key: Optional[str] = None,
|
|
53
|
+
**kwargs: Any,
|
|
54
|
+
) -> None:
|
|
55
|
+
"""
|
|
56
|
+
Initialize A3M Router adapter.
|
|
57
|
+
"""
|
|
58
|
+
self.model = model
|
|
59
|
+
self.temperature = temperature
|
|
60
|
+
self.max_tokens = max_tokens
|
|
61
|
+
self.parallel_ensemble = parallel_ensemble
|
|
62
|
+
self.api_key = api_key
|
|
63
|
+
self._a3m_router = None
|
|
64
|
+
self._initialized = False
|
|
65
|
+
|
|
66
|
+
def _ensure_router(self) -> None:
|
|
67
|
+
"""Lazily initialize the A3M router."""
|
|
68
|
+
if self._initialized:
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
if not A3M_AVAILABLE:
|
|
72
|
+
raise ImportError(
|
|
73
|
+
"A3M Router is not installed. "
|
|
74
|
+
"Install with: pip install adaptive-memory-multi-model-router"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
self._a3m_router = A3MRouter(
|
|
78
|
+
model=self.model,
|
|
79
|
+
temperature=self.temperature,
|
|
80
|
+
parallel_ensemble=self.parallel_ensemble,
|
|
81
|
+
)
|
|
82
|
+
self._initialized = True
|
|
83
|
+
logger.info(
|
|
84
|
+
"A3M Router initialized: model=%s, ensemble=%d",
|
|
85
|
+
self.model,
|
|
86
|
+
self.parallel_ensemble,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def metadata(self) -> Dict[str, Any]:
|
|
91
|
+
"""Return LLM metadata as a dict (framework-agnostic)."""
|
|
92
|
+
return {
|
|
93
|
+
"context_window": 128000,
|
|
94
|
+
"num_output": self.max_tokens or 4096,
|
|
95
|
+
"model_name": self.model,
|
|
96
|
+
"is_chat_model": True,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
def complete(self, prompt: str, **kwargs: Any) -> CompletionResponse:
|
|
100
|
+
"""Complete a prompt using A3M Router."""
|
|
101
|
+
self._ensure_router()
|
|
102
|
+
|
|
103
|
+
messages = [{"role": "user", "content": prompt}]
|
|
104
|
+
|
|
105
|
+
import asyncio
|
|
106
|
+
loop = asyncio.get_event_loop()
|
|
107
|
+
route_result = loop.run_in_executor(
|
|
108
|
+
None,
|
|
109
|
+
lambda: self._a3m_router.route(
|
|
110
|
+
messages=messages,
|
|
111
|
+
temperature=self.temperature,
|
|
112
|
+
max_tokens=self.max_tokens,
|
|
113
|
+
**kwargs,
|
|
114
|
+
),
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
return CompletionResponse(text=route_result.content, raw=route_result)
|
|
118
|
+
|
|
119
|
+
def chat(self, messages: Sequence[ChatMessage], **kwargs: Any) -> CompletionResponse:
|
|
120
|
+
"""Chat completion using A3M Router."""
|
|
121
|
+
self._ensure_router()
|
|
122
|
+
|
|
123
|
+
a3m_messages = self._convert_messages(messages)
|
|
124
|
+
|
|
125
|
+
import asyncio
|
|
126
|
+
loop = asyncio.get_event_loop()
|
|
127
|
+
route_result = loop.run_in_executor(
|
|
128
|
+
None,
|
|
129
|
+
lambda: self._a3m_router.route(
|
|
130
|
+
messages=a3m_messages,
|
|
131
|
+
temperature=self.temperature,
|
|
132
|
+
max_tokens=self.max_tokens,
|
|
133
|
+
**kwargs,
|
|
134
|
+
),
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
return CompletionResponse(text=route_result.content, raw=route_result)
|
|
138
|
+
|
|
139
|
+
def _convert_messages(self, messages: Sequence[ChatMessage]) -> List[Dict[str, Any]]:
|
|
140
|
+
"""Convert LlamaIndex ChatMessages to A3M format."""
|
|
141
|
+
a3m_messages = []
|
|
142
|
+
for msg in messages:
|
|
143
|
+
role = msg.role.value if hasattr(msg.role, 'value') else str(msg.role).lower()
|
|
144
|
+
role_map = {
|
|
145
|
+
"system": "system",
|
|
146
|
+
"user": "user",
|
|
147
|
+
"assistant": "assistant",
|
|
148
|
+
"tool": "tool",
|
|
149
|
+
"function": "function",
|
|
150
|
+
}
|
|
151
|
+
a3m_role = role_map.get(role, "user")
|
|
152
|
+
a3m_messages.append({"role": a3m_role, "content": msg.content})
|
|
153
|
+
return a3m_messages
|
|
154
|
+
|
|
155
|
+
def __repr__(self) -> str:
|
|
156
|
+
return (
|
|
157
|
+
f"A3MLlamaIndexAdapter("
|
|
158
|
+
f"model={self.model!r}, "
|
|
159
|
+
f"temperature={self.temperature}, "
|
|
160
|
+
f"max_tokens={self.max_tokens}, "
|
|
161
|
+
f"ensemble={self.parallel_ensemble})"
|
|
162
|
+
)
|
|
@@ -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
|
+
)
|