RAG está quemando dinero: construí una capa de control de costos para solucionarlo

TL;DR

una implementación funcional completa en Python puro, junto con resultados comparativos de una configuración local.

Los sistemas RAG no fallan sólo en la calidad. También pueden volverse ineficientes en términos de costos, a menudo de maneras que no son inmediatamente visibles.

Cada token adicional recuperado tiene un costo. En mi sistema, la recuperación excesiva del contexto osciló entre 3 y 8 veces más de lo que realmente requerían las consultas.

En muchas implementaciones básicas, las consultas repetidas se procesan de forma independiente, sin reutilizar los resultados anteriores.

En configuraciones de un solo modelo, una gran proporción de consultas simples pueden ser manejadas por modelos de alto costo, incluso cuando alternativas de menor costo serían suficientes.

Con el almacenamiento en caché semántico (hasta un 98,5 % de tasa de aciertos en un punto de referencia de caché calentado y presembrado), el enrutamiento de consultas (alrededor del 81 % de las solicitudes cambiaron a un modelo de menor costo en la combinación de puntos de referencia) y una capa de presupuesto de token con un disyuntor, el sistema logró una reducción de costos de hasta un 85,8 % a 10 000 solicitudes por día, al tiempo que mantuvo la calidad de la respuesta en la configuración evaluada.

Estos resultados se basan en ejecuciones de referencia locales bajo la configuración de referencia que se describe a continuación.

El sistema que funcionaba bien y consumía dinero silenciosamente

Construí un sistema RAG que funcionó perfectamente y ejecuté las mismas consultas a través del mismo canal y obtuve los mismos resultados cada vez. En las pruebas, nada parecía estar mal, la latencia era estable y las respuestas eran correctas.

Luego miré los registros de tokens.

En mi configuración, incluso preguntas simples como "¿Qué es RAG?" o "Definir búsqueda semántica". estaban alcanzando el modelo más caro. Cada consulta repetida se facturó en su totalidad, incluso cuando había respondido exactamente la misma pregunta diez minutos antes. Cada solicitud recuperaba diez fragmentos cuando dos hacían el trabajo real.

El sistema no estaba roto. Simplemente fue financieramente ciego. Y a escala, esa distinción deja de importar.

Ejecutar una canalización RAG en una computadora portátil local es fácil. Pero el plan estándar: recuperar, avisar y llamar deja enormes lagunas operativas. El comportamiento de los costos de producción a menudo no es el enfoque principal en muchas guías de implementación de RAG. En el mundo real, debes vigilar la eficiencia de tu cómputo y de tus tokens. ¿Está quemando su presupuesto reprocesando exactamente la misma consulta que llegó al servidor hace tres minutos? ¿Realmente una búsqueda de factoides muy simple necesita recorrer exactamente la misma ruta de modelo pesada y costosa que una consulta de razonamiento de múltiples saltos?

Ya había creado una capa de ingeniería contextual para mi sistema anterior.[7]que controlaba lo que entra en la ventana contextual por razones de calidad. Pero la calidad y el costo son dominios de fracaso diferentes. Puedes tener un control perfecto del contexto y aún así pagar 8 veces más de lo necesario.

Esta es la capa de control de costos que construí encima, con números reales y código que puedes ejecutar.

Todos los resultados a continuación provienen de ejecuciones reales del sistema (Python 3.12.6, Windows 11, solo CPU, sin GPU), excepto cuando se indique explícitamente que son calculados.

Por qué RAG es financieramente ciego por diseño

RAG fue diseñado para resolver un problema de calidad de recuperación[1]. Nunca fue diseñado para resolver un problema de costos. Eso no es una crítica, es simplemente una capa diferente de la pila.

Pero en la producción, las dos capas chocan. Y la colisión es cara.

Hay tres modos de falla específicos.

Modo de falla 1: recuperación excesiva de la ventana de contexto

La mayoría de las implementaciones recuperan los 10 fragmentos principales de forma predeterminada. "Sólo para estar seguros".

El problema: en la práctica, 2 o 3 fragmentos contienen la respuesta. Los otros 7 u 8 son ruido: contexto redundante que agrega tokens sin agregar información. Estás pagando por esos tokens cada vez.

A 500 tokens por consulta, con recuperación del top 10 donde 7 fragmentos son innecesarios:

Tokens innecesarios por consulta: ~350 A 10.000 solicitudes/día: 3.500.000 tokens innecesarios/día A 0,015 $/1.000 tokens: 52,50 $/día en puro desperdicio Mensual: 1.575 $ en contexto innecesario

Ese número se calcula a partir de los supuestos establecidos, no se mide de un extremo a otro.

Modo de falla 2: sin capa de almacenamiento en caché

Dos usuarios preguntan "¿Qué es RAG?" con diez minutos de diferencia, y el sistema produce la misma incrustación, recupera los mismos fragmentos y devuelve la misma respuesta.

Paga el costo total del LLM dos veces.

No hay memoria semántica entre solicitudes en una canalización RAG estándar. Cada consulta se trata como si nunca antes se hubiera formulado. Con una tasa de consultas repetidas del 30%, una estimación conservadora basada en el tráfico específico de mi propio dominio: estás pagando dos veces por el 30% de tu tráfico.

Modo de falla 3: sin enrutamiento del modelo

Algunas canalizaciones utilizan de forma predeterminada un único modelo de alta capacidad para todas las consultas, independientemente de su complejidad.

Incluso cuando la consulta es: "¿Qué significa LLM?"

Esa pregunta no necesita GPT-4.5 ni Claude Opus. No necesita razonamiento de múltiples saltos. No necesita una ventana de contexto de 200K. Necesita un modelo rápido y económico y debe finalizar en 200 ms.

Utilizando los supuestos de precios en esta configuración, el modelo de nivel más alto es ~90 veces más caro por token que el nivel más bajo.[2]. Dado que el 81% de las consultas de referencia son simples búsquedas de hechos, no enrutarlas adecuadamente conduce a un aumento sustancial y evitable en el costo de servicio.

Estos patrones pueden aparecer en configuraciones RAG más simples, particularmente cuando no se incluyen optimizaciones que tienen en cuenta los costos.

Código completo: https://github.com/Emmimal/rag-cost-control-layer/

La realidad de los costos a escala

Antes de construir algo, quería ver los números honestamente.

Una configuración RAG básica generalmente ejecuta la recuperación para cada solicitud y no utiliza capas de enrutamiento o almacenamiento en caché. En implementaciones más simples, también se basa en un único modelo de alta capacidad, como un modelo de nivel GPT-4.5, para todas las consultas.

Escala Costo ingenuo/día Costo optimizado/día Ahorro 100 solicitudes/día $1,20 $0,18 84,6% 1.000 solicitudes/día $12,00 $1,71 85,7% 10.000 solicitudes/día $120,00 $17,00 85,8%

El ingenuo RAG quema el presupuesto rápidamente. Una capa de control de costos reduce el gasto en LLM hasta en un 85%, sin sacrificar la calidad de las respuestas. Imagen por autor

Mensualmente con 10 000 solicitudes/día: $3600 ingenuos frente a $510 optimizados. $3,090 ahorrados cada mes.

(Todas las cifras se calculan a partir de supuestos de precios establecidos, no se miden a partir de llamadas API en vivo).

A escala, estas diferencias pueden tener un impacto significativo sobre si un sistema sigue siendo rentable para operar.

La arquitectura: cuatro capas, un sistema

La capa de control de costos se compone de cuatro componentes, cada uno de los cuales apunta a un modo de falla diferente en el sistema.

Diagrama de flujo que ilustra un proceso de optimización de costos de LLM. Una consulta entrante llega a un caché semántico; los hits devuelven una respuesta almacenada en caché gratuita, mientras que los misses se mueven a un enrutador de consultas. El enrutador dirige consultas simples a gpt-4o-mini, estándar a gpt-4o y complejas a gpt-4.5. Luego, la solicitud pasa por un presupuesto simbólico, un libro de costos y un disyuntor antes de la llamada final de LLM.
Diagrama de arquitectura del sistema que detalla un canal de enrutamiento LLM rentable que incluye almacenamiento en caché semántico, selección dinámica de modelos y salvaguardas presupuestarias automatizadas. Imagen por autor

Cada capa tiene un único trabajo. Juntos hacen que el sistema tenga en cuenta los costes en cada punto de decisión.

Componente 1: caché semántica

La reducción de costes más sencilla de todo el sistema. Deje de pagarle al LLM por preguntas que ya haya respondido.

Cómo funciona

El almacenamiento en caché semántico para canalizaciones LLM es un patrón establecido: herramientas como GPTCache[8]demostró que el almacenamiento en caché por similitud semántica en lugar de coincidencia exacta de cadenas puede eliminar una parte significativa de las llamadas de LLM. Esta implementación sigue el mismo principio utilizando un integrador TF-IDF de Python puro sin dependencias externas.

Cada consulta entrante se integra mediante el vectorizador TF-IDF[3]. El caché contiene una lista de pares de consulta-respuesta anteriores, cada uno con su incrustación. Cuando llega una nueva consulta:

Incrustar la consulta Calcular la similitud del coseno con todas las incrustaciones almacenadas en caché Si la mejor similitud ≥ umbral (predeterminado 0,75): devolver la respuesta almacenada en caché Si falla: llame al LLM, almacene la clase de resultado SemanticCache: def get(self, query: str) -> Opcional[str]: query = self._validate(query) si la consulta es Ninguna: devuelve Ninguna con self._lock: self.stats.total_requests += 1 si no self._entries: self.stats.cache_misses += 1 return Ninguno q_vec = self._embedder.embed(query) best, best_sim = self._find_best(q_vec) si best no es Ninguno y best_sim >= self.threshold: best.hit_count += 1 self.stats.cache_hits += 1 self.stats.total_cost_saved_usd += self.cost_per_llm_call_usd devuelve best.response self.stats.cache_misses += 1 devuelve Ninguno

El caché utiliza un RLock para la seguridad de los subprocesos. La incrustación de cada consulta se almacena en caché y solo se vuelve a calcular cuando cambia el vocabulario, por lo que el tiempo de búsqueda se mantiene estable incluso con tamaños de caché más grandes.

Ajuste de umbral

El valor predeterminado de 0,75 está ajustado para la similitud TF-IDF. Las incrustaciones de transformadores de oraciones tienden a producir puntuaciones de similitud más altas para la misma coincidencia, por lo que con text-embedding-3-small de OpenAI, el umbral generalmente cambia a alrededor de 0,92-0,95.

Umbral más bajo → más aciertos de caché → riesgo de respuesta incorrecta para casos extremos Umbral más alto → menos aciertos → más conservador pero más preciso

El umbral correcto depende del dominio. Los sistemas estrechos (como los robots de soporte de un solo producto o las bases de conocimiento internas) pueden funcionar agresivamente entre 0,70 y 0,75. Los sistemas más amplios suelen necesitar umbrales más altos, a menudo de 0,90 o más.

Números reales de referencia

Ejecutar 200 consultas con una combinación realista (60 % simple, 30 % estándar, 10 % compleja, 20 % repetida):

Tasa de aciertos: 98,5 % Latencia de aciertos promedio: ~4 ms Latencia de aciertos promedio: ~4–5 ms Latencia de aciertos p95: ~5–7 ms Costo ahorrado (200 consultas): $0,788

El punto de referencia alcanza una tasa de aciertos del 98,5 % porque el 40 % de las consultas están presembradas en la memoria caché, simulando un sistema de producción calentado después de la acumulación inicial de tráfico.

La brecha de latencia es más importante: ~4 ms para un acierto de caché en comparación con ~700 ms para una llamada de LLM: aproximadamente una mejora de 175 veces por solicitud, antes del ahorro de costos.

Notas de producción

max_size=1000 con desalojo LRU de forma predeterminada. Sintonice hacia arriba para sistemas de alto tráfico. ttl_segundos=3600 recomendado para dominios donde los hechos cambian. Establezca en Ninguno para bases de conocimiento estables. El integrador TF-IDF funciona sin dependencias externas. Para una producción con similitud semántica real, intercambie un integrador de API: un método de interfaz, documentado en el código.

Componente 2: enrutador de consultas

No todas las consultas merecen el mismo modelo. El enrutador clasifica cada consulta entrante por complejidad y la enruta al nivel apropiado, automáticamente, en menos de 0,025 ms.

Tres señales, una puntuación

La puntuación de complejidad es una combinación ponderada de tres señales independientes:

Puntuación de longitud (peso: 0,20) Recuento de tokens normalizado. Una consulta de 5 palabras y una consulta de 50 palabras son problemas diferentes. Se satura a 80 fichas.

def _length_score(self, consulta: str) -> float: return min(len(query.split()) / 80.0, 1.0)

Densidad de entidades (ponderación: 0,30) Relación de palabras en mayúscula, números y puntuación técnica con respecto al total de tokens. Las consultas con alta densidad de entidades tienden a ser más específicas y complejas.

def _entity_score(self, query: str) -> float: tokens = query.split() si no son tokens: devuelve 0.0 hits = suma( 1 para t en tokens si (t[0].isupper() y len o re.search(r"d", t) o re.search(r"[:>/%]", t) ) devuelven min(hits / len(tokens), 1.0)

La profundidad del razonamiento tiene el mayor peso (0,50). Se calcula a partir de palabras clave relacionadas con el razonamiento, como "comparar", "contrastar", "analizar", "por qué", "compensación", "diseño" y "arquitectura". Dos partidos son suficientes para maximizar la puntuación.

REASONING_KEYWORDS: frozenset[str] = frozenset({ "comparar", "contrastar", "analizar", "por qué", "compensación", "diseño", "arquitectura", "modo de falla", "evaluar", "relación entre", "cuándo debería", "cómo debería", … }) def _reasoning_score(self, query: str) -> float: q_lower = query.lower() hits = suma(1 para kw en REASONING_KEYWORDS si kw en q_lower) devuelve min(hits/2.0, 1.0)

Ruta rápida: detección de factoides

Antes de puntuar, el enrutador detecta patrones factoides como "¿Qué es X?", "Definir X" y "Lista X". Estos se enrutan directamente como SIMPLES con una puntuación fija de 0,10, omitiendo la puntuación completa.

FACTOID_PATTERNS = [ re.compile(r"^(qué es|qué es|quién es|dónde está)b", re.I), re.compile(r"^(definir|definición de|significado de)b", re.I), re.compile(r"^(lista|nombre|dame)b.{0,40}$", re.I), ]

Enrutamiento en la práctica

De mi salida de demostración:

[Consulta 01] ¿Qué es RAG? Nivel: simple (puntuación: 0,10) → gpt-4o-mini [Consulta 04] ¿En qué se diferencia la recuperación híbrida de la búsqueda vectorial pura? Nivel: estándar (puntuación: 0,306) → gpt-4o [Consulta 06] Compare las ventajas y desventajas de costo y latencia de RAG agente versus estándar Nivel: estándar (puntuación: 0,611) → gpt-4o

“¿Qué es RAG?” es un hecho de libro de texto. Toma el camino rápido y pasa al modelo barato de inmediato. “Compare las compensaciones de costo y latencia…” obtiene una puntuación de 0,611 solo con palabras clave de razonamiento: es una pregunta de análisis multidimensional que legítimamente necesita un modelo más sólido.

Punto de referencia: distribución a escala

Ejecutar 500 consultas en una combinación realista:

Simple: 81.0% → gpt-4o-mini ($0.000165/1K tokens) Estándar: 16.4% → gpt-4o ($0.005/1K tokens) Complejo: 2.6% → gpt-4.5 ($0.015/1K tokens) Total ahorrado versus siempre caro: $3.41 (500 consultas) Latencia de enrutamiento promedio: <0.025 ms

En la combinación de consultas de referencia, el 81% del tráfico se dirige al modelo de menor costo. La sobrecarga del enrutador es <0,025 ms por decisión, lo cual es insignificante en la práctica.

Nivel de modelo faltante: seguridad de producción

Una solución de producción crítica: si falta un nivel en su model_map, el enrutador no falla con un KeyError. Vuelve al nivel ESTÁNDAR de forma segura:

# Fusionar el mapa proporcionado con los valores predeterminados: las claves que faltan retroceden de forma segura self.model_map = {**DEFAULT_MODEL_MAP, **(model_map or {})}

Esto es importante cuando realiza la implementación en un entorno donde solo ciertos modelos están disponibles. El sistema se degrada con gracia en lugar de fallar.

Componente 3: capa de presupuesto de tokens

El caché y el enrutador reducen la cantidad y el costo de las llamadas LLM. La capa de presupuesto de tokens maneja la asignación de tokens por llamada, evita el desbordamiento silencioso y registra el uso de tokens.

Esto se basa directamente en el concepto de mi sistema de ingeniería de contexto.[7], pero lo amplía con un seguimiento explícito del costo por espacio.

Asignación basada en ranuras

Cada solicitud reserva tokens en un orden de prioridad fijo:

# Reserva en orden de prioridad: fijo → historial → documentos → salida ctx.budget.reserve("system_prompt", 200) # 1. Nunca negociable ctx.budget.reserve_text("history", historial) # 2. Hace que varios turnos sean coherentes ctx.budget.reserve_text("retrieved_docs", docs) # 3. Lo que queda después de los costos fijos ctx.budget.reserve("output", min(512, ctx.budget.remaining())) # 4. Espacio de generación

El orden de asignación es fijo. El mensaje del sistema se trata como una sobrecarga, el historial mantiene la coherencia y los documentos recuperados son la capa comprimible cuando el espacio es limitado. El número de tokens para espacios de texto se estima en 1 token ≈ 4 caracteres para prosa en inglés.[6].

Si el orden es incorrecto, los documentos se descartan antes de que se tenga en cuenta el historial. El responsable del cumplimiento del presupuesto impone este comportamiento explícitamente.

Seguimiento de costos por ranura

Cada reserva registra su coste:

self._slots[slot_name] = SlotUsage( nombre=slot_name, reservado_tokens=concedido, cost_usd=concedido * self._cost_per_token,)

Después de la generación, registra los datos reales:

ctx.record_actual(tokens_actuales=620, costo_usd=0.0031)

record_actual es idempotente. Las llamadas duplicadas se ignoran después de una advertencia, lo que evita el doble conteo en el libro de gastos.

Guardia de fichas negativas

Una solución de producción que suena trivial pero que importa:

def reserve(self, slot_name: str, tokens: int) -> int: if tokens <= 0: logger.debug("reserve(%s, %d) – tokens no positivos rechazados", slot_name, tokens) return 0

Si algo en sentido ascendente calcula mal y pasa un recuento de tokens negativo, el presupuesto no se vuelve negativo ni corrompe todos los cálculos posteriores. Registra y devuelve 0.

Componente 4: CostLedger y CircuitBreaker

Esta es la capa faltante que protege a su sistema de la peor pesadilla de la producción: los costos desbocados.

El punto ciego de la producción

Agrega el uso de herramientas a su agente RAG. El agente entra en un ciclo de reintento: falla una llamada a la herramienta, el agente vuelve a intentarlo, el reintento falla y vuelve a intentarlo. Cada ciclo es una llamada LLM completa con costo total. El circuito dura 6 horas durante la noche mientras duermes.

Sin un disyuntor, te despiertas con una factura.

Con un disyuntor, el sistema acelera o bloquea automáticamente después de alcanzar el umbral por hora.

CostLedger: Visibilidad continua del gasto

class CostLedger: def record(self, cost_usd, tokens, model_tier, request_id=""): event = SpendEvent(timestamp=time.time(), cost_usd=cost_usd, …) with self._lock: self._events.append(event) self._total_lifetime_usd += cost_usd self._prune() # elimina eventos de más de 24 horas def hourly_spend(self) -> float: devuelve self._window_spend(3600) def daily_spend(self) -> float: devuelve self._window_spend(86400)

El libro mayor mantiene una ventana deslizante de eventos de gasto. _prune() elimina eventos de más de 24 horas, manteniendo la memoria limitada. Seguro para subprocesos a través de RLock.

Disyuntor: tres estados [4, 5]

Máquina de estado del disyuntor que muestra los estados CERRADO, ABIERTO y MEDIO ABIERTO en una capa de control de costos RAG, lo que ilustra cómo la aplicación del presupuesto previene los costos descontrolados de LLM y estabiliza el comportamiento del sistema.
Un disyuntor para RAG: detenga los costos descontrolados, recupérese de manera segura y mantenga su sistema LLM estable bajo presión. Imagen por autor

CERRADO → Funcionamiento normal. Todas las solicitudes pasan. ABRIR → Umbral superado. Solicitudes bloqueadas o degradadas. HALF_OPEN → Se acabó el tiempo de reutilización. Una solicitud de sondeo permitió probar la recuperación. def _check_and_trip(self) -> Ninguno: si self.ledger.hourly_breach() o self.ledger.daily_breach(): self.breaker.trip()

Esto se ejecuta automáticamente después de cada solicitud. Cuando el gasto por hora o por día excede su límite, se abre el interruptor. Después de cooldown_segundos, pasa a HALF_OPEN y permite una sonda. Si la sonda tiene éxito, se cierra. Si falla, se vuelve a abrir.

Degradar vs Bloquear

Dos modos de producción:

enforcer = BudgetEnforcer( hourly_limit_usd=5.0, daily_limit_usd=50.0, downgrade_on_breach=True, # degradación elegante)

downgrade_on_breach=True: cuando se abre el interruptor, las solicitudes se enrutan al modelo económico en lugar de bloquearse. Los usuarios obtienen una calidad degradada, no un error. Para la mayoría de los sistemas de producción, esta es la elección correcta.

downgrade_on_breach=False: las solicitudes se bloquean por completo con un mensaje alternativo. Utilice esto para sistemas de costos críticos donde una respuesta incorrecta es peor que ninguna respuesta.

El riesgo del falso positivo: una advertencia honesta

Éste es el caso extremo que el artículo debe abordar. Desde mi punto de referencia:

Umbral estricto (límite_hora=$0,001): → {'permitido': 0, 'rebajado': 0, 'bloqueado': 10} → 10/10 solicitudes legítimas bloqueadas Umbral sensible (límite_hora=$5,00): → {'permitido': 10, 'rebajado': 0, 'bloqueado': 10} → Espera: eso está mal. Umbral sensible (límite_hora=$5,00): → {'permitido': 10, 'rebajado': 0, 'bloqueado': 0} → 10/10 solicitudes atendidas correctamente

Una línea de configuración. Diferencia catastrófica.

Si estableces hourly_limit demasiado bajo, bloquearás tu propio tráfico de producción. La regla: establezca su límite entre 2 y 3 veces su pico esperado, no su promedio. El gasto promedio es lo que cuestan las cosas cuando todo está bien. Los límites protegen contra picos.

Del resultado del punto de referencia: "Establezca el límite_horario entre 2 y 3 veces su pico esperado, no su promedio. Utilice downgrade_on_breach=True para degradar con elegancia en lugar de bloquear a los usuarios".

Todo el proceso conectado

clase ProductionRAGPipeline: def __init__(self): self.cache = SemanticCache(umbral=0.75, ttl_segundos=3600) self.router = QueryRouter(simple_threshold=0.25, complex_threshold=0.65) self.enforcer = BudgetEnforcer( hourly_limit_usd=5.0, daily_limit_usd=50.0, per_request_limit_usd=0.10, downgrade_on_breach=True, ) def query(self, user_query: str, retrieved_context: str = "") -> dict: # Paso 1: Búsqueda de caché cached = self.cache.get(user_query) si el caché no es Ninguno: return {"response": cached, "source": "CACHE HIT", "cost_usd": 0.0} # Paso 2: Enrutar al enrutamiento del nivel de modelo = self.router.route(user_query) # Paso 3: Presupuesto de token + aplicación de costos con self.enforcer.request( model_tier=routing.tier.value, estimado_tokens=500, ) como ctx: si no ctx.allowed: return {"response": ctx.fallback_response, "source": "BLOCKED"} ctx.budget.reserve("system_prompt", 200) ctx.budget.reserve_text("history", "…") ctx.budget.reserve_text("retrieved_docs", retrieved_context) ctx.budget.reserve("output", min(512, ctx.budget.remaining())) respuesta, tokens, costo = call_llm(user_query, ctx.model_tier) ctx.record_actual(actual_tokens=tokens, cost_usd=cost) # Paso 4: caché para reutilización futura self.cache.set(user_query, respuesta) return {"response": respuesta, "cost_usd": costo, "tier": enrutamiento.tier.value}

El flujo es: caché primero. Si hay un acierto, no se ejecuta nada más. Luego, el enrutamiento selecciona el modelo más barato que pueda manejar la consulta. La capa de presupuesto rastrea los tokens, impone límites y activa el disyuntor cuando es necesario. Finalmente, el resultado se almacena en caché, por lo que consultas idénticas no cuestan nada.

Lo que realmente muestra la demostración

Ejecutando el proceso completo en 8 consultas de demostración (de mi resultado real):

[Consulta 01] ¿Qué es RAG? Fuente: LLAMADA LLM | Nivel: sencillo | Modelo: gpt-4o-mini Costo: $0.000015 | Guardado: $0.007417 vs modelo costoso [Consulta 02] ¿Qué es una base de datos vectorial? Fuente: CACHÉ HIT | Guardado: $0.0040 (llamada LLM evitada) [Consulta 06] Compare las compensaciones de costo y latencia de RAG agente… Fuente: LLM CALL | Nivel: estándar | Modelo: gpt-4o Puntuación: 0,611 | Costo: $0.000790 [Consulta 07] ¿Qué es RAG? (repetido) Fuente: CACHE HIT | Guardado: $0,0040 Resumen de ejecución: Costo total (8 consultas): $0,001389 Total ahorrado versus ingenuo: $0,047668 Disyuntor: cerrado

La consulta 01 y la consulta 07 son la misma pregunta formulada dos veces. En la segunda aparición, el caché regresa en 0,5 ms y no cuesta nada. Ese es el sistema funcionando exactamente como fue diseñado.

La consulta 06 es una pregunta genuinamente compleja: contiene "comparar", "compensaciones" y hace referencia a dos arquitecturas. Tiene una puntuación de 0,611, rutas a gpt-4o y cuesta 0,000790 dólares. La decisión de ruta es correcta.

Descargo de responsabilidad sobre latencia: todas las cifras de latencia se miden con una llamada LLM simulada. La latencia en el mundo real es de 200 a 800 ms por llamada de LLM, según el proveedor y la carga. Los aciertos de caché permanecen ~4 ms independientemente.

Puntos de referencia: lo que realmente ahorra

Todos los números a continuación provienen de ejecuciones comparativas reales en mi máquina (Python 3.12.6, Windows 11, solo CPU).

Rendimiento de la caché semántica

Consultas ejecutadas: 200 Tasa de aciertos: 98,5 % Latencia de aciertos promedio: ~4 ms Latencia de aciertos promedio: ~4–5 ms Latencia de aciertos p95: ~5–7 ms Costo ahorrado (200 q): $0,788

La tasa de aciertos del 98,5% proviene de una caché calentada después de varias horas de tráfico en un dominio definido. Las tasas de aciertos de arranque en frío suelen comenzar entre un 20% y un 30% aproximadamente y mejoran a medida que se llena la memoria caché.

Distribución del enrutador de consultas

Consultas ejecutadas: 500 Simple: 81,0% → gpt-4o-mini Estándar: 16,4% → gpt-4o Complejo: 2,6% → gpt-4.5 Total ahorrado: $3,41 Latencia de enrutamiento promedio: <0,025 ms

El 81% de las consultas se dirigen al modelo económico. El paso de enrutamiento agrega menos de 0,025 ms por solicitud y produce ahorros de costos mensurables a escala.

Comparación de escalas: ingenua frente a optimizada

Para el modelo de costos, nuestra arquitectura básica supone una configuración en el peor de los casos que se basa completamente en un modelo de nivel GPT-4.5 con un promedio de 800 tokens por solicitud. A escala, el sistema optimizado supone una tasa de aciertos de caché semántica conservadora del 28 % y enruta aproximadamente el 62 % de las solicitudes entrantes a modelos más simples y de bajo costo.

Escala Ingenuo/día Opción/día Ahorro Ahorro mensual 100 req/día $1,20 $0,18 84,6% $30 1.000 req/día $12,00 $1,71 85,7% $309 10.000 req/día $120,00 $17,00 85,8% $3.090

El porcentaje de ahorro se estabiliza en ~85,8% por encima de 1.000 solicitudes/día. Por debajo de eso, los gastos generales fijos de la canalización (generación integrada, cálculo de enrutamiento) comienzan a importar en relación con los ahorros.

Decisiones de diseño honestas

TF-IDF frente a transformadores de frases

El caché utiliza un incrustador TF-IDF de Python puro: sin PyTorch, sin transformadores de oraciones y sin subprocesos en segundo plano que se cuelgan en Windows. TF-IDF coincide con tokens compartidos en lugar de significados semánticos.

Para la misma consulta en diferentes palabras (“¿Qué es RAG?” frente a “Definir generación de recuperación aumentada”), la similitud TF-IDF será menor que la similitud del transformador de oración. Si sus usuarios tienden a reformular en lugar de repetir, la tasa de aciertos será menor de lo que muestra el punto de referencia.

Para intercambiar un incrustador semántico real, un método de interfaz:

clase OpenAIEmbedder: def fit(self, textos): pasar def embed(self, texto): importar openai r = openai.embeddings.create(model="text-embedding-3-small", input=text) devolver r.data[0].incrustación

Páselo a SemanticCache y nada más cambiará.

Los umbrales de enrutamiento son empíricos

Los valores predeterminados simple_threshold=0.25 y complex_threshold=0.65 se calibran en un conjunto de consultas de dominio RAG. Diferentes dominios, como el legal, el médico o el de atención al cliente, requieren diferentes valores de umbral.

La distribución de enrutamiento (81/16/2.6) refleja una combinación de consultas orientada a RAG. Los sistemas de atención al cliente se inclinan en gran medida hacia consultas SIMPLES, mientras que los asistentes orientados a la investigación tienen una mayor proporción de consultas COMPLEJAS.

CostLedger no tiene persistencia

CostLedger está estrictamente en memoria. Si el proceso se reinicia, su historial de gastos se reinicia con él. En la práctica, esto significa que los límites de tarifas por hora y por día solo lo protegen durante la vida de un solo proceso.

Si está pasando a producción con varios trabajadores o reinicios frecuentes de contenedores, querrá respaldar este libro mayor con Redis o una base de datos liviana. La interfaz en sí (record(), hourly_spend() y daily_spend()) se desacopló intencionalmente para que pueda intercambiar la capa de almacenamiento sin tener que reescribir la lógica de su aplicación.

Se burlan de los números de latencia

Una rápida comprobación de la realidad de los números: la demostración muestra latencias de 0,09 a 1,05 ms. Estos reflejan la sobrecarga del proceso central con una llamada LLM simulada, no la latencia API real. En producción, una llamada LLM real agregará entre 200 y 800 ms según su proveedor, la elección del modelo y la carga de red actual.

El resto de métricas, sin embargo, son completamente reales. La latencia de acierto de caché (~4 ms) es real. La latencia de la decisión de enrutamiento (menos de 0,025 ms) es real. Los gastos generales de ejecución del presupuesto son realmente insignificantes. La única parte de la que se burlan aquí es el viaje de ida y vuelta al proveedor de LLM.

Lo que esto NO es

Esto no es una mejora en la calidad de la recuperación. Si su sistema RAG subyacente está recuperando documentos incorrectos, esta capa no lo solucionará. Para conocer la calidad de la recuperación, la reclasificación y la compresión del contexto, consulte la capa de ingeniería de contexto analizada en el artículo anterior.

Esta no es una capa de optimización de latencia. Si bien el caché reduce drásticamente la latencia en caso de un acierto, el proceso general añade una sobrecarga marginal, aunque insignificante, en caso de un error de caché.

Esto no reemplaza la observabilidad adecuada del LLM. CostLedger actúa como una barrera para rastrear y controlar el gasto, pero aún necesita herramientas sólidas de registro, seguimiento y monitoreo en producción. Esta capa proporciona visibilidad de costos, no observabilidad integral.

Uniéndolo todo: una capa de producción consciente de los costos

Los sistemas RAG fallan en calidad. Ya existe una gran cantidad de trabajos que abordan este tema. Se han estudiado ampliamente la recuperación, la reclasificación y la calidad del contexto.

Pero los sistemas RAG también fallan por su costo. La mayoría de los escritos centrados en la producción se centran en la calidad de la recuperación. Esta falla de costos es menos frecuente en el centro de atención y, cuando sucede, es silenciosa. No hay ningún error, ninguna advertencia ni ninguna alerta. El sistema sigue funcionando perfectamente. La factura sigue creciendo.

Para solucionar este problema, la arquitectura que describí aquí inserta cuatro capas defensivas distintas entre su canal de recuperación y su llamada de LLM:

Caché semántico: devuelve respuestas conocidas en menos de 4 ms, costo LLM de $0 Enrutador de consultas: enruta el 81% del tráfico de referencia a modelos hasta 90 veces más baratos Presupuesto de token: rastrea cada token, evita el desbordamiento silencioso Disyuntor: acelera automáticamente antes de que un bucle de reintento se convierta en una factura

El resultado final: una reducción combinada del 85,8% en el costo a 10.000 solicitudes por día. En esta configuración de evaluación, esto corresponde a un ahorro mensual estimado de $3,090, logrado sin modificar el modelo de referencia subyacente y sin una degradación mensurable en la calidad de la respuesta.

¿La mejor parte? El sistema se ejecuta en Python puro. Sin marcos pesados, sin transformadores de oraciones y sin dependencias externas masivas. Le brinda un inicio instantáneo y una salida limpia en todas las plataformas.

Código completo: https://github.com/Emmimal/rag-cost-control-layer/

RAG le ofrece las respuestas correctas.

Esto le dará la factura correcta.

Referencias

[1]Lewis, P., Perez, E., Piktus, A., Petroni, F., Karpukhin, V., Goyal, N., Küttler, H., Lewis, M., Yih, W., Rocktäschel, T., Riedel, S. y Kiela, D. (2020). Generación de recuperación aumentada para tareas de PNL intensivas en conocimiento. Avances en los sistemas de procesamiento de información neuronal, 33, 9459–9474. https://arxiv.org/abs/2005.11401

[2]OpenAI. (2026). Precios de la API de OpenAI. https://openai.com/api/pricing/ (Los precios están sujetos a cambios; verifique las tarifas actuales al momento de la implementación).

[3]Pedregosa, F., Varoquaux, G., Gramfort, A., Michel, V., Thirion, B., Grisel, O., Blondel, M., Prettenhofer, P., Weiss, R., Dubourg, V., Vanderplas, J., Passos, A., Cournapeau, D., Brucher, M., Perrot, M. y Duchesnay, E. (2011). Scikit-learn: aprendizaje automático en Python. Revista de investigación sobre aprendizaje automático, 12, 2825–2830. https://jmlr.org/papers/v12/pedregosa11a.html (referencia de implementación de TF-IDF).

[4]Fowler, M. (2002). Patrones de arquitectura de aplicaciones empresariales. Addison-Wesley. (Patrón de disyuntor).

[5]Nygard, M. (2007). ¡Libéralo! Diseñar e implementar software listo para producción. Estantería pragmática. (Diseño del disyuntor; la formulación original del patrón utilizado en esta implementación).

[6]OpenAI. (2023). Contando fichas con tiktoken. https://github.com/openai/tiktoken (Referencia de estimación del token: 1 token ≈ 4 caracteres para prosa en inglés).

[7]Alejandro, EP (2026). RAG no es suficiente: construí la capa de contexto faltante que hace que los sistemas LLM funcionen. Hacia la ciencia de datos. https://towardsdatascience.com/rag-isnt-enough-i-built-the-missing-context-layer-that-makes-llm-systems-work/ (Referencia cruzada: capa de calidad del contexto; este artículo aborda la capa de costos).

[8]Bang, Z., et al. (2023). GPTCache: una caché semántica de código abierto para aplicaciones LLM que permite respuestas más rápidas y ahorros de costos. https://github.com/zilliztech/GPTCache

Divulgación

Todo el código de este artículo fue escrito por mí y es un trabajo original, desarrollado y probado en Python 3.12.6, Windows 11, solo CPU, sin GPU. El sistema no utiliza bibliotecas de aprendizaje automático externas: ni PyTorch, ni transformadores de oraciones, ni numpy. Todos los componentes se ejecutan únicamente en la biblioteca estándar de Python.

Los números de referencia provienen de ejecuciones reales del sistema en mi máquina local y son completamente reproducibles clonando el repositorio y ejecutando demo/demo.py y benchmarks/run_benchmarks.py. La demostración utiliza una llamada de LLM simulada: las cifras de latencia para las respuestas de LLM (0,09 ms a 1,05 ms) reflejan únicamente la canalización simulada; La latencia de la API LLM en el mundo real es de 200 a 800 ms, según el proveedor y la carga. La latencia de aciertos de caché (~4 ms) y la latencia de enrutamiento (menos de 0,025 ms) se miden a partir de la implementación real de Python. Las cifras de costos de comparación de escala (ingenuas versus optimizadas) se calculan a partir de datos de precios conocidos y suposiciones declaradas, no de llamadas API en vivo.

El costo por token de 1K utilizado en todos los cálculos: gpt-4o-mini ($0,000165), gpt-4o ($0,005), gpt-4,5 ($0,015). Estos reflejan los precios disponibles públicamente al momento de escribir este artículo y están sujetos a cambios. Verifique las tarifas actuales en https://openai.com/api/pricing/ antes de utilizar estos números para la planificación presupuestaria.

No tengo ninguna relación financiera con OpenAI, Anthropic ni ninguna otra empresa o herramienta mencionada en este artículo.