Inicio / Artículos

Enriquecimiento de metadatos en RAG: filtros, citaciones, actualidad y control de acceso

Por qué un sistema RAG devuelve la política de la región incorrecta, y cómo los metadatos de origen, organizacional, temporal y de seguridad corrigen la recuperación al adjuntarse durante la ingesta.

Editor: REACTAPP.TOP14 min de lectura3163 palabras

Fuentes: Metadata Enrichment in RAG: The Secret Ingredient for Better Retrieval

Supongamos que un equipo ha creado un chatbot de generación con mejora mediante recuperación de información para la empresa. Se pasaron días eligiendo el modelo de embedding, se configuró una base de datos vectorial y se utilizó un potente LLM en la capa superior. Satisfechos con el resultado, alguien escribe una pregunta sobre la política de licencia de maternidad para el personal de la oficina europea.

La respuesta llega rápidamente: los empleados tienen derecho a 20 semanas de licencia de maternidad remunerada. Parece correcto. Sin embargo, al verificar los documentos originales se descubre que el texto recuperado proviene de la política destinada a los empleados de Estados Unidos, no de Europa. El sistema de recuperación encontró un fragmento sobre licencia de maternidad, pero no supo a qué región pertenecía, ya que el contexto circundante se perdió durante el proceso de fragmentación e indexación.

Este es uno de los fallos más comunes en los sistemas RAG. La mayoría de las conversaciones sobre RAG se centran en los embeddings, las estrategias de fragmentación y las bases de datos vectoriales, mientras que se pasa por alto una de las mayores influencias en la calidad de la recuperación: el enriquecimiento de metadatos. Los metadatos permiten a un sistema de recuperación comprender no solo lo que dice un fragmento, sino también de dónde proviene, a quién se aplica, cuándo fue escrito y dónde encaja entre el resto del material. En muchas aplicaciones empresariales, esto permite distinguir entre un resultado que solo es relevante para el tema y uno que es realmente correcto.

Qué sale mal con la recuperación basada únicamente en el contenido

Mire cómo funciona típicamente un sistema RAG para principiantes. Una organización posee tres documentos de política: un manual para empleados en EE. UU., uno para Europa y otro para Asia-Pacífico. Los tres incluyen una sección sobre licencia de maternidad. Tras dividir el texto en fragmentos, la base de datos vectorial almacena oraciones casi idénticas que solo difieren en la cantidad: 20 semanas en una, 16 en otra y 26 en la tercera.

Para un modelo de embedding, estos fragmentos son extremadamente similares, ya que todos tratan sobre el mismo tema. Si se pregunta por la licencia de maternidad, el sistema de recuperación puede devolver cualquiera de ellos. Entiende el significado semántico, pero no comprende automáticamente el contexto empresarial. Ahí es donde los metadatos se vuelven esenciales.

Qué son los metadatos

Los metadatos son datos sobre datos. En un sistema RAG, describen un fragmento sin formar parte de su contenido principal. Incluyen todo lo que es cierto sobre un texto, pero que no constituye lo que dice el texto en sí.

Tome el fragmento del ejemplo inicial:

Employees are entitled to 20 weeks of paid maternity leave.

Por sí sola, esa oración es clara y completa, y el contenido proporciona una respuesta. Pero no indica de qué documento proviene, qué política de qué región describe, quién se encarga de mantenerla actualizada ni cuándo fue revisada por última vez. El texto responde a lo que dice la política. No puede responder si se trata de la política adecuada para la situación de esa persona, y eso es exactamente lo que salió mal cuando un fragmento de EE. UU. fue devuelto para una pregunta europea.

Ahora el mismo fragmento con metadatos añadidos:

{
  "source": "EU_HR_Policy.pdf",
  "region": "Europe",
  "department": "Human Resources",
  "last_updated": "2025-03-15"
}

Ninguno de estos cuatro campos forma parte de la oración. La palabra “Europa” y el nombre del archivo no aparecen en el texto real. Sin embargo, el sistema de recuperación ahora cuenta con mucho más con lo que trabajar. El fragmento contiene contexto que puede utilizarse para la recuperación, filtrado, clasificación y generación de respuestas, y ese contexto se encuentra junto al contenido sin mezclarse con él.

Esa separación es la fuente del poder de los metadatos. Por diseño, el modelo de incrustación solo codifica el page_content del fragmento y nunca ve los metadatos. Los metadatos no están destinados a influir en cómo se incrusta un fragmento. Existen para que todas las demás partes del sistema, incluidos los filtros, las verificaciones de acceso, las citaciones y la lógica de clasificación, tengan algo concreto en qué actuar, independientemente de lo que el modelo de incrustación haya concluido sobre la similitud semántica.

Un modelo mental sencillo: los metadatos son el conjunto de etiquetas asociadas a cada fragmento de conocimiento. Sin etiquetas, cada fragmento es simplemente un significado que flota sin contexto, y la recuperación debe adivinar únicamente a partir de la similitud, como ocurrió cuando tres políticas regionales parecían casi idénticas para el modelo de incrustación. Con etiquetas, la adivinación es reemplazada por algo que el sistema puede verificar directamente. La pregunta pasa de si un fragmento suena similar a la consulta a si realmente pertenece a la situación sobre la que se pregunta.

Cómo los metadatos mejoran la recuperación

Vuelva al caso de la licencia de maternidad y siga el paso de recuperación con y sin metadatos.

Sin metadatos, una consulta sobre el personal europeo se realiza en toda la base de datos vectorial. Solo cuenta la similitud semántica, y como los tres fragmentos regionales son casi idénticos a un embedding, el recuperador puede devolver con confianza texto de políticas de la región incorrecta. Nada en la puntuación de similitud indica un problema.

Un sistema que cuenta con metadatos puede, en cambio, aplicar un filtro antes o junto con la búsqueda por similitud:

filter = {
    "region": "Europe"
}

La búsqueda ahora está restringida a los fragmentos etiquetados como Europa. En lugar de escanear cada documento de política, el buscador solo examina el subconjunto relevante, de modo que el conjunto de candidatos se reduce antes de que la similitud pueda tomar una decisión errónea.

El lugar donde se realiza el filtrado marca la diferencia. La mayoría de las bases de datos vectoriales en producción admiten filtrado previo, que aplica primero el filtro de metadatos para que la búsqueda de similitud solo se realice sobre los fragmentos que coinciden. Esto es más rápido y fiable que el filtrado posterior, en el cual la búsqueda completa de similitud se ejecuta sobre todo el contenido y los resultados que no coinciden se descartan después. El filtrado posterior puede desperdiciar recursos en regiones irrelevantes y, en casos extremos, devolver menos resultados de los solicitados cuando demasiadas coincidencias principales se filtran tarde. Tanto Qdrant como Weaviate están diseñados para admitir un filtrado previo eficiente.

Aquí se presenta el escenario de licencia de maternidad de principio a fin de manera concreta:

from sentence_transformers import SentenceTransformer
import numpy as np

embedding_model = SentenceTransformer("all-MiniLM-L6-v2")
chunks = [
    {"text": "Employees are entitled to 20 weeks of paid maternity leave.", "region": "United States"},
    {"text": "Employees are entitled to 16 weeks of paid maternity leave.", "region": "Europe"},
    {"text": "Employees are entitled to 26 weeks of paid maternity leave.", "region": "Asia-Pacific"},
]

for c in chunks:
    c["embedding"] = embedding_model.encode(c["text"])

query = "What is the maternity leave policy for employees in the European office?"
query_embedding = embedding_model.encode(query)

def search(chunks, query_embedding, region_filter=None):
    candidates = chunks if region_filter is None else [c for c in chunks if c["region"] == region_filter]
    scored = [(np.dot(query_embedding, c["embedding"]), c) for c in candidates]
    scored.sort(key=lambda x: x[0], reverse=True)
    return scored[0][1]

print("Without metadata filter:")
result = search(chunks, query_embedding)
print(f"  Returned: '{result['text']}' (region: {result['region']})")
print("\nWith metadata filter (region='Europe'):")
result = search(chunks, query_embedding, region_filter="Europe")
print(f"  Returned: '{result['text']}' (region: {result['region']})")

Eso reproduce directamente el fallo de inicio. Sin filtro, la similitud por sí sola elige el fragmento de EE. UU. simplemente porque obtiene una puntuación ligeramente más alta, no porque sea el adecuado para la pregunta. Una vez se aplica el filtro regional, el espacio de búsqueda se reduce al único fragmento europeo y se obtiene la respuesta correcta, por similares que sean los otros embeddings.

La precisión mejora mucho, y la brecha se amplía a medida que crece el corpus. Entre tres documentos, un modelo de embeddings podría clasificar al correcto primero por pura suerte. Entre cientos de documentos que abarcan muchas regiones, departamentos y años, la similitud por sí sola se vuelve poco fiable, y filtrar por metadatos es la única forma confiable de eliminar todo lo que no pertenece.

Cuatro tipos de metadatos útiles

No todos los metadatos son igualmente útiles. Cuatro categorías aparecen repetidamente en los sistemas empresariales reales, y cada una resuelve un problema diferente.

Metadatos de origen

Estos indican de dónde proviene la información. Los campos típicos son el nombre del archivo, el número de página, la URL y el ID del documento. Son importantes cuando los usuarios desean citaciones o quieren verificar una respuesta. Así es como se crea una citación:

def format_citation(metadata):
    return f"Source: {metadata['source']}, Page {metadata['page']}"

chunk_metadata = {"source": "Employee_Handbook_2025.pdf", "page": 42}
print(format_citation(chunk_metadata))

Qué imprime:

Source: Employee_Handbook_2025.pdf, Page 42

Esa cadena de citación no es el resultado de que el LLM adivine de dónde proviene una respuesta. Se trata de una búsqueda determinista de campos capturados en el momento de cargar el documento. La confianza aumenta cuando los usuarios pueden rastrear la información hasta su origen, y eso depende de que los metadatos se conserven desde su ingreso hasta la respuesta final.

Metadatos organizacionales

El conocimiento empresarial suele estar distribuido entre departamentos y unidades de negocio como recursos humanos, finanzas, legal e ingeniería. Etiquetar los datos de esta manera permite que la recuperación de información limite la búsqueda a la parte relevante de la empresa antes de realizar la comparación de similitud, utilizando el mismo mecanismo de filtrado mencionado anteriormente:

filter = {"department": "Finance"}

Buscar documentación de ingeniería para responder una pregunta financiera es una pérdida de tiempo, pero existe un segundo problema: una consulta financiera analizada contra texto de ingeniería puede devolver un fragmento claramente incorrecto y fuera de tema que, sin embargo, obtiene una puntuación engañosamente alta en similitud bruta.

Metadatos temporales

La información cambia con el tiempo a medida que se actualizan las políticas, evolucionan los procedimientos y se modifican las regulaciones. Sin marcas de tiempo, un sistema de recuperación no puede distinguir entre una política actual y una obsoleta. Solo sabe qué fragmentos son semánticamente cercanos a la consulta, pero no cuáles siguen en vigor.

Considere dos fragmentos sobre la misma política con fechas de actualización diferentes:

chunks = [
    {"text": "Employees receive 15 vacation days per year.", "last_updated": "2023-01-10"},
    {"text": "Employees receive 20 vacation days per year.", "last_updated": "2025-03-15"},
]

Probablemente ambos obtendrán resultados muy similares ante una pregunta sobre días de vacaciones, ya que el tema y la redacción son casi idénticos. Al almacenar una marca de tiempo, el sistema puede favorecer deliberadamente la más reciente y no dejarlo al azar:

most_recent = max(chunks, key=lambda c: c["last_updated"])
print(most_recent["text"])

Resultado:

Employees receive 20 vacation days per year.

Esto es crucial en industrias donde la información cambia con frecuencia. Sin una verificación explícita de actualidad, una política antigua y su reemplazo son funcionalmente indistinguibles para un sistema que solo analiza la similitud.

Metadatos de seguridad

Muchas organizaciones necesitan un control de acceso basado en roles: los documentos de recursos humanos solo son visibles para el personal de HR, y los informes financieros solo para los equipos de finanzas. Los metadatos permiten hacerlo cumplir. Un bloque de datos puede contener campos como estos:

chunk_metadata = {
    "access_level": "confidential",
    "allowed_roles": ["HR_Manager", "HR_Admin"]
}

Antes de recuperarla, el sistema compara el rol del usuario que realiza la solicitud con los roles autorizados para ver el fragmento:

def can_access(user_role, chunk_metadata):
    return user_role in chunk_metadata["allowed_roles"]

print(can_access("HR_Manager", chunk_metadata))   # True
print(can_access("Engineering", chunk_metadata))  # False

Resultado:

True
False

En una pipeline real, esta verificación se realiza como un filtro durante la recuperación del contenido y no como una acción posterior una vez que el LLM ya posee dicho contenido. Un fragmento al que el usuario no tiene autorización para ver nunca debería llegar hasta el prompt. Sin metadatos, es extremadamente difícil crear sistemas RAG seguros, ya que no existe una señal fiable asociada a cada fragmento que permita al sistema compararla con los permisos del usuario antes de decidir si recuperarlo.

Agregar metadatos durante la ingesta

Los metadatos no aparecen por sí solos. Deben añadirse durante la ingesta de los documentos, y si no se capturan allí, no es posible recuperarlos, porque para cuando un fragmento llega al proceso de embedding o al almacenamiento, el enlace hacia su fuente ya ha desaparecido.

Tomemos un PDF con políticas de recursos humanos que se está cargando. La pipeline habitual de ingesta sigue estos pasos en secuencia:

  1. Cargar el documento.
  2. Extraer el texto.
  3. Dividir el contenido en fragmentos.
  4. Adjuntar metadatos.
  5. Generar embeddings.
  6. Escribir los fragmentos, los embeddings y los metadatos en la base de datos vectorial.

El adjunto de metadatos se realiza después del procesamiento en fragmentos y no antes, y el orden es importante. Los metadatos deben incluirse en cada uno de los fragmentos en los que se divide un documento, en lugar de adjuntarse una sola vez al documento completo.

from langchain_core.documents import Document

doc = Document(
    page_content="Employees receive 20 weeks of maternity leave.",
    metadata={
        "department": "HR",
        "region": "Europe",
        "source": "EU_HR_Policy.pdf"
    }
)

print(doc.page_content)
print(doc.metadata)

Resultado:

Employees receive 20 weeks of maternity leave.
{'department': 'HR', 'region': 'Europe', 'source': 'EU_HR_Policy.pdf'}

Esto es sencillo cuando un documento es lo suficientemente pequeño como para quedar en un solo bloque. Sin embargo, los documentos reales se dividen en muchos bloques, y los metadatos deben copiarse en cada uno de ellos:

def chunk_with_metadata(document, chunk_size=60):
    text = document.page_content
    chunks = [text[i:i+chunk_size] for i in range(0, len(text), chunk_size)]
    return [
        Document(page_content=chunk_text, metadata=document.metadata)
        for chunk_text in chunks
    ]

source_doc = Document(
    page_content="Employees receive 20 weeks of maternity leave. Eligibility begins after six months of employment.",
    metadata={"department": "HR", "region": "Europe", "source": "EU_HR_Policy.pdf"}
)
chunked_docs = chunk_with_metadata(source_doc)
for i, chunk in enumerate(chunked_docs, 1):
    print(f"Chunk {i}: {chunk.page_content!r}")
    print(f"  Metadata: {chunk.metadata}\n")

Resultado:

Chunk 1: 'Employees receive 20 weeks of maternity leave. Eligibility b'
  Metadata: {'department': 'HR', 'region': 'Europe', 'source': 'EU_HR_Policy.pdf'}
Chunk 2: 'egins after six months of employment.'
  Metadata: {'department': 'HR', 'region': 'Europe', 'source': 'EU_HR_Policy.pdf'}

Aunque los dos fragmentos son partes distintas de un mismo documento, cada uno hereda los mismos valores de región, departamento y origen. Eso es lo que hace que funcionen los ejemplos de filtrado anteriores: sin importar cómo se haya dividido el texto, cada fragmento sigue pudiendo indicar que forma parte de la política de recursos humanos europea. Cada uno ahora contiene un contexto valioso además de su información de inserción, gracias a la recuperación, el filtrado y la citación.

Permitir que un LLM genere los metadatos

Los sistemas modernos de RAG utilizan cada vez más LLMs para generar metadatos, en lugar de depender exclusivamente de la etiquetación manual. En lugar de que una persona lea cada documento y asigne etiquetas a mano, un modelo puede analizar un documento y determinar campos como el tema, el departamento, la categoría, las palabras clave y un resumen. El código a continuación muestra el prompt que lo genera, y no solo la salida:

def generate_metadata(text, llm):
    prompt = f"""Analyze the following document and return metadata as JSON with these fields:
topic, category, and keywords (a list of 3-5 relevant terms).

Document:
{text}
Respond with only the JSON, no other text."""
    response = llm.generate(prompt)
    return response

sample_text = "Employees are entitled to 20 weeks of paid maternity leave, with eligibility beginning after six months of continuous employment. Additional unpaid leave may be requested with manager approval."
# In practice, llm.generate() would call an actual model (Claude, GPT, etc.)
# Below is the kind of output this prompt is designed to produce:

example_output = {
    "topic": "Employee Benefits",
    "category": "HR Policy",
    "keywords": ["maternity leave", "eligibility", "employee benefits"]
}

print(example_output)

Resultado:

{'topic': 'Employee Benefits', 'category': 'HR Policy', 'keywords': ['maternity leave', 'eligibility', 'employee benefits']}

Estos metadatos se adjuntan al fragmento de la misma manera que lo harían los metadatos asignados manualmente. Solo difiere la entidad que decide los valores. El resultado es una experiencia de recuperación más enriquecedora con menos esfuerzo manual, especialmente a gran escala: etiquetar unos pocos documentos a mano es factible, pero etiquetar decenas de miles de fragmentos en una base de conocimientos en crecimiento generalmente no lo es. Cada vez más pipelines avanzados tratan este paso como un preprocesamiento rutinario, que se ejecuta una sola vez al momento de la ingesta, junto con la división en fragmentos y la generación de embeddings.

Una precaución práctica: realice verificaciones puntuales de los resultados generados por los LLM, especialmente al principio. Un modelo puede asignar ocasionalmente un tema o categoría que no coincide con el contenido real, y a diferencia de que un humano etiquete mal un documento, un error automático puede propagarse silenciosamente por muchos documentos a la vez si el prompt tiene un punto ciego sistemático.

Filtrado de metadatos y recuperación híbrida

Los metadatos son más poderosos cuando se combinan con la búsqueda semántica, aunque la precisión en esta combinación es útil, ya que el término “híbrido” se utiliza para más de una técnica en RAG.

En sentido estricto, la recuperación híbrida implica combinar dos métodos de búsqueda: la búsqueda vectorial densa y la búsqueda por palabras clave dispersas (BM25), ejecutándolos simultáneamente y fusionando los resultados. El filtrado de metadatos es algo distinto; no es otro método de búsqueda, sino una forma de reducir el conjunto de candidatos antes o junto con el método, o combinación de métodos, que ya se utilice.

La búsqueda vectorial determina qué fragmentos son semánticamente similares a la consulta. El filtrado de metadatos determina qué fragmentos pertenecen a la categoría, región, departamento o período de tiempo adecuados. Estas dos tareas son independientes, por lo que un filtro funciona igualmente bien con una búsqueda vectorial simple, con BM25 por sí solo, o con un recuperador híbrido completo. Se trata de una capa adicional que puede aplicarse sobre cualquiera de ellos.

Juntos, mejoran significativamente la calidad de la recuperación, como lo demuestra el ejemplo de la licencia de maternidad. El sistema deja de buscar de forma aleatoria en toda la base de conocimientos y busca de manera inteligente dentro del subconjunto más relevante, siendo la similitud la única que determina el ranking dentro de ese conjunto ya reducido y adecuado. Por eso, los sistemas RAG empresariales modernos rara vez dependen únicamente de los embeddings: los embeddings deciden qué es similar, pero los metadatos deciden qué es apto para ser considerado en absoluto.

La herramienta pasada por alto

Muchas organizaciones invierten mucho esfuerzo en elegir modelos de incrustación y bases de datos vectoriales. Esas decisiones son importantes, pero en la práctica la recuperación a menudo falla por una razón mucho más simple: la información contextual relevante nunca se capturó, por buen que sea el modelo o la base de datos.

Imagínese una biblioteca en la que cada libro haya perdido su título, autor, fecha de publicación y categoría, quedando solo el texto en bruto. Encontrar algo sería muy difícil incluso con un excelente sistema de estanterías y un catálogo de búsqueda. Almacenar fragmentos sin metadatos es lo mismo: la infraestructura de recuperación puede ser impecable y, aun así, no servirá de nada si no hay contexto al que pueda recurrir.

Los metadatos proporcionan la estructura organizativa que permite a los sistemas de recuperación navegar por grandes bases de conocimiento. A medida que las colecciones pasan de cientos de documentos a millones de fragmentos, su importancia aumenta cada vez más. A pequeña escala, un modelo de embedding puede clasificar el fragmento correcto en primer lugar por pura suerte, como casi ocurrió con tres documentos normativos. En una verdadera base de conocimiento empresarial, esa suerte se agota rápidamente. Muchos equipos empresariales informan que mejorar la calidad de los metadatos genera mayores beneficios que cambiar a un modelo de embedding más reciente, algo que vale la pena recordar teniendo en cuenta la atención que se presta a la selección de modelos y la poca atención que se dedica a la estrategia de metadatos.

Conclusión

Enriquecer los fragmentos con metadatos se cuenta entre las mejores formas de mejorar los resultados de RAG, y recibe mucha menos atención que los embeddings, las bases de datos vectoriales y los LLM. Los embeddings capturan el significado del contenido, mientras que los metadatos capturan el contexto que lo rodea; juntos permiten a un sistema encontrar no solo información similar, sino también la información adecuada. Ya sea para rastrear fuentes, filtrar por región, garantizar la seguridad, registrar marcas de tiempo o añadir etiquetas escritas por IA, los metadatos ayudan a transformar una configuración básica de recuperación en una plataforma de conocimiento lista para su uso en producción.

Por lo tanto, al evaluar un pipeline de RAG, la pregunta no es solo cuál modelo de embeddings utilizar, sino también qué metadatos deben adjuntarse a los fragmentos, ya que en muchos sistemas reales, mejores metadatos significan una mejor recuperación de información, una mejor recuperación implica respuestas más precisas, y respuestas mejores generan una mejor experiencia para los usuarios.

Leer más

  • El método de fragmentación padre-hijo y el indexado jerárquico para una mejor recuperación en RAG — Por qué los fragmentos pequeños ganan las coincidencias pero pierden la respuesta, y cómo la fragmentación padre-hijo, la expansión mediante eliminación de duplicados y el indexado jerárquico restauran el contexto en la recuperación de información en RAG.
  • Receta de Spring AI: Filtrar resultados de RAG con metadatos por título del juego — Una guía paso a paso de Spring AI que etiqueta los documentos con un título, utiliza un LLM para seleccionar el valor adecuado a partir de la pregunta y filtra la búsqueda vectorial en función de él.
  • Recuperación en una wiki de ingeniería: BM25, ventanas densas seguras para tokens y RRF — Recuperación basada en la evaluación para una wiki técnica: objetivos de evidencia exacta, un punto de referencia BM25, un error de truncación en MiniLM, ventanas de tokens y resultados de fusión de rango recíproco.