De datos crudos a un RAG con ponderación temporal
    Desarrollo

    De datos crudos a un RAG con ponderación temporal

    3 Dic 20258 min de lecturaPor Fernando López, Líder técnico en Wovi
    ChatbotIAArquitecturaVectores

    Esta nota cuenta cómo construimos Chatty, un asistente conversacional que responde sobre un corpus de más de 10.000 documentos que se actualiza solo.

    El recorrido va desde los primeros intentos fallidos con datos estructurados hasta una arquitectura RAG (Retrieval-Augmented Generation) con un algoritmo propio de ponderación vectorial para resolver un problema que rara vez se discute: la alucinación temporal.

    El problema

    El objetivo era simple de enunciar y bastante difícil de implementar: una interfaz en lenguaje natural sobre una base de conocimiento que crece todos los días.

    Lo difícil no era el volumen

    Diez mil archivos no son nada para una base vectorial moderna. El problema era otro: la información cambia con el tiempo. Las cifras de 2024 no son las de 2025, aunque hablen exactamente del mismo tema y se parezcan mucho a nivel semántico.

    Fase 1: la trampa de creer que el LLM iba a calcular

    Lo que probamos

    Ingesta directa de datos crudos

    Le pasamos al modelo Excel y CSV con varias columnas y le pedimos que respondiera consultas de negocio. Primeras iteraciones a inicios de 2024.

    Lo que pasó

    No funcionó

    Los LLM no razonan aritméticamente sobre matrices grandes. En cuanto pedías comparar dos años, el modelo inventaba cifras o cruzaba columnas equivocadas. El error no era ocasional, era estructural.

    De ahí salió la decisión que cambió el proyecto: el valor no estaba en el dato crudo, sino en el dato ya interpretado. Cambiamos la fuente. En vez de tablas, ingestamos los documentos donde alguien ya había analizado esos números: informes, presentaciones, PDFs. Chatty no tenía que calcular nada; tenía que recuperar la interpretación.

    Backend: por qué nos salimos de Pinecone

    Antes

    Pinecone

    Base vectorial gestionada. Cómoda para empezar, pero limitada cuando uno quiere meterle lógica propia a la recuperación.

    Ahora

    PostgreSQL + pgvector

    Permite mezclar operaciones sobre vectores y metadatos en la misma consulta SQL. Esa flexibilidad terminó siendo la base del algoritmo que vamos a describir más abajo.

    Pipeline de ingesta

    Hay un proceso nocturno de crawling que hace tres cosas:

    1

    Extracción

    Una API conecta con el CMS y baja documentos nuevos o actualizados.

    2

    Chunking

    Los documentos se parten en fragmentos lógicos, cuidando que cada bloque mantenga sentido por sí mismo.

    3

    Embeddings

    Cada fragmento se convierte en vector usando modelos de OpenAI (text-embedding-3-small en este caso).

    Vectores separados, no un blob único

    Una decisión que vale la pena explicar: en vez de vectorizar el documento completo como un solo bloque, indexamos por separado el título, el contenido y guardamos los metadatos temporales como columna aparte.

    Vector del título

    Captura la intención temática del documento.

    Vector del contenido

    Captura los detalles y los datos específicos.

    Metadatos temporales

    Fecha de publicación, usada después para ponderar relevancia.

    El problema temporal: cuando dos respuestas se parecen y solo una sirve

    El bug más interesante del proyecto fue este. Lo bautizamos internamente como "ambigüedad semántica temporal", que suena rimbombante pero describe algo concreto.

    Escenario: el usuario pregunta "¿Cómo está la venta de tarjetas gráficas?"

    Informe A · Enero 2020

    "Las ventas de tarjetas gráficas subieron."

    Informe B · Octubre 2025

    "Las ventas de tarjetas gráficas cayeron."

    Para una similitud de coseno estándar, los dos documentos son casi idénticos respecto a la pregunta. Peor: el de 2020 podía obtener un score levemente superior y la respuesta terminaba siendo la versión obsoleta.

    Solución: ponderación vectorial dinámica

    Lo que hicimos fue agregar una capa lógica sobre pgvector que no se queda en la similitud, sino que calcula un índice combinando similitud y antigüedad.

    Fórmula

    Rfinal = (α · Scontenido) + (β · Stítulo) + (γ · Ptemporal)

    Scontenido

    Distancia vectorial del cuerpo del texto. Menor distancia, mayor similitud semántica.

    Stítulo

    Distancia vectorial del título. Sirve para reforzar la intención temática.

    Ptemporal

    Penalización por antigüedad. Días desde la fecha actual, normalizados.

    α, β, γ

    Pesos. Se ajustan empíricamente según el dominio del corpus; no hay receta universal.

    Cómo se ve esto en debugging

    Documento Similitud Factor fecha Score ponderado Resultado
    Doc_2025 0.15 1.0 0.86 Selecciona
    Doc_2020 0.12 1.5 1.24 Descarta

    Un score por debajo de 1.0 indica una coincidencia de muy alta calidad.

    Continuidad conversacional

    Vector híbrido

    El sistema detecta cuando una pregunta es continuación de la anterior. Si hay correlación, se arma un vector híbrido que combina ambas para refinar la búsqueda. Es la forma de evitar que el hilo de la conversación se corte cada vez que el usuario hace una pregunta de seguimiento.

    La capa LLM

    Cuando Postgres devuelve los diez fragmentos mejor rankeados, recién ahí entra el LLM (OpenAI, en esta implementación).

    Una distinción que vale la pena marcar

    El LLM no sabe la respuesta. La redacta a partir de lo que el algoritmo ya seleccionó. Si el retrieval falla, el LLM también; no hay magia que compense una recuperación mala.

    Lo que recibe el modelo

    1

    System prompt: reglas estrictas sobre cómo comportarse y qué no hacer.

    2

    Contexto: los diez fragmentos filtrados por el algoritmo.

    3

    Pregunta: la consulta del usuario en lenguaje natural.

    La instrucción es deliberadamente cerrada: "Usando exclusivamente la información proporcionada a continuación, responde a la pregunta del usuario humanizando los datos." Sin esa cláusula, el modelo se permite improvisar.

    Lo que viene: agentes de enrutamiento

    La conclusión práctica de este proyecto es que para RAG sobre datos históricos la búsqueda vectorial pura no alcanza. La ponderación temporal sobre pgvector fue lo que nos permitió responder con la verdad actual y no solo con la más parecida.

    El próximo paso es agregar agentes de enrutamiento: un modelo liviano que clasifique la intención de la pregunta (por ejemplo, ventas vs. marketing) y dirija la consulta a particiones específicas de la base. La meta es bajar latencia y costo manteniendo precisión.

    ¿Estás armando un RAG y te suenan estos problemas?

    Si estás peleando con alucinaciones, datos viejos o relevancia, conversemos. En Wovi diseñamos arquitecturas de IA pensadas para que la respuesta sea útil, no solo parecida.

    Hablemos por WhatsApp

    ¿Tu tecnología actual te genera más problemas que soluciones?

    Agenda una llamada con nuestro equipo y descubre cómo podemos ayudarte