La factura que nadie mira
Montas un RAG, funciona, lo subes a producción. Un mes después llega la factura del proveedor y alguien pregunta por qué un chatbot cuesta lo que cuesta.
La respuesta está casi siempre en lo mismo: repetición. Cada consulta vuelve a embeber la pregunta, recupera fragmentos y reenvía al modelo un bloque de contexto que apenas cambia entre una llamada y la siguiente. Las mismas instrucciones de sistema, los mismos documentos de referencia, viajando una y otra vez y pagándose cada vez.
La caché es la palanca de coste más ignorada de un RAG. Bien puesta, recorta la factura de tokens sin tocar la calidad de las respuestas. Mal puesta, sirve respuestas equivocadas con total aplomo. Merece la pena entender las dos caras.
Dónde se va el dinero
Una consulta a un RAG gasta en tres sitios, y no cuestan lo mismo.
Primero, el embedding de la pregunta: una llamada barata, pero que se repite en cada consulta aunque la pregunta sea idéntica a la de hace un minuto. Segundo, los tokens de entrada de la generación: aquí está el grueso. El prompt de sistema, las instrucciones y los fragmentos recuperados pueden ser miles de tokens que se envían enteros en cada petición. Tercero, los tokens de salida, que dependen de la respuesta y son los más difíciles de recortar sin degradar el resultado.
La conclusión práctica: el dinero está en la entrada, no en la salida. Y buena parte de esa entrada es contenido que ya habías enviado antes.
Tres cachés, no una
“Poner caché” no es una decisión, son tres, y atacan gastos distintos.
| Capa | Qué cachea | Cuándo ayuda | Riesgo principal |
|---|---|---|---|
| Caché de prompt | Contexto estable enviado al modelo | System largo, documentos fijos, pocos cambios | Casi ninguno; lo gestiona el proveedor |
| Caché de embeddings | Vectores de textos ya procesados | Reindexados, preguntas repetidas | Embeddings obsoletos si cambia el modelo |
| Caché semántica | Respuestas a preguntas parecidas | Preguntas frecuentes muy similares | Servir una respuesta que no encaja |
El orden de esta tabla no es casual: es el orden en que conviene adoptarlas, de menos a más riesgo.
Caché de prompt: la victoria fácil
La mayoría de proveedores serios ofrecen ya caché de contexto: marcas la parte estable del prompt (el sistema, las instrucciones, los documentos que no cambian) y solo pagas el precio completo la primera vez. Las siguientes llamadas que reutilizan ese prefijo pagan una fracción por esos tokens.
# El bloque estable va primero y se marca como cacheable.
# Solo la pregunta cambia entre llamadas.
respuesta = cliente.mensajes.crear(
model="modelo-llm",
system=[
{
"type": "text",
"text": INSTRUCCIONES_Y_DOCUMENTOS_FIJOS, # miles de tokens
"cache_control": {"type": "ephemeral"}, # cachear este prefijo
}
],
messages=[{"role": "user", "content": pregunta}],
)
La clave está en el orden: lo estable delante, lo variable detrás. La caché funciona sobre prefijos, así que cualquier cambio al principio la invalida entera. Si metes la fecha actual o el nombre del usuario al comienzo del sistema, acabas de tirar el ahorro por la borda. Colócalos al final.
Es la primera caché que activar porque el riesgo es mínimo: el contenido que se envía es idéntico, solo cambia lo que pagas por él.
Caché de embeddings: no pagues dos veces por lo mismo
Embeber texto cuesta poco por llamada, pero se acumula. Reindexas un corpus, reprocesas un documento que solo cambió en una línea, o recibes la misma pregunta cien veces al día. En todos esos casos estás calculando vectores que ya tenías.
La solución es una tabla clave-valor donde la clave es un hash del texto y del modelo de embeddings.
import hashlib
def clave_cache(texto: str, modelo: str) -> str:
# El modelo forma parte de la clave: si cambias de modelo de
# embeddings, los vectores viejos ya no valen y deben recalcularse.
crudo = f"{modelo}:{texto}"
return hashlib.sha256(crudo.encode()).hexdigest()
def embeber(texto, modelo, cache, cliente):
clave = clave_cache(texto, modelo)
if clave in cache:
return cache[clave]
vector = cliente.embeddings(texto, modelo)
cache[clave] = vector
return vector
Incluir el nombre del modelo en la clave no es un detalle menor. Es el error clásico: cambias a un modelo de embeddings mejor, olvidas invalidar la caché y acabas mezclando vectores de dos espacios distintos que no se pueden comparar entre sí. El hash con el modelo dentro te protege de eso gratis.
Caché semántica: potente y traicionera
Aquí es donde se gana más y donde más gente se corta los dedos. La idea es tentadora: si dos usuarios preguntan casi lo mismo, ¿para qué recuperar y generar dos veces? Guardas la respuesta de la primera y se la sirves a la segunda.
El problema es ese “casi”. La caché semántica no compara texto exacto, compara significado por similitud de embeddings. Y “casi lo mismo” no siempre es lo mismo.
def responder_con_cache(pregunta, cache_semantica, rag, umbral=0.97):
vector = embeber(pregunta)
parecida, score = cache_semantica.mas_cercana(vector)
# Umbral alto y deliberado: solo reutilizamos ante preguntas
# casi idénticas. Un umbral flojo sirve respuestas equivocadas.
if score >= umbral:
return parecida.respuesta
respuesta = rag.responder(pregunta)
cache_semantica.guardar(vector, respuesta)
return respuesta
Fíjate en el umbral: 0,97, no 0,85. La diferencia entre “¿cuánto cuesta el plan básico?” y “¿cuánto cuesta el plan premium?” es semánticamente pequeña y económicamente enorme. Un umbral generoso ahorra tokens sirviendo la respuesta del plan equivocado, y eso no es un ahorro, es un fallo con descuento.
Reserva la caché semántica para preguntas de alta frecuencia y respuesta estable (información de producto, preguntas frecuentes reales), pon el umbral incómodamente alto y mide cuántas veces acierta antes de fiarte de ella.
El problema difícil: invalidar
Cachear es fácil; saber cuándo tirar la caché es lo que separa un sistema fiable de uno que miente con datos viejos. Si actualizas un documento y la caché sigue devolviendo la respuesta anterior, has cambiado una factura alta por un problema de confianza, que sale más caro.
Dos reglas simples cubren la mayoría de los casos. Pon un TTL (tiempo de vida) acorde a lo que cambia el contenido: horas para catálogos que se mueven, días para documentación estable. Y ata la caché a una versión del índice: cuando reindexas, cambias el número de versión y las entradas viejas dejan de usarse solas. Es más barato regenerar de más que servir información caduca.
Por dónde empezar
Actívalas en orden de riesgo creciente. Primero la caché de prompt: casi cero riesgo, la gestiona el proveedor y suele ser el mayor recorte de un plumazo. Después la de embeddings, con el modelo dentro de la clave. Y solo entonces, si los datos lo piden, la semántica, con umbral alto y medición encima.
Medir importa aquí tanto como en la recuperación. Registra tu tasa de aciertos y el coste por consulta antes y después. Una caché que ahorra un 40 % pero sirve una respuesta equivocada de cada veinte no está ahorrando: está pasando la factura a otra parte del sistema, y a esa parte le importa más.