adaptive-memory-multi-model-router 2.1.0 → 2.2.0

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/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "adaptive-memory-multi-model-router",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "shortName": "A3M Router",
5
5
  "displayName": "A3M Router - Adaptive Memory Multi-Model Router",
6
- "description": "99.5% ±1 tier routing accuracy, zero ML. Exceeds RouteLLM BERT. Drop-in OpenAI proxy, 40 providers, domain-aware. 245% growth in 3 days.",
6
+ "description": "LLM router & AI gateway with OpenAI-compatible proxy. 99.5% routing accuracy, 40 providers (Groq, Cerebras, Ollama, DeepSeek, Mistral). Semantic cache, guardrails, cost optimization. Drop-in for LangChain, Vercel AI SDK.",
7
7
  "main": "dist/index.js",
8
8
  "bin": {
9
9
  "a3m-router": "dist/cli.js",
@@ -46,38 +46,78 @@
46
46
  "import": "./dist/analytics/costAnalytics.js",
47
47
  "require": "./dist/analytics/costAnalytics.js",
48
48
  "types": "./dist/analytics/costAnalytics.d.ts"
49
+ },
50
+ "./sdk": {
51
+ "import": "./dist/sdk.js",
52
+ "require": "./dist/sdk.js"
49
53
  }
50
54
  },
51
55
  "keywords": [
52
- "llm-router",
53
- "llm-routing",
54
- "llm-gateway",
55
- "openai-proxy",
56
- "cost-optimization",
56
+ "ai",
57
+ "ai-agent",
58
+ "ai-cost-optimization",
57
59
  "ai-gateway",
58
- "routellm-alternative",
59
- "litellm-alternative",
60
- "portkey-alternative",
61
- "semantic-cache",
62
- "guardrails",
63
- "multi-provider",
64
- "openai-compatible",
65
- "llm-proxy",
66
- "llm-orchestration",
67
- "groq",
68
- "cerebras",
69
- "mistral",
70
- "deepseek",
60
+ "ai-guardrails",
61
+ "ai-load-balancer",
62
+ "ai-proxy",
63
+ "ai-router",
64
+ "ai-sdk",
71
65
  "anthropic",
66
+ "anthropic-proxy",
72
67
  "benchmark",
73
- "routing-accuracy",
74
- "lightweight",
75
- "no-gpu",
68
+ "cerebras",
69
+ "cerebras-proxy",
70
+ "chatbot",
71
+ "chatgpt",
72
+ "claude",
73
+ "claude-proxy",
74
+ "cost-optimization",
75
+ "deepseek",
76
+ "deepseek-proxy",
77
+ "domain-routing",
78
+ "gpt",
79
+ "groq",
80
+ "groq-proxy",
81
+ "guardrails",
82
+ "helicone",
76
83
  "keyword-routing",
77
- "ai",
84
+ "langchain",
85
+ "language-model",
86
+ "lightweight",
87
+ "litellm-alternative",
78
88
  "llm",
89
+ "llm-gateway",
90
+ "llm-load-balancer",
91
+ "llm-manager",
92
+ "llm-orchestration",
93
+ "llm-proxy",
94
+ "llm-router",
95
+ "llm-routing",
96
+ "mistral",
97
+ "mistral-proxy",
98
+ "model-router",
99
+ "model-routing",
100
+ "multi-llm",
101
+ "multi-provider",
79
102
  "nlp",
80
- "language-model"
103
+ "no-gpu",
104
+ "ollama",
105
+ "ollama-proxy",
106
+ "openai",
107
+ "openai-api",
108
+ "openai-compatible",
109
+ "openai-proxy",
110
+ "openai-sdk",
111
+ "openrouter",
112
+ "portkey-alternative",
113
+ "provider-fallback",
114
+ "query-routing",
115
+ "routellm-alternative",
116
+ "routing-accuracy",
117
+ "semantic-cache",
118
+ "smart-routing",
119
+ "token-counter",
120
+ "vercel-ai"
81
121
  ],
82
122
  "author": "Das-rebel <subho@example.com>",
83
123
  "license": "MIT",
@@ -0,0 +1,102 @@
1
+ # A3M Router Python SDK
2
+
3
+ Python SDK for the A3M Router — an intelligent LLM routing proxy that selects the best model for each query based on complexity, cost, and capability.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install a3m-router
9
+ ```
10
+
11
+ Requires Python 3.8+. Only dependency: `httpx`.
12
+
13
+ ## Quick Start
14
+
15
+ ### Async Client (recommended)
16
+
17
+ ```python
18
+ import asyncio
19
+ from a3m import A3MRouter
20
+
21
+ async def main():
22
+ async with A3MRouter() as router:
23
+ # Chat with automatic model routing
24
+ response = await router.chat("What is 2+2?")
25
+ print(response["choices"][0]["message"]["content"])
26
+
27
+ # Check routing decision without executing
28
+ decision = await router.route("Write a Python web scraper")
29
+ print(decision) # RoutingDecision(model=groq/llama-3.3-70b, tier=cheap, cost=$0.000000, complexity=0.35)
30
+
31
+ # Stream a response
32
+ async for token in router.stream_chat("Tell me a joke"):
33
+ print(token, end="", flush=True)
34
+
35
+ # List available models
36
+ models = await router.models()
37
+
38
+ # Get cost analytics
39
+ report = await router.cost_report()
40
+ print(f"Total requests: {report.total_requests}")
41
+ print(f"Savings: {report.savings_percentage:.1f}%")
42
+
43
+ asyncio.run(main())
44
+ ```
45
+
46
+ ### Sync Client
47
+
48
+ ```python
49
+ from a3m.sync_client import A3MRouterSync
50
+
51
+ with A3MRouterSync() as router:
52
+ response = router.chat("What is 2+2?")
53
+ print(response["choices"][0]["message"]["content"])
54
+
55
+ decision = router.route("Explain quantum computing")
56
+ print(f"Routed to {decision.model} (tier={decision.tier}, cost=${decision.cost:.6f})")
57
+ ```
58
+
59
+ ### With OpenAI SDK
60
+
61
+ The router is OpenAI-compatible, so you can use the standard OpenAI SDK:
62
+
63
+ ```python
64
+ from openai import AsyncOpenAI
65
+
66
+ client = AsyncOpenAI(base_url="http://localhost:8787/v1", api_key="not-needed")
67
+ response = await client.chat.completions.create(
68
+ model="auto",
69
+ messages=[{"role": "user", "content": "Hello"}]
70
+ )
71
+ ```
72
+
73
+ ## API Reference
74
+
75
+ ### A3MRouter (async)
76
+
77
+ | Method | Description |
78
+ |--------|-------------|
79
+ | `chat(message, model="auto", max_tokens=100, temperature=0.7, system=None)` | Send a chat message with automatic routing |
80
+ | `route(query)` | Get routing decision without executing |
81
+ | `route_batch(queries)` | Route multiple queries |
82
+ | `stream_chat(message, model="auto", max_tokens=100)` | Stream response tokens |
83
+ | `models()` | List available models |
84
+ | `health()` | Check router health |
85
+ | `cost_report()` | Get cost analytics |
86
+
87
+ ### RoutingDecision
88
+
89
+ | Field | Type | Description |
90
+ |-------|------|-------------|
91
+ | `model` | str | Selected model name |
92
+ | `tier` | str | Cost tier (free/cheap/mid/premium) |
93
+ | `cost` | float | Estimated cost per request |
94
+ | `complexity` | float | Query complexity score (0-1) |
95
+ | `reasoning` | str | Why this model was chosen |
96
+ | `fallback_models` | list | Alternative models if primary fails |
97
+ | `is_free` | bool | Property — True if cost is $0 |
98
+ | `is_expert` | bool | Property — True if complexity >= 0.65 |
99
+
100
+ ## License
101
+
102
+ MIT
@@ -0,0 +1,6 @@
1
+ """A3M Router Python SDK"""
2
+ from .client import A3MRouter
3
+ from .models import RoutingDecision, CostReport
4
+
5
+ __version__ = "2.1.0"
6
+ __all__ = ["A3MRouter", "RoutingDecision", "CostReport"]
@@ -0,0 +1,190 @@
1
+ """A3M Router Python SDK — Async client"""
2
+ import json
3
+ import httpx
4
+ from typing import Optional, List, Dict, Any, AsyncIterator
5
+ from .models import RoutingDecision, CostReport
6
+
7
+
8
+ class A3MRouter:
9
+ """Python client for A3M Router.
10
+
11
+ Usage:
12
+ # Auto-start proxy: pip install a3m-router, then:
13
+ router = A3MRouter(base_url="http://localhost:8787")
14
+
15
+ # Route a query (returns OpenAI-compatible response)
16
+ response = await router.chat("What is 2+2?")
17
+
18
+ # Get routing decision only (no LLM call)
19
+ decision = await router.route("What is 2+2?")
20
+ print(decision.model, decision.tier, decision.cost)
21
+
22
+ # Use with OpenAI SDK
23
+ from openai import AsyncOpenAI
24
+ client = AsyncOpenAI(base_url="http://localhost:8787/v1", api_key="not-needed")
25
+ response = await client.chat.completions.create(
26
+ model="auto",
27
+ messages=[{"role": "user", "content": "Hello"}]
28
+ )
29
+ """
30
+
31
+ def __init__(
32
+ self,
33
+ base_url: str = "http://localhost:8787",
34
+ timeout: float = 30.0,
35
+ api_key: str = "not-needed",
36
+ ):
37
+ self.base_url = base_url.rstrip("/")
38
+ self.api_key = api_key
39
+ self._client = httpx.AsyncClient(
40
+ base_url=self.base_url,
41
+ timeout=timeout,
42
+ headers={"Authorization": f"Bearer {api_key}"},
43
+ )
44
+
45
+ async def chat(
46
+ self,
47
+ message: str,
48
+ model: str = "auto",
49
+ max_tokens: int = 100,
50
+ temperature: float = 0.7,
51
+ system: Optional[str] = None,
52
+ ) -> Dict[str, Any]:
53
+ """Send a chat message and get a routed response.
54
+
55
+ Args:
56
+ message: User message
57
+ model: Model to use (default "auto" for intelligent routing)
58
+ max_tokens: Max response tokens
59
+ temperature: Response temperature
60
+ system: Optional system prompt
61
+
62
+ Returns:
63
+ OpenAI-compatible response dict
64
+ """
65
+ messages = []
66
+ if system:
67
+ messages.append({"role": "system", "content": system})
68
+ messages.append({"role": "user", "content": message})
69
+
70
+ response = await self._client.post(
71
+ "/v1/chat/completions",
72
+ json={
73
+ "model": model,
74
+ "messages": messages,
75
+ "max_tokens": max_tokens,
76
+ "temperature": temperature,
77
+ },
78
+ )
79
+ response.raise_for_status()
80
+ return response.json()
81
+
82
+ async def route(self, query: str) -> RoutingDecision:
83
+ """Get routing decision for a query without executing it.
84
+
85
+ Args:
86
+ query: The query to analyze
87
+
88
+ Returns:
89
+ RoutingDecision with model, tier, cost, reasoning
90
+ """
91
+ response = await self._client.post(
92
+ "/v1/route",
93
+ json={"query": query},
94
+ )
95
+ response.raise_for_status()
96
+ data = response.json()
97
+ return RoutingDecision(
98
+ model=data.get("model", "unknown"),
99
+ tier=data.get("tier", "unknown"),
100
+ cost=data.get("cost", 0),
101
+ complexity=data.get("complexity", 0),
102
+ reasoning=data.get("reasoning", ""),
103
+ fallback_models=data.get("fallback_models", []),
104
+ )
105
+
106
+ async def route_batch(self, queries: List[str]) -> List[RoutingDecision]:
107
+ """Route multiple queries in batch.
108
+
109
+ Args:
110
+ queries: List of queries to route
111
+
112
+ Returns:
113
+ List of RoutingDecision objects
114
+ """
115
+ decisions = []
116
+ for q in queries:
117
+ d = await self.route(q)
118
+ decisions.append(d)
119
+ return decisions
120
+
121
+ async def models(self) -> List[Dict[str, Any]]:
122
+ """List available models and their metadata."""
123
+ response = await self._client.get("/v1/models")
124
+ response.raise_for_status()
125
+ return response.json().get("data", [])
126
+
127
+ async def health(self) -> Dict[str, Any]:
128
+ """Check router health and provider availability."""
129
+ response = await self._client.get("/health")
130
+ response.raise_for_status()
131
+ return response.json()
132
+
133
+ async def cost_report(self) -> CostReport:
134
+ """Get cost analytics and savings report."""
135
+ response = await self._client.get("/dashboard")
136
+ response.raise_for_status()
137
+ data = response.json()
138
+ return CostReport(
139
+ total_requests=data.get("total_requests", 0),
140
+ total_cost=data.get("total_cost", 0),
141
+ savings_vs_premium=data.get("savings_vs_premium", 0),
142
+ by_provider=data.get("by_provider", {}),
143
+ )
144
+
145
+ async def stream_chat(
146
+ self,
147
+ message: str,
148
+ model: str = "auto",
149
+ max_tokens: int = 100,
150
+ ) -> AsyncIterator[str]:
151
+ """Stream a chat response token by token.
152
+
153
+ Args:
154
+ message: User message
155
+ model: Model to use
156
+ max_tokens: Max response tokens
157
+
158
+ Yields:
159
+ Response tokens as they arrive
160
+ """
161
+ async with self._client.stream(
162
+ "POST",
163
+ "/v1/chat/completions",
164
+ json={
165
+ "model": model,
166
+ "messages": [{"role": "user", "content": message}],
167
+ "max_tokens": max_tokens,
168
+ "stream": True,
169
+ },
170
+ ) as response:
171
+ response.raise_for_status()
172
+ async for line in response.aiter_lines():
173
+ if line.startswith("data: "):
174
+ data = line[6:]
175
+ if data == "[DONE]":
176
+ break
177
+ chunk = json.loads(data)
178
+ content = chunk.get("choices", [{}])[0].get("delta", {}).get("content", "")
179
+ if content:
180
+ yield content
181
+
182
+ async def close(self):
183
+ """Close the HTTP client."""
184
+ await self._client.aclose()
185
+
186
+ async def __aenter__(self):
187
+ return self
188
+
189
+ async def __aexit__(self, *args):
190
+ await self.close()
@@ -0,0 +1,40 @@
1
+ """Data models for A3M Router Python SDK"""
2
+ from dataclasses import dataclass, field
3
+ from typing import List, Dict, Any, Optional
4
+
5
+
6
+ @dataclass
7
+ class RoutingDecision:
8
+ """Result of a routing decision."""
9
+ model: str
10
+ tier: str
11
+ cost: float
12
+ complexity: float = 0.0
13
+ reasoning: str = ""
14
+ fallback_models: List[str] = field(default_factory=list)
15
+
16
+ @property
17
+ def is_free(self) -> bool:
18
+ return self.cost == 0
19
+
20
+ @property
21
+ def is_expert(self) -> bool:
22
+ return self.complexity >= 0.65
23
+
24
+ def __str__(self) -> str:
25
+ return f"RoutingDecision(model={self.model}, tier={self.tier}, cost=${self.cost:.6f}, complexity={self.complexity:.2f})"
26
+
27
+
28
+ @dataclass
29
+ class CostReport:
30
+ """Cost analytics report."""
31
+ total_requests: int = 0
32
+ total_cost: float = 0.0
33
+ savings_vs_premium: float = 0.0
34
+ by_provider: Dict[str, Any] = field(default_factory=dict)
35
+
36
+ @property
37
+ def savings_percentage(self) -> float:
38
+ if self.total_cost + self.savings_vs_premium == 0:
39
+ return 0
40
+ return self.savings_vs_premium / (self.total_cost + self.savings_vs_premium) * 100
@@ -0,0 +1,61 @@
1
+ """A3M Router Python SDK — Synchronous client"""
2
+ import httpx
3
+ from typing import Optional, List, Dict, Any
4
+ from .models import RoutingDecision, CostReport
5
+
6
+
7
+ class A3MRouterSync:
8
+ """Synchronous Python client for A3M Router.
9
+
10
+ Usage:
11
+ router = A3MRouterSync()
12
+ response = router.chat("What is 2+2?")
13
+ decision = router.route("Write a Python function")
14
+ print(decision.model, decision.cost)
15
+ """
16
+
17
+ def __init__(self, base_url: str = "http://localhost:8787", timeout: float = 30.0):
18
+ self._client = httpx.Client(base_url=base_url.rstrip("/"), timeout=timeout)
19
+
20
+ def chat(self, message: str, model: str = "auto", max_tokens: int = 100, **kwargs) -> Dict[str, Any]:
21
+ messages = [{"role": "user", "content": message}]
22
+ if kwargs.get("system"):
23
+ messages.insert(0, {"role": "system", "content": kwargs["system"]})
24
+ response = self._client.post("/v1/chat/completions", json={
25
+ "model": model, "messages": messages,
26
+ "max_tokens": max_tokens, "temperature": kwargs.get("temperature", 0.7),
27
+ })
28
+ response.raise_for_status()
29
+ return response.json()
30
+
31
+ def route(self, query: str) -> RoutingDecision:
32
+ response = self._client.post("/v1/route", json={"query": query})
33
+ response.raise_for_status()
34
+ data = response.json()
35
+ return RoutingDecision(
36
+ model=data.get("model", "unknown"), tier=data.get("tier", "unknown"),
37
+ cost=data.get("cost", 0), complexity=data.get("complexity", 0),
38
+ reasoning=data.get("reasoning", ""), fallback_models=data.get("fallback_models", []),
39
+ )
40
+
41
+ def route_batch(self, queries: List[str]) -> List[RoutingDecision]:
42
+ return [self.route(q) for q in queries]
43
+
44
+ def models(self) -> List[Dict[str, Any]]:
45
+ response = self._client.get("/v1/models")
46
+ response.raise_for_status()
47
+ return response.json().get("data", [])
48
+
49
+ def health(self) -> Dict[str, Any]:
50
+ response = self._client.get("/health")
51
+ response.raise_for_status()
52
+ return response.json()
53
+
54
+ def close(self):
55
+ self._client.close()
56
+
57
+ def __enter__(self):
58
+ return self
59
+
60
+ def __exit__(self, *args):
61
+ self.close()
@@ -0,0 +1,23 @@
1
+ [build-system]
2
+ requires = ["setuptools>=64", "wheel"]
3
+ build-backend = "setuptools.backends._legacy:_Backend"
4
+
5
+ [project]
6
+ name = "a3m-router"
7
+ version = "2.1.0"
8
+ description = "Python SDK for A3M Router — intelligent LLM routing with 99.5% accuracy"
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.8"
12
+ dependencies = ["httpx>=0.24.0"]
13
+ keywords = ["llm", "routing", "openai", "proxy", "ai-gateway", "routellm", "litellm"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Topic :: Software Development :: Libraries :: Python Modules",
20
+ ]
21
+
22
+ [project.urls]
23
+ Homepage = "https://github.com/Das-rebel/adaptive-memory-multi-model-router"