Model routing: un router de 300ms que decide si gastas $0.001 o $0.05
Por qué Opus para clasificar mensajes es absurdo, y cómo armé un router con Haiku 4.5 que decide en menos de 300ms qué modelo merece cada tarea.
Anthropic vende 3 modelos: Haiku, Sonnet, Opus. Los precios:
| Modelo | Input | Output | Velocidad |
|---|---|---|---|
| Haiku 4.5 | $0.80/M | $4/M | rápido |
| Sonnet 4.6 | $3/M | $15/M | medio |
| Opus 4.7 | $15/M | $75/M | lento |
Opus es ~19× más caro que Haiku. Sonnet es ~4× más caro que Haiku.
La pregunta que casi nadie se hace: ¿cada tarea de mi app necesita Opus?
Spoiler: no. La mayoría son tareas baratas que Haiku resuelve perfecto. Pero la gente usa Opus porque “es el mejor” y luego se queja del costo.
El router
Mi sistema de 11 agentes tiene un router que clasifica cada mensaje y elige el modelo:
TASK_MODELS = {
"routing": "claude-haiku-4-5", # el router en sí
"simple": "claude-haiku-4-5", # FAQ, clasificar, summarize
"standard": "claude-sonnet-4-6", # code review, drafts
"complex": "claude-opus-4-7", # estrategia, decisiones
}
Cada agente declara su complejidad:
class GeneralAgent(Agent):
complexity = "simple" # → Haiku
class CreativeAgent(Agent):
complexity = "standard" # → Sonnet
class OptimizerAgent(Agent):
complexity = "complex" # → Opus
El router decide a qué agente mandar el mensaje. Y el agente decide su propio modelo.
El router: cómo funciona
Es un Haiku con un system prompt corto y structured output forzado:
SYSTEM = """Eres un router de un equipo de agentes IA.
Dado un mensaje, decide qué agente debe atenderlo.
Responde SOLO con JSON válido: {"agent": "<nombre>", "reason": "<una frase>"}
Agentes:
- "coordinator": tareas multi-paso (busca leads Y envía emails)
- "dev": código, debugging, arquitectura
- "ops": estado del servidor, deploys, métricas
- "sales": buscar leads, CRM
- "finance": invoices Stripe, cobrar, pagos
- "email": leer/resumir emails, drafts
- "creative": copy LinkedIn/Twitter, posts
- "marketing": cotizar, propuestas abstractas
- "security": revisar configs, hardening
- "general": saludos, charla, lo demás
"""
Con structured outputs y JSON schema:
resp = await client.messages.create(
model="claude-haiku-4-5",
max_tokens=200,
system=[{
"type": "text",
"text": SYSTEM,
"cache_control": {"type": "ephemeral", "ttl": "1h"},
}],
messages=[{"role": "user", "content": user_message}],
output_config={
"format": {"type": "json_schema", "schema": ROUTER_SCHEMA},
},
)
Latencia típica: 150-300ms. Costo por decisión: <$0.0001 USD.
Sin structured outputs, antes hacía un try/except json.JSONDecodeError con regex de markdown-stripping. Ya no — el output siempre es JSON válido por schema.
Por qué Haiku para el router (y no Sonnet)
La intuición: “el router es la decisión más importante, dale Sonnet para que no se equivoque”.
La realidad: el router tiene una tarea trivial. Clasificar un mensaje en 10 buckets predefinidos no requiere razonamiento de alto nivel. Haiku lo hace perfecto el 99% del tiempo.
Y cuando se equivoca, el cliente (yo) lo redirige con un mensaje explícito (@finance crea un invoice...). Costo del error: 1 mensaje más. Costo de usar Sonnet en cada llamada: 4× más caro × millones de llamadas.
Optimiza el caso común. Acepta el peor caso poco frecuente.
Mi distribución real
Después de 3 meses corriendo:
Routing decisions: ~140/día (todas Haiku)
Simple (Haiku): ~80% del tráfico
Standard (Sonnet): ~15%
Complex (Opus): ~5%
Costo promedio por llamada: $0.001 USD. Con prompt caching encima, $0.0001.
Si usara Opus para todo: 20-50× más caro, latencia 3-5× más lenta, sin ganancia perceptible para tareas simples.
Cuándo usar el modelo grande
Mi regla:
- ✅ Haiku — clasificar, extraer datos estructurados, FAQ, summarize, traducir, decisiones binarias
- ✅ Sonnet — generar contenido (posts, propuestas, emails), code review, planning de tareas, conversaciones de soporte
- ✅ Opus — estrategia (“¿qué debería hacer con mi pricing?”), análisis profundo de logs, decisiones que afectan a muchas otras
La heurística: ¿qué tan grave es si el modelo se equivoca? Si “renombrar variable” se equivoca → fix de 5 segundos. Si “decidir mi estrategia de pricing del próximo trimestre” se equivoca → impacto de meses. Paga Opus solo donde el costo del error importa.
El error que NO debes cometer
Vi gente hacer routing con modelos grandes en producción y luego subir a HackerNews quejándose de la factura. El patrón:
# ❌ MAL
response = await client.messages.create(
model="claude-opus-4-7", # usar Opus para clasificar
system="Decide si este mensaje es bug, feature o pregunta.",
messages=[{"role": "user", "content": msg}],
)
Eso es como pagar consultor senior $500/hora para hacer triage de tickets. Función válida, modelo absolutamente incorrecto.
Combina con prompt caching
El router corre cada mensaje. Su system prompt (~600 tokens) se cachea. 99% de los routes son cache reads → casi gratis.
Más detalles en Bajé mi factura de Claude 90% con prompt caching.
La conclusión
Si tu app de Claude cuesta más de $20/mes y no estás haciendo nada heavy-compute, casi seguro tienes:
- Modelo too-big para el 80% de las llamadas
- Sin prompt caching activo
- Sin estructura de “complejidad declarada” por endpoint/feature
Implementar el router (~50 líneas de código) y declarar complejidades te baja la factura entre 5× y 20×. Sin sacrificar calidad porque la calidad importa solo donde la tarea lo justifica.
Si quieres ver el router en acción, abre oficina.lalu.dev — el modelo mix de la derecha muestra qué porcentaje de llamadas va a cada modelo. En tiempo real.