Minimal LLM Router
Multi-model fallback chain. Zero dependencies. Works everywhere.
Quick Start Β· Features Β· Why Styrr? Β· Ecosystem
Styrr (Old Norse: "rudder") steers your LLM requests to the right model. If one model fails (rate limit, timeout, error), it automatically tries the next. One consistent API for OpenAI, OpenRouter, Bedrock, Ollama β or any OpenAI-compatible endpoint.
import { StyrRouter } from 'styrr';
const router = new StyrRouter({
apiKey: process.env.OPENROUTER_API_KEY!,
models: [
{ id: 'nvidia/nemotron-3-super-120b:free' },
{ id: 'meta-llama/llama-3.3-70b-instruct:free' },
{ id: 'qwen/qwen3-coder:free' },
],
});
const result = await router.prompt('Explain FinOps in 2 sentences.');
console.log(result.text); // "FinOps is..."
console.log(result.modelUsed); // which model responded
console.log(result.latencyMs); // how long it took
console.log(result.fallbacksTried); // 0 if primary workednpm install styrrnpm install styrrimport { StyrRouter } from 'styrr';
const router = new StyrRouter({
apiKey: process.env.OPENROUTER_API_KEY!,
models: [
{ id: 'meta-llama/llama-3.3-70b-instruct:free' },
],
});
const result = await router.prompt('Hello!');
console.log(result.text);| Feature | Description |
|---|---|
| Multi-model fallback | If model 1 returns 429/5xx, automatically tries model 2, 3, etc. |
| Fail-fast on auth errors | 401/400 throws immediately β don't retry with different model |
| Structured JSON output | Auto-parses JSON responses, strips markdown fences |
| Tool calling | Pass tool schemas, get parsed tool_calls back |
| Timeout per model | AbortSignal.timeout per call |
| Zero dependencies | Just fetch() β works anywhere |
| Observable | onFallback and onAllFailed hooks |
const result = await router.call(messages, {
tools: [{
type: 'function',
function: {
name: 'get_weather',
description: 'Get current weather',
parameters: { type: 'object', properties: { city: { type: 'string' } } }
}
}]
});
if (result.toolCalls) {
console.log(result.toolCalls[0].name); // 'get_weather'
console.log(result.toolCalls[0].arguments); // { city: 'Lima' }
}const router = new StyrRouter({
apiKey: '...',
models: [...],
onFallback: (failed, error, next) => {
console.warn(`[Styrr] ${failed} failed (${error}), trying ${next}`);
},
onAllFailed: (errors) => {
console.error('[Styrr] All models exhausted:', errors);
},
});const router = new StyrRouter({
apiKey: 'not-used',
models: [
{ id: 'llama3.2', baseUrl: 'http://localhost:11434/v1', provider: 'ollama' },
{ id: 'gpt-4o', baseUrl: 'https://api.openai.com/v1', apiKey: 'sk-...' },
],
});Pattern for using Styrr as the routing layer inside a Bedrock AgentCore deployment, with ordered fallback by budget + latency (Bedrock β external β free). Lazy-imports the AWS SDK, so the package stays zero-dependency:
const router = new StyrRouter({
apiKey: process.env.OPENROUTER_API_KEY!,
models: [
{ id: 'anthropic.claude-3-sonnet-20240229-v1:0', provider: 'bedrock' },
{ id: 'openai/o1', provider: 'openrouter' },
{ id: 'meta-llama/llama-3.3-70b-instruct:free', provider: 'openrouter' },
],
});See docs/bedrock-agentcore.md for the full pattern (IAM auth, AgentCore tool integration, Sayay budget-aware degrade).
| Feature | Styrr | LiteLLM | OpenRouter |
|---|---|---|---|
| Zero dependencies | β | β | N/A |
| Self-hosted | β | β | β |
| Works in CF Workers | β | β | N/A |
| Fallback chain | β | β | β |
| Tool calling | β | β | β |
| Cost-aware routing | π | β | β |
| Size | ~5KB | ~500KB | β |
| Package | Role | npm |
|---|---|---|
| Styrr | LLM router (this) | styrr |
| Sayay | Cost guardrails | GitHub |
| Tinkuy | Agent framework | @carloscortezcloud/tinkuy-agent |
| Qhaway | Agent observability | @carloscortezcloud/qhaway |
| TideRAG | Edge RAG pipeline | @carloscortezcloud/tiderag |
Apache 2.0 β see LICENSE.
Built by engineers who got tired of vendor lock-in.
Tinkuy Labs Β· finoptix.dev
Works on free models. Zero deps. Ship it.