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.
Files changed (173) hide show
  1. package/.github/workflows/adapters-ci.yml +142 -0
  2. package/.github/workflows/ci.yml +2 -5
  3. package/.github/workflows/pypi-publish.yml +102 -0
  4. package/AGENT_COUNCIL_FINDINGS.md +123 -98
  5. package/CAMPAIGN_SUMMARY.md +87 -0
  6. package/CHANGELOG.md +7 -1
  7. package/ENTERPRISE_INTEGRATIONS.md +94 -0
  8. package/INTEGRATION_PROGRESS.md +96 -0
  9. package/OPPORTUNITIES_100.md +277 -0
  10. package/POPULARITY_BOOSTERS.md +1 -1
  11. package/README.md +137 -142
  12. package/README_ja.md +17 -21
  13. package/README_zh.md +13 -17
  14. package/TODO_BROWSER_AUTOMATION.md +87 -0
  15. package/a3m-integrations-summary.md +73 -0
  16. package/adapters/a3m_adapter/__init__.py +39 -3
  17. package/adapters/a3m_adapter/adapter/__init__.py +16 -1
  18. package/adapters/a3m_adapter/adapter/autogen.py +169 -0
  19. package/adapters/a3m_adapter/adapter/haystack.py +197 -0
  20. package/adapters/a3m_adapter/adapter/langgraph.py +196 -0
  21. package/adapters/a3m_adapter/adapter/pinecone.py +217 -0
  22. package/adapters/a3m_adapter/adapter/vercel.py +188 -0
  23. package/adapters/a3m_adapter/tests/test_adapters.py +29 -31
  24. package/adapters/a3m_adapter/tests/test_integration.py +80 -0
  25. package/adapters/requirements-dev.txt +6 -0
  26. package/adapters/requirements.txt +4 -0
  27. package/apps/cost-calculator/README.md +72 -0
  28. package/apps/cost-calculator/calculator.css +280 -0
  29. package/apps/cost-calculator/calculator.js +150 -0
  30. package/apps/cost-calculator/index.html +321 -0
  31. package/apps/cost-calculator/package.json +13 -0
  32. package/articles/ANNOUNCEMENT_reddit_ml.md +76 -0
  33. package/articles/ANNOUNCEMENT_vc/347/244/276/345/214/272.md +71 -0
  34. package/articles/ANNOUNCEMENT_vercel.md +85 -0
  35. package/articles/CHINESE_DIRECTORIES.md +2 -2
  36. package/articles/CHINESE_SUBMISSIONS_READY.md +5 -5
  37. package/articles/HN_POST_READY.md +16 -20
  38. package/articles/INDIEHACKERS_READY.md +19 -34
  39. package/demo.py +251 -0
  40. package/dist/analytics/costAnalytics.d.ts +0 -1
  41. package/dist/benchmark/reproducible.d.ts +0 -1
  42. package/dist/cache/semanticCache.d.ts +0 -1
  43. package/dist/cost/budgetEnforcer.d.ts +0 -1
  44. package/dist/cost/costTracker.d.ts +0 -1
  45. package/dist/ensemble/multiRoundDialog.d.ts +0 -1
  46. package/dist/ensemble/shapleyValue.d.ts +0 -1
  47. package/dist/ensemble.d.ts +0 -1
  48. package/dist/index.d.ts +0 -1
  49. package/dist/integrations/oauth.d.ts +0 -1
  50. package/dist/integrations/scienceAdapter.d.ts +0 -1
  51. package/dist/memory/autoFetch.d.ts +0 -1
  52. package/dist/memory/hybridMemory.d.ts +0 -1
  53. package/dist/memory/memoryTree.d.ts +0 -1
  54. package/dist/memory/obsidianVault.d.ts +0 -1
  55. package/dist/memory/reasoningBank.d.ts +0 -1
  56. package/dist/observability/changeWatch.d.ts +0 -1
  57. package/dist/observability/fatigueDetector.d.ts +0 -1
  58. package/dist/observability/index.d.ts +0 -1
  59. package/dist/observability/metrics.d.ts +0 -1
  60. package/dist/observability/middleware.d.ts +0 -1
  61. package/dist/observability/tracer.d.ts +0 -1
  62. package/dist/observability/types.d.ts +0 -1
  63. package/dist/providers/providerConfig.d.ts +5 -2
  64. package/dist/providers/providerConfig.js +1006 -1
  65. package/dist/providers/providerConfig.js.map +1 -1
  66. package/dist/routing/advancedRouter.d.ts +1 -2
  67. package/dist/routing/advancedRouter.js +84 -0
  68. package/dist/routing/advancedRouter.js.map +1 -1
  69. package/dist/routing/crossModelValidation.d.ts +0 -1
  70. package/dist/routing/providerHealth.d.ts +77 -2
  71. package/dist/routing/providerHealth.js +190 -4
  72. package/dist/routing/providerHealth.js.map +1 -1
  73. package/dist/routing/providerRetry.d.ts +0 -1
  74. package/dist/routing/shadowSampler.d.ts +187 -0
  75. package/dist/routing/shadowSampler.js +306 -0
  76. package/dist/routing/shadowSampler.js.map +1 -0
  77. package/dist/sdk.d.ts +0 -1
  78. package/dist/security/guardrails.d.ts +0 -1
  79. package/dist/server/dashboard.d.ts +0 -1
  80. package/dist/server/handlers/chatHandler.d.ts +0 -1
  81. package/dist/server/handlers/completionsHandler.d.ts +0 -1
  82. package/dist/server/handlers/embeddingsHandler.d.ts +0 -1
  83. package/dist/server/handlers/healthHandler.d.ts +0 -1
  84. package/dist/server/handlers/metricsHandler.d.ts +0 -1
  85. package/dist/server/handlers/modelsHandler.d.ts +0 -1
  86. package/dist/server/metrics.d.ts +0 -1
  87. package/dist/server/modelMapper.d.ts +0 -1
  88. package/dist/server/proxyServer.d.ts +0 -1
  89. package/dist/server/router.d.ts +0 -1
  90. package/dist/server/state.d.ts +0 -1
  91. package/dist/tui/dashboard.d.ts +0 -1
  92. package/dist/utils/costUtils.d.ts +0 -1
  93. package/dist/utils/sorting.d.ts +0 -1
  94. package/dist/utils/tokenUtils.d.ts +0 -1
  95. package/docker-compose.yml +84 -60
  96. package/docs/comparison.md +2 -2
  97. package/docs/llms-full.txt +89 -0
  98. package/docs/llms.txt +25 -67
  99. package/docs-site/index.html +9 -9
  100. package/integrations/browser-use/README.md +155 -0
  101. package/integrations/browser-use/examples/form-filling.ts +179 -0
  102. package/integrations/browser-use/index.ts +145 -0
  103. package/integrations/browser-use/package.json +34 -0
  104. package/integrations/browser-use/tsconfig.json +17 -0
  105. package/integrations/mcp-browser/README.md +177 -0
  106. package/integrations/mcp-browser/package.json +38 -0
  107. package/integrations/mcp-browser/src/index.ts +297 -0
  108. package/integrations/sota-browser/README.md +191 -0
  109. package/integrations/sota-browser/package.json +34 -0
  110. package/llms-full.txt +6 -6
  111. package/llms.txt +25 -67
  112. package/package.json +34 -5
  113. package/packages/a3m-vercel-ai/README.md +161 -0
  114. package/packages/a3m-vercel-ai/dist/a3m-language-model.d.ts +12 -0
  115. package/packages/a3m-vercel-ai/dist/a3m-language-model.d.ts.map +1 -0
  116. package/packages/a3m-vercel-ai/dist/a3m-language-model.js +289 -0
  117. package/packages/a3m-vercel-ai/dist/a3m-language-model.js.map +1 -0
  118. package/packages/a3m-vercel-ai/dist/index.d.ts +82 -0
  119. package/packages/a3m-vercel-ai/dist/index.d.ts.map +1 -0
  120. package/packages/a3m-vercel-ai/dist/index.js +79 -0
  121. package/packages/a3m-vercel-ai/dist/index.js.map +1 -0
  122. package/packages/a3m-vercel-ai/dist/types.d.ts +97 -0
  123. package/packages/a3m-vercel-ai/dist/types.d.ts.map +1 -0
  124. package/packages/a3m-vercel-ai/dist/types.js +5 -0
  125. package/packages/a3m-vercel-ai/dist/types.js.map +1 -0
  126. package/packages/a3m-vercel-ai/package-lock.json +969 -0
  127. package/packages/a3m-vercel-ai/package.json +46 -0
  128. package/packages/a3m-vercel-ai/src/a3m-language-model.ts +381 -0
  129. package/packages/a3m-vercel-ai/src/index.ts +104 -0
  130. package/packages/a3m-vercel-ai/src/types.ts +116 -0
  131. package/packages/a3m-vercel-ai/tsconfig.json +20 -0
  132. package/packages/agentkit-adapter/LICENSE +21 -0
  133. package/packages/agentkit-adapter/README.md +126 -0
  134. package/packages/agentkit-adapter/examples/agentkit-example.ts +139 -0
  135. package/packages/agentkit-adapter/package.json +57 -0
  136. package/packages/agentkit-adapter/src/adapter.ts +381 -0
  137. package/packages/agentkit-adapter/src/index.ts +36 -0
  138. package/packages/agentkit-adapter/src/types.ts +105 -0
  139. package/packages/agentkit-adapter/src/util.ts +13 -0
  140. package/packages/agentkit-adapter/tsconfig.json +22 -0
  141. package/prometheus.yml +8 -0
  142. package/python/README.md +92 -65
  143. package/python/a3m/__init__.py +32 -3
  144. package/python/a3m/adapters/__init__.py +21 -0
  145. package/python/a3m/adapters/langchain.py +190 -0
  146. package/python/a3m/adapters/llamaindex.py +249 -0
  147. package/python/a3m/adapters/qdrant.py +240 -0
  148. package/python/a3m/adapters/weaviate.py +263 -0
  149. package/python/a3m/client.py +5 -0
  150. package/python/a3m_router.egg-info/PKG-INFO +172 -0
  151. package/python/a3m_router.egg-info/SOURCES.txt +17 -0
  152. package/python/a3m_router.egg-info/dependency_links.txt +1 -0
  153. package/python/a3m_router.egg-info/requires.txt +24 -0
  154. package/python/a3m_router.egg-info/top_level.txt +1 -0
  155. package/python/build_verify.sh +32 -0
  156. package/python/dist/a3m_router-2.2.1-py3-none-any.whl +0 -0
  157. package/python/dist/a3m_router-2.2.1.tar.gz +0 -0
  158. package/python/dist/a3m_router-2.2.2-py3-none-any.whl +0 -0
  159. package/python/dist/a3m_router-2.2.2.tar.gz +0 -0
  160. package/python/mcp-server/README.md +172 -0
  161. package/python/mcp-server/a3m_mcp/__init__.py +15 -0
  162. package/python/mcp-server/a3m_mcp/__main__.py +15 -0
  163. package/python/mcp-server/a3m_mcp/server.py +339 -0
  164. package/python/mcp-server/pyproject.toml +24 -0
  165. package/python/pyproject.toml +56 -5
  166. package/python/setup.py +3 -28
  167. package/src/providers/providerConfig.ts +1053 -1
  168. package/summary.txt +38 -0
  169. package/tsconfig.build.json +1 -2
  170. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -94
  171. package/.github/ISSUE_TEMPLATE/config.yml +0 -17
  172. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -71
  173. package/.well-known/ai-plugin.json +0 -16
@@ -0,0 +1,36 @@
1
+ /**
2
+ * A3M Router AgentKit Adapter
3
+ *
4
+ * @example
5
+ * ```typescript
6
+ * import { createAgenticModelFromAiAdapter } from "@inngest/agent-kit";
7
+ * import { createA3MAdapter } from "@a3m/agentkit-adapter";
8
+ *
9
+ * // Create A3M adapter
10
+ * const a3m = createA3MAdapter({
11
+ * baseUrl: "http://localhost:8787",
12
+ * parallel: true, // Enable parallel ensemble mode
13
+ * });
14
+ *
15
+ * // Wrap as AgentKit model
16
+ * const model = createAgenticModelFromAiAdapter(a3m as unknown as AiAdapter.Any);
17
+ *
18
+ * // Use with AgentKit agent
19
+ * const agent = new Agent({
20
+ * model,
21
+ * tools: [/* your tools *\/],
22
+ * });
23
+ * ```
24
+ */
25
+
26
+ export { A3MAdapter, createA3MAdapter } from "./adapter";
27
+ export type {
28
+ A3MConfig,
29
+ A3MInferenceOptions,
30
+ A3MMessage,
31
+ A3MTool,
32
+ A3MToolCall,
33
+ A3MStreamChunk,
34
+ A3MResponse,
35
+ ToolChoice,
36
+ } from "./types";
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Types for A3M Router AgentKit Adapter
3
+ */
4
+
5
+ import { z } from "zod";
6
+
7
+ /**
8
+ * Configuration for A3M Router
9
+ */
10
+ export interface A3MConfig {
11
+ /** A3M Router base URL (default: http://localhost:8787) */
12
+ baseUrl?: string;
13
+ /** API key for A3M Router (if required) */
14
+ apiKey?: string;
15
+ /** Default model to use (optional - A3M will route automatically) */
16
+ defaultModel?: string;
17
+ /** Enable parallel ensemble mode */
18
+ parallel?: boolean;
19
+ /** Temperature for generation (0-2) */
20
+ temperature?: number;
21
+ /** Maximum tokens to generate */
22
+ maxTokens?: number;
23
+ /** Additional headers to send with each request */
24
+ headers?: Record<string, string>;
25
+ }
26
+
27
+ /**
28
+ * A3M-specific options for inference
29
+ */
30
+ export interface A3MInferenceOptions {
31
+ /** Override the model for this call */
32
+ model?: string;
33
+ /** Enable parallel ensemble for this call */
34
+ parallel?: boolean;
35
+ /** Temperature (0-2) */
36
+ temperature?: number;
37
+ /** Maximum tokens */
38
+ maxTokens?: number;
39
+ /** Provider to use (bypass routing) */
40
+ provider?: string;
41
+ /** Stop sequences */
42
+ stop?: string[];
43
+ /** Retry on failure */
44
+ retry?: boolean;
45
+ }
46
+
47
+ /**
48
+ * Message format compatible with AgentKit
49
+ */
50
+ export interface A3MMessage {
51
+ role: "user" | "assistant" | "system" | "tool";
52
+ content: string;
53
+ name?: string;
54
+ toolCallId?: string;
55
+ }
56
+
57
+ /**
58
+ * Tool call format for A3M
59
+ */
60
+ export interface A3MToolCall {
61
+ id: string;
62
+ name: string;
63
+ arguments: Record<string, unknown>;
64
+ }
65
+
66
+ /**
67
+ * Tool definition for A3M
68
+ */
69
+ export interface A3MTool {
70
+ name: string;
71
+ description: string;
72
+ parameters?: z.ZodType<unknown>;
73
+ strict?: boolean;
74
+ }
75
+
76
+ /**
77
+ * Streaming chunk from A3M
78
+ */
79
+ export interface A3MStreamChunk {
80
+ type: "text" | "tool_call" | "done" | "error";
81
+ content?: string;
82
+ toolCall?: A3MToolCall;
83
+ error?: string;
84
+ }
85
+
86
+ /**
87
+ * Response from A3M inference
88
+ */
89
+ export interface A3MResponse {
90
+ content: string;
91
+ toolCalls?: A3MToolCall[];
92
+ model?: string;
93
+ provider?: string;
94
+ usage?: {
95
+ promptTokens: number;
96
+ completionTokens: number;
97
+ totalTokens: number;
98
+ };
99
+ finishReason?: "stop" | "length" | "tool_calls" | "content_filter";
100
+ }
101
+
102
+ /**
103
+ * Tool choice options
104
+ */
105
+ export type ToolChoice = "auto" | "none" | { type: "function"; function: { name: string } };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Utility functions
3
+ */
4
+
5
+ export const stringifyError = (err: unknown): string => {
6
+ if (err instanceof Error) {
7
+ return err.message;
8
+ }
9
+ if (typeof err === "string") {
10
+ return err;
11
+ }
12
+ return JSON.stringify(err);
13
+ };
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022"],
7
+ "declaration": true,
8
+ "declarationMap": true,
9
+ "sourceMap": true,
10
+ "outDir": "./dist",
11
+ "rootDir": "./src",
12
+ "strict": true,
13
+ "esModuleInterop": true,
14
+ "skipLibCheck": true,
15
+ "forceConsistentCasingInFileNames": true,
16
+ "resolveJsonModule": true,
17
+ "isolatedModules": true,
18
+ "noEmit": false
19
+ },
20
+ "include": ["src/**/*"],
21
+ "exclude": ["node_modules", "dist", "examples"]
22
+ }
package/prometheus.yml ADDED
@@ -0,0 +1,8 @@
1
+ global:
2
+ scrape_interval: 15s
3
+
4
+ scrape_configs:
5
+ - job_name: 'a3m-router'
6
+ static_configs:
7
+ - targets: ['a3m-router:8787']
8
+ metrics_path: /metrics
package/python/README.md CHANGED
@@ -1,101 +1,128 @@
1
1
  # A3M Router Python SDK
2
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.
3
+ **Intelligent LLM routing auto-selects the cheapest capable model from 47+ providers.**
4
4
 
5
- ## Install
5
+ Routes queries to the best model for your needs — whether it's Groq for simple Q&A ($0.001/1K) or GPT-4o for complex reasoning ($0.15/1K).
6
+
7
+ ## Installation
6
8
 
7
9
  ```bash
8
10
  pip install a3m-router
9
11
  ```
10
12
 
11
- Requires Python 3.8+. Only dependency: `httpx`.
12
-
13
13
  ## Quick Start
14
14
 
15
- ### Async Client (recommended)
16
-
17
15
  ```python
18
- import asyncio
19
16
  from a3m import A3MRouter
20
17
 
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"])
18
+ router = A3MRouter(base_url="http://localhost:8787")
19
+
20
+ # Auto-routes to optimal provider
21
+ response = await router.chat("What is 2+2?")
22
+ # → Routes to Groq, costs ~$0.000001
26
23
 
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)
24
+ # See routing decision before executing
25
+ decision = await router.route("Explain quantum computing")
26
+ print(f"Model: {decision.model}")
27
+ print(f"Tier: {decision.tier}")
28
+ print(f"Cost: ${decision.cost:.6f}")
30
29
 
31
- # Stream a response
32
- async for token in router.stream_chat("Tell me a joke"):
33
- print(token, end="", flush=True)
30
+ # Stream responses
31
+ async for token in router.stream_chat("Tell me a story"):
32
+ print(token, end="", flush=True)
33
+ ```
34
+
35
+ ## Key Features
34
36
 
35
- # List available models
36
- models = await router.models()
37
+ - **Auto-routing**: Picks the right model based on query complexity, budget, and requirements
38
+ - **Cost savings**: 70-95% cheaper than always using premium models
39
+ - **47+ providers**: Groq, DeepSeek, GPT-4o, Claude, Mistral, and more
40
+ - **Framework adapters**: Drop-in for LangChain, LlamaIndex, Qdrant, Weaviate
41
+ - **Health monitoring**: Check provider status and availability
42
+ - **Cost analytics**: Track spending and savings
37
43
 
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}%")
44
+ ## Framework Adapters
42
45
 
43
- asyncio.run(main())
46
+ | Adapter | Use Case | Install |
47
+ |---------|----------|---------|
48
+ | **LangChain** | Chain-based AI workflows | `pip install a3m-router[langchain]` |
49
+ | **LlamaIndex** | RAG and document QA | `pip install a3m-router[llamaindex]` |
50
+ | **Qdrant** | Vector search + RAG | `pip install a3m-router[qdrant]` |
51
+ | **Weaviate** | Vector search + RAG | `pip install a3m-router[weaviate]` |
52
+
53
+ All adapters:
54
+ ```bash
55
+ pip install a3m-router[all]
44
56
  ```
45
57
 
46
- ### Sync Client
58
+ ## Routing Tiers
47
59
 
48
- ```python
49
- from a3m.sync_client import A3MRouterSync
60
+ | Tier | Providers | Cost | When Used |
61
+ |------|-----------|------|-----------|
62
+ | **free** | Ollama, vLLM | $0 | Local inference |
63
+ | **cheap** | Groq, DeepSeek | ~$0.001/1K | Simple Q&A, short code |
64
+ | **mid** | GPT-4o-mini, Claude-haiku | ~$0.01/1K | Standard tasks |
65
+ | **premium** | GPT-4o, Claude-sonnet | ~$0.15/1K | Complex reasoning |
66
+
67
+ ## API Reference
50
68
 
51
- with A3MRouterSync() as router:
52
- response = router.chat("What is 2+2?")
53
- print(response["choices"][0]["message"]["content"])
69
+ ### A3MRouter
54
70
 
55
- decision = router.route("Explain quantum computing")
56
- print(f"Routed to {decision.model} (tier={decision.tier}, cost=${decision.cost:.6f})")
71
+ ```python
72
+ router = A3MRouter(
73
+ base_url="http://localhost:8787", # A3M Router server URL
74
+ timeout=30.0, # Request timeout
75
+ )
57
76
  ```
58
77
 
59
- ### With OpenAI SDK
78
+ | Method | Description |
79
+ |--------|-------------|
80
+ | `chat(message)` | Send chat message with auto-routing |
81
+ | `route(query)` | Get routing decision (no execution) |
82
+ | `route_batch(queries)` | Route multiple queries |
83
+ | `stream_chat(message)` | Stream response tokens |
84
+ | `models()` | List all available models |
85
+ | `health()` | Provider health status |
86
+ | `cost_report()` | Cost analytics |
87
+
88
+ ### LangChain Example
89
+
90
+ ```python
91
+ from a3m import LangChainAdapter
92
+ from langchain.schema import HumanMessage
93
+
94
+ llm = LangChainAdapter(base_url="http://localhost:8787")
95
+ response = llm([HumanMessage(content="What is RAG?")])
96
+ ```
60
97
 
61
- The router is OpenAI-compatible, so you can use the standard OpenAI SDK:
98
+ ### LlamaIndex Example
62
99
 
63
100
  ```python
64
- from openai import AsyncOpenAI
101
+ from a3m import LlamaIndexAdapter
65
102
 
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
- )
103
+ llm = LlamaIndexAdapter()
104
+ response = llm.complete("Explain transformers")
71
105
  ```
72
106
 
73
- ## API Reference
107
+ ## Server Setup
74
108
 
75
- ### A3MRouter (async)
109
+ Start the A3M Router server:
76
110
 
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 |
111
+ ```bash
112
+ # Via npm
113
+ npx a3m-router serve
114
+
115
+ # Via Docker
116
+ docker-compose up -d
117
+ ```
118
+
119
+ Server runs on `http://localhost:8787` by default.
120
+
121
+ ## Links
122
+
123
+ - **GitHub**: https://github.com/Das-rebel/a3m-router
124
+ - **npm Package**: https://www.npmjs.com/package/adaptive-memory-multi-model-router
125
+ - **Documentation**: https://das-rebel.github.io/a3m-router
99
126
 
100
127
  ## License
101
128
 
@@ -1,6 +1,35 @@
1
1
  """A3M Router Python SDK"""
2
- from .client import A3MRouter
2
+ from .client import A3MRouter, A3MRouterError
3
+ from .sync_client import A3MRouterSync
3
4
  from .models import RoutingDecision, CostReport
4
5
 
5
- __version__ = "2.1.0"
6
- __all__ = ["A3MRouter", "RoutingDecision", "CostReport"]
6
+ __version__ = "2.2.1"
7
+
8
+ __all__ = [
9
+ "A3MRouter",
10
+ "A3MRouterSync",
11
+ "A3MRouterError",
12
+ "RoutingDecision",
13
+ "CostReport",
14
+ # Framework adapters
15
+ "LangChainAdapter",
16
+ "LlamaIndexAdapter",
17
+ "QdrantAdapter",
18
+ "WeaviateAdapter",
19
+ ]
20
+
21
+ # Lazy-load adapters
22
+ def __getattr__(name: str):
23
+ if name == "LangChainAdapter":
24
+ from .adapters.langchain import LangChainAdapter
25
+ return LangChainAdapter
26
+ if name == "LlamaIndexAdapter":
27
+ from .adapters.llamaindex import LlamaIndexAdapter
28
+ return LlamaIndexAdapter
29
+ if name == "QdrantAdapter":
30
+ from .adapters.qdrant import QdrantAdapter
31
+ return QdrantAdapter
32
+ if name == "WeaviateAdapter":
33
+ from .adapters.weaviate import WeaviateAdapter
34
+ return WeaviateAdapter
35
+ raise AttributeError(f"module 'a3m' has no attribute '{name}'")
@@ -0,0 +1,21 @@
1
+ """
2
+ A3M Router framework adapters.
3
+
4
+ Lazy-loaded adapter classes for popular frameworks:
5
+ - LangChain: from a3m.adapters import LangChainAdapter
6
+ - LlamaIndex: from a3m.adapters import LlamaIndexAdapter
7
+ - Qdrant: from a3m.adapters import QdrantAdapter
8
+ - Weaviate: from a3m.adapters import WeaviateAdapter
9
+ """
10
+
11
+ from .langchain import LangChainAdapter
12
+ from .llamaindex import LlamaIndexAdapter
13
+ from .qdrant import QdrantAdapter
14
+ from .weaviate import WeaviateAdapter
15
+
16
+ __all__ = [
17
+ "LangChainAdapter",
18
+ "LlamaIndexAdapter",
19
+ "QdrantAdapter",
20
+ "WeaviateAdapter",
21
+ ]
@@ -0,0 +1,190 @@
1
+ """
2
+ A3M Router LangChain adapter.
3
+
4
+ Drop-in replacement for ChatOpenAI that routes through A3M Router.
5
+
6
+ Usage:
7
+ from langchain.chat_models import ChatOpenAI
8
+ from a3m.adapters import LangChainAdapter
9
+
10
+ # As replacement for ChatOpenAI
11
+ llm = LangChainAdapter(
12
+ base_url="http://localhost:8787",
13
+ model="auto",
14
+ temperature=0.7,
15
+ )
16
+
17
+ from langchain.schema import HumanMessage
18
+ response = llm([HumanMessage(content="What is 2+2?")])
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import logging
24
+ from typing import Any, Dict, List, Optional, Type
25
+
26
+ from pydantic import Field
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+ LANCHAIN_AVAILABLE = False
31
+ try:
32
+ from langchain.chat_models import BaseChatModel
33
+ from langchain.schema import (
34
+ BaseMessage,
35
+ ChatResult,
36
+ AIMessage,
37
+ HumanMessage,
38
+ SystemMessage,
39
+ )
40
+ from langchain.callbacks.manager import CallbackManagerForLLMRun
41
+ LANCHAIN_AVAILABLE = True
42
+ except ImportError:
43
+ logger.warning("LangChain not installed. pip install langchain")
44
+
45
+ from a3m.client import A3MRouter, A3MRouterError
46
+
47
+ if LANCHAIN_AVAILABLE:
48
+ class LangChainAdapter(BaseChatModel):
49
+ """
50
+ LangChain chat model that routes through A3M Router.
51
+
52
+ Drop-in replacement for ChatOpenAI with automatic model selection.
53
+
54
+ Args:
55
+ base_url: A3M Router server URL.
56
+ model: Model name or "auto" for intelligent routing.
57
+ temperature: Sampling temperature.
58
+ max_tokens: Max tokens to generate.
59
+ parallel_ensemble: Number of providers for ensemble calls.
60
+ **kwargs: Additional A3MRouter options.
61
+ """
62
+
63
+ base_url: str = Field(default="http://localhost:8787")
64
+ model: str = Field(default="auto")
65
+ temperature: float = Field(default=0.7)
66
+ max_tokens: Optional[int] = Field(default=None)
67
+ parallel_ensemble: int = Field(default=1)
68
+ api_key: Optional[str] = Field(default=None)
69
+
70
+ class Config:
71
+ arbitrary_types_allowed = True
72
+
73
+ def _get_router(self) -> A3MRouter:
74
+ """Get or create A3M Router client."""
75
+ if not hasattr(self, "_router"):
76
+ self._router = A3MRouter(
77
+ base_url=self.base_url,
78
+ api_key=self.api_key,
79
+ default_model=self.model,
80
+ default_temperature=self.temperature,
81
+ default_max_tokens=self.max_tokens,
82
+ parallel_ensemble=self.parallel_ensemble,
83
+ )
84
+ return self._router
85
+
86
+ def _convert_messages(
87
+ self,
88
+ messages: List[BaseMessage],
89
+ ) -> List[Dict[str, str]]:
90
+ """Convert LangChain messages to A3M format."""
91
+ result = []
92
+ for msg in messages:
93
+ if isinstance(msg, HumanMessage):
94
+ result.append({"role": "user", "content": msg.content})
95
+ elif isinstance(msg, AIMessage):
96
+ result.append({"role": "assistant", "content": msg.content})
97
+ elif isinstance(msg, SystemMessage):
98
+ result.append({"role": "system", "content": msg.content})
99
+ else:
100
+ result.append({"role": "user", "content": str(msg.content)})
101
+ return result
102
+
103
+ def _convert_response(
104
+ self,
105
+ response_content: str,
106
+ ) -> AIMessage:
107
+ """Convert A3M response to LangChain message."""
108
+ return AIMessage(content=response_content)
109
+
110
+ @property
111
+ def _llm_type(self) -> str:
112
+ return "a3m-router"
113
+
114
+ def _generate(
115
+ self,
116
+ messages: List[BaseMessage],
117
+ stop: Optional[List[str]] = None,
118
+ run_manager: Optional[CallbackManagerForLLMRun] = None,
119
+ **kwargs: Any,
120
+ ) -> ChatResult:
121
+ """Generate a chat response."""
122
+ router = self._get_router()
123
+
124
+ a3m_messages = self._convert_messages(messages)
125
+
126
+ try:
127
+ response = router.chat(
128
+ messages=a3m_messages,
129
+ model=self.model,
130
+ temperature=self.temperature,
131
+ max_tokens=self.max_tokens,
132
+ parallel_ensemble=self.parallel_ensemble,
133
+ **kwargs,
134
+ )
135
+ except A3MRouterError as e:
136
+ logger.error(f"A3M Router error: {e}")
137
+ raise
138
+
139
+ return ChatResult(
140
+ generations=[{"message": self._convert_response(response.content), "text": response.content}],
141
+ llm_output={
142
+ "provider": response.provider,
143
+ "model": response.route.model,
144
+ "cost": response.cost,
145
+ "route": str(response.route),
146
+ },
147
+ )
148
+
149
+ async def _agenerate(
150
+ self,
151
+ messages: List[BaseMessage],
152
+ stop: Optional[List[str]] = None,
153
+ run_manager: Optional[CallbackManagerForLLMRun] = None,
154
+ **kwargs: Any,
155
+ ) -> ChatResult:
156
+ """Async generate a chat response."""
157
+ router = self._get_router()
158
+ a3m_messages = self._convert_messages(messages)
159
+
160
+ try:
161
+ response = await router.achat(
162
+ messages=a3m_messages,
163
+ model=self.model,
164
+ temperature=self.temperature,
165
+ max_tokens=self.max_tokens,
166
+ parallel_ensemble=self.parallel_ensemble,
167
+ **kwargs,
168
+ )
169
+ except A3MRouterError as e:
170
+ logger.error(f"A3M Router error: {e}")
171
+ raise
172
+
173
+ return ChatResult(
174
+ generations=[{"message": self._convert_response(response.content), "text": response.content}],
175
+ llm_output={
176
+ "provider": response.provider,
177
+ "model": response.route.model,
178
+ "cost": response.cost,
179
+ "route": str(response.route),
180
+ },
181
+ )
182
+
183
+ else:
184
+ # Stub class when LangChain is not installed
185
+ class LangChainAdapter:
186
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
187
+ raise ImportError(
188
+ "LangChain is not installed. "
189
+ "Install with: pip install langchain"
190
+ )