DEV Community

Cover image for Cómo Evitar que las Alucinaciones RAG Envenenen tu Vector Store
Elizabeth Fuentes L for AWS Español

Posted on

Cómo Evitar que las Alucinaciones RAG Envenenen tu Vector Store

Esta semana estaba leyendo un post-mortem en The New Stack, "The 'silent hallucination' loop: how our autonomous data pipeline poisoned its own vector store" de Emmanuel Akita (julio 2026), y tuve que parar a mitad de lectura. Había llegado por su cuenta a la tesis detrás de cada post de esta serie Resilient Harness: la confiabilidad vive en el harness alrededor del modelo, no en el prompt dentro de él. Su conclusión, en sus propias palabras:

"Probabilistic systems require deterministic boundaries." (Los sistemas probabilísticos requieren fronteras deterministas.)

Esa es toda la idea en siete palabras. Así que déjame contarte qué le pasó a su pipeline RAG, y luego mostrarte dónde reproduje la misma lección desde la otra dirección. Eso incluye el único punto donde su historia parece contradecir mi propio tutorial, y por qué no lo hace.

¿Qué falló dentro del pipeline?

El equipo de Akita construyó un pipeline RAG para un cliente fintech: ingerir miles de PDFs financieros sin estructura, extraer los campos clave, generar embeddings de todo, y alimentar un chatbot interno de preguntas y respuestas. Al principio funcionó sin problemas. Después el chatbot empezó a responder preguntas sobre 2022 citando datos de 2018, y a atribuir ingresos de la competencia a las subsidiarias del cliente.

La parte aterradora, en sus palabras: el retrieval funcionaba correctamente. El dashboard estaba en verde, la latencia por debajo de 100 ms, la búsqueda vectorial devolvía exactamente lo que se le pedía. Los datos que devolvía eran basura. Por eso lo llama silencioso: sin error, sin excepción, sin brecha de seguridad. Un sistema perfectamente sano sirviendo respuestas equivocadas con total confianza.

Fallo silencioso de alucinación RAG: el dashboard de observabilidad muestra todas las métricas en verde mientras el chatbot RAG sirve con confianza datos alucinados, citando un reporte de 2018 para una pregunta sobre ingresos de 2022 — sin error, sin excepción, solo basura con confianza

Esta es la cadena del fallo, y por qué cada eslabón coincide con algo sobre lo que ya escribí.

1. El agente de extracción alucinó, y la alucinación terminó en los embeddings

Su agente de ingesta usaba un LLM frontier para extraer metadata de cada PDF (document_type, fiscal_year, company_entity, un resumen) como JSON, y luego adjuntaba eso a los chunks de texto antes de generar los embeddings. Cuando el LLM se topó con un año fiscal ilegible en un PDF mal escaneado, no lanzó una excepción. Adivinó "2024". Esa adivinanza quedó embebida junto al texto, así que ahora tenían, como dice Akita, "high-speed searches for documents that didn't exist" (búsquedas de alta velocidad de documentos que no existen).

Su diagnóstico de la causa raíz: "treating a probabilistic extraction process as deterministic" (tratar un proceso de extracción probabilístico como determinista).

Este es el fallo silencioso del que escribí en Por Qué los Agentes de IA Fallan en Tareas Multi-Paso: el agente reporta éxito, todos los dashboards se ven sanos, y el resultado está silenciosamente mal. No lo detectas vigilando crashes. Lo detectas verificando el contenido del trabajo contra una fuente de verdad.

2. El validador LLM-as-a-judge le puso el sello a todo

Esta es la parte que me pareció más útil, porque mata un patrón que muchos equipos usan por defecto. El equipo de Akita tenía un segundo LLM, un "Validator Agent", revisando el JSON del extractor contra el texto crudo antes de llegar a la base vectorial. Aun así dejó pasar las alucinaciones. ¿Por qué?

"Using a probabilistic model to police another probabilistic model doesn't give you a firewall; it gives you a confirmation bias loop." (Usar un modelo probabilístico para vigilar a otro modelo probabilístico no te da un firewall; te da un loop de sesgo de confirmación.)

El validador le daba la razón al extractor por pura sycophancy (adulación): no encontraba el año en el texto y racionalizaba "el primer modelo debe haber visto algo que yo me perdí". Dos sistemas probabilísticos revisándose entre sí no suman una garantía determinista. Suman consenso, que no es lo mismo que correctitud.

Validador LLM-as-a-judge fallando en detectar una alucinación: la expectativa muestra un escudo validador estricto rechazando datos inventados antes de la base vectorial; la realidad muestra al juez LLM adulador aprobando los mismos datos alucinados hacia el vector store porque el primer modelo debe haber visto algo que él se perdió

Ahora bien, yo misma he recomendado LLM-as-a-judge para evaluación. ¿Eso me deja mal parada? No, y la distinción es justo el punto. En mi tutorial de LLM-as-Judge (y en la comparación de 3 frameworks que le siguió) uso un juez LLM offline, en experimentos batch y suites de tests, para puntuar calidad. E incluso ahí nunca trabaja solo: corre con rúbricas explícitas, checks deterministas (Contains, ToolCalled) y evaluación de trayectoria. El error de Akita no fue usar un juez LLM. Fue ponerlo inline en el write path como la puerta de integridad, el componente que decide qué se convierte en verdad confiable en producción. Ahí es exactamente donde un revisor probabilístico no puede ser la última línea. Usa el juez para medir; usa código para bloquear.

LLM-as-a-judge es la herramienta correcta para Es la herramienta equivocada para
Puntuar calidad subjetiva offline: utilidad, tono, calificación con rúbrica La puerta de integridad inline en tu write path
Evaluación batch en suites de tests, seguir tendencias de calidad entre releases Cualquier cosa que el código puede verificar exacto ("¿este año aparece en la fuente?")
Comparar agentes/prompts donde no existe ground truth Decisiones de seguridad o integridad de datos donde un solo fallo envenena producción

3. El prompt engineering lo empeoró

Su primer instinto fue arreglarlo en el prompt: "DO NOT HALLUCINATE", "If you are not sure 100% certain of the metadata, output NULL", "You are a strict financial auditor." El resultado, según su reporte: el validador se volvió excesivamente defensivo, empezó a rechazar datos perfectamente buenos, y los pasos de razonamiento subieron los costos de API ~40%.

Hice casi este mismo argumento en Cómo Detener la Inyección de Prompts en Agentes de IA: no puedes salir de un problema de confianza a punta de prompts, porque la cooperación del modelo es un estado de ánimo, no una garantía. Allá me defendía del texto inyectado por un atacante; Akita se defendía de la alucinación de su propio modelo. Misma conclusión: el prompt es la capa equivocada. El arreglo pertenece al harness.

Prompt engineering vs validación determinista para alucinaciones RAG: agregar DO NOT HALLUCINATE al system prompt hace que el LLM rechace datos buenos y sube los costos de API 40 por ciento, mientras que una puerta de código determinista en el harness verifica cada valor contra el texto fuente antes de llegar al vector store

La solución: una frontera determinista antes de que algo se vuelva estado confiable

El equipo de Akita le quitó toda autoridad de decisión al paso de validación y reemplazó el Validator LLM con Python simple y aburrido. Ese es el principio del harness del que trata toda esta serie: la validación vive en el código alrededor del modelo, no en el prompt dentro de él. El modelo propone; el harness decide. Tres movimientos:

  1. Grounding con Pydantic. Que un fiscal_year sea un entero plausible no basta: el año tiene que aparecer físicamente en el texto fuente crudo (un check con regex). El "2024" alucinado para un documento de 2018 falla, porque "2024" no está en el texto.
  2. Cross-referencing determinista. El company_entity se compara con fuzzy matching contra una tabla SQL fija con las entidades reales del cliente, con un flag de competidor para que un análisis legítimo de la competencia no sea rechazado por error.
  3. Cuarentena por defecto. Nada de la extracción va directo al vector store. Todo pasa primero por una tabla intermedia en PostgreSQL, y solo los payloads que pasan los checks de Pydantic + SQL se convierten en embeddings.

Su resultado reportado: el envenenamiento de datos paró de inmediato, y reemplazar el validador LLM redujo los gastos de API ~50%. (Esos son sus números de un solo post-mortem de producción, no un benchmark reproducible, pero el mecanismo es lo que importa.)

Vuelve a leer ese fix y es la misma forma que Detener Alucinaciones de Agentes de IA: Validar Antes de Escribir en Memoria. La tesis completa de ese post es validar antes de que el agente escriba en memoria: una puerta determinista que decide qué puede convertirse en estado confiable, antes de que se convierta en estado confiable. Akita bloquea escrituras a un vector store con Pydantic; yo bloqueo escrituras a la memoria del agente con un hook BeforeToolCallEvent de Strands. Distinta herramienta, forma idéntica: la frontera vive en el write path, y es código, no un modelo.

Dos caras del mismo bug: auto-envenenamiento vs memory poisoning

El fallo de Akita no tuvo atacante: el sistema se envenenó solo con su propia alucinación. Mi post sobre inyección de prompts y memory poisoning es la imagen en espejo: un atacante planta una instrucción maliciosa en contenido que el agente lee, el agente la guarda como memoria confiable, y una sesión completamente nueva la recarga del disco y actúa según ella. Ambos son "el sistema confía en un almacén persistido, y el almacén está mal".

El pipeline de Akita (auto-infligido) El caso espejo (adversarial)
¿Quién metió los datos malos? La alucinación del propio LLM La instrucción inyectada por un atacante
Almacén confiable Base de datos vectorial Memoria del agente (agent.state)
Qué falló Validador LLM-as-a-judge (sycophancy) El "ignora instrucciones malas" del prompt
El fix Grounding Pydantic + SQL, antes del embedding Puerta BeforeToolCallEvent, antes de que corra la tool
Investigación Post-mortem independiente; refleja Misevolution Reproduce Zombie Agents

El respaldo académico también coincide. Your Agent May Misevolve (Shao et al., sep 2025) es el primer estudio sistemático de agentes que derivan hacia comportamiento inseguro sin atacante externo, que es exactamente el loop de auto-envenenamiento de Akita. Zombie Agents (Yang et al., feb 2026) cubre el lado adversarial: una inyección única guardada en memoria se convierte en un compromiso persistente entre sesiones.

La única regla que cubre todo

Ya sea que los datos malos lleguen por ataque o por alucinación, la defensa es la misma y no vive en el prompt:

Deja que el LLM extraiga, analice y resuma. Pero en el momento en que un dato se escribe en almacenamiento como verdad confiable, debe pasar una barrera más estricta, de ingeniería tradicional.

Esa es la regla de Akita, y es la columna vertebral de todo lo que vengo escribiendo:

Distintos posts, un solo principio: un componente probabilístico puede proponer; solo una frontera determinista debería poder confirmar.

Preguntas que quizás tengas

¿No se supone que un segundo LLM ("LLM-as-a-judge") atrape esto?
Atrapa algunas cosas, pero es probabilístico, así que no puede darte una garantía. Como descubrió Akita, tiende al acuerdo adulador, poniéndole el sello a la salida del primer modelo. Dos modelos de acuerdo es consenso, no correctitud. Usa un juez LLM para medir calidad offline (con rúbricas y checks deterministas, como muestro aquí); para una restricción dura de integridad en el write path ("este año debe aparecer en el texto fuente"), usa código.

¿No puedo arreglar esto con un prompt más estricto?
Esa es la trampa en la que cayó Akita y sobre la que construí mi post de inyección de prompts. Los prompts más estrictos hicieron que su validador rechazara datos buenos y subieron los costos ~40%. El prompt es la capa equivocada: la garantía tiene que vivir donde el estado de ánimo del modelo no alcance.

¿Esto solo aplica a pipelines RAG?
No. El de Akita es un path de ingesta RAG; mis demos son tool calls de agentes. La forma compartida es cualquier punto donde la salida de un componente probabilístico se confirma como estado confiable: memoria, un vector store, una base de datos, una acción externa. Pon una puerta determinista ahí.

¿Dónde va esto en producción?
El mismo allow/deny se mueve a una capa de políticas en la frontera de la tool o del gateway (por ejemplo Amazon Bedrock AgentCore), de modo que la regla queda centralizada y una memoria envenenada (o auto-envenenada) no puede editarla.

Pruébalo tú misma

Construí las versiones agénticas de esto (validar-antes-de-escribir y la puerta en la frontera de la tool) como demos ejecutables. Clona el repo y córrelos:

git clone https://github.com/elizabethfuentes12/resilient-agent-harness-sample-for-aws.git

# Validar antes de que el agente escriba en memoria (el fix de Akita, en forma de agente)
cd resilient-agent-harness-sample-for-aws/01-memory-guardrails
uv venv && source .venv/bin/activate
uv pip install -r requirements.txt
echo "OPENAI_API_KEY=sk-..." > .env
uv run test_memory_guardrails.py

# La imagen espejo adversarial: el veneno sobrevive un restart, una puerta de tool lo bloquea
cd ../02-memory-poisoning-defense
uv pip install -r requirements.txt
cp ../01-memory-guardrails/.env .env
uv run test_memory_poisoning_defense.py
Enter fullscreen mode Exit fullscreen mode

Todo el crédito a Emmanuel Akita por el post-mortem original. Ve a leerlo: es un análisis limpio y honesto de un fallo que la mayoría de los equipos nunca publica.

¿Alguna vez desplegaste un pipeline perfectamente sano y perfectamente equivocado? Cuéntame en los comentarios qué fue lo que finalmente lo detectó.


📬 ¿Construyendo agentes de IA confiables? Escribo sobre memoria de agentes, guardrails, evaluación y patrones multi-agente. Suscríbete a mi newsletter para recibir el próximo.

Gracias!

🇻🇪 Dev.to Linkedin GitHub Twitter Instagram Youtube


Top comments (0)