AI Engineering Academy · Lección

Procesamiento por lotes, enrutamiento de modelos y paneles de costes

Envíe las solicitudes sencillas a modelos más económicos como GPT-4o-mini y las complejas a GPT-4o, agrupe las solicitudes no urgentes y cree un panel de costes que registre el gasto por funcionalidad.

Lección 4 de 413 pasos

Procesamiento por lotes, enrutamiento de modelos y paneles de costes es una lección gratuita de AI Engineering Academy en CoddyKit. Esta es la lección 4 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de AI Engineering Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de AI Engineering Academy incluye 4 lecciones en total.

Tres palancas más para optimizar costes

Después del almacenamiento en caché, hay tres estrategias adicionales que reducen drásticamente los costes operativos de los LLM: el procesamiento por lotes (aplazar las solicitudes no urgentes y enviarlas en bloque con una tarifa de API más baja), el enrutamiento de modelos (dirigir las consultas sencillas a modelos económicos y las complejas a modelos potentes) y los paneles de costes (supervisar el gasto por funcionalidad para identificar dónde tienen mayor retorno de la inversión las optimizaciones). En conjunto, pueden reducir los costes otro 40-60 por ciento además de lo que se ahorra mediante el almacenamiento en caché.

Batch API de OpenAI: 50 % de descuento para cargas de trabajo asíncronas

La Batch API de OpenAI acepta un archivo JSONL con hasta 50.000 solicitudes y las procesa de forma asíncrona en un plazo de 24 horas por el 50 por ciento del precio estándar. Es ideal para cargas de trabajo no interactivas: generar embeddings de grandes corpus de documentos, crear descripciones de productos, ejecutar evaluaciones nocturnas o preprocesar datos de entrenamiento. La contrapartida es la latencia: los resultados estarán disponibles horas más tarde, no de inmediato.

import json
from openai import OpenAI

client = OpenAI()

# Prepare batch file
requests = [
    {
        'custom_id': f'req_{i}',
        'method': 'POST',
        'url': '/v1/chat/completions',
        'body': {
            'model': 'gpt-4o-mini',
            'messages': [
                {'role': 'user', 'content': f'Summarize: {document}'}
            ],
            'max_tokens': 150,
        }
    }
    for i, document in enumerate(documents_to_process)
]

# Write JSONL batch file
with open('/tmp/batch_requests.jsonl', 'w') as f:
    for req in requests:
        f.write(json.dumps(req) + '\n')

# Upload and submit batch
with open('/tmp/batch_requests.jsonl', 'rb') as f:
    batch_file = client.files.create(file=f, purpose='batch')

batch = client.batches.create(
    input_file_id=batch_file.id,
    endpoint='/v1/chat/completions',
    completion_window='24h',
)
print(f'Batch {batch.id} submitted, status: {batch.status}')

Consultar los resultados de un lote

Después de enviar un lote, consulte periódicamente su estado hasta que se complete (el estado cambia de in_progress a completed). Una vez completado, descargue el archivo de salida que contiene los resultados de todas las solicitudes. Cada línea de salida es un objeto JSON con el custom_id de la solicitud y un campo response o error; gestione siempre ambos casos, ya que las solicitudes individuales de un lote pueden fallar de forma independiente.

import time

def wait_for_batch(batch_id: str, poll_interval: int = 60) -> str:
    while True:
        batch = client.batches.retrieve(batch_id)
        print(f'Status: {batch.status}, completed: {batch.request_counts.completed}')

        if batch.status == 'completed':
            return batch.output_file_id
        elif batch.status == 'failed':
            raise RuntimeError(f'Batch failed: {batch.errors}')

        time.sleep(poll_interval)

def download_batch_results(output_file_id: str) -> list[dict]:
    content = client.files.content(output_file_id)
    results = []
    for line in content.text.strip().split('\n'):
        results.append(json.loads(line))
    return results

output_file_id = wait_for_batch(batch.id)
results = download_batch_results(output_file_id)
for result in results[:3]:
    print(result['custom_id'], result.get('response', {}).get('body', {}).get('choices', [{}])[0])

Enrutamiento de modelos: adapte la complejidad al tamaño del modelo

El enrutamiento de modelos asigna cada solicitud al modelo más económico capaz de gestionarla correctamente. GPT-4o-mini cuesta aproximadamente 30 veces menos que GPT-4o, pero resuelve igual de bien tareas sencillas de clasificación, extracción y preguntas y respuestas breves. Enrute las tareas sencillas y estructuradas a modelos pequeños y económicos, y el razonamiento complejo, la síntesis de contextos extensos y la generación matizada a modelos grandes y potentes. Incluso enrutar el 60 por ciento del tráfico a un modelo económico permite ahorrar una cantidad significativa en costes.

CHEAP_MODEL = 'gpt-4o-mini'
POWERFUL_MODEL = 'gpt-4o'

def classify_query_complexity(query: str) -> str:
    # Heuristic-based routing (replace with ML classifier for production)
    words = query.split()
    has_code = any(c in query for c in ['```', 'def ', 'class ', 'SELECT ', 'function '])
    is_multi_step = any(w in query.lower() for w in ['compare', 'analyze', 'explain why', 'evaluate'])
    is_long = len(words) > 50

    if has_code or is_multi_step or is_long:
        return POWERFUL_MODEL
    return CHEAP_MODEL

def routed_completion(messages: list[dict]) -> str:
    user_query = messages[-1].get('content', '')
    model = classify_query_complexity(user_query)
    print(f'Routing to: {model}')
    response = client.chat.completions.create(model=model, messages=messages)
    return response.choices[0].message.content

Enrutamiento basado en LLM para una mayor precisión

El enrutamiento heurístico es rápido, pero frágil. Un enfoque más preciso utiliza un modelo de clasificación pequeño y económico para decidir a qué modelo enrutar cada solicitud. Ajuste un modelo pequeño con ejemplos de consultas sencillas y complejas de su dominio, o utilice prompting few-shot con el propio GPT-4o-mini. La llamada al clasificador cuesta unos cientos de tokens de entrada, mucho menos que enrutar incorrectamente una consulta compleja a un modelo económico que produzca una respuesta errónea.

CLASSIFIER_SYSTEM = '''You are a query complexity classifier.
Classify the user query as SIMPLE or COMPLEX.
SIMPLE: factual lookup, extraction, classification with clear answer.
COMPLEX: multi-step reasoning, synthesis, comparison, code generation, long-form writing.
Reply with just SIMPLE or COMPLEX.'''

def llm_classify_complexity(query: str) -> str:
    response = client.chat.completions.create(
        model='gpt-4o-mini',  # use cheap model for routing
        messages=[
            {'role': 'system', 'content': CLASSIFIER_SYSTEM},
            {'role': 'user', 'content': query},
        ],
        max_tokens=10,
        temperature=0,
    )
    label = response.choices[0].message.content.strip()
    return POWERFUL_MODEL if label == 'COMPLEX' else CHEAP_MODEL

Seguimiento del coste por funcionalidad

Para saber dónde concentrar los esfuerzos de optimización, debe hacer un seguimiento del coste por funcionalidad de la aplicación, no solo del gasto total. Añada una etiqueta de funcionalidad a cada llamada al LLM y acumule los costes de tokens por etiqueta. 'search_summarization' podría consumir el 40 por ciento de su presupuesto y atender solo el 5 por ciento del tráfico, lo que lo convierte en un objetivo prioritario de optimización. 'user_onboarding' podría ser costoso, pero atender un flujo de gran valor que no querrá degradar.

from collections import defaultdict

cost_tracker = defaultdict(lambda: {'prompt_tokens': 0, 'completion_tokens': 0, 'cost_usd': 0.0})

MODEL_PRICING = {
    'gpt-4o-mini': {'input': 0.15 / 1e6, 'output': 0.60 / 1e6},
    'gpt-4o':      {'input': 2.50 / 1e6, 'output': 10.00 / 1e6},
}

def tracked_completion(feature: str, messages: list[dict], model: str = 'gpt-4o-mini') -> str:
    response = client.chat.completions.create(model=model, messages=messages)
    usage = response.usage
    pricing = MODEL_PRICING.get(model, {'input': 0, 'output': 0})
    cost = usage.prompt_tokens * pricing['input'] + usage.completion_tokens * pricing['output']

    cost_tracker[feature]['prompt_tokens'] += usage.prompt_tokens
    cost_tracker[feature]['completion_tokens'] += usage.completion_tokens
    cost_tracker[feature]['cost_usd'] += cost

    return response.choices[0].message.content

def print_cost_report():
    print(f'{"Feature":<30} {"Prompt":<10} {"Completion":<12} {"Cost USD":<12}')
    for feature, stats in sorted(cost_tracker.items(), key=lambda x: -x[1]['cost_usd']):
        print(f'{feature:<30} {stats["prompt_tokens"]:<10} {stats["completion_tokens"]:<12} ${stats["cost_usd"]:.4f}')

Creación de un panel de costes sencillo

Un panel de costes práctico agrega los datos de gasto por funcionalidad y los expone mediante un endpoint HTTP sencillo. Almacene los costes acumulados en Redis con claves de consolidación diaria para poder observar la evolución del gasto a lo largo del tiempo. Añada este panel a sus herramientas internas para desarrolladores, de modo que el equipo pueda ver casi en tiempo real el impacto en los costes de las versiones de las funcionalidades y detectar un gasto descontrolado antes de que se convierta en una factura elevada.

from fastapi import FastAPI
import datetime

app = FastAPI()

async def record_cost(feature: str, model: str, prompt_tokens: int, completion_tokens: int):
    pricing = MODEL_PRICING.get(model, {'input': 0, 'output': 0})
    cost = prompt_tokens * pricing['input'] + completion_tokens * pricing['output']
    today = datetime.date.today().isoformat()
    key = f'cost:{today}:{feature}:{model}'
    await async_r.incrbyfloat(key, cost)
    await async_r.expire(key, 86400 * 30)  # keep 30 days

@app.get('/dashboard/costs')
async def cost_dashboard():
    today = datetime.date.today().isoformat()
    pattern = f'cost:{today}:*'
    costs = {}
    async for key in async_r.scan_iter(match=pattern):
        value = await async_r.get(key)
        parts = key.split(':')
        feature_model = ':'.join(parts[2:])
        costs[feature_model] = float(value or 0)
    return {'date': today, 'costs': costs, 'total': sum(costs.values())}

Alertas de presupuesto mensual

Configure alertas de presupuesto mensual para detectar picos de costes inesperados antes de que se conviertan en facturas elevadas. Calcule el gasto diario acumulado a partir de su sistema de seguimiento de costes, proyéctelo hasta el final del mes y envíe una alerta de Slack cuando la proyección supere el umbral de presupuesto. Una proyección sencilla — daily_spend * days_remaining — detecta pronto las solicitudes descontroladas, incluso si los patrones reales no son lineales.

import datetime
import httpx

SLACK_WEBHOOK = 'https://hooks.slack.com/services/YOUR/WEBHOOK'
MONTHLY_BUDGET_USD = 500.0

async def check_budget_alert():
    today = datetime.date.today()
    days_in_month = 30
    day_of_month = today.day
    days_remaining = days_in_month - day_of_month

    # Sum today's costs
    today_total = sum(cost_tracker[f]['cost_usd'] for f in cost_tracker)
    avg_daily = today_total  # simplified: just today's spend
    projected_month = avg_daily * days_in_month

    if projected_month > MONTHLY_BUDGET_USD:
        message = (
            f'LLM Budget Alert: Projected monthly spend ${projected_month:.2f} '
            f'exceeds budget ${MONTHLY_BUDGET_USD:.2f}. '
            f'Today spend: ${today_total:.2f}'
        )
        async with httpx.AsyncClient() as client:
            await client.post(SLACK_WEBHOOK, json={'text': message})

Cola de solicitudes para gestionar los límites de tasa

Cuando el tráfico aumenta bruscamente, las solicitudes alcanzan los límites de tasa de OpenAI y fallan con 429 Too Many Requests. Una cola de solicitudes almacena temporalmente las solicitudes entrantes y las envía a una velocidad controlada, suavizando los picos de tráfico. En producción, utilice una cola asíncrona respaldada por Redis o por un bróker de mensajes como RabbitMQ, e implemente una lógica de reintento con retroceso exponencial para los errores 429 temporales.

import asyncio
from asyncio import Queue

class RateLimitedLLMClient:
    def __init__(self, requests_per_minute: int = 500):
        self.rpm = requests_per_minute
        self.queue: Queue = Queue(maxsize=1000)
        self.interval = 60.0 / requests_per_minute

    async def start(self):
        asyncio.create_task(self._worker())

    async def _worker(self):
        while True:
            request_fn, future = await self.queue.get()
            try:
                result = await request_fn()
                future.set_result(result)
            except Exception as e:
                future.set_exception(e)
            await asyncio.sleep(self.interval)

    async def submit(self, request_fn) -> str:
        loop = asyncio.get_event_loop()
        future = loop.create_future()
        await self.queue.put((request_fn, future))
        return await future

Integración de todo: pila de optimización de costes

Una pila completa de optimización de costes de LLM funciona por capas: la caché exacta elimina las llamadas de consultas idénticas repetidas, la caché semántica elimina las llamadas de consultas similares, el almacenamiento en caché de prefijos reduce el coste de entrada de todas las llamadas restantes, el enrutamiento de modelos utiliza modelos económicos para las consultas sencillas, el procesamiento por lotes pospone el trabajo no urgente con un descuento del 50 por ciento, y los paneles y las alertas mantienen los costes visibles y controlados. Impleméntelos gradualmente, siguiendo el orden de impacto para su aplicación específica.

# Decision framework for cost optimization priority:
#
# 1. Enable prefix caching (free, zero effort, automatic)
# 2. Add exact caching (high hit rate for FAQ/support bots)
# 3. Add model routing (simple heuristics first, ML classifier later)
# 4. Add semantic caching (complex, high ROI for paraphrase-heavy use cases)
# 5. Enable batch API (only for non-real-time pipelines)
# 6. Build cost dashboard (essential for ongoing monitoring)
#
# Typical combined result in a customer support bot:
# Before: $1,000/month
# After step 1-2: $400/month (-60%)
# After step 3-4: $200/month (-50% of remaining)
# After step 5-6: $150/month and visible

Fallback en cascada cuando falla el modelo económico

Al enrutar una solicitud a un modelo económico, debe gestionar los casos en los que este produzca una respuesta insatisfactoria. Implemente una comprobación de calidad de la salida del modelo económico: compruebe la longitud de la respuesta, la presencia de los campos obligatorios o ejecute una evaluación rápida de LLM-as-judge, y recurra automáticamente al modelo potente si la calidad es insuficiente. Esta red de seguridad le permite enrutar de forma agresiva a modelos económicos sin arriesgarse a degradar la experiencia del usuario.

async def routing_with_fallback(messages: list[dict], min_length: int = 50) -> str:
    # Try cheap model first
    cheap_response = await async_client.chat.completions.create(
        model=CHEAP_MODEL, messages=messages, temperature=0.0
    )
    answer = cheap_response.choices[0].message.content

    # Quality check: response too short indicates poor answer
    if len(answer.strip()) < min_length:
        print(f'Cheap model answer too short ({len(answer)} chars), escalating...')
        powerful_response = await async_client.chat.completions.create(
            model=POWERFUL_MODEL, messages=messages, temperature=0.0
        )
        return powerful_response.choices[0].message.content

    return answer

Comprobación rápida

Compruebe sus conocimientos sobre el procesamiento por lotes, el enrutamiento de modelos y los paneles de costes de esta lección.

Resumen de la lección

En esta lección ha aprendido que la OpenAI Batch API ofrece un descuento del 50 por ciento para cargas de trabajo asíncronas que no son en tiempo real; que el enrutamiento de modelos utiliza modelos económicos como GPT-4o-mini para tareas sencillas y modelos costosos para tareas complejas; y que el seguimiento de costes por funcionalidad revela qué partes de su aplicación consumen la mayor parte del presupuesto, para que pueda priorizar las optimizaciones de forma eficaz. Combinadas con las estrategias de almacenamiento en caché de las lecciones anteriores, estas técnicas pueden reducir entre un 60 y un 80 por ciento los costes de infraestructura de LLM. Ya ha completado el curso de almacenamiento en caché y optimización de costes de LLM.

Gratis para empezar

Aprende Python con un tutor de IA — gratis

Escribe y ejecuta código real en tu navegador, obtén ayuda instantánea de un tutor de IA disponible 24/7 y continúa donde lo dejaste en la web o en la aplicación.

Cursos
30
Lecciones
120

Preguntas frecuentes

¿La lección «Procesamiento por lotes, enrutamiento de modelos y paneles de costes» es gratis?

Sí — el texto completo de «Procesamiento por lotes, enrutamiento de modelos y paneles de costes» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de AI Engineering Academy, actualiza a CoddyKit PRO. El curso de AI Engineering Academy incluye 4 lecciones en total.

¿Qué aprenderé en «Procesamiento por lotes, enrutamiento de modelos y paneles de costes»?

Envíe las solicitudes sencillas a modelos más económicos como GPT-4o-mini y las complejas a GPT-4o, agrupe las solicitudes no urgentes y cree un panel de costes que registre el gasto por funcionalida… Practicas AI Engineering Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar AI Engineering Academy?

No se requiere experiencia previa. AI Engineering Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 4 de 4.

¿Cuánto tiempo toma la lección «Procesamiento por lotes, enrutamiento de modelos y paneles de costes»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de AI Engineering Academy?

Sí. Cada lección de AI Engineering Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Caché exacta con Redis
  2. Caché semántica con embeddings
  3. Caché de prefijos de prompts de OpenAI
  4. Procesamiento por lotes, enrutamiento de modelos y paneles de costes
← Volver a AI Engineering Academy