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,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,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
package/python/README.md
CHANGED
|
@@ -1,101 +1,128 @@
|
|
|
1
1
|
# A3M Router Python SDK
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Intelligent LLM routing — auto-selects the cheapest capable model from 47+ providers.**
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
36
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
58
|
+
## Routing Tiers
|
|
47
59
|
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
52
|
-
response = router.chat("What is 2+2?")
|
|
53
|
-
print(response["choices"][0]["message"]["content"])
|
|
69
|
+
### A3MRouter
|
|
54
70
|
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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
|
-
|
|
98
|
+
### LlamaIndex Example
|
|
62
99
|
|
|
63
100
|
```python
|
|
64
|
-
from
|
|
101
|
+
from a3m import LlamaIndexAdapter
|
|
65
102
|
|
|
66
|
-
|
|
67
|
-
response =
|
|
68
|
-
model="auto",
|
|
69
|
-
messages=[{"role": "user", "content": "Hello"}]
|
|
70
|
-
)
|
|
103
|
+
llm = LlamaIndexAdapter()
|
|
104
|
+
response = llm.complete("Explain transformers")
|
|
71
105
|
```
|
|
72
106
|
|
|
73
|
-
##
|
|
107
|
+
## Server Setup
|
|
74
108
|
|
|
75
|
-
|
|
109
|
+
Start the A3M Router server:
|
|
76
110
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
|
package/python/a3m/__init__.py
CHANGED
|
@@ -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
|
|
6
|
-
|
|
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
|
+
)
|