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,172 @@
|
|
|
1
|
+
# A3M Router MCP Server (Python)
|
|
2
|
+
|
|
3
|
+
[MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server for [A3M Router](https://github.com/Das-rebel/a3m-router) — parallel multi-LLM execution for AI agents.
|
|
4
|
+
|
|
5
|
+
Allows any MCP-compatible AI agent (Claude Desktop, Cursor, Windsurf, Codex, n8n, etc.) to use A3M's intelligent routing and ensemble execution directly from Python.
|
|
6
|
+
|
|
7
|
+
## Tools
|
|
8
|
+
|
|
9
|
+
| Tool | Description |
|
|
10
|
+
|------|-------------|
|
|
11
|
+
| `a3m_route` | Route a query to the optimal LLM provider — returns model, tier, cost, reasoning |
|
|
12
|
+
| `a3m_ensemble` | Execute a query across multiple providers in parallel and merge results |
|
|
13
|
+
| `a3m_classify` | Classify query type (fast/creative/deep/code) and get provider recommendations |
|
|
14
|
+
| `a3m_providers` | List all configured providers with cost and availability |
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# From source
|
|
20
|
+
cd python/mcp-server
|
|
21
|
+
pip install -e .
|
|
22
|
+
|
|
23
|
+
# Or install the package directly (when published)
|
|
24
|
+
pip install a3m-mcp-server
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Configuration
|
|
28
|
+
|
|
29
|
+
Set environment variables:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Required: A3M Router URL (must be running)
|
|
33
|
+
export A3M_BASE_URL=http://localhost:8787
|
|
34
|
+
|
|
35
|
+
# Optional
|
|
36
|
+
export A3M_API_KEY=not-needed # default
|
|
37
|
+
export A3M_TIMEOUT=30.0
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Start A3M Router
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Node.js way
|
|
44
|
+
npx a3m-router serve
|
|
45
|
+
|
|
46
|
+
# Python way
|
|
47
|
+
pip install a3m-router
|
|
48
|
+
a3m-router serve
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Usage
|
|
52
|
+
|
|
53
|
+
### Claude Desktop
|
|
54
|
+
|
|
55
|
+
Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"mcpServers": {
|
|
60
|
+
"a3m-router": {
|
|
61
|
+
"command": "python",
|
|
62
|
+
"args": ["-m", "a3m_mcp"],
|
|
63
|
+
"env": {
|
|
64
|
+
"A3M_BASE_URL": "http://localhost:8787"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Cursor
|
|
72
|
+
|
|
73
|
+
Settings → Features → MCP Servers → Add:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
Name: A3M Router
|
|
77
|
+
Command: python
|
|
78
|
+
Arguments: -m a3m_mcp
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### n8n MCP Trigger
|
|
82
|
+
|
|
83
|
+
Add MCP Trigger node with:
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"server": "a3m-router",
|
|
88
|
+
"command": "python",
|
|
89
|
+
"args": ["-m", "a3m_mcp"]
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Direct stdio (pipe JSON-RPC)
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# List tools
|
|
97
|
+
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | python -m a3m_mcp
|
|
98
|
+
|
|
99
|
+
# Call a3m_route
|
|
100
|
+
echo '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"a3m_route","arguments":{"query":"Write a Python quicksort"}}}' | python -m a3m_mcp
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Examples
|
|
104
|
+
|
|
105
|
+
### Route a query
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
# a3m_route tool
|
|
109
|
+
Input: {"query": "Write a Python function to sort a list"}
|
|
110
|
+
Output:
|
|
111
|
+
{
|
|
112
|
+
"model": "groq/llama-3.3-70b-versatile",
|
|
113
|
+
"tier": "cheap",
|
|
114
|
+
"cost": 0.000012,
|
|
115
|
+
"reasoning": "Code query detected, routing to fast coding provider",
|
|
116
|
+
"classification": "code"
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Ensemble execution
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
# a3m_ensemble tool
|
|
124
|
+
Input: {"query": "Explain quantum computing in 3 sentences", "providers": ["groq", "google", "cerebras"]}
|
|
125
|
+
Output:
|
|
126
|
+
{
|
|
127
|
+
"query": "Explain quantum computing in 3 sentences",
|
|
128
|
+
"parallel_responses": [
|
|
129
|
+
{"provider": "groq", "content": "Quantum computing is..."},
|
|
130
|
+
{"provider": "google", "content": "At its core, quantum..."},
|
|
131
|
+
{"provider": "cerebras", "content": "Quantum computing leverages..."}
|
|
132
|
+
],
|
|
133
|
+
"best_answer": "Quantum computing is...",
|
|
134
|
+
"stats": {"total_providers": 3, "successful": 3, "failed": 0}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Architecture
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
┌──────────────────────────────────────────────────────┐
|
|
142
|
+
│ MCP Client (Claude, Cursor, Codex, n8n) │
|
|
143
|
+
└──────────────────────┬───────────────────────────────┘
|
|
144
|
+
│ MCP Protocol (stdio)
|
|
145
|
+
┌──────────────────────▼───────────────────────────────┐
|
|
146
|
+
│ A3M Router MCP Server (Python) │
|
|
147
|
+
│ │
|
|
148
|
+
│ a3m_route a3m_ensemble a3m_classify a3m_providers │
|
|
149
|
+
└──────────────────────┬───────────────────────────────┘
|
|
150
|
+
│ HTTP API
|
|
151
|
+
┌──────────────────────▼───────────────────────────────┐
|
|
152
|
+
│ A3M Router (a3m-router serve) │
|
|
153
|
+
│ │
|
|
154
|
+
│ routeQuery() parallelEnsemble() providers │
|
|
155
|
+
└───────┬────────────┬────────────┬─────────────────────┘
|
|
156
|
+
│ │ │
|
|
157
|
+
┌────▼───┐ ┌────▼───┐ ┌────▼───┐
|
|
158
|
+
│ Groq │ │ Google │ │Cerebras│ ...
|
|
159
|
+
└────────┘ └────────┘ └────────┘
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Testing
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
cd python/mcp-server
|
|
166
|
+
pip install -e ".[dev]"
|
|
167
|
+
pytest
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
MIT
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""A3M Router MCP Server — Model Context Protocol server for A3M Router.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
python -m a3m_mcp # Start the MCP server
|
|
5
|
+
pip install a3m-mcp-server # Install as package
|
|
6
|
+
"""
|
|
7
|
+
try:
|
|
8
|
+
from .server import main, HAS_MCP, SERVER_NAME
|
|
9
|
+
except ImportError:
|
|
10
|
+
main = None
|
|
11
|
+
HAS_MCP = False
|
|
12
|
+
SERVER_NAME = "a3m-router"
|
|
13
|
+
|
|
14
|
+
__version__ = "1.0.0"
|
|
15
|
+
__all__ = ["main", "HAS_MCP", "SERVER_NAME", "__version__"]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Entry point: python -m a3m_mcp"""
|
|
2
|
+
import sys
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
# Add parent directory to path so a3m_mcp can find sibling packages
|
|
6
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
7
|
+
|
|
8
|
+
from .server import main, HAS_MCP
|
|
9
|
+
|
|
10
|
+
if __name__ == "__main__":
|
|
11
|
+
if not HAS_MCP:
|
|
12
|
+
print("ERROR: mcp package required. Install with: pip install mcp", file=sys.stderr)
|
|
13
|
+
sys.exit(1)
|
|
14
|
+
import asyncio
|
|
15
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,339 @@
|
|
|
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
|
+
"""
|
|
14
|
+
import asyncio
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
17
|
+
import sys
|
|
18
|
+
from typing import Any, Dict, List, Optional
|
|
19
|
+
|
|
20
|
+
# MCP imports — use correct lowlevel API for installed version
|
|
21
|
+
try:
|
|
22
|
+
from mcp.server.lowlevel import Server
|
|
23
|
+
from mcp.server.stdio import stdio_server
|
|
24
|
+
from mcp.types import Tool, CallToolResult, TextContent
|
|
25
|
+
HAS_MCP = True
|
|
26
|
+
except ImportError as e:
|
|
27
|
+
HAS_MCP = False
|
|
28
|
+
print(f"WARNING: mcp package not installed. Run: pip install mcp. Error: {e}", file=sys.stderr)
|
|
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"))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class A3MRouterClient:
|
|
38
|
+
"""Thin async client for the A3M Router HTTP API."""
|
|
39
|
+
|
|
40
|
+
def __init__(self, base_url: str = A3M_BASE_URL, api_key: str = A3M_API_KEY):
|
|
41
|
+
self.base_url = base_url.rstrip("/")
|
|
42
|
+
self.api_key = api_key
|
|
43
|
+
self._client: Optional[Any] = None
|
|
44
|
+
|
|
45
|
+
async def _get_client(self) -> Any:
|
|
46
|
+
if self._client is None:
|
|
47
|
+
import httpx
|
|
48
|
+
self._client = httpx.AsyncClient(
|
|
49
|
+
base_url=self.base_url,
|
|
50
|
+
timeout=A3M_TIMEOUT,
|
|
51
|
+
headers={"Authorization": f"Bearer {self.api_key}"},
|
|
52
|
+
)
|
|
53
|
+
return self._client
|
|
54
|
+
|
|
55
|
+
async def close(self):
|
|
56
|
+
if self._client:
|
|
57
|
+
await self._client.aclose()
|
|
58
|
+
self._client = None
|
|
59
|
+
|
|
60
|
+
async def route(self, query: str) -> Dict[str, Any]:
|
|
61
|
+
"""Route a query — returns routing decision without LLM execution."""
|
|
62
|
+
client = await self._get_client()
|
|
63
|
+
try:
|
|
64
|
+
response = await client.get(
|
|
65
|
+
"/route",
|
|
66
|
+
params={"query": query},
|
|
67
|
+
headers={"Authorization": f"Bearer {self.api_key}"},
|
|
68
|
+
)
|
|
69
|
+
if response.status_code == 200:
|
|
70
|
+
return response.json()
|
|
71
|
+
except Exception:
|
|
72
|
+
pass
|
|
73
|
+
# Fallback
|
|
74
|
+
return {
|
|
75
|
+
"model": "groq/llama-3.3-70b-versatile",
|
|
76
|
+
"tier": "cheap",
|
|
77
|
+
"cost": 0.0,
|
|
78
|
+
"reasoning": "A3M adaptive routing",
|
|
79
|
+
"complexity": 0.3,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async def chat(
|
|
83
|
+
self,
|
|
84
|
+
query: str,
|
|
85
|
+
model: str = "auto",
|
|
86
|
+
system: Optional[str] = None,
|
|
87
|
+
max_tokens: int = 1024,
|
|
88
|
+
temperature: float = 0.7,
|
|
89
|
+
) -> Dict[str, Any]:
|
|
90
|
+
"""Send a chat message via A3M Router."""
|
|
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,
|
|
105
|
+
},
|
|
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
|
+
},
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async def classify(self, query: str) -> Dict[str, Any]:
|
|
147
|
+
"""Classify a query by type and get provider recommendations."""
|
|
148
|
+
route = await self.route(query)
|
|
149
|
+
complexity = route.get("complexity", 0.5)
|
|
150
|
+
model = route.get("model", "groq/llama-3.3-70b-versatile")
|
|
151
|
+
|
|
152
|
+
query_lower = query.lower()
|
|
153
|
+
if any(k in query_lower for k in ["write code", "function", "class", "def ", "import "]):
|
|
154
|
+
classification = "code"
|
|
155
|
+
recommended = ["groq/llama-3.3-70b-versatile", "cerebras/cerebras-coder"]
|
|
156
|
+
elif any(k in query_lower for k in ["explain", "what is", "how does", "why is"]):
|
|
157
|
+
classification = "fast"
|
|
158
|
+
recommended = ["groq/llama-3.1-8b-instant", "google/gemini-2.0-flash"]
|
|
159
|
+
elif complexity >= 0.65:
|
|
160
|
+
classification = "deep"
|
|
161
|
+
recommended = ["openai/gpt-4o", "anthropic/claude-3.5-sonnet"]
|
|
162
|
+
else:
|
|
163
|
+
classification = "creative"
|
|
164
|
+
recommended = ["groq/llama-3.3-70b-versatile", "google/gemini-2.0-flash"]
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
"query": query,
|
|
168
|
+
"classification": classification,
|
|
169
|
+
"complexity": complexity,
|
|
170
|
+
"recommended_model": model,
|
|
171
|
+
"recommended_providers": recommended,
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async def providers(self) -> Dict[str, Any]:
|
|
175
|
+
"""List all configured providers with cost and status."""
|
|
176
|
+
client = await self._get_client()
|
|
177
|
+
try:
|
|
178
|
+
response = await client.get(
|
|
179
|
+
"/providers",
|
|
180
|
+
headers={"Authorization": f"Bearer {self.api_key}"},
|
|
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
|
+
|
|
198
|
+
|
|
199
|
+
# ─── MCP Server ───────────────────────────────────────────────────────────────
|
|
200
|
+
|
|
201
|
+
if HAS_MCP:
|
|
202
|
+
SERVER_NAME = "a3m-router"
|
|
203
|
+
SERVER_VERSION = "1.0.0"
|
|
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
|
+
]
|
|
279
|
+
|
|
280
|
+
@server.call_tool()
|
|
281
|
+
async def call_tool(name: str, arguments: Dict[str, Any]) -> CallToolResult:
|
|
282
|
+
"""Execute an MCP tool call."""
|
|
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"),
|
|
295
|
+
)
|
|
296
|
+
text = json.dumps(result, indent=2)
|
|
297
|
+
return CallToolResult(content=[TextContent(type="text", text=text)])
|
|
298
|
+
|
|
299
|
+
elif name == "a3m_classify":
|
|
300
|
+
result = await router.classify(arguments["query"])
|
|
301
|
+
text = json.dumps(result, indent=2)
|
|
302
|
+
return CallToolResult(content=[TextContent(type="text", text=text)])
|
|
303
|
+
|
|
304
|
+
elif name == "a3m_providers":
|
|
305
|
+
result = await router.providers()
|
|
306
|
+
text = json.dumps(result, indent=2)
|
|
307
|
+
return CallToolResult(content=[TextContent(type="text", text=text)])
|
|
308
|
+
|
|
309
|
+
else:
|
|
310
|
+
return CallToolResult(
|
|
311
|
+
content=[TextContent(type="text", text=f"Unknown tool: {name}")],
|
|
312
|
+
isError=True,
|
|
313
|
+
)
|
|
314
|
+
except Exception as e:
|
|
315
|
+
return CallToolResult(
|
|
316
|
+
content=[TextContent(type="text", text=f"Error: {str(e)}")],
|
|
317
|
+
isError=True,
|
|
318
|
+
)
|
|
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
|
+
|
|
334
|
+
|
|
335
|
+
if __name__ == "__main__":
|
|
336
|
+
if not HAS_MCP:
|
|
337
|
+
print("ERROR: mcp package required. Install with: pip install mcp", file=sys.stderr)
|
|
338
|
+
sys.exit(1)
|
|
339
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "a3m-mcp-server"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "MCP server for A3M Router — parallel multi-LLM execution for AI agents"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"mcp>=1.0.0",
|
|
14
|
+
"httpx>=0.24.0",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.optional-dependencies]
|
|
18
|
+
dev = ["pytest>=7.4.0", "pytest-asyncio>=0.21.0", "black>=23.0.0"]
|
|
19
|
+
|
|
20
|
+
[project.urls]
|
|
21
|
+
Homepage = "https://github.com/Das-rebel/a3m-router"
|
|
22
|
+
|
|
23
|
+
[tool.setuptools.packages.find]
|
|
24
|
+
where = ["."]
|
package/python/pyproject.toml
CHANGED
|
@@ -1,23 +1,74 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["setuptools>=
|
|
3
|
-
build-backend = "setuptools.
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "a3m-router"
|
|
7
|
-
version = "2.
|
|
8
|
-
description = "
|
|
7
|
+
version = "2.2.2"
|
|
8
|
+
description = "Auto-selects cheapest capable LLM from 47+ providers — 70-95% cost savings"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
11
11
|
requires-python = ">=3.8"
|
|
12
12
|
dependencies = ["httpx>=0.24.0"]
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
keywords = [
|
|
15
|
+
"llm",
|
|
16
|
+
"routing",
|
|
17
|
+
"openai",
|
|
18
|
+
"proxy",
|
|
19
|
+
"ai-gateway",
|
|
20
|
+
"routellm",
|
|
21
|
+
"litellm",
|
|
22
|
+
"multi-llm",
|
|
23
|
+
"model-selection",
|
|
24
|
+
"cost-optimization",
|
|
25
|
+
"langchain",
|
|
26
|
+
"llamaindex",
|
|
27
|
+
"rag",
|
|
28
|
+
"vector-search",
|
|
29
|
+
"embeddings",
|
|
30
|
+
"parallel",
|
|
31
|
+
"ensemble",
|
|
32
|
+
"router",
|
|
33
|
+
]
|
|
34
|
+
|
|
14
35
|
classifiers = [
|
|
15
36
|
"Development Status :: 4 - Beta",
|
|
16
37
|
"Intended Audience :: Developers",
|
|
17
38
|
"License :: OSI Approved :: MIT License",
|
|
39
|
+
"Operating System :: OS Independent",
|
|
18
40
|
"Programming Language :: Python :: 3",
|
|
41
|
+
"Programming Language :: Python :: 3.8",
|
|
42
|
+
"Programming Language :: Python :: 3.9",
|
|
43
|
+
"Programming Language :: Python :: 3.10",
|
|
44
|
+
"Programming Language :: Python :: 3.11",
|
|
45
|
+
"Programming Language :: Python :: 3.12",
|
|
19
46
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
47
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
20
48
|
]
|
|
21
49
|
|
|
50
|
+
[project.optional-dependencies]
|
|
51
|
+
langchain = ["langchain>=0.1.0"]
|
|
52
|
+
llamaindex = ["llamaindex>=0.9.0"]
|
|
53
|
+
qdrant = ["qdrant-client>=1.7.0"]
|
|
54
|
+
weaviate = ["weaviate-client>=4.0.0"]
|
|
55
|
+
adapters = [
|
|
56
|
+
"langchain>=0.1.0",
|
|
57
|
+
"llamaindex>=0.9.0",
|
|
58
|
+
"qdrant-client>=1.7.0",
|
|
59
|
+
"weaviate-client>=4.0.0",
|
|
60
|
+
]
|
|
61
|
+
dev = ["pytest>=7.4.0", "pytest-asyncio>=0.21.0", "black>=23.0.0"]
|
|
62
|
+
|
|
22
63
|
[project.urls]
|
|
23
64
|
Homepage = "https://github.com/Das-rebel/a3m-router"
|
|
65
|
+
Documentation = "https://das-rebel.github.io/a3m-router"
|
|
66
|
+
Repository = "https://github.com/Das-rebel/a3m-router"
|
|
67
|
+
Changelog = "https://github.com/Das-rebel/a3m-router/releases"
|
|
68
|
+
|
|
69
|
+
[tool.setuptools.packages.find]
|
|
70
|
+
where = ["."]
|
|
71
|
+
include = ["a3m*"]
|
|
72
|
+
|
|
73
|
+
[tool.black]
|
|
74
|
+
line-length = 100
|
package/python/setup.py
CHANGED
|
@@ -1,28 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
"description": "Python bindings for tmlpd-pi - Parallel Multi-LLM Processing with TypeScript compatibility",
|
|
5
|
-
"main": "tmlpd.py",
|
|
6
|
-
"keywords": [
|
|
7
|
-
"tmlpd",
|
|
8
|
-
"python",
|
|
9
|
-
"bindings",
|
|
10
|
-
"parallel",
|
|
11
|
-
"multi-llm",
|
|
12
|
-
"llm",
|
|
13
|
-
"openai",
|
|
14
|
-
"anthropic",
|
|
15
|
-
"google",
|
|
16
|
-
"groq",
|
|
17
|
-
"cerebras",
|
|
18
|
-
"langchain",
|
|
19
|
-
"agent",
|
|
20
|
-
"orchestration"
|
|
21
|
-
],
|
|
22
|
-
"author": "Subho Das",
|
|
23
|
-
"license": "MIT",
|
|
24
|
-
"python_requires": ">=3.8",
|
|
25
|
-
"dependencies": {},
|
|
26
|
-
"devDependencies": {},
|
|
27
|
-
"readme": "See python/examples.py for usage"
|
|
28
|
-
}
|
|
1
|
+
from setuptools import setup
|
|
2
|
+
|
|
3
|
+
setup()
|