Tiering de Modelos: Enrutamiento de Tareas Simples a Haiku, Tareas Difíciles a Opus
Enviar cada solicitud en un pipeline al mismo modelo es lo más simple de construir y, a cualquier volumen real, una de las opciones más caras que puedes tomar.
Un enrutador que clasifica la complejidad de la tarea primero y solo escala a un modelo más potente y caro cuando la tarea realmente lo necesita puede reducir drásticamente el costo combinado sin afectar la calidad de la salida en las tareas que importan.
Esta página construye ese enrutador: un clasificador de primera pasada barato en Haiku 4.5, una ruta de escalada a Opus 4.8 para cualquier cosa marcada como difícil, y las salvaguardas que evitan que una mala clasificación degrade silenciosamente la calidad.
Receta
Tarjeta de receta de referencia rápida, lista para copiar y pegar.
import anthropic
client = anthropic.Anthropic()
def classify_difficulty(client, task_text: str) -> str:
response = client.messages.create(
model="claude-haiku-4-5",
max_tokens=10,
messages=[{
"role": "user",
"content": f"Clasifica esta tarea como SIMPLE o DIFÍCIL. Responde con una sola palabra.\n\nTarea: {task_text}",
}],
)
return response.content[0].text.strip().upper()
def route(client, task_text: str) -> str:
difficulty = classify_difficulty(client, task_text)
model = "claude-opus-4-8" if difficulty == "HARD" else "claude-sonnet-5"
response = client.messages.create(
model=model,
max_tokens=500,
messages=[{"role": "user", "content": task_text}],
)
return response.content[0].textCuándo usar esto:
- Tu pipeline maneja una mezcla de dificultades de tareas, algunas genuinamente simples (clasificación, extracción, formato), otras genuinamente difíciles (razonamiento de varios pasos, requisitos ambiguos).
- El volumen es lo suficientemente alto como para que la brecha de costo por llamada plana se acumule en una preocupación presupuestaria real.
- Puedes tolerar una pequeña sobrecarga de clasificación (una llamada extra barata) a cambio de precisión en el enrutamiento.
- Quieres una razón defendible y auditable de qué modelo manejó una solicitud dada, no un valor predeterminado arbitrario.
Ejemplo de Trabajo
import anthropic
from dataclasses import dataclass
client = anthropic.Anthropic()
MODELS = {
"haiku": "claude-haiku-4-5",
"sonnet": "claude-sonnet-5",
"opus": "claude-opus-4-8",
}
@dataclass
class RoutedResponse:
text: str
model_used: str
difficulty: str
def classify_difficulty(client, task_text: str) -> str:
"""Clasificación barata de primera pasada en Haiku. Devuelve SIMPLE, MODERADO o DIFÍCIL."""
response = client.messages.create(
model=MODELS["haiku"],
max_tokens=10,
messages=[{
"role": "user",
"content": (
"Clasifica la dificultad de la siguiente tarea con exactamente una palabra: "
"SIMPLE, MODERADO o DIFÍCIL.\n"
"SIMPLE: clasificación, extracción, formato, búsqueda.\n"
"MODERADO: resumen, redacción a partir de requisitos claros.\n"
"DIFÍCIL: razonamiento de varios pasos, requisitos ambiguos, análisis de compensaciones.\n\n"
f"Tarea: {task_text}"
),
}],
)
label = response.content[0].text.strip().upper()
return label if label in {"SIMPLE", "MODERATE", "HARD"} else "MODERATE"
def route_task(client, task_text: str) -> RoutedResponse:
"""Clasifica la dificultad y luego la envía al nivel de modelo correspondiente."""
difficulty = classify_difficulty(client, task_text)
model = {
"SIMPLE": MODELS["haiku"],
"MODERATE": MODELS["sonnet"],
"HARD": MODELS["opus"],
}[difficulty]
response = client.messages.create(
model=model,
max_tokens=800,
messages=[{"role": "user", "content": task_text}],
)
return RoutedResponse(
text=response.content[0].text,
model_used=model,
difficulty=difficulty,
)
result = route_task(client, "Etiqueta este ticket de soporte: 'Mi paquete llegó dañado.'")
print(result.model_used, result.difficulty)
result2 = route_task(
client,
"Necesitamos decidir entre el event sourcing y un simple registro de auditoría para nuestro "
"pipeline de pedidos. Pondera las compensaciones dado que tenemos 3 ingenieros y un plazo de 6 semanas.",
)
print(result2.model_used, result2.difficulty)Lo que esto demuestra:
- Una clasificación de tres niveles (SIMPLE/MODERADO/DIFÍCIL) se mapea limpiamente en la línea de modelos de tres niveles utilizada aquí (Haiku/Sonnet/Opus), en lugar de una división binaria simple o difícil.
- El prompt del clasificador proporciona ejemplos concretos de cada categoría, lo que es más importante para la precisión de la clasificación que el nivel del modelo que realiza la clasificación.
- La clasificación en sí se ejecuta en Haiku, el nivel más barato, ya que clasificar la dificultad es en sí una tarea con forma SIMPLE.
- La clase
RoutedResponsetransportamodel_usedydifficultyjunto con la respuesta, por lo que cada respuesta enrutada es auditable a posteriori. - Un valor predeterminado de respaldo (
MODERATE) maneja el caso en que el clasificador devuelve algo fuera de las tres etiquetas esperadas, en lugar de fallar el pipeline.
Análisis Detallado
Cómo Funciona
- El enrutador agrega exactamente una llamada extra por solicitud: una llamada corta de clasificación de Haiku con un
max_tokensmínimo (10 en este ejemplo), que cuesta una fracción de centavo incluso repetida a gran volumen. - Esa sobrecarga de clasificación es el precio de la decisión de enrutamiento, y debe seguir siendo barata en relación con los ahorros que permite, por eso el clasificador en sí siempre se ejecuta en el nivel más barato.
- La ruta de escalada solo paga precios de nivel Opus para las solicitudes marcadas como DIFÍCIL, por lo que el gasto total sigue la verdadera mezcla de dificultad de tu carga de trabajo en lugar de su peor caso.
- La precisión de la clasificación es el riesgo más importante en este patrón: una tarea DIFÍCIL mal clasificada como SIMPLE se enruta a un modelo que puede producir una respuesta peor, silenciosamente, sin ningún error que la detecte.
Protección Contra la Clasificación Errónea
def route_with_confidence_check(client, task_text: str) -> RoutedResponse:
"""Escala automáticamente si la respuesta de nivel SIMPLE parece demasiado corta o se reserva."""
difficulty = classify_difficulty(client, task_text)
model = {"SIMPLE": MODELS["haiku"], "MODERATE": MODELS["sonnet"], "HARD": MODELS["opus"]}[difficulty]
response = client.messages.create(model=model, max_tokens=800, messages=[{"role": "user", "content": task_text}])
text = response.content[0].text
# Una respuesta de nivel SIMPLE que se reserva mucho es una señal de que el clasificador subestimó la dificultad.
hedge_markers = ("depende", "no estoy seguro", "esto es complejo", "poco claro")
if difficulty == "SIMPLE" and any(marker in text.lower() for marker in hedge_markers):
response = client.messages.create(model=MODELS["opus"], max_tokens=800, messages=[{"role": "user", "content": task_text}])
return RoutedResponse(text=response.content[0].text, model_used=MODELS["opus"], difficulty="HARD (escalado)")
return RoutedResponse(text=text, model_used=model, difficulty=difficulty)Esta es una heurística barata, no una puntuación de confianza rigurosa, pero captura el modo de fallo específico donde una tarea parecía simple de forma aislada, pero el modelo en sí mismo señala incertidumbre. Un sistema de producción que haga esto a escala normalmente registraría las escaladas y las revisaría periódicamente para refinar las definiciones de categoría del clasificador.
Comparación de Costos: Enrutado vs. Plano
| Enfoque | 1,000 tareas SIMPLES + 200 MODERADAS + 50 DIFÍCILES (forma de token ilustrativa) | Costo relativo |
|---|---|---|
| Opus 4.8 plano para todo | Las 1,250 tareas a tarifas de Opus | El más alto |
| Sonnet 5 plano para todo | Las 1,250 tareas a tarifas de Sonnet | Línea base |
| Enrutado (este patrón) | 1,000 en Haiku + 200 en Sonnet + 50 en Opus, más 1,250 llamadas de clasificación | El más bajo, típicamente muy por debajo de la línea base plana de Sonnet |
Los ahorros exactos dependen de la mezcla de tus tareas y las formas de token por tarea, pero la dirección se mantiene en general: cuanto más sesgada esté tu carga de trabajo hacia tareas simples y de alto volumen, más ahorrará un enrutador en comparación con cualquier política plana de un solo modelo.
Parámetros y Valores de Retorno
| Parámetro | Tipo | Descripción |
|---|---|---|
task_text | str | La tarea/prompt sin procesar para clasificar y enrutar. |
classify_difficulty(...) | str | Devuelve uno de SIMPLE, MODERATE, HARD (con un respaldo MODERATE ante una etiqueta inesperada). |
route_task(...) | RoutedResponse | Agrupa la respuesta del modelo con qué modelo lo manejó y por qué, para auditabilidad. |
Posibles Problemas
- Permitir que los errores de clasificación degraden silenciosamente la calidad de la salida. Una tarea DIFÍCIL mal enrutada a Haiku no genera un error, simplemente devuelve una respuesta peor que parece superficialmente correcta. Solución: agrega una verificación de confianza ligera (ver arriba) o audita periódicamente muestras de salidas enrutadas contra una línea base humana o de modelo más potente.
- Hacer que el prompt del clasificador sea demasiado vago. " ¿Es esta tarea simple o difícil?" sin ejemplos produce etiquetas inconsistentes. Solución: dale al clasificador ejemplos concretos de categorías, como en el Ejemplo de Trabajo, ajustados a tu distribución real de tareas.
- Olvidar que la llamada de clasificación en sí tiene un costo. A un volumen extremadamente alto con costos por tarea muy bajos, la sobrecarga de clasificación puede acercarse a los ahorros que se supone que debe generar. Solución: mide el costo del propio clasificador frente a los ahorros que genera; para una carga de trabajo donde cada tarea ya se sabe que tiene una dificultad uniforme, el enrutamiento puede no valer la pena la sobrecarga.
- Codificar las categorías de dificultad para que coincidan solo con dos niveles de modelo. Una división binaria SIMPLE/DIFÍCIL fuerza una elección falsa cuando una tarea es genuinamente MODERADA. Solución: usa tantos niveles como tu línea de modelos admita, mapeando categoría a modelo uno a uno.
- No registrar qué modelo manejó cada solicitud. Sin
model_used(o equivalente) en tu salida, no puedes auditar si las decisiones de enrutamiento fueron correctas después del hecho. Solución: siempre transporta la decisión de enrutamiento junto con la respuesta, como lo haceRoutedResponseaquí. - Tratar el enrutador como una construcción única. Las distribuciones de tareas cambian a medida que un producto evoluciona, y un clasificador ajustado para la mezcla de tareas del trimestre pasado puede desactualizarse. Solución: revisa periódicamente una muestra de decisiones de enrutamiento frente a la dificultad real y reajusta los ejemplos del clasificador.
Alternativas
| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
| Enrutador clasificador basado en LLM (esta página) | La dificultad de la tarea varía y no se puede conocer a partir de metadatos estáticos solamente | La dificultad se determina completamente por un campo que ya tienes (por ejemplo, un tipo de tarea conocido) |
| Enrutador basado en reglas (por ejemplo, por tipo de tarea o longitud de entrada) | La dificultad se correlaciona limpiamente con algo que puedes verificar sin una llamada API adicional | La dificultad depende genuinamente del contenido, no solo de la forma o categoría |
| Política plana de un solo modelo | El volumen de tareas es lo suficientemente bajo como para que la sobrecarga de tiering no valga el costo de ingeniería | La carga de trabajo es de alto volumen y se inclina hacia tareas simples, el tiering es la palanca de mayor apalancamiento disponible |
| Escalar siempre al modelo más potente | La corrección es crítica para la seguridad y el costo es una preocupación secundaria | El costo importa y la mayoría de las tareas no necesitan la capacidad del modelo más potente |
Preguntas Frecuentes
¿Por qué clasificar con Haiku en lugar de una verificación basada en reglas?
Las reglas funcionan bien cuando la dificultad se correlaciona con algo estructural, como la longitud de entrada o un tipo de tarea conocido. Cuando la dificultad depende del contenido real, un clasificador LLM generaliza mejor que un conjunto de reglas escrito a mano.
¿No añade la llamada de clasificación costo y latencia?
Sí, ambas cosas, pero una llamada corta de Haiku es barata y rápida en relación con los ahorros obtenidos al enrutar correctamente lejos de un modelo caro. Mide la compensación para tu volumen específico y presupuesto de latencia.
¿Qué sucede si el clasificador etiqueta erróneamente una tarea DIFÍCIL como SIMPLE?
Se enruta a un modelo más débil y puede devolver una respuesta de menor calidad sin un error explícito. Este es el principal riesgo del patrón, mitigado por verificaciones de confianza, auditorías de muestreo o aceptando una tasa de error como compensación por los ahorros.
¿Debería usar tres niveles o solo dos (simple/difícil)?
Haz coincidir el número de niveles con tu línea de modelos y la distribución de tareas. Tres niveles (Haiku/Sonnet/Opus) se mapean naturalmente en SIMPLE/MODERADO/DIFÍCIL; una división de dos niveles funciona si tus tareas realmente se agrupan en dos cubos limpios.
¿Puede este patrón también enrutar a Fable 5?
Sí, agrega una cuarta categoría para tareas que necesitan la capacidad del nivel superior, y resérvala para una porción genuinamente estrecha del trabajo más difícil y de mayor riesgo, ya que el multiplicador de costo de Fable es el más grande en la línea.
¿Vale la pena construir un enrutador para una herramienta interna de bajo volumen?
Generalmente no. El costo de ingeniería de construir, ajustar y mantener un enrutador solo vale la pena una vez que el volumen es lo suficientemente alto como para que los ahorros por llamada sumen una cantidad significativa.
¿Cómo sé si mi clasificador es realmente preciso?
Muestrea periódicamente las solicitudes enrutadas y compara la salida del modelo enrutado con lo que un modelo más potente habría producido para la misma entrada, luego rastrea la tasa de desacuerdo con el tiempo como tu señal de precisión.
¿El enrutamiento reemplaza la necesidad de caché de prompts o lotes?
No, estas son palancas independientes. Un enrutador bien nivelado aún puede beneficiarse de un prompt del sistema en caché o del procesamiento por lotes para la parte no urgente de su tráfico.
¿Cuál es el mayor riesgo de equivocarse con el tiering de modelos?
La degradación silenciosa de la calidad en tareas difíciles mal enrutadas es el mayor riesgo, ya que no se presenta como un error, sino solo como una salida sutilmente peor que puede no ser detectada hasta que un usuario o revisor la note.
Relacionado
- [Sonnet 5 vs Opus 4.8 vs Fable 5: Una