Request a routing decision from /v1/should-call before your inference call.
Choose first. Run with a fallback.
Use saved evidence to select a provider, then call your provider directly. The router is advisory; your application owns execution and the safe fallback path.
Send the request with your own provider credentials in headers—not in URLs.
On missing, stale, or failed routing data, use a caller-configured fallback and keep your app available.
"""Python 3, standard library only. Import never sends a request.
python router.py --base-url YOUR_SERVICE_URL # routing only
python router.py --base-url YOUR_SERVICE_URL --execute # billable inference
Own provider credentials stay in this process, never go to the routing service.
Fallbacks are YOUR policy and can differ from the routing service's limits.
"""
import argparse
from datetime import datetime, timezone
import json
import os
import time
from urllib.parse import urlencode, urlsplit
from urllib.request import Request, urlopen
MODELS = {
"openai": "gpt-4o",
"fireworks": "accounts/fireworks/models/gpt-oss-120b",
"cerebras": "gpt-oss-120b",
}
PROVIDERS = {
"openai": ("https://api.openai.com/v1/chat/completions", "OPENAI_API_KEY"),
"fireworks": ("https://api.fireworks.ai/inference/v1/chat/completions", "FIREWORKS_API_KEY"),
"cerebras": ("https://api.cerebras.ai/v1/chat/completions", "CEREBRAS_API_KEY"),
}
PARAMETERS = {
"task_type", "latency_sensitivity", "cost_sensitivity", "provider",
"max_latency_ms", "max_cost_per_1k_tokens_usd",
}
class Router:
def __init__(self, base_url, fallback, models=None, routing_api_key=None):
url = urlsplit(base_url)
if (url.scheme not in ("http", "https") or not url.netloc or url.username or url.password
or url.query or url.fragment):
raise ValueError("Use an http(s) service URL without credentials")
self.base_url = base_url.rstrip("/")
self.models = dict(models or MODELS)
if self.models.get(fallback["provider"]) != fallback["model"]:
raise ValueError("Fallback must match your supported provider/model map")
self.fallback = dict(fallback)
self.routing_api_key = routing_api_key
self.cache = {}
def _fallback(self, reason):
return {**self.fallback, "source": "caller_fallback", "reason": reason}
def choose(self, **params):
if not set(params).issubset(PARAMETERS):
return self._fallback("invalid_parameters")
key = urlencode(sorted(params.items()))
now = time.time()
cached = self.cache.get(key)
if cached and cached[0] > now:
choice = cached[1]
if self.models.get(choice["provider"]) == choice["model"]:
return {**choice, "source": "cache"}
self.cache.pop(key, None)
headers = {"Accept": "application/json"}
if self.routing_api_key:
headers["X-API-Key"] = self.routing_api_key
try:
request = Request(f"{self.base_url}/v1/should-call?{key}", headers=headers)
with urlopen(request, timeout=2) as response:
data = json.load(response)
if data.get("should_call") is not True:
return self._fallback("no_eligible_choice")
provider, model = data["recommended_provider"], data["recommended_model"]
if self.models.get(provider) != model:
return self._fallback("unsupported_model")
measurement = data["measurement"]
expiry = datetime.fromisoformat(measurement["expires_at"].replace("Z", "+00:00"))
if expiry.tzinfo is None or measurement["sample_count"] < 1:
return self._fallback("invalid_evidence")
until = min(time.time() + 30, expiry.astimezone(timezone.utc).timestamp() - 1)
if until <= time.time():
return self._fallback("expired_evidence")
choice = {"provider": provider, "model": model, "source": "routing_service"}
if len(self.cache) >= 32:
self.cache.clear()
self.cache[key] = (until, choice)
return choice
except Exception:
# Do not log URLs, headers or provider error bodies.
return self._fallback("routing_unavailable")
def run(self, prompt, call_provider, **params):
choice = self.choose(**params)
try:
return call_provider(choice["provider"], choice["model"], prompt)
except Exception:
if (choice["provider"], choice["model"]) == (self.fallback["provider"], self.fallback["model"]):
raise
self.cache.clear()
# At most one fallback attempt, never an unbounded retry loop.
return call_provider(self.fallback["provider"], self.fallback["model"], prompt)
def call_provider(provider, model, prompt):
endpoint, key_name = PROVIDERS[provider]
api_key = os.environ.get(key_name)
if not api_key:
raise RuntimeError(f"Configure your own {key_name}")
request = Request(endpoint, data=json.dumps({
"model": model, "messages": [{"role": "user", "content": prompt}], "max_tokens": 256,
}).encode(), headers={"Content-Type": "application/json", "Authorization": f"Bearer {api_key}"})
with urlopen(request, timeout=30) as response:
result = json.load(response)
content = result["choices"][0]["message"]["content"]
if not isinstance(content, str) or not content.strip():
raise RuntimeError("Provider returned no usable text")
return content
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--base-url", required=True)
parser.add_argument("--execute", action="store_true", help="Accept billable provider inference")
parser.add_argument("--prompt", default="Explain what an AI agent is in one sentence.")
args = parser.parse_args()
available = {key: model for key, model in MODELS.items() if os.environ.get(PROVIDERS[key][1])}
if args.execute and not available:
parser.error("Configure at least one of your own provider keys before --execute")
models = available if args.execute else MODELS
fallback_provider = next(iter(models))
router = Router(args.base_url, {"provider": fallback_provider, "model": models[fallback_provider]},
models=models, routing_api_key=os.environ.get("INFERENCELATENCY_API_KEY"))
if args.execute:
try:
print(router.run(args.prompt, call_provider))
except Exception:
parser.exit(1, "Provider inference failed; check your own credentials, quota and model support.\n")
else:
print(json.dumps(router.choose(), indent=2))
if __name__ == "__main__":
main()
/**
* Node.js 18+. No packages. Import never sends a request.
* node router.mjs --base-url YOUR_SERVICE_URL // routing only
* node router.mjs --base-url YOUR_SERVICE_URL --execute // billable inference
* Own provider keys stay client-side. Fallbacks are your policy, not budget guarantees.
*/
import { pathToFileURL } from "node:url";
export const MODELS = {
openai: "gpt-4o",
fireworks: "accounts/fireworks/models/gpt-oss-120b",
cerebras: "gpt-oss-120b",
};
const PROVIDERS = {
openai: ["https://api.openai.com/v1/chat/completions", "OPENAI_API_KEY"],
fireworks: ["https://api.fireworks.ai/inference/v1/chat/completions", "FIREWORKS_API_KEY"],
cerebras: ["https://api.cerebras.ai/v1/chat/completions", "CEREBRAS_API_KEY"],
};
const PARAMETERS = new Set([
"task_type", "latency_sensitivity", "cost_sensitivity", "provider",
"max_latency_ms", "max_cost_per_1k_tokens_usd",
]);
export class Router {
constructor(baseUrl, fallback, { models = MODELS, routingApiKey = null } = {}) {
const url = new URL(baseUrl);
if (!["https:", "http:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) {
throw new Error("Use an http(s) service URL without credentials");
}
this.baseUrl = baseUrl.replace(/\/$/, "");
this.models = { ...models };
if (this.models[fallback.provider] !== fallback.model) throw new Error("Unsupported fallback model");
this.fallback = { ...fallback };
this.routingApiKey = routingApiKey;
this.cache = new Map();
}
fallbackChoice(reason) {
return { ...this.fallback, source: "caller_fallback", reason };
}
async choose(params = {}) {
if (!Object.keys(params).every((key) => PARAMETERS.has(key))) return this.fallbackChoice("invalid_parameters");
const key = new URLSearchParams(Object.entries(params).sort(([a], [b]) => a.localeCompare(b))).toString();
const cached = this.cache.get(key);
if (cached && cached.until > Date.now() && this.models[cached.choice.provider] === cached.choice.model) {
return { ...cached.choice, source: "cache" };
}
this.cache.delete(key);
try {
const headers = { Accept: "application/json" };
if (this.routingApiKey) headers["X-API-Key"] = this.routingApiKey;
const response = await fetch(`${this.baseUrl}/v1/should-call?${key}`, {
headers, signal: AbortSignal.timeout(2000),
});
if (!response.ok) throw new Error("Routing unavailable");
const data = await response.json();
if (data.should_call !== true) return this.fallbackChoice("no_eligible_choice");
const { recommended_provider: provider, recommended_model: model, measurement } = data;
if (typeof provider !== "string" || typeof model !== "string" || this.models[provider] !== model) {
return this.fallbackChoice("unsupported_model");
}
if (!(measurement?.sample_count >= 1) || !/Z$|[+-]\d\d:\d\d$/.test(measurement.expires_at)) {
return this.fallbackChoice("invalid_evidence");
}
const until = Math.min(Date.now() + 30_000, Date.parse(measurement.expires_at) - 1000);
if (!Number.isFinite(until) || until <= Date.now()) return this.fallbackChoice("expired_evidence");
const choice = { provider, model, source: "routing_service" };
if (this.cache.size >= 32) this.cache.clear();
this.cache.set(key, { until, choice });
return choice;
} catch {
return this.fallbackChoice("routing_unavailable");
}
}
async run(prompt, callProvider, params = {}) {
const choice = await this.choose(params);
try {
return await callProvider(choice.provider, choice.model, prompt);
} catch (error) {
if (choice.provider === this.fallback.provider && choice.model === this.fallback.model) throw error;
this.cache.clear();
return await callProvider(this.fallback.provider, this.fallback.model, prompt);
}
}
}
export async function callProvider(provider, model, prompt) {
const [endpoint, keyName] = PROVIDERS[provider];
const apiKey = process.env[keyName];
if (!apiKey) throw new Error(`Configure your own ${keyName}`);
const response = await fetch(endpoint, {
method: "POST", signal: AbortSignal.timeout(30_000),
headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
body: JSON.stringify({ model, messages: [{ role: "user", content: prompt }], max_tokens: 256 }),
});
if (!response.ok) throw new Error("Provider request failed");
const content = (await response.json()).choices?.[0]?.message?.content;
if (typeof content !== "string" || !content.trim()) throw new Error("No usable provider text");
return content;
}
async function main() {
const args = process.argv.slice(2);
const baseIndex = args.indexOf("--base-url");
if (baseIndex < 0 || !args[baseIndex + 1]) {
throw new Error("Usage: node router.mjs --base-url YOUR_SERVICE_URL [--execute]");
}
const execute = args.includes("--execute");
const available = Object.fromEntries(Object.entries(MODELS).filter(([key]) => process.env[PROVIDERS[key][1]]));
if (execute && !Object.keys(available).length) throw new Error("Configure your own provider keys before --execute");
const models = execute ? available : MODELS;
const provider = Object.keys(models)[0];
const router = new Router(args[baseIndex + 1], { provider, model: models[provider] }, {
models, routingApiKey: process.env.INFERENCELATENCY_API_KEY,
});
if (execute) console.log(await router.run("Explain what an AI agent is in one sentence.", callProvider));
else console.log(JSON.stringify(await router.choose(), null, 2));
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
main().catch(() => {
console.error("Request failed. Check the service URL and your own credentials; provider bodies are not logged.");
process.exitCode = 1;
});
}
Keep failures outside your app
Routing results are evidence-backed suggestions, not a promise that a provider is reachable now. Handle routing timeouts, missing decisions, and provider errors in your own code.
- Use a small client-side cache: maximum 30 seconds, and never beyond evidence expiry.
- Configure a fallback provider that your caller is prepared to use.
- Keep your own provider credentials client-side and send API keys in request headers, never URLs.
Read first. Execute only when asked.
Importing either example does not make an inference request. A decision read does not benchmark providers. The optional --execute flag makes billable provider calls; use it deliberately.
Use routing status to inspect saved evidence. Its normal refresh reloads status only.
The code panels show the exact source files supplied to this page. Pricing, availability, and latency can change; validate behavior and provider terms in your own environment.