Filtrado de metadatos para Databricks RAG utilizando ai_extract y ai_prep_search
Combinar ai_parse_document, ai_extract v2.1 y ai_prep_search para que cada fragmento contenga metadatos estructurados, lo que permite a la Búsqueda Vectorial filtrar antes de clasificar por similitud.
Fuentes: Adding ai_extract to the mix: building a unified RAG pipeline with three Databricks SQL AI functions
Un pipeline de Databricks construido sobre la función SQL AI ai_prep_search ya hace mucho por sí solo. Se ingresa el texto de los documentos analizados y se obtienen fragmentos enriquecidos semántica y contextualmente, listos para su uso en búsquedas vectoriales. El flujo es sencillo: se analizan los documentos, se preparan los fragmentos, se aplanan en una tabla Delta y se indexa.
Pero falta algo cuando ese proceso se ejecuta sobre un corpus de documentos, en este caso un conjunto de manuales de Orion Robotics. Los fragmentos en sí son buenos. El campo chunk_to_embed contiene el contexto documental adecuado, y chunk_to_retrieve está lo suficientemente limpio como para ser entregado a un LLM. El problema surge en el momento de la consulta, ya que no existe una forma fiable de filtrar. Una solicitud como "encuéntrame la guía del firmware para el Orion Motion Controller" debe satisfacerse únicamente mediante la similitud semántica, dado que el índice no cuenta con columnas estructuradas como document_type, product_name o target_audience.
Para la recuperación impulsada por agentes existe una brecha real. La búsqueda semántica pura funciona bien en las demostraciones. En producción, un agente debe reducir el espacio de búsqueda antes de ejecutar la comparación de vectores, y los filtros de metadatos hacen exactamente eso. La solución es agregar ai_extract v2.1 al pipeline.
Por qué son importantes los filtros de metadatos
La búsqueda híbrida en RAG suele significar una combinación de búsqueda vectorial con coincidencia de palabras clave como BM25. Es útil, pero para colecciones de documentos estructurados otro tipo de híbrido es más importante: búsqueda vectorial más filtrado de metadatos.
Imagínese un agente que ya sabe que el usuario necesita un manual para Orion Arm. Clasificar 10,000 fragmentos de todo tipo de documentos sería un desperdicio. Un plan mejor es primero reducir los datos a los documentos relevantes y dejar que la similitud vectorial ordene las mejores coincidencias dentro de ese conjunto más pequeño. Databricks Vector Search permite hacer esto mediante columnas de filtro declaradas al crear el índice.
El problema es que los metadatos deben estar limpios, consistentes y tener el tipo correcto. Si document_type indica “Manual del usuario” en una fila, “manual” en otra y nada en una tercera, los filtros no funcionan correctamente. La calidad de la recuperación empeora, y se pasan horas depurándola cuando el verdadero culpable son los metadatos inconsistentes creados anteriormente.
ai_extract v2.1 aborda este problema. Recibe el VARIANT estructurado generado por ai_parse_document y devuelve metadatos tipados verificados contra un esquema. Los campos de tipo enum establecen valores fijos, los campos de texto dirigen la extracción de texto libre y los arrays cubren los campos con múltiples valores. Dado que la extracción sigue un esquema, la salida permanece uniforme entre los documentos, incluso cuando sus diseños difieren.
Tres funciones, tres tareas
Las funciones no se superponen, por lo que es útil separarlas antes de examinar el cuaderno de notas.
- ai_parse_document v2.0 convierte archivos binarios en bruto (PDF, DOCX, imágenes) en un VARIANT estructurado, identificando bloques de texto, encabezados, tablas, figuras, cuadros delimitadores y la jerarquía de secciones. Conoce la estructura interna del documento pero no tiene idea de su contenido.
Cada función tiene una responsabilidad, y el proceso los combina entre sí.
Paso a paso en el cuaderno de notas
El cuaderno de notas funciona con el corpus de Orion Robotics, que se encuentra en un volumen de Unity Catalog y combina PDF, archivos DOCX e imágenes. Primero se realiza el análisis:
SELECT
path,
ai_parse_document(
content,
MAP('version', '2.0')
) AS parsed
FROM READ_FILES(
'/Volumes/<YOUR_CATALOG>/<YOUR_SCHEMA>/orion_docs/',
format => 'binaryFile'
)
Cada archivo del volumen se lee como binario y se convierte en un VARIANT estructurado. Es sensato especificar explícitamente la versión 2.0, ya que ese esquema incluye las coordenadas del cuadro delimitador y las descripciones de las figuras en las que ai_prep_search depende para tomar decisiones de particionamiento. Mantenerse en la configuración predeterminada permitiría a Databricks cambiar el comportamiento detrás del pipeline.
La ejecución de ejemplo produjo este resultado:
La siguiente consulta extrae metadatos del resultado analizado:
WITH parsed_documents AS (
SELECT path, ai_parse_document(content, MAP('version', '2.0')) AS parsed
FROM READ_FILES('/Volumes/<YOUR_CATALOG>/<YOUR_SCHEMA>/orion_docs/', format => 'binaryFile')
)
SELECT
path,
ai_extract(
parsed,
'{
"document_title": {"type": "string", "description": "Main title or heading of the document"},
"document_type": {"type": "enum", "labels": ["manual", "datasheet", "guide", "report", "specification", "policy", "other"]},
"product_name": {"type": "string", "description": "Primary product or system referenced"},
"version": {"type": "string", "description": "Document or product version if mentioned"},
"key_topics": {"type": "array", "items": {"type": "string"}},
"target_audience": {"type": "enum", "labels": ["engineer", "operator", "manager", "customer", "general"]}
}',
MAP(
'version', '2.1',
'instructions', 'These are technical documents from Orion Robotics. Extract metadata that would help users filter and find relevant documents.'
)
) AS metadata
FROM parsed_documents
Algunas opciones requieren explicación. Tanto document_type como target_audience son enums, ya que los filtros necesitan un conjunto cerrado de valores. Si se ingresan como texto libre, podrían devolverse valores como “manual”, “User Manual” o “technical manual”, lo cual dificulta la coincidencia. La opción instructions proporciona contexto; en este caso, se trata de documentos técnicos de Orion Robotics, por lo que la extracción mejora. Además, ai_extract acepta directamente el valor VARIANT de ai_parse_document, sin realizar conversión a texto plano.
Resultado del ejemplo de ejecución:
A continuación, el proceso de fragmentación:
WITH parsed_documents AS ( ... )
SELECT
path,
ai_prep_search(parsed) AS chunked
FROM parsed_documents
ai_prep_search utiliza la misma VARIANT analizada. Los documentos se dividen en fragmentos almacenados en un array document.contents, y cada fragmento incluye chunk_id, chunk_position, chunk_to_retrieve y chunk_to_embed. Los dos últimos no contienen necesariamente el mismo texto.
chunk_to_embed incluye contexto adicional como el título del documento, los encabezados de sección y detalles de la página para que el modelo de incrustación pueda colocar adecuadamente el fragmento. chunk_to_retrieve es un texto más limpio, generalmente la versión que se muestra a los usuarios o que se pasa al LLM.
Dividirlos es un diseño sólido. Muchos pipelines RAG incrustan y responden a partir del mismo texto, pero el texto rico en contexto es adecuado para las incrustaciones mientras que el texto limpio es mejor para la lectura, por lo que es mejor conservar ambos.
Ejemplo de salida:
Uniendo todo
La consulta combinada es donde se unen todas las partes:
WITH parsed_documents AS (
SELECT path, ai_parse_document(content, MAP('version', '2.0')) AS parsed
FROM READ_FILES('/Volumes/<YOUR_CATALOG>/<YOUR_SCHEMA>/orion_docs/', format => 'binaryFile')
),
doc_metadata AS (
SELECT
path,
parsed,
ai_extract(
parsed,
'{ ... }',
MAP('version', '2.1', 'instructions', 'These are technical documents from Orion Robotics. Extract metadata for search filtering.')
) AS metadata
FROM parsed_documents
),
doc_chunks AS (
SELECT path, ai_prep_search(parsed) AS chunked
FROM doc_metadata
)
SELECT
chunk.value:chunk_id::STRING AS chunk_id,
chunk.value:chunk_position::INT AS chunk_position,
chunk.value:chunk_to_retrieve::STRING AS chunk_to_retrieve,
chunk.value:chunk_to_embed::STRING AS chunk_to_embed,
doc_chunks.chunked:document.source_uri::STRING AS source_uri,
dm.metadata:response:document_title::STRING AS document_title,
dm.metadata:response:document_type::STRING AS document_type,
dm.metadata:response:product_name::STRING AS product_name,
dm.metadata:response:version::STRING AS doc_version,
dm.metadata:response:target_audience::STRING AS target_audience
FROM
doc_chunks
JOIN doc_metadata dm ON doc_chunks.path = dm.path,
LATERAL variant_explode(doc_chunks.chunked:document.contents) AS chunk
El paso LATERAL variant_explode expande el array anidado de fragmentos en filas. La operación JOIN sobre la ruta adjunta metadatos a nivel de documento a cada fragmento de dicho documento. El resultado es una fila por fragmento, que contiene tanto los campos de recuperación como las columnas de metadatos filtrables. Esa es la estructura almacenada en la tabla Delta y que se utiliza para alimentar el índice de búsqueda vectorial.
Resultado del ejecución de ejemplo:
La tabla se materializa de la siguiente manera:
CREATE OR REPLACE TABLE <YOUR_CATALOG>.<YOUR_SCHEMA>.orion_docs_rag_chunks AS
-- [full CTE chain from Step 4]
Una vez establecida la tabla, se crea un índice de sincronización Delta para búsquedas vectoriales de Mosaic AI con las siguientes configuraciones:
- Columna de origen para el embedding: chunk_to_embed
- Clave principal: chunk_id
- Campos filtrables: doc_version junto con document_type, product_name y target_audience
El chunk_id permanece estable de una ejecución a otra, compuesto por el ID del documento y la posición del chunk. Por eso, las actualizaciones incrementales MERGE en la tabla Delta dejan intactos los chunks en lugar de volver a indexarlos, lo cual representa un verdadero ahorro cuando el corpus se actualiza según un calendario preestablecido.
Los beneficios al realizar consultas
El beneficio se manifiesta cuando un usuario hace una consulta, y no en el momento de la carga. Supongamos que la solicitud es "Encuentre la guía del firmware para el Orion Motion Controller". La recuperación ahora puede realizarse en dos pasos:
- Determine los filtros de metadatos correspondientes, como document_type = “guide” y product_name = “Orion Motion Controller”.
- Incluya la consulta y realice la búsqueda únicamente entre los documentos que cumplan con esos filtros.
Al no contar con ai_extract, un agente depende únicamente de la similitud semántica. Esto puede ser aceptable para unos pocos documentos similares. Sin embargo, con miles de archivos que abarcan numerosos productos, tipos y públicos objetivo, se vuelve poco fiable. Los metadatos basados en enumeraciones solucionan este problema: el agente no necesita adivinar si se trata de un manual o una guía, y simplemente aplica el filtro document_type = “manual” para obtener resultados exactos.
Límites y costos
ai_prep_search no permite control alguno sobre el particionamiento. No se pueden establecer el tamaño de los fragmentos, la superposición ni el algoritmo utilizado. Para muchos casos de RAG con documentos, esto es aceptable, ya que el particionamiento semántico con contexto adicional suele ser mejor que las divisiones simples de tamaño fijo. El control detallado de fragmentos padre-hijo o de la superposición sigue requiriendo lógica personalizada.
ai_extract incrementa los costos y la latencia, ya que cada documento necesita una llamada adicional al LLM. Delta Sync puede omitir los documentos sin cambios, pero la primera ingestión completa podría seguir siendo costosa; por eso es sensato probar con una muestra antes de procesar todo el corpus.
Utilice la versión 2.0 de ai_parse_document cuando su salida VARIANT se envíe a ai_prep_search o ai_extract. Esa versión proporciona cuadros delimitadores, tipos de elementos y descripciones de figuras. El texto en bruto puede pasarse a ai_extract, aunque se pierden las ventajas estructurales que ofrece el análisis.
Resumen del diseño
Añadir ai_extract v2.1 requiere poco código adicional, pero modifica significativamente la arquitectura, ya que los fragmentos ahora contienen metadatos limpios, tipados y filtrables, lo que hace que la recuperación híbrida sea más fiable. El proceso se mantiene en tres etapas claras:
- ai_parse_document captura la estructura del documento.
- ai_extract obtiene metadatos útiles.
- ai_prep_search crea fragmentos semánticos.
Esta separación resulta beneficiosa al trabajar con documentos de diferentes productos, versiones, públicos y tipos. Sin filtros de metadatos, la recuperación se basa principalmente en búsquedas por similitud. Con ellos, el agente primero realiza una selección preliminar y luego establece un ranking.
Este enfoque no sirve para todo. La metadatos a nivel de fragmentos, la recuperación padre-hijo o el particionamiento personalizado siguen requiriendo código especial. Sin embargo, para muchos pipelines RAG de documentos en Databricks, es un punto de partida más limpio y sencillo. Además, los fragmentos deben seguir siendo probados y evaluados, y nunca se debe confiar en ellos ciegamente.
Lecturas relacionadas
- Receta de Spring AI: Filtrar resultados RAG con metadatos por título del juego — Una guía 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.