Todo lo construido en este módulo consume dos recursos que no aparecen en el código: dinero y tiempo. Cada token del SYSTEM_DOCUBOT, de los few-shot del clasificador y del historial de 03-03 se factura en cada llamada; y cada llamada tarda un tiempo que el empleado de Nubelia percibe. En esta lección ponemos números a ambos: entenderemos el modelo de precios por tokens, escribiremos una función de estimación de coste y la proyección mensual de DocuBot (refinando aquella estimación de ~400 €/mes que hicimos a ojo en 01-04), repasaremos las palancas para bajarla, mediremos la latencia con las métricas correctas y terminaremos con la palanca que ataca coste y latencia a la vez: el caching, tanto el de prompts del proveedor como uno de respuestas hecho por nosotros.
Contenido
- El modelo de precios: tokens de entrada y de salida
- Estimar el coste por llamada en Python
- La proyección mensual de DocuBot (refinando la cifra de 01-04)
- Palancas de coste
- Latencia: TTFT, tiempo total y cómo medirlos
- Prompt caching del proveedor: el prefijo estable
- Caching de respuestas propio: hash, TTL y validez
- Presupuestos y alertas: diseño
El modelo de precios: tokens de entrada y de salida
Las APIs de LLM facturan por tokens procesados, con dos tarifas distintas expresadas normalmente en precio por millón de tokens:
- Tokens de entrada (input): todo lo que envías — system prompt, few-shot, documentación, historial, pregunta.
- Tokens de salida (output): todo lo que el modelo genera. Suele costar entre 3 y 5 veces más que la entrada, porque generar es computacionalmente más caro que leer.
Las cifras concretas cambian con frecuencia y dependen del modelo y del proveedor, así que en este curso trabajaremos siempre con variables ilustrativas — sustitúyelas por las tarifas vigentes de tu proveedor el día que calcules en serio:
# precios.py — valores ILUSTRATIVOS, no tarifas reales. # Consulta la página de precios de tu proveedor y actualiza aquí. PRECIO_ENTRADA = 3.0 # € por millón de tokens de entrada (modelo gama media) PRECIO_SALIDA = 15.0 # € por millón de tokens de salida PRECIO_ENTRADA_MINI = 0.5 # € por millón — modelo pequeño (para el clasificador) PRECIO_SALIDA_MINI = 2.5 # Factores típicos del prompt caching del proveedor (ver sección 6): FACTOR_CACHE_LECTURA = 0.1 # leer del caché ≈ 10% del precio de entrada FACTOR_CACHE_ESCRITURA = 1.25 # escribir en caché ≈ 25% de recargo
Dos consecuencias estructurales de este modelo que ya hemos ido intuyendo:
- La entrada domina en sistemas tipo DocuBot. Nuestras llamadas envían miles de tokens (system + few-shot + documentación) para generar unos cientos. Optimizar la entrada rinde más que acortar respuestas.
- Todo lo repetido se re-paga. El
SYSTEM_DOCUBOTviaja íntegro en cada una de las miles de llamadas del mes. Esa repetición es exactamente lo que el prompt caching ataca.
Estimar el coste por llamada en Python
El objeto usage de 03-01 nos da los datos exactos; solo falta ponerles precio:
# costes.py
from precios import PRECIO_ENTRADA, PRECIO_SALIDA
def estimar_coste(usage, precio_entrada: float = PRECIO_ENTRADA,
precio_salida: float = PRECIO_SALIDA) -> float:
"""Coste en euros de una llamada a partir de su objeto usage."""
coste_entrada = usage.input_tokens / 1_000_000 * precio_entrada
coste_salida = usage.output_tokens / 1_000_000 * precio_salida
return coste_entrada + coste_salida
# Uso tras cualquier llamada:
# respuesta = client.messages.create(...)
# print(f"Esta llamada: {estimar_coste(respuesta.usage):.4f} €")Para estimar antes de llamar (por ejemplo, para presupuestar un lote), los proveedores ofrecen un endpoint de conteo de tokens que tokeniza sin generar (en el SDK de Anthropic, client.messages.count_tokens(...), que devuelve input_tokens); a falta de él, la regla aproximada de 01-02 (1 token ≈ 0,75 palabras en español/inglés) sirve para órdenes de magnitud.
Conecta estimar_coste a la contabilidad de la clase Conversacion de 03-03 (que ya acumula tokens_entrada_total y tokens_salida_total) y tendrás el coste por conversación sin esfuerzo extra.
La proyección mensual de DocuBot
En 01-04 estimamos "~400 €/mes" casi a ojo, como orden de magnitud para decidir si el proyecto tenía sentido. Ahora tenemos datos reales de usage y una arquitectura concreta (clasificador + respuesta, el encadenado de 02-02). Refinemos con números ficticios pero coherentes con lo medido en el módulo:
Perfil de una consulta a DocuBot (tokens, medidos/estimados):
| Componente | Entrada | Salida |
|---|---|---|
Llamada de clasificación (few-shot de EJEMPLOS_CLASIFICACION) |
~900 | ~10 |
Llamada de respuesta: SYSTEM_DOCUBOT (~600) + documentación inyectada (~2.000) + pregunta (~100) + plantilla JSON (~150) |
~2.850 | ~300 |
Volumen ficticio: 250 empleados, ~1.200 consultas por día laborable, 22 días/mes → 26.400 consultas/mes.
# proyeccion.py
from precios import (PRECIO_ENTRADA, PRECIO_SALIDA,
PRECIO_ENTRADA_MINI, PRECIO_SALIDA_MINI)
def coste_llamada(t_entrada: int, t_salida: int,
p_entrada: float, p_salida: float) -> float:
return t_entrada / 1e6 * p_entrada + t_salida / 1e6 * p_salida
def proyeccion_mensual(consultas_mes: int = 26_400,
clasificador_en_mini: bool = False) -> float:
"""Proyección mensual de DocuBot en euros (cifras ilustrativas)."""
p_ent_cls = PRECIO_ENTRADA_MINI if clasificador_en_mini else PRECIO_ENTRADA
p_sal_cls = PRECIO_SALIDA_MINI if clasificador_en_mini else PRECIO_SALIDA
clasificacion = coste_llamada(900, 10, p_ent_cls, p_sal_cls)
respuesta = coste_llamada(2_850, 300, PRECIO_ENTRADA, PRECIO_SALIDA)
return consultas_mes * (clasificacion + respuesta)
print(f"Base (todo en gama media): {proyeccion_mensual():.0f} €/mes")
print(f"Clasificador en modelo pequeño: {proyeccion_mensual(clasificador_en_mini=True):.0f} €/mes")Con las tarifas ilustrativas: la llamada de clasificación cuesta ~0,0029 € y la de respuesta ~0,0130 €, es decir ~0,016 € por consulta y ~417 €/mes. La estimación de servilleta de 01-04 (~400 €) era, para variar, razonable — pero ahora sabemos de dónde sale cada céntimo, y eso es lo que convierte una cifra en una palanca: el 82% del coste es la llamada de respuesta, y dentro de ella el 78% es entrada. Ya sabemos dónde apretar.
Palancas de coste
Ordenadas por relación esfuerzo/impacto para DocuBot, todas con lo aprendido en el módulo:
- Modelo más pequeño donde sobra capacidad. El clasificador elige entre 6 etiquetas con few-shot; no necesita el modelo de respuestas. Moverlo al modelo pequeño (
clasificador_en_mini=True) baja su coste de ~0,0029 € a ~0,00047 € por consulta: ~62 €/mes ahorrados cambiando un parámetro. La regla general: cada tarea del pipeline al modelo más barato que supere tu evaluación de 02-04 — la rúbrica decide, no la intuición. - Prompt caching del proveedor (sección 6): el prefijo estable (
SYSTEM_DOCUBOT+ few-shot) pasa a facturarse a ~10% en las llamadas con acierto de caché. max_tokensajustado y formato conciso. El system prompt ya pide <150 palabras (02-01);max_tokens=1024es red de seguridad, no invitación. La salida se paga a precio caro: cada palabra de relleno evitada es ahorro directo.- Recortar la entrada repetida: ¿los 6 ejemplos few-shot son necesarios o la evaluación de 02-04 da igual con 4? ¿La documentación inyectada puede ser más selectiva? (Esta última pregunta es literalmente el módulo 4: RAG es, entre otras cosas, una palanca de coste — inyectar 2 fragmentos relevantes en vez de 10 genéricos.)
- Gestión del historial (03-03). En conversaciones, truncado o ventana deslizante convierten el coste cuadrático acumulado en lineal. El resumen con LLM cuesta una llamada, pero se amortiza si evita reenviar miles de tokens por turno.
- Caching de respuestas propio (sección 7): la consulta repetida no llama a la API en absoluto — coste cero y latencia cero.
Latencia: TTFT, tiempo total y cómo medirlos
La segunda moneda. Dos métricas distintas que ya rozamos en 03-02, ahora con nombre propio:
- TTFT (time to first token): tiempo desde que envías la petición hasta que llega el primer token. Es lo que el usuario percibe como "reacción" del bot. Con streaming, es LA métrica de experiencia.
- Tiempo total: hasta el último token. Gobierna el throughput de procesos por lotes y los timeouts.
Qué influye en cada una:
| Factor | Afecta a TTFT | Afecta al total | Está en tu mano |
|---|---|---|---|
| Tamaño de la entrada (prompt + historial) | ✅ (hay que procesarla entera antes del primer token) | ✅ | Sí: palancas 4 y 5 |
| Longitud de la salida | ❌ | ✅ (token a token) | Sí: max_tokens, formato conciso |
| Modelo elegido (tamaño) | ✅ | ✅ | Sí: palanca 1 |
| Carga del proveedor / hora del día | ✅ | ✅ | No: solo medir (y reintentos de 03-02) |
| Prompt caching del proveedor | ✅ (el prefijo cacheado no se reprocesa) | ✅ | Sí: sección 6 |
Medición con el streaming de 03-02 — el ejercicio 1 de aquella lección era exactamente esto; lo consolidamos como utilidad:
# latencia.py
import time
def medir_llamada(fn_stream, *args, **kwargs) -> dict:
"""Ejecuta una función de streaming y devuelve TTFT y tiempo total."""
inicio = time.perf_counter()
ttft = None
fragmentos = []
for texto in fn_stream(*args, **kwargs): # fn_stream: generador de fragmentos
if ttft is None:
ttft = time.perf_counter() - inicio
fragmentos.append(texto)
return {
"ttft_s": round(ttft, 2),
"total_s": round(time.perf_counter() - inicio, 2),
"texto": "".join(fragmentos),
}Dos consejos de medición: (1) mide percentiles, no medias — la latencia de las APIs tiene cola larga, y el p95 (el peor caso que ve 1 de cada 20 usuarios) cuenta la verdad que la media esconde; (2) mide sobre el conjunto de evaluación de 02-04, que ya representa la distribución real de preguntas — así coste, calidad y latencia se evalúan sobre los mismos casos.
Prompt caching del proveedor: el prefijo estable
Los proveedores ofrecen prompt caching: si el comienzo de tu petición (el prefijo) es byte a byte idéntico al de una petición reciente, el proveedor reutiliza el procesamiento ya hecho y te cobra esa parte a una fracción del precio (lectura de caché ≈ 10% del precio de entrada, con un recargo la primera vez que se escribe ≈ +25%; el caché expira a los pocos minutos sin uso).
La palabra clave es prefijo: el caché casa desde el principio de la petición y cualquier byte distinto invalida todo lo que venga después. De ahí la regla de oro de diseño: lo estable primero, lo variable al final.
Mira nuestra llamada de respuesta con esos ojos:
[system: SYSTEM_DOCUBOT] ← idéntico en TODAS las llamadas ✅ [few-shot / plantilla fija] ← idéntico en todas ✅ [documentación inyectada] ← varía por pregunta ❌ [pregunta del usuario] ← varía siempre ❌
SYSTEM_DOCUBOT y los EJEMPLOS_CLASIFICACION son el candidato perfecto: miles de tokens idénticos repetidos en cada una de las 26.400 llamadas mensuales. En el SDK de Anthropic se marca el final del prefijo cacheable con cache_control; otros proveedores lo aplican automáticamente sobre prefijos repetidos — el concepto es el mismo:
respuesta = client.messages.create(
model=MODELO,
max_tokens=1024,
system=[{
"type": "text",
"text": SYSTEM_DOCUBOT,
"cache_control": {"type": "ephemeral"}, # "cachear hasta aquí"
}],
messages=[{"role": "user", "content": prompt_respuesta(pregunta, documentacion)}],
)
# Verificación: ¿está funcionando?
print(respuesta.usage.cache_creation_input_tokens) # tokens escritos en caché
print(respuesta.usage.cache_read_input_tokens) # tokens LEÍDOS del caché (el ahorro)Si cache_read_input_tokens es 0 llamada tras llamada, algo rompe el prefijo. Los saboteadores silenciosos habituales:
- Un timestamp o dato variable interpolado en el system prompt ("Hoy es {fecha}..."): cada llamada tiene un prefijo distinto. Lo variable, al final del mensaje de usuario.
- Prefijo demasiado corto: hay un mínimo cacheable (del orden de mil tokens según el modelo); prefijos menores no se cachean, sin error.
- Versiones mezcladas: si media flota envía
PROMPT_VERSION 1.2y la otra media la 1.1, cada variante compite por su propio caché. Otro motivo para el versionado disciplinado de 02-04.
Cuánto vale en DocuBot: los ~600 tokens del system (más few-shot si comparten prefijo) a 10% en vez de 100%, sobre 26.400 llamadas/mes, son del orden de 40–50 €/mes con nuestras tarifas ilustrativas — y una mejora de TTFT gratis, porque el prefijo cacheado no se reprocesa. En el módulo 4, cuando la documentación inyectada crezca, el orden "estable primero" será aún más rentable.
Caching de respuestas propio: hash, TTL y validez
El prompt caching abarata la llamada; un caché de respuestas la elimina. Si veinte empleados preguntan "¿cómo exporto a CSV?" la misma semana, ¿por qué generar veinte veces la misma respuesta?
La implementación clásica: clave = hash de la pregunta normalizada, valor = la respuesta validada, con un TTL (tiempo de vida) tras el cual la entrada caduca:
# cache_respuestas.py
import hashlib
import time
import unicodedata
def normalizar(texto: str) -> str:
"""Reduce variantes triviales a una misma clave de caché."""
texto = texto.lower().strip()
# quitar acentos: "cómo" y "como" deben dar la misma clave
texto = unicodedata.normalize("NFKD", texto)
texto = "".join(c for c in texto if not unicodedata.combining(c))
return " ".join(texto.split()) # espacios repetidos -> uno
class CacheRespuestas:
def __init__(self, ttl_segundos: int = 24 * 3600):
self.ttl = ttl_segundos
self.datos: dict[str, tuple[float, dict]] = {} # clave -> (timestamp, respuesta)
def _clave(self, pregunta: str) -> str:
return hashlib.sha256(normalizar(pregunta).encode("utf-8")).hexdigest()
def obtener(self, pregunta: str) -> "dict | None":
entrada = self.datos.get(self._clave(pregunta))
if entrada is None:
return None
timestamp, respuesta = entrada
if time.time() - timestamp > self.ttl:
del self.datos[self._clave(pregunta)] # caducada
return None
return respuesta
def guardar(self, pregunta: str, respuesta: dict):
self.datos[self._clave(pregunta)] = (time.time(), respuesta)Integración delante del pipeline de 03-02:
cache = CacheRespuestas(ttl_segundos=24 * 3600)
def responder_con_cache(pregunta: str, documentacion: str) -> dict:
if (guardada := cache.obtener(pregunta)) is not None:
return guardada # 0 €, ~0 ms
resultado = responder(pregunta, documentacion) # pipeline completo de 03-02
if resultado["confianza"] != "baja": # no fosilizar respuestas dudosas
cache.guardar(pregunta, resultado)
return resultadoCuándo es válido cachear — y cuándo no. Este caché solo es correcto si se cumplen tres condiciones:
- Misma pregunta ⇒ misma respuesta deseada. Cierto para preguntas de documentación ("¿cómo exporto?"); falso para todo lo que dependa del usuario o su estado ("¿qué plan tengo yo?"). En DocuBot conviene cachear solo consultas sin contexto de sesión — de hecho, integrar el caché antes del historial de 03-03: si la pregunta llega dentro de una conversación con contexto, no aplica.
- La fuente no ha cambiado. Si la documentación de Nubelia se actualiza, las respuestas cacheadas quedan obsoletas. El TTL es la defensa pasiva (24h acota el desfase); la activa es invalidar el caché al publicar documentación — un hook en el proceso editorial que haga
cache.datos.clear()o invalide por sección. - La respuesta merecía ser conservada. De ahí el filtro por
confianza: cachear un fallback o una respuesta de baja confianza multiplica un fallo puntual por todos los usuarios que hereden la entrada.
La normalización tiene un límite: "¿cómo exporto a CSV?" y "¿de qué manera saco un CSV del proyecto?" son la misma pregunta para un humano y claves distintas para el hash. El caché exacto solo captura repeticiones literales (que en soporte interno son más frecuentes de lo que parece: la gente copia y pega). La versión que agrupa por significado usa embeddings — la técnica que abre el módulo 4 y que nos regalará, de paso, el caché semántico.
Como con las sesiones de 03-03: el dict en memoria sirve para un proceso; con varios, el caché vive en un almacén compartido (Redis es lo habitual) — el patrón no cambia.
Presupuestos y alertas: diseño
Última pieza, a nivel de diseño (la instrumentación fina — dashboards, trazas — es de 06-04). Un sistema que factura por uso necesita tres números acordados antes de producción:
- Presupuesto mensual (p. ej. 500 €): la cifra que dirección ha aprobado. De él se deriva el diario (~23 €/día laborable) — la señal temprana.
- Umbral de alerta (p. ej. 80%): al cruzarlo, aviso al equipo. No corta el servicio; da tiempo a investigar (¿pico legítimo de adopción? ¿bucle de reintentos descontrolado? ¿alguien pegando documentos enormes?).
- Techo duro (p. ej. 150%): al cruzarlo, el sistema degrada — por ejemplo, activa
RESPUESTA_FALLBACKpara consultas nuevas o restringe a caché — en vez de gastar sin límite. Duele, pero es la diferencia entre un mal día y una mala factura.
En el diseño de DocuBot esto se concreta en poco código y mucha disciplina: un contador de coste por llamada (estimar_coste volcado a un registro, junto al CSV de evaluación de 02-04 que ya conocemos), agregado por día, y comparado contra los umbrales. Además, los proveedores ofrecen límites de gasto y alertas en su consola de facturación: actívalos como red externa — tu contador puede tener bugs; el suyo es el que factura. Y una métrica de diseño más valiosa que el total: coste por consulta resuelta. Si baja mes a mes mientras la calidad (rúbrica de 02-04) se mantiene, estás optimizando de verdad y no simplemente gastando menos en dar peores respuestas.
Errores Comunes y Consejos
- Presupuestar solo la salida (o solo "las respuestas"). En DocuBot la entrada es ~80% del coste: system, few-shot, documentación e historial pesan más que lo generado.
- Copiar tarifas de un tutorial (incluido este). Los precios cambian; las variables
PRECIO_*existen para actualizarse. Fija la fecha de la tarifa junto al número en tu documentación. - Poner la fecha/hora dentro del system prompt y preguntarse por qué el prompt caching no acierta nunca. Lo variable rompe el prefijo: siempre al final, en el mensaje de usuario.
- Cachear respuestas sin TTL ni invalidación. El día que cambie la documentación de Nubelia, DocuBot repetirá instrucciones obsoletas con total seguridad en sí mismo — una alucinación fabricada por tu propia infraestructura.
- Cachear también los fallbacks. Un error transitorio de la API queda fosilizado y servido a todos. Filtra qué merece entrar al caché (confianza, validez del contrato).
- Medir latencia con medias. La media esconde la cola: informa p50 y p95. Un p50 de 1,2s con p95 de 9s es un problema real que la media de 2s disimula.
- Consejo: apunta las tres cifras de cada optimización — coste, latencia (p95) y calidad (rúbrica de 02-04) — antes y después. Una palanca que ahorra 30% de coste pero suspende la evaluación no es una palanca: es un recorte.
- Consejo: revisa
usage.cache_read_input_tokensen tu registro la primera semana tras activar prompt caching. Es la forma más barata de descubrir un saboteador de prefijo silencioso.
Ejercicios
Ejercicio 1
Amplía proyeccion_mensual() con dos parámetros: tasa_acierto_cache: float (fracción de consultas servidas por el caché de respuestas propio, que cuestan 0 €) y prompt_caching: bool (si es True, los ~600 tokens del system de la llamada de respuesta se facturan al 10%). Calcula el escenario combinado: clasificador en mini + 20% de acierto de caché + prompt caching. ¿Cuánto queda de los ~417 €/mes base?
Ejercicio 2
La normalización de CacheRespuestas decide qué preguntas comparten clave. Para cada par, indica si comparten clave con la implementación actual y si deberían compartirla: (a) "¿Cómo exporto a CSV?" / "como exporto a csv"; (b) "¿Cómo exporto a CSV?" / "¿Cómo exporto a PDF?"; (c) "¿Cómo exporto a CSV?" / "¿De qué forma se puede sacar un CSV?"; (d) "¿Qué plan tengo?" / "¿qué plan tengo?". Justifica (d) con especial cuidado.
Ejercicio 3
Diseña la función registrar_llamada(usage, tipo_llamada: str) que alimente el control de presupuesto: debe acumular el coste del día (usa estimar_coste) en un fichero o estructura simple, y devolver una de tres señales: "ok", "alerta" (superado el 80% del presupuesto diario) o "techo" (superado el 150%). Indica en qué punto del pipeline de 03-02 la integrarías y qué debería hacer responder() con cada señal.
Soluciones
Solución 1:
def proyeccion_mensual(consultas_mes=26_400, clasificador_en_mini=False,
tasa_acierto_cache=0.0, prompt_caching=False):
p_ent_cls = PRECIO_ENTRADA_MINI if clasificador_en_mini else PRECIO_ENTRADA
p_sal_cls = PRECIO_SALIDA_MINI if clasificador_en_mini else PRECIO_SALIDA
clasificacion = coste_llamada(900, 10, p_ent_cls, p_sal_cls)
entrada_respuesta = 2_850
if prompt_caching:
# ~600 tokens de system al 10% del precio de entrada
entrada_respuesta = 2_850 - 600
extra_system = 600 * FACTOR_CACHE_LECTURA / 1e6 * PRECIO_ENTRADA * 1e6 / 1e6
# más legible: coste del system cacheado calculado aparte
coste_system = 600 / 1e6 * PRECIO_ENTRADA * FACTOR_CACHE_LECTURA
else:
coste_system = 0.0
entrada_respuesta = 2_850
respuesta = coste_llamada(entrada_respuesta, 300, PRECIO_ENTRADA, PRECIO_SALIDA) + coste_system
consultas_api = consultas_mes * (1 - tasa_acierto_cache) # el resto: caché propio, 0 €
return consultas_api * (clasificacion + respuesta)
print(f"{proyeccion_mensual(clasificador_en_mini=True, tasa_acierto_cache=0.2, prompt_caching=True):.0f} €/mes")Resultado con las tarifas ilustrativas: ~270 €/mes — un ~35% menos que la base de ~417 €, sin tocar la calidad (el clasificador cambia de modelo solo si supera la evaluación de 02-04, el caché solo sirve respuestas ya validadas y el prompt caching es neutro en calidad). Nota didáctica: el cálculo omite el recargo de escritura de caché (~25% en fallos de caché) por simplicidad; inclúyelo si buscas la cifra fina.
Solución 2:
- (a) Comparten clave (minúsculas + acentos + espacios lo igualan; los signos
¿?conviene también eliminarlos ennormalizar— mejora razonable a la implementación). Deberían: ✅ coherente. - (b) No comparten (difieren en "CSV"/"PDF") y no deberían: son preguntas distintas con respuestas distintas. La normalización nunca debe borrar contenido semántico.
- (c) No comparten, pero un humano diría que deberían. Es el límite del caché exacto: capturar esta equivalencia exige comparar significados, no bytes — caché semántico con embeddings, módulo 4.
- (d) Comparten clave... y no deberían estar en el caché en absoluto. La respuesta depende de quién pregunta: cachearla serviría el plan de un empleado a otro — fuga de información entre usuarios, el mismo fallo del ejercicio 3 de 03-03 pero fabricado por el caché. La defensa está antes de la normalización: excluir del caché toda consulta dependiente de sesión/usuario (por ejemplo, cacheando solo la ruta sin historial, o filtrando por categoría del clasificador).
Solución 3:
import datetime, json, pathlib
PRESUPUESTO_DIARIO = 23.0 # € (≈ 500 €/mes entre 22 días laborables)
FICHERO = pathlib.Path("gasto_diario.json")
def registrar_llamada(usage, tipo_llamada: str) -> str:
hoy = datetime.date.today().isoformat()
datos = json.loads(FICHERO.read_text()) if FICHERO.exists() else {}
datos.setdefault(hoy, 0.0)
datos[hoy] += estimar_coste(usage)
FICHERO.write_text(json.dumps(datos))
gasto = datos[hoy]
if gasto >= PRESUPUESTO_DIARIO * 1.5:
return "techo"
if gasto >= PRESUPUESTO_DIARIO * 0.8:
return "alerta"
return "ok"Integración: en responder() (03-02), justo después de cada llamada exitosa — es donde existe usage. Con "ok" no pasa nada; con "alerta" se notifica al equipo (log de nivel warning hoy; canal de avisos cuando exista la observabilidad de 06-04) pero se sigue sirviendo; con "techo" las consultas nuevas pasan a modo degradado (caché propio + RESPUESTA_FALLBACK para lo no cacheado) hasta revisión humana. Nótese la coherencia con todo el curso: la decisión de cortar o seguir gastando es humana; el sistema solo la prepara y la acota.
Conclusión
Con esta lección, el módulo de integración queda completo — y DocuBot deja de ser solo un sistema que funciona para ser un sistema que sabemos operar: conoce el precio de cada token que envía y recibe (estimar_coste, sobre el usage de 03-01), tiene una proyección mensual con nombre y apellidos que confirma y refina la cifra de 01-04 (~417 €/mes base, ~270 € tras aplicar las palancas), distingue TTFT de tiempo total y los mide en percentiles sobre el conjunto de evaluación de 02-04, explota el prompt caching del proveedor gracias a una decisión de diseño que ahora se revela premonitoria (el SYSTEM_DOCUBOT y los few-shot de 02-01/02-02 son un prefijo estable perfecto), evita llamadas enteras con el caché de respuestas propio (hash normalizado, TTL, invalidación al publicar documentación) y opera dentro de un presupuesto con alerta y techo cuya última palabra es humana. Mirando atrás, el módulo 3 convirtió los artefactos de papel del módulo 2 en un sistema vivo: la primera llamada real con roles system/user (03-01), streaming y una estrategia seria de errores, reintentos y degradación (03-02), conversaciones con memoria fabricada y sesiones aisladas (03-03) y, hoy, la economía y la física del conjunto. Pero el talón de Aquiles sigue intacto: DocuBot responde bien cuando le damos la documentación correcta en la mano — y hasta ahora se la hemos dado nosotros, a mano, en cada ejemplo. ¿Quién encuentra, entre cientos de páginas de documentación de Nubelia, los dos fragmentos que responden a cada pregunta? Ese es exactamente el problema que resuelve la generación aumentada por recuperación. En el módulo 4 construimos esa pieza: empezando por los embeddings y la búsqueda semántica (04-01) — la técnica que compara significados en vez de palabras y que, de regalo, resolverá la limitación de nuestro caché exacto —, siguiendo por las bases de datos vectoriales, la ingesta y el chunking de la documentación, y culminando con el pipeline RAG completo que convertirá a DocuBot en lo que prometimos en 01-01: un asistente que de verdad conoce Nubelia.
Curso de IA Generativa y LLMs para Desarrolladores
Módulo 1: Fundamentos de la IA generativa
- Qué es la IA generativa y por qué importa a los desarrolladores
- Cómo funciona un LLM: tokens, embeddings y atención
- Panorama de modelos y proveedores
- Limitaciones y riesgos: alucinaciones, contexto y costes
Módulo 2: Prompt engineering
- Anatomía de un prompt: roles, instrucciones y contexto
- Técnicas de prompting: few-shot, cadena de razonamiento y plantillas
- Salidas estructuradas: JSON y control de formato
- Iteración y evaluación de prompts
Módulo 3: Integración de LLMs en aplicaciones
- Primera integración con la API de un LLM
- Streaming, errores y reintentos
- Conversaciones y gestión del contexto
- Costes, latencia y caching
Módulo 4: RAG - Generación aumentada por recuperación
- Embeddings y búsqueda semántica
- Bases de datos vectoriales
- Ingesta y chunking de documentos
- Construcción de un pipeline RAG completo
- Evaluación de sistemas RAG
Módulo 5: Function calling y agentes
- Function calling: conectar el LLM con tu código
- De LLM a agente: el bucle de razonamiento y acción
- Frameworks de orquestación
