Adrian RomoAdrian Romo
Todos los textos
Nota de arquitectura 3 min de lectura

Un segundo cerebro que se niega a adivinar

Responder una pregunta operativa le costaba a un modelo local un volcado de 30-50K tokens. Construí una capa de memoria compilada que lo hace en 2-5K y se bloquea cuando no puede citar una fuente.

Mi homelab corre 14 hosts y alrededor de 110 contenedores. Quería un modelo local en mi propia GPU para responder preguntas sobre él — “¿de qué depende la caja de DNS?”, “¿por qué este servicio está configurado así?” — sin enviar nada a una API alojada.

La versión ingenua funcionó, pero era inutilizable. Un RAG simple sobre mis documentos hacía que cada pregunta arrastrara entre 30,000 y 50,000 tokens de fragmentos poco ordenados al contexto. En una tarjeta de 24GB, eso consume casi todo tu presupuesto antes de que el modelo piense en algo. Peor aún, los fragmentos recuperados no tenían noción de tiempo: un registro de decisión de junio y una observación de ejecución de esta mañana regresaban con la misma autoridad.

El problema real

No era la calidad de la recuperación. Era que no tenía una representación de qué es verdad ahora mismo, separada de lo que alguna vez se escribió.

Tres vacíos específicos:

  • Sin etapa léxica. La similitud de embeddings por sí sola es mala para identificadores exactos. Si preguntas por el nombre de un servicio específico, obtienes vecinos temáticamente similares en lugar de la fila que lo nombra.
  • Sin reranking. El top-k por distancia coseno no es el top-k por utilidad.
  • Sin temporalidad. Nada distinguía “este es el estado actual” de “este era el estado cuando alguien escribió el documento”.

Lo que construí

Una capa de memoria compilada. La palabra clave es compilada — es una vista derivada, eliminable y reconstruible sobre fuentes que siguen siendo autoritativas. Explícitamente no es otra base de datos que mantener sincronizada:

Git + observaciones en tiempo real   <- autoritativo
Hechos temporales (SQLite)           <- actual vs histórico, cerrar-no-sobrescribir
Grafo de contexto                    <- relaciones (940 nodos / 1,449 aristas)
Índice vectorial                     <- búsqueda semántica
Cápsulas jerárquicas                 <- resúmenes de 400-1,200 tokens, con sello de generación
Paquete de contexto de 2-5K          <- lo que realmente ve el modelo
r

La recuperación se ejecuta en etapas y se detiene temprano: coincidencia exacta, luego léxica (SQLite FTS5/BM25 — stdlib, sin servicios extra que operar), luego cápsula, luego grafo, luego semántica. La mayoría de las preguntas operativas nunca llegan a la etapa semántica.

La resolución de conflictos es una precedencia fija: runtime vence a hecho vence a cápsula. Si el sello de generación de un artefacto derivado se retrasa respecto a su fuente, se marca como obsoleto y la ruta de consulta recae en la evidencia cruda en vez de servir un resumen confiado de un mundo que ya cambió.

El reranker, y por qué el default es aburrido

Tres backends. El puntaje determinista por relevancia x prioridad x frescura es el default y tarda aproximadamente medio segundo, completamente offline y reproducible. Un pase listwise de LLM cuesta de 2 a 5 segundos. Un cross-encoder real en la 3090 cuesta unos 3 segundos en caliente, 11 en frío.

La restricción que hizo esto seguro para producción: un reranker solo puede reordenar candidatos. Nunca puede agregar ni eliminar uno. Eso se valida en pruebas. Cualquier falla de backend degrada al orden determinista en vez de arrojar error. Esto significa que el camino interesante-pero-inestable nunca puede tumbar el camino aburrido.

El resultado

Aproximadamente una reducción del 99% en tokens por consulta, y respuestas que incluyen citas. La métrica que importó más: ejecuté la ruta compilada en modo sombra contra la ruta RAG anterior antes de promoverla, así que “mejor” era un número y no una corazonada.

Qué haría diferente

Construí el almacén de hechos antes de construir lo que verifica si los hechos siguen siendo ciertos. Esas dos cosas debieron salir juntas — una memoria sin auto-auditoría es solo un caché confiado. Esa corrección se volvió su propio proyecto, y su propio post.

Escrito por

Adrian Romo

Ingeniero Backend Senior que diseña APIs escalables en Python, arquitecturas sobre AWS Lambda, sistemas de voz e integraciones empresariales.

Continúa

¿A dónde sigues?

Explora más textos técnicos, revisa los casos de estudio o escríbeme directo.