Skip to content
26 changes: 21 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# A3M Router

**Universal LLM routing gateway — routes requests to the cheapest capable provider across 47+ models.**
**Universal LLM routing gateway with biology-inspired provider selection — routes requests to the cheapest capable model across 47+ providers.**

A3M Router is a stateless proxy between your application and 47+ LLM providers. It inspects each request, estimates how complex it is, and routes it to the cheapest capable provider — without retraining a model or managing GPU infrastructure.
A3M Router is a stateless proxy between your application and 47+ LLM providers. It inspects each request, estimates how complex it is, and routes it to the cheapest capable provider — without retraining a model or managing GPU infrastructure. Provider selection is guided by ecological theory: EXP3 prevents monoculture, Charnov MVT optimizes rate-limit rotation, and Optimal Defense Theory allocates shadow verification to high-stakes queries.

The API uses the OpenAI format (same endpoints, same request/response shapes), so existing SDKs and prompts work without changes. But it routes across any provider you configure, not just OpenAI.

Expand Down Expand Up @@ -57,12 +57,21 @@ A3M stores no training data, requires no GPU, and routes in ~140ms overhead.

## Why Not Just Use LiteLLM?

LiteLLM is the dominant open-source AI gateway (54K stars). It handles unified API access well. A3M Router adds two capabilities LiteLLM doesn't have built-in:
LiteLLM is the dominant open-source AI gateway (54K stars). It handles unified API access well. A3M Router adds three capabilities LiteLLM doesn't have built-in:

### 1. Heuristic Routing
LiteLLM routes by model name or requires you to specify which model to call. A3M's `model="auto"` mode analyzes the query content and picks the cheapest capable provider automatically. This is useful when you want cost efficiency without writing routing logic.

### 2. Parallel Ensemble Execution
### 2. Biology-Inspired Provider Selection
A3M applies established ecological and evolutionary theory to routing decisions:

**EXP3 Diversity Weighting** — Negative frequency-dependent selection prevents any single provider from dominating traffic. Providers above their fair share (1/n of total) receive a penalty proportional to their deviation. This mirrors how ecological niches prevent competitive exclusion — no species dominates when resource competition is symmetric.

**Charnov MVT Rate-Limit Rotation** — When a provider's rate-limit window becomes depleted, A3M uses the Marginal Value Theorem (Charnov 1976) to decide the optimal time to switch. It leaves when the marginal remaining rate falls below the average rate including switch cost — the same logic that explains when animals should leave a depleting food patch.

**ODT Shadow Verification** — For high-stakes queries, A3M can probabilistically sample a shadow provider to verify the primary's answer. The sampling probability follows Optimal Defense Theory: tissue value (query stakes) and attack probability (risk profile) scale verification effort proportionally. This is how plants allocate defensive compounds — expensive defenses go to valuable tissues.

### 3. Parallel Ensemble Execution
Comment on lines +60 to +74

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "README relevant section:"
sed -n '50,85p' README.md

echo
echo "LiteLLM references in repo:"
rg -n "LiteLLM|54K|model=\"auto\"|Heuristic Routing|Biology-Inspired|Parallel Ensemble" -S README.md . --glob '!/.git/**' || true

echo
echo "Current LiteLLM repository stats/features if network API is available:"
python3 - <<'PY'
import urllib.request, json, urllib.error
try:
    req = urllib.request.Request("https://api.github.com/repos/BerriAI/llm_router", headers={"User-Agent": "CodeRabbit-review-verifier"})
    with urllib.request.urlopen(req, timeout=20) as r:
        data = json.load(r)
    print("repo:", data.get("full_name"))
    print("stargazers_count:", data.get("stargazers_count"))
    print("description:", data.get("description"))
    print("topics:", data.get("topics"))
except Exception as e:
    print("api_unavailable_or_error:", repr(e))
PY

Repository: Das-rebel/a3m-router

Length of output: 16387


🌐 Web query:

GitHub BerriAI/llm_router repository stars description model routing

💡 Result:

The functionality related to llm_router is integrated directly into the BerriAI/litellm repository [1][2], rather than existing as a standalone repository named BerriAI/llm_router. LiteLLM is a popular open-source AI gateway and Python SDK that provides model routing, load balancing, and failover capabilities for over 100 LLM APIs [1][3]. The llm_router is a core component within LiteLLM that handles the intelligent distribution of API requests across configured model deployments [1][4][2]. Key aspects of its model routing functionality include: Supported Routing Strategies: The router supports several configurable strategies, including simple-shuffle, least-busy, usage-based, latency-based, and cost-based routing [5]. Routing Groups: A feature that allows users to create named, persisted routing pipelines, grouping multiple model deployments under a single logical model name with specific strategies such as priority-failover or weighted distribution [6]. Configuration and Persistence: The router can be configured via YAML files or managed dynamically through the LiteLLM Proxy server [1][6]. It handles fallbacks, retries, and context window management to ensure robust request execution [5][7]. Deployment: It is instantiated within the LiteLLM proxy server to manage live traffic, and it supports hot-reloading of configurations without requiring service restarts [2][3][6]. As of mid-2026, the LiteLLM repository has surpassed 50,000 stars [3]. Users looking for model routing documentation or implementation details should refer to the LiteLLM documentation and the litellm/router.py module within the main BerriAI/litellm repository [1][5].

Citations:


🌐 Web query:

BerriAI/llm_router GitHub repository

💡 Result:

There is no standalone GitHub repository named BerriAI/llm_router. The functionality referred to as the llm_router is a core component within the BerriAI/litellm repository [1][2][3]. LiteLLM is an open-source AI gateway and Python library that provides a unified interface for over 100 LLM providers [1][4]. Within this project, the llm_router (specifically the Router class) is the module responsible for managing model routing, load balancing, and fallback logic across multiple deployments or providers [1][3][4]. Key details regarding the llm_router component include: Role and Functionality: It acts as a controller for handling requests, enabling features such as automatic retries, fallback chains (e.g., trying a different provider if one fails), and load balancing across various API keys or deployments [4]. It can be used directly within a Python application via the LiteLLM SDK or automatically initialized by the LiteLLM Proxy Server when a configuration file or specific model is provided [1][2][3]. Architecture: In the LiteLLM Proxy Server, a global llm_router instance is typically populated from a configuration file (often config.yaml) to manage traffic centrally [3]. It is integral to the proxy's ability to handle multi-tenancy, cost tracking, and model selection [1][3]. For developers looking to implement or configure routing logic, documentation and reference implementations (such as cookbooks for custom routing strategies) are maintained within the main BerriAI/litellm repository [5].

Citations:


Date or omit the LiteLLM star count and soften the capability comparison.

“54K stars” is specific and can become stale, while “A3M Router adds three capabilities LiteLLM doesn’t have built-in” and “LiteLLM routes by model name or requires you to specify which model to call” overstate the gap as LiteLLM supports configurable router strategies, routing groups, fallbacks, retries, load balancing, and cost-based routing. Frame this as A3M’s particular model="auto" content-based heuristic routing and parallel-ensemble behavior instead of absolute missing capability.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 60 - 74, Revise the README comparison to remove or
omit the “54K stars” count and replace absolute claims about LiteLLM’s missing
capabilities with qualified wording. In the heuristic-routing section, describe
A3M’s particular model="auto" content-based provider selection without asserting
LiteLLM only routes by model name, and frame parallel ensemble execution as
A3M’s behavior rather than a capability LiteLLM lacks.

Source: MCP tools

Sometimes you want the best answer regardless of cost. A3M can call multiple providers in parallel, score each response, and return the best one — with full provenance of which provider won and why.

```typescript
Expand Down Expand Up @@ -107,7 +116,10 @@ Request → Guardrails → Cache → Router → Provider → Response

**Semantic Cache** — Optional. Uses embedding similarity to return cached responses for repeated queries. Cache hit = instant response, zero provider cost.

**Router** — Scores the query, selects tier, picks the cheapest healthy provider in that tier. Model quality scores update online via exponential moving average after each real call — no retraining.
**Router** — Scores the query, selects tier, picks the cheapest healthy provider in that tier. Model quality scores update online via exponential moving average after each real call — no retraining. Three biologically-inspired mechanisms run inside the router:
- **EXP3 diversity weighting** — negative frequency-dependent selection prevents any provider from dominating traffic (no competitive exclusion)
- **Charnov MVT rate-limit rotation** — optimal departure time from depleting rate-limit windows
- **ODT shadow sampler** — probabilistically verifies high-stakes queries proportional to query value (tissue value) and risk (attack probability)

**Ensemble** — Optional. Calls multiple providers in parallel, scores responses on specificity and structure, returns the winner.

Expand Down Expand Up @@ -238,6 +250,10 @@ Two lines total.
- **Providers**: 47+
- **License**: MIT

## Independent Benchmarks

A3M ranks **No. 1** among known public routing baselines on RouterArena (8,400 queries, 96.77% accuracy, $0.0768/1K cost, 1.0000 robustness). See [`docs/BENCHMARK.md`](docs/BENCHMARK.md) for full reproducible benchmarks.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not label the separate benchmark as RouterArena accuracy.

The PR objectives state that RouterArena remains 0.5893, while 96.77% belongs to a separate benchmark. This text currently presents 96.77% as RouterArena accuracy and claims “No. 1,” which is misleading. Name the benchmark and metric explicitly, or reconcile the source results before publishing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 253 - 255, Update the “Independent Benchmarks”
section in README.md to clearly distinguish the 96.77% result and its metric
from RouterArena, whose accuracy must remain 0.5893. Remove or qualify the “No.
1” claim unless it is supported by the correctly identified benchmark, and link
to benchmark documentation consistent with the stated results.


---

## License
Expand Down
101 changes: 101 additions & 0 deletions src/routing/advancedRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,86 @@ import { estimateCost } from "../utils/tokenUtils";
import { logScaleCostScore } from "../utils/costUtils";
import { quickselectTopK, selectTop } from "../utils/sorting";

// ============================================================
// TRAFFIC SHARES FOR ADAPTIVE DIVERSITY WEIGHT (EXP3-inspired)
// Based on negative frequency-dependent selection — as provider i's traffic
// share f_i grows above uniform (1/n), impose a diversity penalty.
// Formula: gamma = sqrt(n * log(n) / (T * G^2)) [Auer et al. 2002, EXP3]
// diversityPenalty_i = gamma * (f_i - 1/n)
// ============================================================

/** Number of times each provider has been selected (cumulative) */
const _selectionCount: Record<string, number> = {};

/** Total routing decisions since last reset */
let _totalDecisions = 0;

/** Reward range estimate for gamma computation (quality_score scale 0-1) */
const _REWARD_RANGE = 1.0;

/**
* Compute the EXP3-inspired diversity penalty for a provider.
* Based on: Auer et al. 2002 — "The Nonstochastic Multiarmed Bandit Problem"
* and the insight that as provider share f_i grows above uniform (1/n),
* imposing a penalty proportional to (f_i - 1/n) prevents monoculture
* (competitive exclusion — negative frequency-dependent selection).
*
* @param providerName - provider key
* @param nProviders - total number of available providers
* @param recentDecayFactor - optional decay for non-stationary environments
*/
function computeDiversityPenalty(
providerName: string,
nProviders: number,
recentDecayFactor = 0.0
): number {
if (nProviders < 2) return 0;
if (_totalDecisions < 2) return 0;

const share = _selectionCount[providerName] || 0;
const uniform = 1.0 / nProviders;

// Deviation from uniform distribution (can be negative if under-used)
const deviation = share - uniform;
if (Math.abs(deviation) < 1e-6) return 0;

// EXP3 learning rate: gamma = sqrt(n * log(n) / (T * G^2))
// G = reward range (quality_score in [0,1] → G = 1)
const T = Math.max(_totalDecisions, 10);
const gamma = Math.sqrt((nProviders * Math.log(nProviders)) / (T * _REWARD_RANGE * _REWARD_RANGE));

// Clamp gamma to prevent extreme penalties when T is very small
const clampedGamma = Math.min(gamma, 0.5);

// Optional: decay factor for non-stationary environments
// (higher share = stronger penalty, but decays over time)
// Currently disabled (recentDecayFactor=0) — re-enable if providers change frequently
const effectiveGamma = clampedGamma * (1.0 - recentDecayFactor);

// Penalty is proportional to how far above uniform the provider's share is
// (negative deviation = under-used = reward, not penalty)
if (deviation <= 0) return 0; // Reward already captured implicitly by below-uniform penalty

return effectiveGamma * deviation;
}
Comment on lines +47 to +80

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

share is a raw count, not a traffic fraction — diversity penalty math is broken.

Line 55 sets share = _selectionCount[providerName] || 0 (a cumulative integer count), then Line 59 computes deviation = share - uniform where uniform = 1/nProviders is a fraction (e.g. 0.5). The docstring and formula (lines 21-24, 39-41) both describe this as the provider's traffic share f_i, which should be _selectionCount[providerName] / _totalDecisions. As written, deviation grows unbounded with cumulative usage count instead of reflecting actual over-representation, so after just a couple of selections the "penalty" already dwarfs typical quality_score/cost_score values and never meaningfully reflects current traffic distribution.

🐛 Proposed fix
-  const share = _selectionCount[providerName] || 0;
+  const share = _totalDecisions > 0 ? (_selectionCount[providerName] || 0) / _totalDecisions : 0;
   const uniform = 1.0 / nProviders;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function computeDiversityPenalty(
providerName: string,
nProviders: number,
recentDecayFactor = 0.0
): number {
if (nProviders < 2) return 0;
if (_totalDecisions < 2) return 0;
const share = _selectionCount[providerName] || 0;
const uniform = 1.0 / nProviders;
// Deviation from uniform distribution (can be negative if under-used)
const deviation = share - uniform;
if (Math.abs(deviation) < 1e-6) return 0;
// EXP3 learning rate: gamma = sqrt(n * log(n) / (T * G^2))
// G = reward range (quality_score in [0,1] → G = 1)
const T = Math.max(_totalDecisions, 10);
const gamma = Math.sqrt((nProviders * Math.log(nProviders)) / (T * _REWARD_RANGE * _REWARD_RANGE));
// Clamp gamma to prevent extreme penalties when T is very small
const clampedGamma = Math.min(gamma, 0.5);
// Optional: decay factor for non-stationary environments
// (higher share = stronger penalty, but decays over time)
// Currently disabled (recentDecayFactor=0) — re-enable if providers change frequently
const effectiveGamma = clampedGamma * (1.0 - recentDecayFactor);
// Penalty is proportional to how far above uniform the provider's share is
// (negative deviation = under-used = reward, not penalty)
if (deviation <= 0) return 0; // Reward already captured implicitly by below-uniform penalty
return effectiveGamma * deviation;
}
function computeDiversityPenalty(
providerName: string,
nProviders: number,
recentDecayFactor = 0.0
): number {
if (nProviders < 2) return 0;
if (_totalDecisions < 2) return 0;
const share = _totalDecisions > 0 ? (_selectionCount[providerName] || 0) / _totalDecisions : 0;
const uniform = 1.0 / nProviders;
// Deviation from uniform distribution (can be negative if under-used)
const deviation = share - uniform;
if (Math.abs(deviation) < 1e-6) return 0;
// EXP3 learning rate: gamma = sqrt(n * log(n) / (T * G^2))
// G = reward range (quality_score in [0,1] → G = 1)
const T = Math.max(_totalDecisions, 10);
const gamma = Math.sqrt((nProviders * Math.log(nProviders)) / (T * _REWARD_RANGE * _REWARD_RANGE));
// Clamp gamma to prevent extreme penalties when T is very small
const clampedGamma = Math.min(gamma, 0.5);
// Optional: decay factor for non-stationary environments
// (higher share = stronger penalty, but decays over time)
// Currently disabled (recentDecayFactor=0) — re-enable if providers change frequently
const effectiveGamma = clampedGamma * (1.0 - recentDecayFactor);
// Penalty is proportional to how far above uniform the provider's share is
// (negative deviation = under-used = reward, not penalty)
if (deviation <= 0) return 0; // Reward already captured implicitly by below-uniform penalty
return effectiveGamma * deviation;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/routing/advancedRouter.ts` around lines 47 - 80, Update
computeDiversityPenalty so share is calculated as the provider’s selection count
divided by _totalDecisions before comparing it with uniform. Keep the existing
early returns, deviation handling, and penalty calculation unchanged, ensuring
the resulting penalty reflects traffic fraction rather than an unbounded
cumulative count.


/**
* Record that a provider was selected (call after each routing decision).
* Used to compute the diversity penalty on subsequent decisions.
*/
function recordSelection(providerName: string): void {
_selectionCount[providerName] = (_selectionCount[providerName] || 0) + 1;
_totalDecisions += 1;
}

/**
* Reset traffic tracking (call when provider pool changes or for eval resets).
*/
function resetDiversityState(): void {
Object.keys(_selectionCount).forEach(k => delete _selectionCount[k]);
_totalDecisions = 0;
}

Comment on lines +91 to +98

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n 'resetDiversityState' -g '*.ts'

Repository: Das-rebel/a3m-router

Length of output: 158


🏁 Script executed:

#!/bin/bash
set -u

printf 'Tracked files matching advancedRouter.ts:\n'
git ls-files | rg '(^|/)advancedRouter\.ts$|src/routing/advancedRouter\.ts$' || true

printf '\nReferences to resetDiversityState (all tracked files):\n'
git ls-files -z | xargs -0 rg -n 'resetDiversityState' || true

printf '\nRelevant src/routing/advancedRouter.ts lines around selection/diversity state:\n'
if [ -f src/routing/advancedRouter.ts ]; then
  nl -ba src/routing/advancedRouter.ts | sed -n '1,140p'
fi

printf '\nExports from src/routing/advancedRouter.ts:\n'
if [ -f src/routing/advancedRouter.ts ]; then
  rg -n '^\s*(export\s+)?(function|const|let|var|class|async function)\b' src/routing/advancedRouter.ts || true
fi

printf '\nImports/references in typescript files containing resetDiversityState text (case-sensitive):\n'
rg -n -S '\bresetDiversityState\b' '*.ts' . || true

printf '\nImports/references in repository files containing resetDiversityState text:\n'
rg -n -a -S '\bresetDiversityState\b' . || true

Repository: Das-rebel/a3m-router

Length of output: 10980


Export resetDiversityState or remove it if eval resets are not intended.

resetDiversityState is referenced only by its own declaration, but its JSDoc explicitly says callers should invoke it for provider pool or eval resets. Make it exportable/importable where this is needed, or remove it if dead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/routing/advancedRouter.ts` around lines 91 - 98, Resolve the unused
resetDiversityState declaration by exporting it for the provider-pool and
evaluation reset callers that its JSDoc describes, then update those callers to
import and invoke it where needed; if no such callers are intended, remove the
function and its JSDoc instead.

// ============================================================
// CACHE FOR MODEL PROFILES (avoids O(n*m) rebuild on every routeQuery)
// ============================================================
Expand Down Expand Up @@ -767,6 +847,24 @@ export function routeQuery(prompt: string, available_models?: string[], budget_m

let topCandidates = quickselectTopK(candidates, 4, scoreFn);

// === DIVERSITY PENALTY (EXP3-inspired, negative frequency-dependence) ===
// As provider share f_i grows above uniform (1/n), impose a diversity penalty.
// This prevents provider monoculture (competitive exclusion) — the core insight
// from the Paradox of the Plankton / negative frequency-dependent selection.
// Penalty is applied only to the quality dimension (complexity_bias-weighted),
// since cost already naturally distributes across providers.
// See: Auer et al. 2002, "The Nonstochastic Multiarmed Bandit Problem"
const nProviders = candidates.length;
for (const c of candidates) {
const divPenalty = computeDiversityPenalty(c.name, nProviders);
// Diversity penalty applies to quality dimension only (cost already disperses traffic)
c.total_score -= divPenalty * complexity_bias;
}

// Re-rank after diversity adjustment
candidates.sort((a, b) => b.total_score - a.total_score);
topCandidates = candidates.slice(0, 4);

// Adaptive quality floor: for complex queries, prefer models above the floor
if (adaptiveQualityFloor > 0) {
const qualified = topCandidates.filter(c => c.quality_score >= adaptiveQualityFloor);
Expand All @@ -779,6 +877,9 @@ export function routeQuery(prompt: string, available_models?: string[], budget_m
const primary = topCandidates[0];
const secondary = topCandidates.slice(1, 3);

// Record selection for diversity tracking (after final decision, before return)
recordSelection(primary.name);

// Calculate confidence based on score gap
let confidence = 0.5;
if (candidates.length > 1) {
Expand Down
Loading
Loading