adaptive-memory-multi-model-router 2.15.5 → 2.16.1
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/auto-submit-sitemap.yml +41 -0
- package/.github/workflows/mcp-pypi-publish.yml +34 -0
- package/.github/workflows/pypi-publish.yml +61 -17
- package/.github/workflows/tmlpd-publish.yml +23 -0
- package/README.md +181 -207
- package/RELEASE_v2.16.0.md +149 -0
- package/TECHNICAL_README.md +253 -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.js +96 -67
- 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.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/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/deepseek.html +44 -0
- package/docs/google.html +47 -0
- package/docs/groq.html +44 -0
- 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 +59 -0
- 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/package.json +36 -124
- package/python/README.md +33 -106
- package/python/mcp-server/a3m_mcp/__init__.py +20 -10
- package/python/mcp-server/a3m_mcp/server.py +172 -306
- package/python/pyproject.toml +6 -3
- 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 +2 -0
- 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/python/a3m_router.egg-info/PKG-INFO +0 -172
- package/python/a3m_router.egg-info/SOURCES.txt +0 -17
- package/python/a3m_router.egg-info/dependency_links.txt +0 -1
- package/python/a3m_router.egg-info/requires.txt +0 -24
- package/python/a3m_router.egg-info/top_level.txt +0 -1
- 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/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
|
@@ -1,339 +1,205 @@
|
|
|
1
|
-
"""A3M Router MCP Server — 4 tools for parallel multi-LLM routing.
|
|
2
|
-
|
|
3
|
-
Tools:
|
|
4
|
-
a3m_route - Route a query to the optimal LLM provider (no execution)
|
|
5
|
-
a3m_ensemble - Execute query across multiple providers in parallel
|
|
6
|
-
a3m_classify - Classify query type and get provider recommendations
|
|
7
|
-
a3m_providers - List all configured providers with cost and availability
|
|
8
|
-
|
|
9
|
-
Run:
|
|
10
|
-
python -m a3m_mcp
|
|
11
|
-
# Or pipe JSON-RPC directly:
|
|
12
|
-
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | python -m a3m_mcp
|
|
13
1
|
"""
|
|
2
|
+
A3M Router MCP Server - MCP Protocol Server for A3M Router
|
|
3
|
+
|
|
4
|
+
MCP 2.0.0 compatible server that provides A3M Router functionality
|
|
5
|
+
as MCP tools for AI agents.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
14
9
|
import asyncio
|
|
15
10
|
import json
|
|
16
|
-
import
|
|
17
|
-
import sys
|
|
18
|
-
from typing import Any, Dict, List, Optional
|
|
11
|
+
from typing import Optional, List, Any
|
|
19
12
|
|
|
20
|
-
# MCP imports — use correct lowlevel API for installed version
|
|
21
13
|
try:
|
|
22
14
|
from mcp.server.lowlevel import Server
|
|
15
|
+
from mcp import Tool, ListToolsResult
|
|
16
|
+
from mcp.types import CallToolResult, TextContent
|
|
23
17
|
from mcp.server.stdio import stdio_server
|
|
24
|
-
from mcp.types import Tool, CallToolResult, TextContent
|
|
25
18
|
HAS_MCP = True
|
|
26
|
-
except ImportError
|
|
19
|
+
except ImportError:
|
|
27
20
|
HAS_MCP = False
|
|
28
|
-
print(
|
|
29
|
-
|
|
30
|
-
# ─── A3M Router client ──────────────────────────────────────────────────────
|
|
31
|
-
|
|
32
|
-
A3M_BASE_URL = os.getenv("A3M_BASE_URL", "http://localhost:8787")
|
|
33
|
-
A3M_API_KEY = os.getenv("A3M_API_KEY", "not-needed")
|
|
34
|
-
A3M_TIMEOUT = float(os.getenv("A3M_TIMEOUT", "30.0"))
|
|
21
|
+
print("WARNING: mcp package not installed. Run: pip install mcp", flush=True)
|
|
35
22
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
"
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
client = await self._get_client()
|
|
92
|
-
messages = []
|
|
93
|
-
if system:
|
|
94
|
-
messages.append({"role": "system", "content": system})
|
|
95
|
-
messages.append({"role": "user", "content": query})
|
|
96
|
-
|
|
97
|
-
try:
|
|
98
|
-
response = await client.post(
|
|
99
|
-
"/v1/chat/completions",
|
|
100
|
-
json={
|
|
101
|
-
"model": model,
|
|
102
|
-
"messages": messages,
|
|
103
|
-
"max_tokens": max_tokens,
|
|
104
|
-
"temperature": temperature,
|
|
23
|
+
# A3M Router client
|
|
24
|
+
try:
|
|
25
|
+
import a3m
|
|
26
|
+
A3MRouter = a3m.A3MRouter
|
|
27
|
+
except ImportError:
|
|
28
|
+
A3MRouter = None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
SERVER_NAME = "a3m-router"
|
|
32
|
+
SERVER_VERSION = "1.0.0"
|
|
33
|
+
|
|
34
|
+
_router: Optional["A3MRouter"] = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_router() -> "A3MRouter":
|
|
38
|
+
"""Get or create A3MRouter instance."""
|
|
39
|
+
global _router
|
|
40
|
+
if _router is None:
|
|
41
|
+
base_url = os.environ.get("A3M_BASE_URL", "http://localhost:8787")
|
|
42
|
+
api_key = os.environ.get("A3M_API_KEY", "")
|
|
43
|
+
_router = A3MRouter(base_url=base_url, api_key=api_key)
|
|
44
|
+
return _router
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# Tool definitions
|
|
48
|
+
TOOLS = [
|
|
49
|
+
Tool(
|
|
50
|
+
name="a3m_route",
|
|
51
|
+
title="Route Query",
|
|
52
|
+
description="Route a query to the optimal LLM provider using A3M's adaptive routing. Returns model selection, tier, estimated cost, and reasoning.",
|
|
53
|
+
inputSchema={
|
|
54
|
+
"type": "object",
|
|
55
|
+
"properties": {
|
|
56
|
+
"query": {
|
|
57
|
+
"type": "string",
|
|
58
|
+
"description": "The user query to route (e.g. 'Write Python code to sort a list')",
|
|
59
|
+
},
|
|
60
|
+
"provider": {
|
|
61
|
+
"type": "string",
|
|
62
|
+
"description": "Optional: Force a specific provider (e.g. 'groq', 'google')",
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
"required": ["query"],
|
|
66
|
+
},
|
|
67
|
+
),
|
|
68
|
+
Tool(
|
|
69
|
+
name="a3m_chat",
|
|
70
|
+
title="Chat with A3M",
|
|
71
|
+
description="Send a chat message through A3M Router's adaptive routing.",
|
|
72
|
+
inputSchema={
|
|
73
|
+
"type": "object",
|
|
74
|
+
"properties": {
|
|
75
|
+
"query": {
|
|
76
|
+
"type": "string",
|
|
77
|
+
"description": "The user query",
|
|
105
78
|
},
|
|
106
|
-
headers={"Authorization": f"Bearer {self.api_key}"},
|
|
107
|
-
)
|
|
108
|
-
response.raise_for_status()
|
|
109
|
-
return response.json()
|
|
110
|
-
except Exception as e:
|
|
111
|
-
return {"error": str(e), "choices": [{"message": {"content": f"Error: {e}"}}]}
|
|
112
|
-
|
|
113
|
-
async def ensemble(
|
|
114
|
-
self,
|
|
115
|
-
query: str,
|
|
116
|
-
providers: Optional[List[str]] = None,
|
|
117
|
-
system: Optional[str] = None,
|
|
118
|
-
) -> Dict[str, Any]:
|
|
119
|
-
"""Execute query across multiple providers in parallel."""
|
|
120
|
-
providers = providers or ["groq", "google", "cerebras"]
|
|
121
|
-
tasks = [
|
|
122
|
-
self.chat(query, model=p, system=system)
|
|
123
|
-
for p in providers
|
|
124
|
-
]
|
|
125
|
-
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
126
|
-
|
|
127
|
-
responses = []
|
|
128
|
-
for i, r in enumerate(results):
|
|
129
|
-
if isinstance(r, Exception):
|
|
130
|
-
responses.append({"provider": providers[i], "error": str(r)})
|
|
131
|
-
else:
|
|
132
|
-
content = r.get("choices", [{}])[0].get("message", {}).get("content", "")
|
|
133
|
-
responses.append({"provider": providers[i], "content": content})
|
|
134
|
-
|
|
135
|
-
return {
|
|
136
|
-
"query": query,
|
|
137
|
-
"parallel_responses": responses,
|
|
138
|
-
"best_answer": responses[0].get("content", "") if responses else "",
|
|
139
|
-
"stats": {
|
|
140
|
-
"total_providers": len(providers),
|
|
141
|
-
"successful": sum(1 for r in responses if "error" not in r),
|
|
142
|
-
"failed": sum(1 for r in responses if "error" in r),
|
|
143
79
|
},
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
async def
|
|
175
|
-
"""
|
|
176
|
-
|
|
80
|
+
"required": ["query"],
|
|
81
|
+
},
|
|
82
|
+
),
|
|
83
|
+
Tool(
|
|
84
|
+
name="a3m_cost_report",
|
|
85
|
+
title="Cost Report",
|
|
86
|
+
description="Get cost analytics across all providers.",
|
|
87
|
+
inputSchema={
|
|
88
|
+
"type": "object",
|
|
89
|
+
"properties": {},
|
|
90
|
+
},
|
|
91
|
+
),
|
|
92
|
+
Tool(
|
|
93
|
+
name="a3m_models",
|
|
94
|
+
title="List Models",
|
|
95
|
+
description="List all available models with cost/latency info.",
|
|
96
|
+
inputSchema={
|
|
97
|
+
"type": "object",
|
|
98
|
+
"properties": {},
|
|
99
|
+
},
|
|
100
|
+
),
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class A3MServer:
|
|
105
|
+
"""A3M Router MCP Server with tool handlers."""
|
|
106
|
+
|
|
107
|
+
def __init__(self):
|
|
108
|
+
pass
|
|
109
|
+
|
|
110
|
+
async def handle_list_tools(self, request_context=None) -> ListToolsResult:
|
|
111
|
+
"""Handle tools/list request."""
|
|
112
|
+
return ListToolsResult(
|
|
113
|
+
tools=[
|
|
114
|
+
{
|
|
115
|
+
"name": t.name,
|
|
116
|
+
"description": t.description,
|
|
117
|
+
"inputSchema": t.inputSchema,
|
|
118
|
+
}
|
|
119
|
+
for t in TOOLS
|
|
120
|
+
]
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
async def handle_call_tool(self, request_context, name: str, arguments: dict) -> CallToolResult:
|
|
124
|
+
"""Handle tools/call request."""
|
|
177
125
|
try:
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if response.status_code == 200:
|
|
183
|
-
return response.json()
|
|
184
|
-
except Exception:
|
|
185
|
-
pass
|
|
186
|
-
|
|
187
|
-
default_providers = [
|
|
188
|
-
{"name": "groq", "models": ["llama-3.3-70b-versatile", "llama-3.1-8b-instant"], "cost_per_1k_input": 0.0, "status": "available"},
|
|
189
|
-
{"name": "google", "models": ["gemini-2.0-flash", "gemini-1.5-flash"], "cost_per_1k_input": 0.0, "status": "available"},
|
|
190
|
-
{"name": "cerebras", "models": ["cerebras-coder", "cerebras-llama-3.3-70b"], "cost_per_1k_input": 0.0, "status": "available"},
|
|
191
|
-
{"name": "openai", "models": ["gpt-4o", "gpt-4o-mini"], "cost_per_1k_input": 0.015, "status": "available"},
|
|
192
|
-
{"name": "anthropic", "models": ["claude-3.5-sonnet", "claude-3-opus"], "cost_per_1k_input": 0.015, "status": "available"},
|
|
193
|
-
{"name": "mistral", "models": ["mistral-large", "mistral-small"], "cost_per_1k_input": 0.008, "status": "available"},
|
|
194
|
-
{"name": "deepseek", "models": ["deepseek-chat", "deepseek-coder"], "cost_per_1k_input": 0.001, "status": "available"},
|
|
195
|
-
]
|
|
196
|
-
return {"providers": default_providers, "source": "fallback"}
|
|
197
|
-
|
|
126
|
+
if name == "a3m_route":
|
|
127
|
+
router = get_router()
|
|
128
|
+
query = arguments.get("query")
|
|
129
|
+
provider = arguments.get("provider")
|
|
198
130
|
|
|
199
|
-
|
|
131
|
+
result = router.route(query, provider=provider)
|
|
132
|
+
return CallToolResult(
|
|
133
|
+
content=[TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
134
|
+
)
|
|
200
135
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
_router: Optional[A3MRouterClient] = None
|
|
206
|
-
|
|
207
|
-
def get_router() -> A3MRouterClient:
|
|
208
|
-
global _router
|
|
209
|
-
if _router is None:
|
|
210
|
-
_router = A3MRouterClient()
|
|
211
|
-
return _router
|
|
212
|
-
|
|
213
|
-
server = Server(SERVER_NAME)
|
|
214
|
-
|
|
215
|
-
@server.list_tools()
|
|
216
|
-
async def list_tools() -> List[Tool]:
|
|
217
|
-
"""List all available MCP tools."""
|
|
218
|
-
return [
|
|
219
|
-
Tool(
|
|
220
|
-
name="a3m_route",
|
|
221
|
-
description="Route a query to the optimal LLM provider using A3M's adaptive routing. Returns model selection, tier, estimated cost, and reasoning — no LLM execution.",
|
|
222
|
-
inputSchema={
|
|
223
|
-
"type": "object",
|
|
224
|
-
"properties": {
|
|
225
|
-
"query": {
|
|
226
|
-
"type": "string",
|
|
227
|
-
"description": "The user query to route (e.g. 'Write Python code to sort a list')",
|
|
228
|
-
}
|
|
229
|
-
},
|
|
230
|
-
"required": ["query"],
|
|
231
|
-
},
|
|
232
|
-
),
|
|
233
|
-
Tool(
|
|
234
|
-
name="a3m_ensemble",
|
|
235
|
-
description="Execute a query across multiple LLM providers in parallel and merge results. Returns all responses with stats on success/failure per provider.",
|
|
236
|
-
inputSchema={
|
|
237
|
-
"type": "object",
|
|
238
|
-
"properties": {
|
|
239
|
-
"query": {
|
|
240
|
-
"type": "string",
|
|
241
|
-
"description": "The user query to execute across providers",
|
|
242
|
-
},
|
|
243
|
-
"providers": {
|
|
244
|
-
"type": "array",
|
|
245
|
-
"items": {"type": "string"},
|
|
246
|
-
"description": "Optional list of provider names. Defaults to ['groq', 'google', 'cerebras']",
|
|
247
|
-
},
|
|
248
|
-
"system": {
|
|
249
|
-
"type": "string",
|
|
250
|
-
"description": "Optional system prompt to prepend",
|
|
251
|
-
},
|
|
252
|
-
},
|
|
253
|
-
"required": ["query"],
|
|
254
|
-
},
|
|
255
|
-
),
|
|
256
|
-
Tool(
|
|
257
|
-
name="a3m_classify",
|
|
258
|
-
description="Classify a query by type (fast/creative/deep/code) and get provider recommendations based on query complexity.",
|
|
259
|
-
inputSchema={
|
|
260
|
-
"type": "object",
|
|
261
|
-
"properties": {
|
|
262
|
-
"query": {
|
|
263
|
-
"type": "string",
|
|
264
|
-
"description": "The query to classify",
|
|
265
|
-
}
|
|
266
|
-
},
|
|
267
|
-
"required": ["query"],
|
|
268
|
-
},
|
|
269
|
-
),
|
|
270
|
-
Tool(
|
|
271
|
-
name="a3m_providers",
|
|
272
|
-
description="List all configured LLM providers with their models, cost per 1K tokens, and availability status.",
|
|
273
|
-
inputSchema={
|
|
274
|
-
"type": "object",
|
|
275
|
-
"properties": {},
|
|
276
|
-
},
|
|
277
|
-
),
|
|
278
|
-
]
|
|
136
|
+
elif name == "a3m_chat":
|
|
137
|
+
router = get_router()
|
|
138
|
+
query = arguments.get("query")
|
|
279
139
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
router = get_router()
|
|
284
|
-
try:
|
|
285
|
-
if name == "a3m_route":
|
|
286
|
-
result = await router.route(arguments["query"])
|
|
287
|
-
text = json.dumps(result, indent=2)
|
|
288
|
-
return CallToolResult(content=[TextContent(type="text", text=text)])
|
|
289
|
-
|
|
290
|
-
elif name == "a3m_ensemble":
|
|
291
|
-
result = await router.ensemble(
|
|
292
|
-
query=arguments["query"],
|
|
293
|
-
providers=arguments.get("providers"),
|
|
294
|
-
system=arguments.get("system"),
|
|
140
|
+
result = router.chat(query)
|
|
141
|
+
return CallToolResult(
|
|
142
|
+
content=[TextContent(type="text", text=str(result))]
|
|
295
143
|
)
|
|
296
|
-
text = json.dumps(result, indent=2)
|
|
297
|
-
return CallToolResult(content=[TextContent(type="text", text=text)])
|
|
298
144
|
|
|
299
|
-
elif name == "
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
return CallToolResult(
|
|
145
|
+
elif name == "a3m_cost_report":
|
|
146
|
+
router = get_router()
|
|
147
|
+
report = router.cost_report()
|
|
148
|
+
return CallToolResult(
|
|
149
|
+
content=[TextContent(type="text", text=json.dumps(report, indent=2))]
|
|
150
|
+
)
|
|
303
151
|
|
|
304
|
-
elif name == "
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
return CallToolResult(
|
|
152
|
+
elif name == "a3m_models":
|
|
153
|
+
router = get_router()
|
|
154
|
+
models = router.models()
|
|
155
|
+
return CallToolResult(
|
|
156
|
+
content=[TextContent(type="text", text=json.dumps(models, indent=2))]
|
|
157
|
+
)
|
|
308
158
|
|
|
309
159
|
else:
|
|
310
160
|
return CallToolResult(
|
|
311
161
|
content=[TextContent(type="text", text=f"Unknown tool: {name}")],
|
|
312
162
|
isError=True,
|
|
313
163
|
)
|
|
164
|
+
|
|
314
165
|
except Exception as e:
|
|
315
166
|
return CallToolResult(
|
|
316
167
|
content=[TextContent(type="text", text=f"Error: {str(e)}")],
|
|
317
168
|
isError=True,
|
|
318
169
|
)
|
|
319
|
-
finally:
|
|
320
|
-
await router.close()
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
async def main():
|
|
324
|
-
"""Run the MCP server over stdio."""
|
|
325
|
-
print("A3M Router MCP Server starting...", file=sys.stderr)
|
|
326
|
-
print(f"Connecting to A3M Router at {A3M_BASE_URL}", file=sys.stderr)
|
|
327
|
-
async with stdio_server() as (read_stream, write_stream):
|
|
328
|
-
await server.run(
|
|
329
|
-
read_stream,
|
|
330
|
-
write_stream,
|
|
331
|
-
server.create_initialization_options(),
|
|
332
|
-
)
|
|
333
170
|
|
|
334
171
|
|
|
335
|
-
if
|
|
172
|
+
if HAS_MCP:
|
|
173
|
+
# Create server with handlers
|
|
174
|
+
a3m_server = A3MServer()
|
|
175
|
+
|
|
176
|
+
server = Server(
|
|
177
|
+
SERVER_NAME,
|
|
178
|
+
version=SERVER_VERSION,
|
|
179
|
+
on_list_tools=a3m_server.handle_list_tools,
|
|
180
|
+
on_call_tool=a3m_server.handle_call_tool,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
async def main():
|
|
185
|
+
"""Main entry point for MCP server."""
|
|
336
186
|
if not HAS_MCP:
|
|
337
|
-
print("ERROR: mcp package
|
|
338
|
-
|
|
339
|
-
|
|
187
|
+
print("ERROR: mcp package not installed", flush=True)
|
|
188
|
+
return
|
|
189
|
+
|
|
190
|
+
print(f"Starting {SERVER_NAME} MCP Server v{SERVER_VERSION}", flush=True)
|
|
191
|
+
|
|
192
|
+
async with stdio_server() as (read_stream, write_stream):
|
|
193
|
+
await server.run(
|
|
194
|
+
read_stream,
|
|
195
|
+
write_stream,
|
|
196
|
+
server.create_initialization_options(),
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
if __name__ == "__main__":
|
|
201
|
+
if HAS_MCP:
|
|
202
|
+
asyncio.run(main())
|
|
203
|
+
else:
|
|
204
|
+
print("ERROR: mcp package required", flush=True)
|
|
205
|
+
print("Run: pip install mcp", flush=True)
|
package/python/pyproject.toml
CHANGED
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "a3m-router"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.16.1"
|
|
8
8
|
description = "Auto-selects cheapest capable LLM from 47+ providers — 70-95% cost savings"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -61,10 +61,13 @@ adapters = [
|
|
|
61
61
|
dev = ["pytest>=7.4.0", "pytest-asyncio>=0.21.0", "black>=23.0.0"]
|
|
62
62
|
|
|
63
63
|
[project.urls]
|
|
64
|
-
Homepage = "https://github.com/Das-rebel/a3m-router"
|
|
65
|
-
Documentation = "https://
|
|
64
|
+
Homepage = "https://github.com/Das-rebel/a3m-router#a3m-router"
|
|
65
|
+
Documentation = "https://github.com/Das-rebel/a3m-router#readme"
|
|
66
66
|
Repository = "https://github.com/Das-rebel/a3m-router"
|
|
67
67
|
Changelog = "https://github.com/Das-rebel/a3m-router/releases"
|
|
68
|
+
"NPM Package" = "https://www.npmjs.com/package/adaptive-memory-multi-model-router"
|
|
69
|
+
"PyPI Package" = "https://pypi.org/project/a3m-router/"
|
|
70
|
+
"GitHub Stars" = "https://github.com/Das-rebel/a3m-router/stargazers"
|
|
68
71
|
|
|
69
72
|
[tool.setuptools.packages.find]
|
|
70
73
|
where = ["."]
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# A3M Router — Sitemap Submission Script
|
|
4
|
+
# Submits sitemap to Google, Bing, and IndexNow for faster indexing
|
|
5
|
+
#
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
SITEMAP_URL="${SITEMAP_URL:-https://a3m-router.com/sitemap.xml}"
|
|
9
|
+
INDEXNOW_KEY="${INDEXNOW_KEY:-}"
|
|
10
|
+
|
|
11
|
+
echo "=== A3M Router Sitemap Submission ==="
|
|
12
|
+
echo "Sitemap: $SITEMAP_URL"
|
|
13
|
+
echo ""
|
|
14
|
+
|
|
15
|
+
# ─── Google ───────────────────────────────────────────
|
|
16
|
+
echo "[1/3] Submitting to Google..."
|
|
17
|
+
GOOGLE_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
|
|
18
|
+
"https://www.google.com/ping?sitemap=$SITEMAP_URL")
|
|
19
|
+
if [ "$GOOGLE_STATUS" = "200" ]; then
|
|
20
|
+
echo " ✅ Google: OK (HTTP $GOOGLE_STATUS)"
|
|
21
|
+
else
|
|
22
|
+
echo " ⚠️ Google: HTTP $GOOGLE_STATUS"
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
# ─── Bing ─────────────────────────────────────────────
|
|
26
|
+
echo "[2/3] Submitting to Bing..."
|
|
27
|
+
BING_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
|
|
28
|
+
"https://www.bing.com/ping?sitemap=$SITEMAP_URL")
|
|
29
|
+
if [ "$BING_STATUS" = "200" ]; then
|
|
30
|
+
echo " ✅ Bing: OK (HTTP $BING_STATUS)"
|
|
31
|
+
else
|
|
32
|
+
echo " ⚠️ Bing: HTTP $BING_STATUS"
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
# ─── IndexNow ─────────────────────────────────────────
|
|
36
|
+
echo "[3/3] Submitting to IndexNow..."
|
|
37
|
+
if [ -z "$INDEXNOW_KEY" ]; then
|
|
38
|
+
echo " ⚠️ Skipped: INDEXNOW_KEY env var not set"
|
|
39
|
+
echo " To enable IndexNow: export INDEXNOW_KEY=your_key_from_indexnow.org"
|
|
40
|
+
else
|
|
41
|
+
# Get the sitemap URLs and submit each to IndexNow
|
|
42
|
+
curl -s "$SITEMAP_URL" | grep -oP '(?<=<loc>)[^<]+' | while read -r url; do
|
|
43
|
+
RESPONSE=$(curl -s -w "%{http_code}" -o /dev/null -X POST \
|
|
44
|
+
"https://indexnow.org/ping" \
|
|
45
|
+
-H "Content-Type: text/plain" \
|
|
46
|
+
-d "url=$url&key=$INDEXNOW_KEY")
|
|
47
|
+
echo " IndexNow: $url → HTTP $RESPONSE"
|
|
48
|
+
done
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
echo ""
|
|
52
|
+
echo "=== Done ==="
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
declare module '@my-org/a3m-router/src/providers/registry' {
|
|
2
|
+
const PROVIDERS: Map<string, any>;
|
|
3
|
+
const registry: Map<string, any>;
|
|
4
|
+
export function registerProvider(name: string, config: any): void;
|
|
5
|
+
export function getProvider(name: string): any;
|
|
6
|
+
export function listProviders(): Array<[string, any]>;
|
|
7
|
+
export interface ProviderRegistry {
|
|
8
|
+
registerProvider: (name: string, config: any) => void;
|
|
9
|
+
getProvider: (name: string) => any;
|
|
10
|
+
listProviders: () => Array<[string, any]>;
|
|
11
|
+
}
|
|
12
|
+
const registryInstance: ProviderRegistry;
|
|
13
|
+
export default registryInstance;
|
|
14
|
+
}
|