adaptive-memory-multi-model-router 2.15.3 → 2.15.4

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 (97) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +94 -0
  2. package/.github/ISSUE_TEMPLATE/config.yml +17 -0
  3. package/.github/ISSUE_TEMPLATE/feature_request.md +71 -0
  4. package/.well-known/ai-plugin.json +16 -0
  5. package/AGENT_COUNCIL_FINDINGS.md +98 -123
  6. package/POPULARITY_BOOSTERS.md +1 -1
  7. package/README.md +275 -147
  8. package/README_ja.md +20 -16
  9. package/README_zh.md +17 -13
  10. package/adapters/README.md +36 -0
  11. package/adapters/__init__.py +25 -0
  12. package/adapters/a3m_adapter/__init__.py +15 -0
  13. package/adapters/a3m_adapter/adapter/__init__.py +7 -0
  14. package/adapters/a3m_adapter/adapter/config.py +100 -0
  15. package/adapters/a3m_adapter/adapter/langchain.py +155 -0
  16. package/adapters/a3m_adapter/adapter/llamaindex.py +162 -0
  17. package/adapters/a3m_adapter/tests/__init__.py +1 -0
  18. package/adapters/a3m_adapter/tests/test_adapters.py +120 -0
  19. package/adapters/setup.py +23 -0
  20. package/articles/CHINESE_DIRECTORIES.md +2 -2
  21. package/articles/CHINESE_SUBMISSIONS_READY.md +5 -5
  22. package/articles/HN_POST_READY.md +20 -16
  23. package/articles/INDIEHACKERS_READY.md +34 -19
  24. package/dist/analytics/costAnalytics.d.ts +1 -0
  25. package/dist/benchmark/reproducible.d.ts +1 -0
  26. package/dist/cache/semanticCache.d.ts +1 -0
  27. package/dist/cost/budgetEnforcer.d.ts +1 -0
  28. package/dist/cost/costTracker.d.ts +1 -0
  29. package/dist/ensemble/multiRoundDialog.d.ts +1 -0
  30. package/dist/ensemble/shapleyValue.d.ts +1 -0
  31. package/dist/ensemble.d.ts +1 -0
  32. package/dist/index.d.ts +1 -0
  33. package/dist/integrations/oauth.d.ts +1 -0
  34. package/dist/integrations/scienceAdapter.d.ts +1 -0
  35. package/dist/memory/autoFetch.d.ts +1 -0
  36. package/dist/memory/hybridMemory.d.ts +1 -0
  37. package/dist/memory/memoryTree.d.ts +1 -0
  38. package/dist/memory/obsidianVault.d.ts +1 -0
  39. package/dist/memory/reasoningBank.d.ts +1 -0
  40. package/dist/observability/changeWatch.d.ts +1 -0
  41. package/dist/observability/fatigueDetector.d.ts +1 -0
  42. package/dist/observability/index.d.ts +1 -0
  43. package/dist/observability/metrics.d.ts +1 -0
  44. package/dist/observability/middleware.d.ts +1 -0
  45. package/dist/observability/tracer.d.ts +1 -0
  46. package/dist/observability/types.d.ts +1 -0
  47. package/dist/providers/providerConfig.d.ts +1 -0
  48. package/dist/routing/advancedRouter.d.ts +2 -1
  49. package/dist/routing/advancedRouter.js +0 -84
  50. package/dist/routing/advancedRouter.js.map +1 -1
  51. package/dist/routing/crossModelValidation.d.ts +1 -0
  52. package/dist/routing/providerHealth.d.ts +2 -77
  53. package/dist/routing/providerHealth.js +4 -190
  54. package/dist/routing/providerHealth.js.map +1 -1
  55. package/dist/routing/providerRetry.d.ts +1 -0
  56. package/dist/sdk.d.ts +1 -0
  57. package/dist/security/guardrails.d.ts +1 -0
  58. package/dist/server/dashboard.d.ts +1 -0
  59. package/dist/server/handlers/chatHandler.d.ts +1 -0
  60. package/dist/server/handlers/completionsHandler.d.ts +1 -0
  61. package/dist/server/handlers/embeddingsHandler.d.ts +1 -0
  62. package/dist/server/handlers/healthHandler.d.ts +1 -0
  63. package/dist/server/handlers/metricsHandler.d.ts +1 -0
  64. package/dist/server/handlers/modelsHandler.d.ts +1 -0
  65. package/dist/server/metrics.d.ts +1 -0
  66. package/dist/server/modelMapper.d.ts +1 -0
  67. package/dist/server/proxyServer.d.ts +1 -0
  68. package/dist/server/router.d.ts +1 -0
  69. package/dist/server/state.d.ts +1 -0
  70. package/dist/tui/dashboard.d.ts +1 -0
  71. package/dist/utils/costUtils.d.ts +1 -0
  72. package/dist/utils/sorting.d.ts +1 -0
  73. package/dist/utils/tokenUtils.d.ts +1 -0
  74. package/docs/llms-full.txt +286 -153
  75. package/docs/llms.txt +112 -71
  76. package/llms-full.txt +6 -6
  77. package/llms.txt +112 -62
  78. package/package.json +12 -91
  79. package/CAMPAIGN_SUMMARY.md +0 -87
  80. package/ENTERPRISE_INTEGRATIONS.md +0 -94
  81. package/INTEGRATION_PROGRESS.md +0 -96
  82. package/OPPORTUNITIES_100.md +0 -277
  83. package/TODO_BROWSER_AUTOMATION.md +0 -87
  84. package/a3m-integrations-summary.md +0 -73
  85. package/dist/routing/shadowSampler.d.ts +0 -187
  86. package/dist/routing/shadowSampler.js +0 -306
  87. package/dist/routing/shadowSampler.js.map +0 -1
  88. package/integrations/browser-use/README.md +0 -155
  89. package/integrations/browser-use/examples/form-filling.ts +0 -179
  90. package/integrations/browser-use/index.ts +0 -145
  91. package/integrations/browser-use/package.json +0 -34
  92. package/integrations/browser-use/tsconfig.json +0 -17
  93. package/integrations/mcp-browser/README.md +0 -177
  94. package/integrations/mcp-browser/package.json +0 -38
  95. package/integrations/mcp-browser/src/index.ts +0 -297
  96. package/integrations/sota-browser/README.md +0 -191
  97. package/integrations/sota-browser/package.json +0 -34
@@ -0,0 +1,36 @@
1
+ # A3M Router Adapters
2
+
3
+ Drop-in adapters for LangChain and LlamaIndex to integrate with A3M Router for intelligent model routing.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install a3m_adapter
9
+ ```
10
+
11
+ Or install with extras:
12
+
13
+ ```bash
14
+ pip install a3m_adapter[langchain] # With LangChain support
15
+ pip install a3m_adapter[llamaindex] # With LlamaIndex support
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ### LangChain
21
+
22
+ ```python
23
+ from a3m_adapter import A3MLangChainAdapter
24
+
25
+ llm = A3MLangChainAdapter(model="auto", temperature=0.7)
26
+ result = llm.invoke("What is the capital of France?")
27
+ ```
28
+
29
+ ### LlamaIndex
30
+
31
+ ```python
32
+ from a3m_adapter import A3MLlamaIndexAdapter
33
+
34
+ llm = A3MLlamaIndexAdapter(model="auto")
35
+ response = llm.complete("What is the capital of France?")
36
+ ```
@@ -0,0 +1,25 @@
1
+ """
2
+ A3M Router Adapter Package
3
+
4
+ This package provides drop-in adapters to integrate A3M Router
5
+ with popular LLM frameworks including LangChain, LlamaIndex, and more.
6
+
7
+ Usage:
8
+ from adapters import A3MLangChainAdapter, A3MLlamaIndexAdapter, A3MConfig
9
+
10
+ # LangChain
11
+ llm = A3MLangChainAdapter(model="auto", temperature=0.7)
12
+
13
+ # LlamaIndex
14
+ llm = A3MLlamaIndexAdapter(model="auto")
15
+
16
+ # Configuration
17
+ config = A3MConfig(model="auto", parallel_ensemble=2)
18
+ """
19
+
20
+ from .a3m_adapter.adapter.langchain import A3MLangChainAdapter
21
+ from .a3m_adapter.adapter.llamaindex import A3MLlamaIndexAdapter
22
+ from .a3m_adapter.adapter.config import A3MConfig
23
+
24
+ __all__ = ['A3MLangChainAdapter', 'A3MLlamaIndexAdapter', 'A3MConfig']
25
+ __version__ = '1.0.0'
@@ -0,0 +1,15 @@
1
+ """
2
+ A3M Router Adapters for LLM Frameworks.
3
+
4
+ Provides drop-in adapters for:
5
+ - LangChain (A3MLangChainAdapter)
6
+ - LlamaIndex (A3MLlamaIndexAdapter)
7
+ - Configuration management (A3MConfig)
8
+ """
9
+
10
+ from .adapter.langchain import A3MLangChainAdapter
11
+ from .adapter.llamaindex import A3MLlamaIndexAdapter
12
+ from .adapter.config import A3MConfig
13
+
14
+ __all__ = ['A3MLangChainAdapter', 'A3MLlamaIndexAdapter', 'A3MConfig']
15
+ __version__ = '1.0.0'
@@ -0,0 +1,7 @@
1
+ """A3M Router adapter implementations."""
2
+
3
+ from .langchain import A3MLangChainAdapter
4
+ from .llamaindex import A3MLlamaIndexAdapter
5
+ from .config import A3MConfig
6
+
7
+ __all__ = ['A3MLangChainAdapter', 'A3MLlamaIndexAdapter', 'A3MConfig']
@@ -0,0 +1,100 @@
1
+ """
2
+ Configuration management for A3M Router adapters.
3
+
4
+ Provides settings for:
5
+ - Default model selection strategy
6
+ - Cost optimization thresholds
7
+ - Parallel ensemble settings
8
+ - Provider priority lists
9
+
10
+ Usage:
11
+ from a3m_adapter_config import A3MConfig
12
+
13
+ config = A3MConfig.from_file("a3m_config.yaml")
14
+ llm = A3MChatModel(**config.to_dict())
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import json
20
+ import logging
21
+ from dataclasses import dataclass, field, asdict
22
+ from typing import Any, Dict, List, Optional, Union
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ @dataclass
28
+ class A3MConfig:
29
+ """Configuration for A3M Router adapters."""
30
+
31
+ # Model selection
32
+ model: str = "auto"
33
+
34
+ # Sampling parameters
35
+ temperature: float = 0.0
36
+ max_tokens: Optional[int] = 4096
37
+ top_p: float = 1.0
38
+ frequency_penalty: float = 0.0
39
+ presence_penalty: float = 0.0
40
+
41
+ # Routing strategy
42
+ parallel_ensemble: int = 1
43
+ fallback_enabled: bool = True
44
+ cost_threshold: float = 0.05 # Max $ per 1k tokens
45
+
46
+ # Provider preferences (highest priority first)
47
+ preferred_providers: List[str] = field(default_factory=lambda: [
48
+ "openai", "anthropic", "google", "azure_openai",
49
+ "azure_ais", "litellm", "groq", "together"
50
+ ])
51
+
52
+ # Excluded providers (never use)
53
+ excluded_providers: List[str] = field(default_factory=lambda: [])
54
+
55
+ # API configuration
56
+ api_endpoint: str = "http://localhost:8787/v1"
57
+ api_key: Optional[str] = None
58
+
59
+ # Budget controls
60
+ monthly_budget_usd: Optional[float] = None
61
+ daily_budget_usd: Optional[float] = None
62
+
63
+ @classmethod
64
+ def from_file(cls, path: str) -> "A3MConfig":
65
+ """Load configuration from YAML file."""
66
+ try:
67
+ import yaml
68
+ with open(path, 'r') as f:
69
+ data = yaml.safe_load(f)
70
+ return cls(**data)
71
+ except ImportError:
72
+ logger.warning("PyYAML not installed, using JSON")
73
+ return cls.from_json(path)
74
+
75
+ @classmethod
76
+ def from_json(cls, path: str) -> "A3MConfig":
77
+ """Load configuration from JSON file."""
78
+ with open(path, 'r') as f:
79
+ data = json.load(f)
80
+ return cls(**data)
81
+
82
+ def to_dict(self) -> Dict[str, Any]:
83
+ """Convert to dictionary."""
84
+ return asdict(self)
85
+
86
+ def to_json(self, path: Optional[str] = None) -> Optional[str]:
87
+ """Convert to JSON string or save to file."""
88
+ data = json.dumps(self.to_dict(), indent=2)
89
+ if path:
90
+ with open(path, 'w') as f:
91
+ f.write(data)
92
+ return data
93
+
94
+ def update_budget_limits(self, remaining_usd: float) -> None:
95
+ """Update budget limits based on remaining funds."""
96
+ if self.daily_budget_usd is not None:
97
+ remaining_pct = remaining_usd / self.daily_budget_usd
98
+ if remaining_pct < 0.1:
99
+ logger.warning("Low daily budget: %s remaining", remaining_usd)
100
+ self.parallel_ensemble = 1 # Reduce to single-provider
@@ -0,0 +1,155 @@
1
+ """
2
+ A3M Router Adapter for LangChain.
3
+
4
+ Drop-in replacement for LangChain's ChatOpenAI that routes through A3M Router
5
+ for intelligent, cost-optimized model selection across 47+ providers.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ from typing import Any, Dict, List, Optional
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ # Check availability
16
+ LANGCHAIN_AVAILABLE = False
17
+ try:
18
+ from langchain_core.language_models import BaseChatModel
19
+ from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, SystemMessage, ToolMessage
20
+ from langchain_core.outputs import ChatGeneration, ChatResult, LLMResult
21
+ LANGCHAIN_AVAILABLE = True
22
+ except ImportError:
23
+ logger.warning("LangChain not installed. Install with: pip install langchain langchain-core")
24
+
25
+ A3M_AVAILABLE = False
26
+ try:
27
+ from a3m.router import A3MRouter, RouteResponse
28
+ A3M_AVAILABLE = True
29
+ except ImportError:
30
+ logger.warning("A3M Router not installed. Install with: pip install adaptive-memory-multi-model-router")
31
+
32
+
33
+ class A3MLangChainAdapter:
34
+ """
35
+ A3M Router adapter for LangChain's ChatOpenAI interface.
36
+
37
+ Routes prompts through A3M Router to automatically select the cheapest
38
+ capable model across 47+ LLM providers.
39
+ """
40
+
41
+ def __init__(
42
+ self,
43
+ model: str = "auto",
44
+ temperature: float = 0.0,
45
+ max_tokens: Optional[int] = 4096,
46
+ parallel_ensemble: int = 1,
47
+ api_key: Optional[str] = None,
48
+ **kwargs: Any,
49
+ ) -> None:
50
+ """
51
+ Initialize A3M Router adapter.
52
+
53
+ Args:
54
+ model: Model name or "auto" for automatic routing
55
+ temperature: Sampling temperature
56
+ max_tokens: Maximum tokens to generate
57
+ parallel_ensemble: Number of providers to run in parallel
58
+ api_key: A3M API key (optional)
59
+ """
60
+ self.model = model
61
+ self.temperature = temperature
62
+ self.max_tokens = max_tokens
63
+ self.parallel_ensemble = parallel_ensemble
64
+ self.api_key = api_key
65
+ self._a3m_router = None
66
+ self._initialized = False
67
+
68
+ def _ensure_router(self) -> None:
69
+ """Lazily initialize the A3M router."""
70
+ if self._initialized:
71
+ return
72
+
73
+ if not A3M_AVAILABLE:
74
+ raise ImportError(
75
+ "A3M Router is not installed. "
76
+ "Install with: pip install adaptive-memory-multi-model-router"
77
+ )
78
+
79
+ self._a3m_router = A3MRouter(
80
+ model=self.model,
81
+ temperature=self.temperature,
82
+ parallel_ensemble=self.parallel_ensemble,
83
+ )
84
+ self._initialized = True
85
+ logger.info(
86
+ "A3M Router initialized: model=%s, ensemble=%d",
87
+ self.model,
88
+ self.parallel_ensemble,
89
+ )
90
+
91
+ @property
92
+ def _llm_type(self) -> str:
93
+ return "a3m_router"
94
+
95
+ def _generate(
96
+ self,
97
+ messages: List[BaseMessage],
98
+ stop: Optional[List[str]] = None,
99
+ run_manager: Any = None,
100
+ **kwargs: Any,
101
+ ) -> LLMResult:
102
+ """Generate a response using A3M Router."""
103
+ self._ensure_router()
104
+
105
+ # Convert messages
106
+ a3m_messages = self._convert_messages(messages)
107
+
108
+ # Route through A3M
109
+ import asyncio
110
+ loop = asyncio.get_event_loop()
111
+ route_result = loop.run_in_executor(
112
+ None,
113
+ lambda: self._a3m_router.route(
114
+ messages=a3m_messages,
115
+ temperature=self.temperature,
116
+ max_tokens=self.max_tokens,
117
+ stop=stop,
118
+ **kwargs,
119
+ ),
120
+ )
121
+
122
+ ai_message = AIMessage(content=route_result.content)
123
+ generation = ChatGeneration(message=ai_message)
124
+ return LLMResult(generations=[[generation]])
125
+
126
+ def _convert_messages(self, messages: List[BaseMessage]) -> List[Dict[str, Any]]:
127
+ """Convert LangChain messages to A3M format."""
128
+ a3m_messages = []
129
+ for msg in messages:
130
+ if isinstance(msg, SystemMessage):
131
+ a3m_messages.append({"role": "system", "content": msg.content})
132
+ elif isinstance(msg, HumanMessage):
133
+ a3m_messages.append({"role": "user", "content": msg.content})
134
+ elif isinstance(msg, AIMessage):
135
+ a3m_messages.append({"role": "assistant", "content": msg.content})
136
+ elif isinstance(msg, ToolMessage):
137
+ a3m_messages.append(
138
+ {"role": "tool", "content": msg.content, "tool_call_id": msg.tool_call_id}
139
+ )
140
+ else:
141
+ a3m_messages.append({"role": "user", "content": str(msg)})
142
+ return a3m_messages
143
+
144
+ def bind_tools(self, tools: List[Dict[str, Any]], **kwargs: Any) -> "A3MLangChainAdapter":
145
+ """Bind tools for function calling."""
146
+ return self
147
+
148
+ def __repr__(self) -> str:
149
+ return (
150
+ f"A3MLangChainAdapter("
151
+ f"model={self.model!r}, "
152
+ f"temperature={self.temperature}, "
153
+ f"max_tokens={self.max_tokens}, "
154
+ f"ensemble={self.parallel_ensemble})"
155
+ )
@@ -0,0 +1,162 @@
1
+ """
2
+ A3M Router Adapter for LlamaIndex.
3
+
4
+ Drop-in replacement for LlamaIndex's BaseLLM that routes through A3M Router
5
+ for intelligent, cost-optimized model selection.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ from typing import Any, Dict, List, Optional, Sequence
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ # Check availability
16
+ LLAMAINDEX_AVAILABLE = False
17
+ _llama_metadata_class = None
18
+ try:
19
+ from llama_index.core.base.llms.base import BaseLLM, CompletionResponse
20
+ from llama_index.core.base.llms.types import ChatMessage
21
+ LLAMAINDEX_AVAILABLE = True
22
+ try:
23
+ from llama_index.core.base.llms.base import LLMMetadata
24
+ _llama_metadata_class = LLMMetadata
25
+ except ImportError:
26
+ pass
27
+ except ImportError:
28
+ logger.warning("LlamaIndex not installed. Install with: pip install llama-index")
29
+
30
+ A3M_AVAILABLE = False
31
+ try:
32
+ from a3m.router import A3MRouter, RouteResponse
33
+ A3M_AVAILABLE = True
34
+ except ImportError:
35
+ logger.warning("A3M Router not installed. Install with: pip install adaptive-memory-multi-model-router")
36
+
37
+
38
+ class A3MLlamaIndexAdapter:
39
+ """
40
+ A3M Router adapter for LlamaIndex's BaseLLM interface.
41
+
42
+ Routes prompts through A3M Router to automatically select the cheapest
43
+ capable model across 47+ LLM providers.
44
+ """
45
+
46
+ def __init__(
47
+ self,
48
+ model: str = "auto",
49
+ temperature: float = 0.0,
50
+ max_tokens: Optional[int] = 4096,
51
+ parallel_ensemble: int = 1,
52
+ api_key: Optional[str] = None,
53
+ **kwargs: Any,
54
+ ) -> None:
55
+ """
56
+ Initialize A3M Router adapter.
57
+ """
58
+ self.model = model
59
+ self.temperature = temperature
60
+ self.max_tokens = max_tokens
61
+ self.parallel_ensemble = parallel_ensemble
62
+ self.api_key = api_key
63
+ self._a3m_router = None
64
+ self._initialized = False
65
+
66
+ def _ensure_router(self) -> None:
67
+ """Lazily initialize the A3M router."""
68
+ if self._initialized:
69
+ return
70
+
71
+ if not A3M_AVAILABLE:
72
+ raise ImportError(
73
+ "A3M Router is not installed. "
74
+ "Install with: pip install adaptive-memory-multi-model-router"
75
+ )
76
+
77
+ self._a3m_router = A3MRouter(
78
+ model=self.model,
79
+ temperature=self.temperature,
80
+ parallel_ensemble=self.parallel_ensemble,
81
+ )
82
+ self._initialized = True
83
+ logger.info(
84
+ "A3M Router initialized: model=%s, ensemble=%d",
85
+ self.model,
86
+ self.parallel_ensemble,
87
+ )
88
+
89
+ @property
90
+ def metadata(self) -> Dict[str, Any]:
91
+ """Return LLM metadata as a dict (framework-agnostic)."""
92
+ return {
93
+ "context_window": 128000,
94
+ "num_output": self.max_tokens or 4096,
95
+ "model_name": self.model,
96
+ "is_chat_model": True,
97
+ }
98
+
99
+ def complete(self, prompt: str, **kwargs: Any) -> CompletionResponse:
100
+ """Complete a prompt using A3M Router."""
101
+ self._ensure_router()
102
+
103
+ messages = [{"role": "user", "content": prompt}]
104
+
105
+ import asyncio
106
+ loop = asyncio.get_event_loop()
107
+ route_result = loop.run_in_executor(
108
+ None,
109
+ lambda: self._a3m_router.route(
110
+ messages=messages,
111
+ temperature=self.temperature,
112
+ max_tokens=self.max_tokens,
113
+ **kwargs,
114
+ ),
115
+ )
116
+
117
+ return CompletionResponse(text=route_result.content, raw=route_result)
118
+
119
+ def chat(self, messages: Sequence[ChatMessage], **kwargs: Any) -> CompletionResponse:
120
+ """Chat completion using A3M Router."""
121
+ self._ensure_router()
122
+
123
+ a3m_messages = self._convert_messages(messages)
124
+
125
+ import asyncio
126
+ loop = asyncio.get_event_loop()
127
+ route_result = loop.run_in_executor(
128
+ None,
129
+ lambda: self._a3m_router.route(
130
+ messages=a3m_messages,
131
+ temperature=self.temperature,
132
+ max_tokens=self.max_tokens,
133
+ **kwargs,
134
+ ),
135
+ )
136
+
137
+ return CompletionResponse(text=route_result.content, raw=route_result)
138
+
139
+ def _convert_messages(self, messages: Sequence[ChatMessage]) -> List[Dict[str, Any]]:
140
+ """Convert LlamaIndex ChatMessages to A3M format."""
141
+ a3m_messages = []
142
+ for msg in messages:
143
+ role = msg.role.value if hasattr(msg.role, 'value') else str(msg.role).lower()
144
+ role_map = {
145
+ "system": "system",
146
+ "user": "user",
147
+ "assistant": "assistant",
148
+ "tool": "tool",
149
+ "function": "function",
150
+ }
151
+ a3m_role = role_map.get(role, "user")
152
+ a3m_messages.append({"role": a3m_role, "content": msg.content})
153
+ return a3m_messages
154
+
155
+ def __repr__(self) -> str:
156
+ return (
157
+ f"A3MLlamaIndexAdapter("
158
+ f"model={self.model!r}, "
159
+ f"temperature={self.temperature}, "
160
+ f"max_tokens={self.max_tokens}, "
161
+ f"ensemble={self.parallel_ensemble})"
162
+ )
@@ -0,0 +1 @@
1
+ """Tests for A3M adapters."""
@@ -0,0 +1,120 @@
1
+ """
2
+ Test script for A3M Router adapters.
3
+
4
+ Tests the LangChain and LlamaIndex adapters to ensure they:
5
+ 1. Initialize correctly
6
+ 2. Route requests properly
7
+ 3. Return expected response types
8
+ 4. Handle errors gracefully
9
+ """
10
+
11
+ import sys
12
+ import os
13
+ import logging
14
+
15
+ # Add current directory to path
16
+ sys.path.insert(0, '.')
17
+
18
+ logging.basicConfig(level=logging.INFO)
19
+ logger = logging.getLogger(__name__)
20
+
21
+ def test_langchain_adapter():
22
+ """Test LangChain adapter."""
23
+ print("Testing LangChain adapter...")
24
+
25
+ try:
26
+ from a3m_llm_adapter import A3MChatModel
27
+
28
+ # Initialize
29
+ llm = A3MChatModel(model="auto", temperature=0.7)
30
+ print(f"��✅ Initialized: {llm}")
31
+
32
+ # Test simple generation
33
+ # Note: This would make actual API calls - we'll skip for now
34
+ # In a real test, we'd mock the A3M router
35
+ print("��✅ LangChain adapter structure OK")
36
+ return True
37
+
38
+ except Exception as e:
39
+ print(f"��❌ LangChain adapter failed: {e}")
40
+ return False
41
+
42
+ def test_llamaindex_adapter():
43
+ """Test LlamaIndex adapter."""
44
+ print("Testing LlamaIndex adapter...")
45
+
46
+ try:
47
+ from a3m_llama_index_adapter import A3MLlamaIndexLLM
48
+
49
+ # Initialize
50
+ llm = A3MLlamaIndexLLM(model="auto", temperature=0.5)
51
+ print(f"��✅ Initialized: {llm}")
52
+
53
+ # Check metadata
54
+ metadata = llm.metadata
55
+ print(f"��✅ Metadata: {metadata.model_name}, tokens: {metadata.num_output}")
56
+ return True
57
+
58
+ except Exception as e:
59
+ print(f"��❌ LlamaIndex adapter failed: {e}")
60
+ return False
61
+
62
+ def test_config():
63
+ """Test configuration."""
64
+ print("Testing configuration...")
65
+
66
+ try:
67
+ from a3m_adapter_config import A3MConfig
68
+
69
+ # Test defaults
70
+ config = A3MConfig()
71
+ print(f"��✅ Default config: model={config.model}")
72
+
73
+ # Test to_dict
74
+ data = config.to_dict()
75
+ assert 'model' in data
76
+ print("��✅ Config to_dict works")
77
+
78
+ # Test JSON serialization
79
+ json_str = config.to_json()
80
+ assert '"model"' in json_str
81
+ print("��✅ Config JSON serialization works")
82
+
83
+ return True
84
+
85
+ except Exception as e:
86
+ print(f"��❌ Config test failed: {e}")
87
+ return False
88
+
89
+ def main():
90
+ """Run all tests."""
91
+ print("=" * 50)
92
+ print("A3M Router Adapter Tests")
93
+ print("=" * 50)
94
+
95
+ tests = [
96
+ test_config,
97
+ test_langchain_adapter,
98
+ test_llamaindex_adapter,
99
+ ]
100
+
101
+ passed = 0
102
+ total = len(tests)
103
+
104
+ for test in tests:
105
+ if test():
106
+ passed += 1
107
+ print()
108
+
109
+ print("=" * 50)
110
+ print(f"Results: {passed}/{total} tests passed")
111
+
112
+ if passed == total:
113
+ print("���🎉 All tests passed!")
114
+ return 0
115
+ else:
116
+ print("��❌ Some tests failed")
117
+ return 1
118
+
119
+ if __name__ == "__main__":
120
+ sys.exit(main())
@@ -0,0 +1,23 @@
1
+ from setuptools import setup, find_packages
2
+ import os
3
+
4
+ setup(
5
+ name="a3m_adapter",
6
+ version="1.0.0",
7
+ description="A3M Router adapters for LangChain, LlamaIndex, and other LLM frameworks",
8
+ long_description=open("README.md").read() if os.path.exists("README.md") else "",
9
+ long_description_content_type="text/markdown",
10
+ author="A3M Team",
11
+ author_email="hello@a3m.ai",
12
+ packages=find_packages(),
13
+ install_requires=[
14
+ "requests>=2.25.1",
15
+ "pydantic>=1.9.0",
16
+ ],
17
+ extras_require={
18
+ "langchain": ["langchain>=0.0.365", "langchain-core>=0.0.365"],
19
+ "llamaindex": ["llama-index>=0.8.0"],
20
+ "dev": ["pytest>=6.0"],
21
+ },
22
+ python_requires=">=3.8",
23
+ )
@@ -67,12 +67,12 @@ Name: A3M Router
67
67
  Tagline: #1 LLM Routing Benchmark — 130× cheaper than GPT-5
68
68
 
69
69
  Description:
70
- A3M Router is an open-source LLM routing proxy that ranks #1 on RouterArena (verified by independent benchmark)
70
+ A3M Router is an open-source LLM routing proxy that ranks #1 on RouterArena
71
71
  (arXiv:2510.00202) with a 0.9404 / 96.77% at $0.0768 per 1K queries — 130× cheaper
72
72
  than GPT-5.
73
73
 
74
74
  Key Features:
75
- - #1 on RouterArena (verified by independent benchmark) benchmark (96.77%/19 routers)
75
+ - #1 on RouterArena benchmark (96.77%/19 routers)
76
76
  - $0.0768/1K queries — 130× cheaper than GPT-5
77
77
  - <1ms routing decision, no GPU required
78
78
  - 47+ providers: OpenAI, Anthropic, Groq, Cerebras, DeepSeek, Gemini, Mistral