Un LLM como árbitro en la recuperación de RAG: elegir al candidato adecuado con razones

de Enterprise Document Intelligence, una serie que construye un sistema RAG empresarial a partir de cuatro ladrillos: análisis, análisis de preguntas, recuperación y generación. Cierra las tres partes del ladrillo. La parte anterior, el Artículo 7B (detección de anclas), produjo candidatos clasificados; éste arbitra: una llamada de LLM los clasifica con razones y el resultado es un JSON escrito que un auditor puede defender.

dónde se ubica este artículo en la serie: Artículo 7 (recuperación), la parte del árbitro, dentro de la Parte II (los cuatro ladrillos) – Imagen del autor

La recuperación es filtrar en line_df y toc_df, ancla versus contexto: el modelo mental del Artículo 7A (recuperación como filtrado). Los anclajes en sí surgen de un proceso de detección de tres etapas (Artículo 7B, detección de anclajes): palabra clave + incrustaciones en paralelo, agregación a una unidad estructural y luego una única llamada LLM al final. Este artículo trata sobre esa última llamada.

Este artículo trata sobre esa única convocatoria de LLM. El árbitro. Ve los resultados de las palabras clave, los resultados de las incrustaciones y la sección en la que se encuentra cada candidato, todo en un informe estructurado, y escribe un veredicto por candidato con un motivo. El patrón en una línea: los detectores proponen, el árbitro decide. En una sola llamada.

El artículo también cubre lo que rodea al árbitro: el determinista despachador que elige qué detectores ejecutar por pregunta, la ruta "no encontrada" (un sistema confiable debe poder decir que no) y el contrato JSON unificado (RetrievalResult) que la recuperación pasa a la generación.

A lo largo de este artículo trabajamos en un solo documento, Attention Is All You Need (Vaswani et al. 2017, 15 páginas; licencia de distribución no exclusiva de arXiv, declarada en la página de resumen de arXiv). Incluye un TOC nativo limpio en el esquema del PDF (22 entradas, 3 niveles de profundidad) y el contenido es un territorio familiar para cualquier ingeniero que toque RAG: codificador, decodificador, atención, consultas, claves, valores. Esto mantiene el foco en los métodos de recuperación en lugar de analizar un corpus de dominio específico. Este artículo también asume que el documento lleva su propio TOC; recuperar uno a partir de texto sin formato se deja para el trabajo de seguimiento.

Todos los métodos de este artículo comienzan desde line_df y toc_df – Imagen del autor

1. El árbitro de LLM: la única llamada de LLM al final

Esta es la convocatoria de LLM que el Artículo 7B (detección de anclaje) colocó en la etapa 3. Ese artículo produjo candidatos de cada detector; esta sección es lo que les sucede. Un candidato es un pasaje único que un detector devolvió con su ancla (donde coincidió), su unidad agregada (sección, página o fragmento) y un fragmento del contexto circundante. El árbitro los ve todos en una sola decisión, los clasifica según los motivos y genera la lista final.

Las tres cosas que cubre esta sección:

Por qué Score Fusion (RRF y amigos) pierde la señal que los detectores ya pusieron a disposición. Qué escrito estructurado entregar al árbitro para que pueda clasificarse con las razones. Qué registrar por candidato para que cualquier auditor pueda reconstruir la decisión.

El patrón en una línea: los detectores proponen, el árbitro decide, en una sola llamada.

1.1 La fusión de puntuaciones es el instinto equivocado

Cuando varios métodos devuelven candidatos, el reflejo es combinar sus puntuaciones. Los métodos devuelven números en diferentes escalas: similitud de coseno, BM25 ilimitado y recuentos de enteros de coocurrencia. Agregarlos no tiene sentido. Normalizar la puntuación de cada método tampoco ayuda, porque un coseno de 0,9 y un BM25 normalizado de 0,9 no significan lo mismo sobre el mismo candidato.

La respuesta clásica es la fusión de rangos recíprocos (RRF). Evita el problema de calibración ignorando puntuaciones y usando rangos:

RRF: fusión basada en rangos que ignora las puntuaciones brutas; k = 60 – Imagen del autor

con k=20 por convención. Los candidatos clasificados mediante varios métodos acumulan contribuciones; los candidatos vistos por un solo método obtienen un único mandato pequeño. RRF es el valor predeterminado en muchas bases de datos vectoriales (Pinecone, Weaviate, Elastic) y funciona en casos comunes sin ajuste.

Pero RRF deja una señal real en el suelo. Por qué es importante un método para clasificar a un candidato. TOC lo clasificó porque el título de una sección coincidía. Las palabras clave lo clasificaron porque la prima y el € coincidieron en la misma línea. Las incrustaciones lo clasificaron porque algo cerca de la página estaba vagamente cerca en el espacio vectorial. RRF comprime todo eso en un índice de clasificación. El acuerdo entre métodos se convierte en un número y el motivo del acuerdo desaparece.

Eso es exactamente lo que un experto lee en la pantalla: el título de la sección, las palabras clave coincidentes, las líneas alrededor de la coincidencia. Sostenemos que una pequeña convocatoria de LLM, con la misma información en una forma estructurada, se clasifica mejor que cualquier método de fusión de puntuaciones.

Todavía registramos RRF cuando la herramienta circundante lo usa de forma predeterminada, o lo usamos como un prefiltro económico cuando el grupo de candidatos es demasiado grande para caber en una convocatoria de LLM (top 200 según RRF, luego LLM en los supervivientes). Pero la decisión de clasificación pertenece al LLM, no a una fórmula de puntuación.

1.2 Entregar al LLM un resumen estructurado

El LLM es la capa que clasifica. Su aportación no es “aquí hay cinco pasajes, elige el mejor”. Es un resumen estructurado, una fila por candidato, que enumera lo que descubrió cada método de recuperación:

candidato_id: referencia estable (página + rango de líneas, o sección + desplazamiento de línea). métodos: qué métodos de recuperación mostraron este candidato (TOC, palabras clave, incrustaciones). sección: la sección TOC en la que se sienta el candidato, extraída de toc_df. matched_keywords: las palabras clave de la pregunta analizada que llegaron a este candidato. Fragmento: de tres a cinco líneas de contexto circundante, extraído de line_df.

El resumen es lo que lee el LLM. Se parece a lo que ve un experto en su pantalla: un título de sección en la parte superior, las palabras clave coincidentes en el cuerpo, las líneas alrededor de la coincidencia. Mucho más cerca de eso que de una lista clasificada de puntuaciones de cosenos. El LLM clasifica a los candidatos y escribe una razón de una línea por candidato retenido, que va directamente a la pista de auditoría. Cada candidato obtiene uno de cuatro roles:

primario: lleva la respuesta. apoyo: da contexto, la respuesta debe tener sentido. tangencial: relacionado pero mantenido de baja prioridad. Descartado: caída del LLM, con motivo registrado para auditoría. clase CandidateBrief(BaseModel): candidato_id: str métodos: lista[str] sección: str palabras clave coincidentes: lista[str] fragmento: str clase CandidateRanking(BaseModel): candidato_id: str rol: Literal["primario", "de apoyo", "tangencial", "descartado"] motivo: str def llm_rank( pregunta: str, briefs: lista[CandidateBrief], cliente,) -> list[CandidateRanking]: """Lea los resúmenes estructurados, devuelva una clasificación por candidato.""" …

Un árbitro mínimo ejecutable. Una llamada de LLM, toda la lista de candidatos a la vez, resultados estructurados.

def llm_rank(question: str, briefs: list[CandidateBrief], client) -> list[CandidateRanking]: """Entregue al LLM los resúmenes estructurados, obtenga un rol + motivo por candidato.""" briefs_text = "\n".join( f"[{b.candidate_id}] sección={b.section!r}, métodos={b.methods}, " f"matched={b.matched_keywords}, snippet={b.snippet!r}" for b in briefs ) Prompt = ( f"Pregunta: {pregunta}\n\nCandidatos:\n{briefs_text}\n\n" "Para cada candidato, asigne una función (principal, de apoyo, " "tangencial, descartada) y una razón de una línea. Utilice el ID de candidato. textualmente." ) devuelve client.responses.parse( model=model_chat, input=prompt, text_format=ArbiterOutput, ).output_parsed.rankings

Por qué esto es mejor que la fusión de partituras:

El LLM ve por qué cada método clasificó al candidato. Una coincidencia de alto coseno sin superposición de palabras clave probablemente sea ruido de actualidad. Un acuerdo de palabras clave TOC + es una señal estructural real. RRF convierte ambos en el mismo número de rango. El LLM puede etiquetar a los candidatos más allá de mantener/eliminar: primario, de apoyo, tangencial. Útil cuando la respuesta tiene una parte principal y un contexto de apoyo. El LLM puede señalar contradicciones: dos pasajes que dicen cosas diferentes sobre el mismo punto. Común en contratos con modificaciones, en presentaciones reglamentarias con revisiones. La justificación es texto plano y va directamente a la pista de auditoría. No hay línea rrf_score = 0.0327 para explicar el cumplimiento.

Costo: una llamada de LLM (aproximadamente una segunda para un grupo de 10 candidatos principales). Más barato que ejecutar incrustaciones en todo el documento. Mucho más barato que una respuesta incorrecta en producción.

Un resumen concreto sobre el periódico en ejecución. Pregunta: "¿Qué codificación posicional utiliza el papel?" La coincidencia de TOC (que llega directamente a 3.5 Codificación posicional) más las palabras clave en todo el documento producen cuatro candidatos. Cada uno se convierte en una fila del escrito.

Encabezado de sección, palabras clave coincidentes, fragmento: la misma información que leería un experto – Imagen del autor

El LLM lee los cuatro resúmenes y la pregunta, y responde:

{ "rankings": [ { "candidate_id": "page_5_sinusoidal", "role": "primary", "reason": "Define la codificación posicional sinusoidal utilizada en el artículo" }, { "candidate_id": "page_6_learned", "role": "primary", "reason": "Establece la alternativa (incrustaciones aprendidas) que comparó el artículo" }, { "candidate_id": "page_2_intro", "role": "discarded", "reason": "Menciona la motivación para la codificación posicional, pero no hay opciones" }, { "candidate_id": "page_3_encoder", "role": "descartado", "reason": "Mención única en un contexto diferente, fuera de tema" } ] }

Se quedaron dos de cuatro. Los dos descartados se habrían infiltrado bajo cualquier esquema de fusión de puntajes: tienen aciertos legítimos de palabras clave. Pero los fragmentos se leen como contexto, no como respuestas. El LLM hizo esa llamada a partir del fragmento y escribió el motivo que se incluye en la pista de auditoría.

Una llamada en vivo contra los mismos cuatro escritos.

Llamada LLM en vivo: un rol + un motivo por candidato, primario o tangencial – Imagen del autor

1.3 Conflictos y pista de auditoría

Cuando los métodos no están de acuerdo, el LLM tiene que elegir. Tres reglas generales ayudan:

Confíe en el TOC para obtener coincidencias exactas de títulos. El autor escribió ese título. 3.5 Codificación posicional que coincide directamente con las palabras clave de la pregunta en ejecución. Ningún método estadístico debería anular eso. Confíe en las palabras clave coexistentes con una señal fuerte. Una línea donde las palabras clave primarias y secundarias aparecen juntas (posicional + sinusoidal o posicional + aprendida) es casi con certeza relevante, incluso si se encuentra fuera del alcance restringido del TOC. Sea escéptico con los resultados de solo incrustaciones: un candidato que aparece solo mediante incrustaciones, sin TOC ni respaldo de palabras clave, necesita una mirada cuidadosa. A veces, el método de palabras clave pasa por alto un sinónimo real. Más a menudo se trata de ruido tópico. El LLM, al leer el informe, generalmente puede decir cuál es cuál.

El resumen estructurado es lo que permite al LLM aplicar esas reglas de manera consistente. La fusión de puntuaciones comprime por qué cada método clasificó a un candidato en un índice de clasificación única y elimina las reglas con él.

Todo candidato debe ser defendible. ¿Por qué se le mostró este pasaje al usuario? La respuesta no puede ser "la similitud de incrustación fue 0,78". Tiene que ser una cadena clara: qué métodos le mostraron a este candidato, qué informe se le mostró al LLM, qué rol le asignó el LLM, por qué escribió. Cada candidato lleva ese rastro desde la recuperación hasta la generación.

clase CandidateProvenance(BaseModel): ID_candidato: str métodos_que_encontraron_it: lista[str] rango_por_método: dict[str, int] contexto_estructural: CandidateBrief llm_role: Literal["primario", "de apoyo", "tangencial", "descartado"] llm_reason: str clase AuditedRetrievalResult(BaseModel): candidatos: lista[dict] procedencias: lista[CandidateProvenance] métodos_ejecutar: lista[cadena] métodos_omitidos: lista[cadena] razones_omitidas: dict[cadena, cadena]

Para cada candidato que llega al usuario, el sistema puede responder: qué métodos votaron por él, cuál fue su rango en cada uno, qué vio el LLM, qué rol le asignó el LLM y por qué, si se omitió algún método y por qué.

Una entrada concreta para el candidato de la página 5 del ejemplo anterior:

procedencias = [ CandidateProvenance( candidato_id="página_5_sinusoidal", métodos_that_found_it=["toc", "palabras clave"], ranking_per_method={"toc": 1, "palabras clave": 1}, estructural_context=CandidateBrief( candidato_id="página_5_sinusoidal", métodos=["toc", "palabras clave"], sección="3.5 Codificación posicional", matched_keywords=["positional", "encoding", "sinusoidal"], snippet="Usamos funciones seno y coseno de diferentes frecuencias…", ), llm_role="primary", llm_reason="Define la codificación posicional sinusoidal utilizada en el artículo", ), # … misma estructura para el otro candidato conservado ] métodos_skipped = ["embeddings"] skipped_reasons = { "embeddings": "El TOC y las palabras clave arrojaron fuertes señales de refuerzo" }

Esto no es sólo para depurar. En contextos de cumplimiento, legales y de auditoría, este rastro es la documentación que justifica el resultado del sistema. Sin él, el proceso de recuperación es una caja negra, y una caja negra no pasa una revisión regulatoria.

2. Elegir los métodos

2.1 Por qué las incrustaciones son las últimas

La mayoría de los tutoriales, marcos y charlas de conferencias de RAG presentan la recuperación basada en incrustación como la base del sistema. La suposición implícita es que las incorporaciones son las predeterminadas; la única pregunta es qué modelo de incrustación usar.

Después de recorrer la cuadrícula, la posición de esta serie se vuelve concreta: las incrustaciones ocupan un papel de apoyo en ambas tablas (coincidencia de incrustación de título en toc_df, coincidencia de incrustación de fragmentos en line_df), ninguna de las cuales es la opción predeterminada recomendada para la mayoría de los documentos empresariales. Esto no es un rechazo a las incrustaciones. Es una repriorización basada en lo que funciona.

Razón 1: las incrustaciones diluyen los tokens de señal alta. Insertar “¿Se aplica aquí el artículo L131-1 del código de seguros?” y el vector es un promedio de “artículo”, “seguro”, “código”, “aplicar”, “L131-1”. El token “L131-1” es la consulta completa, pero es una señal entre siete. El vector para el pasaje correcto puede no ser el más parecido. La concordancia de palabras clave del contenido lo encuentra al instante.

Vimos una versión de esto en el proceso RAG mínimo con el famoso error de suavizado de etiquetas épsilon: la página que contenía la respuesta ni siquiera estaba entre las 3 primeras por similitud de coseno. La página que ganó contenía más el estilo visual de la pregunta, no su contenido informativo específico.

Razón 2, las incrustaciones no pueden distinguir conceptos relacionados pero diferentes. El vector de “prima” y el vector de “deducible” están cerca. Ambos son montos en un contrato de seguro, ambos aparecen en contextos similares. Una consulta de "prima" con similitud de coseno devuelve pasajes que mencionan "deducible" junto con pasajes que mencionan "prima". Si la recuperación basada en incrustación devuelve principalmente pasajes deducibles, el LLM posterior no tiene nada de qué desambiguarse.

El mismo problema con la negación, las referencias temporales y cualquier distinción semántica que dependa de pequeños cambios de palabras. Las incrustaciones desdibujan estas distinciones; esa es a la vez su fuerza y ​​su debilidad.

Razón 3: las incrustaciones no comprenden la estructura del documento. Una base de datos vectorial es una colección plana de fragmentos. No tiene noción de "este fragmento está en la sección Definiciones" o "este fragmento es parte de un anexo". Toda esa estructura fue desechada cuando se fragmentó el documento.

Para una pregunta como "¿Qué dice la sección de garantía sobre los daños por inundación?", una búsqueda incrustada devuelve fragmentos que mencionan "garantía" e "inundación", posiblemente de Definiciones o Exclusiones, ninguna de las cuales es lo que el usuario quería. El razonamiento TOC habría elegido la Garantía en un solo paso.

Concreto en el periódico: el usuario pregunta “¿qué muestra la Figura 2?”. La pregunta está dominada por una ficha de señal alta (Figura 2); todo lo demás es relleno. La incrustación lo promedia todo; regex ancla el token exacto al instante.

La incrustación diluye el token de señal; regex extrae cada línea de coincidencia exacta en una sola pasada – Imagen del autor

La incrustación clasifica las páginas por similitud visual con la redacción de la consulta y es posible que no coloque las menciones reales de la Figura 2 entre las 5 primeras en absoluto. La expresión regular encuentra cada línea que contiene el token exacto, en las páginas correctas, de manera determinista. Una señal de cada siete, perdida en el promedio; Una señal obtenida con la herramienta adecuada.

2.2 Cuando ganan las incrustaciones

Las incrustaciones brillan en tres casos:

Falta de coincidencia de vocabulario donde ayudan las reescrituras. La “salida anticipada” se acerca a las “disposiciones de terminación anticipada” una vez que las reescrituras están en juego. La concordancia de palabras clave de título y la concordancia de palabras clave de contenido pasan por alto esto; El fósforo de incrustación de trozos lo atrapa. (El razonamiento TOC también capta esto por una razón diferente: el LLM entiende que la “salida anticipada” se relaciona con la Terminación por implicación, no por similitud). Preguntas conceptuales o confusas: “¿Es razonable el límite de responsabilidad?”: no hay una palabra clave específica para buscar. La incrustación puede exponer pasajes con la forma conceptual correcta. Documentos sin TOC y sin vocabulario especializado. Memos, correos electrónicos, publicaciones de blogs. Sin estructura para navegar; no hay diccionario experto para aplicar.

En los tres casos, las incrustaciones no compiten solas, sino que se combinan con métodos de palabras clave y TOC (la combinación de incrustaciones híbridas) por la conciencia estructural que les falta.

En la naturaleza: en un sistema de producción medimos la contribución de cada método de recuperación eliminándolos uno a la vez, frente a una evaluación por modo de falla:

Línea de base de solo incrustación: 71 % de precisión. Agregar recuperación basada en TOC cuando corresponda: 84%. Agregar recuperación de palabras clave simultáneas con el diccionario experto: 91%. Agregar al orquestador para elegir métodos por pregunta: 94%.

La brecha de 23 puntos entre solo las incrustaciones y todos los métodos con despacho determinista es lo que se compra mejor con la recuperación. Ninguna actualización del modelo integrado que probamos se acercó.

HyDE es un caso especial de este principio. HyDE genera una respuesta hipotética a la pregunta, la incrusta y luego ejecuta la similitud del coseno con los fragmentos del documento. La razón por la que funciona: la respuesta hipotética contiene el vocabulario de dominio del documento del que carece la pregunta. "¿Cómo salgo anticipadamente de un contrato?" se convierte en “rescisión anticipada, período de notificación, tarifas de salida, notificación por escrito…” en el borrador del LLM, que luego coincide con los pasajes reales en el espacio de inserción.

En este marco, la misma lógica se captura una etapa antes, en el momento del análisis de las preguntas, y se codifica en el diccionario de palabras clave experto. HyDE es una “expansión de palabras clave basada en incrustaciones que se realiza implícitamente por consulta”; esta serie favorece la “expansión explícita de palabras clave realizada una vez y reutilizada”. Misma recompensa, con tres beneficios operativos:

el conjunto de palabras clave es auditable: el experto ve qué coincide y por qué paga su costo una vez en lugar de por consulta, crea un activo permanente (el diccionario) en lugar de regenerar una hipótesis única.

En entornos de consumo de dominio abierto sin un experto en el dominio y sin requisitos de auditabilidad, HyDE mantiene una pequeña ventaja; en los contextos empresariales a los que se dirige esta serie, la expansión explícita es prácticamente superior. El ladrillo de análisis de preguntas descompone los mecanismos detrás de HyDE en detalle y explica por qué domina el de palabras clave y sinónimos cuando el vocabulario del documento está limitado.

2.3 El árbol de decisiones

¿Cómo decide el despachador qué combinación de celdas habilitar para una pregunta determinada?

def elegir_métodos(intención, palabras clave, doc_has_toc, has_exact_codes=False, vocabulario_diverges=False): métodos =[]if doc_has_toc e intent in ("qa", "summarization"): métodos.append("toc") if palabras clave: métodos.append("co_occurrence") if has_exact_codes: métodos.append("bm25") si no métodos o vocabulario_diverges: métodos.append("embedding") métodos de retorno Si el documento tiene un TOC limpio, ejecute la coincidencia de palabras clave de título (siempre) y la coincidencia de incrustación de título (opcional) como los detectores del lado del toc. El árbitro utilizará el TOC para razonar sobre coincidencias sutiles de títulos como parte de su decisión única. En line_df, ejecute la coincidencia de palabras clave de contenido con las palabras clave de la pregunta analizada y aumente la coocurrencia. Este es el detector principal; sus golpes alimentan al árbitro directamente. Agregue incrustaciones en line_df en paralelo cuando se espere una discrepancia de vocabulario o cuando la pregunta sea conceptual. Opcional, se puede omitir cuando la señal de palabra clave ya está limpia. El árbitro del LLM (Sección 1) ve todo a la vez y se clasifica según las razones. Esta es la única llamada de LLM que convierte las coincidencias del detector en una lista final. En documentos muy grandes donde el grupo de candidatos es demasiado grande para caber en una llamada del árbitro, utilice el motivo y luego la coincidencia como truco previo al estrechamiento: una llamada LLM adicional en toc_df selecciona las secciones relevantes, luego la palabra clave de contenido se ejecuta dentro y luego el árbitro clasifica a los supervivientes. Dos convocatorias de LLM en total en lugar de una, solo cuando la escala lo exige.

En producción, la mayoría de las preguntas activan la palabra clave + (opcionalmente) detectores de incrustación y terminan con el árbitro. El código de estrategia siguiente utiliza las etiquetas más antiguas "toc_reasoning" / "reason_then_match" para compatibilidad con versiones anteriores; conceptualmente se corresponden con “el árbitro hace el razonamiento” y “estrechan ante el árbitro”, respectivamente. Un canal integrado de seguimiento desarrolla la orquestación en torno al despachador.

Error común: codificar la misma estrategia de recuperación para todas las preguntas. Un canal que siempre ejecuta la recuperación incrustada, incluso para documentos con un índice de contenido limpio y preguntas con palabras clave específicas, está realizando un trabajo adicional que produce candidatos más ruidosos que los que habrían hecho los métodos más simples. El despachador debe elegir; si siempre elige lo mismo, has eliminado la elección.

def elegir_estrategia_retrieval( intención: cadena, palabras clave: lista[cadena], doc_has_toc: bool, has_exact_codes: bool = False, vocabulario_diverges: bool = False, ) -> lista[cadena]: estrategia: lista[cadena] =[]si doc_has_toc e intención en ("qa", "summarization"): estrategia.append("toc_reasoning") estrategia.append("title_keyword_match") si palabras clave: si "toc_reasoning" en estrategia: estrategia.append("reason_then_match") else: estrategia.append("content_keyword_match") si tiene_códigos_exactos: estrategia.append("bm25") si no es estrategia o vocabulario_diverges: estrategia.append("chunk_embedding_match" if not doc_has_toc else "hybrid_embedding") return estrategia # Cuatro preguntas reales de un lector que una persona que estudia el artículo podría hacer, enviadas por el selector de estrategia. doc_has_toc = len(toc_df) > 3 preguntas = [ {"q": "¿Cómo funciona la atención de múltiples cabezas?", "intent": "qa", "keywords": ["multi-head", "attention"], "exact": False, "diverges": False}, {"q": "¿Qué muestra la Figura 2?", "intent": "qa", "keywords": ["Figura 2"], "exact": Verdadero, "diverge": Falso}, {"q": "¿Por qué este modelo se entrena más rápido que los RNN?", "intención": "qa", "palabras clave":[], "exact": False, "diverges": True}, {"q": "Resumir la sección de capacitación.", "intent": "summarization", "keywords": ["Training"], "exact": False, "diverges": False}, ] para q en preguntas: s = elegir_retrieval_strategy( q["intent"], q["keywords"], doc_has_toc, has_exact_codes=q["exact"], vocabulario_diverges=q["diverges"], ) print(f"{q['q']}\n estrategia: {s}\n")

Ejecute cuatro preguntas de muestra de diferentes formas: una pregunta de control de calidad rica en vocabulario, una pregunta que apunta a una figura específica, una pregunta conceptual sin palabras clave y una solicitud de resumen:

Mismo despachador, cuatro preguntas, cuatro estrategias distintas – Imagen del autor

3. Decir "no encontrado" de forma fiable

Un sistema fiable debe poder decir no. Esto es más difícil de lo que parece, porque los LLM están capacitados para producir respuestas, no para retenerlas.

La arquitectura debe admitir "no encontrado" en cada etapa:

El filtrado de metadatos no devuelve ningún documento. No se ejecuta ninguna recuperación. El sistema le dice al usuario que ningún documento coincide con el alcance. La coincidencia de TOC no devuelve ninguna sección relevante. El proceso pasa al siguiente método. Todos los métodos de recuperación devuelven candidatos vacíos o de baja confianza. El oleoducto informa que “no se encontró respuesta en los documentos disponibles”, explicando lo que se buscó. Los candidatos existen pero el LLM, en generación, no puede extraer una respuesta. Se maneja en la etapa de generación, pero la etapa de recuperación debe transmitir suficientes metadatos para que la generación sea conservadora.

3.1 Por qué las palabras clave demuestran su ausencia, las incrustaciones no

Existe una profunda asimetría entre los métodos de recuperación en cuanto a cómo manejan la ausencia. La recuperación incrustada siempre devuelve un top-k con puntuaciones de similitud continuas. Una página sobre primas arroja 0,78 para una pregunta sobre exclusiones; una página sobre exclusiones devuelve 0,81. ¿Dónde se traza la línea entre encontrado y no encontrado? Nada en el espacio de incrustación dice "esta no es la respuesta correcta", sólo "esta está más cerca o más lejos que la otra". Puedes elegir un umbral, pero es arbitrario; en un documento diferente o en una pregunta diferente, el umbral se traspasa.

La recuperación de palabras clave funciona al revés. Si su diccionario experto para “exclusión” cubre todas las formas que el tema puede adoptar en el corpus (“exclusión”, “exclu de”, “non couvert”, “hors champ”, “ne couvre pas”) y el documento no contiene ninguna de ellas, tiene un reclamo defendible: el tema no está en este documento. Lo mismo para montos, fechas, códigos. Cuando la expresión regular para “— no coincide con nada, no hay nada que coincida. La ausencia es evidencia, no incertidumbre.

Esto es exactamente lo que siempre ha hecho un usuario empresarial con Ctrl+F. Buscan el término que esperan y, cuando no aparece nada, concluyen que el documento no lo cubre. La conclusión es válida porque saben qué términos buscar. Treinta años de trabajo documental profesional se basan en este método. Un canal de recuperación que codifica el mismo vocabulario experto hereda la misma propiedad: cero coincidencias en un conjunto exhaustivo de palabras clave devuelve "no encontrado" con un motivo preciso y auditable.

En entornos empresariales (legal, cumplimiento, finanzas), ésta es la diferencia entre “no estoy seguro” (cobertura) y “ninguno de estos términos específicos aparece en el documento, y estarían presentes si la respuesta estuviera aquí” (una búsqueda defendible). El primero es inútil para un responsable de cumplimiento. La segunda es la respuesta.

Este argumento también explica por qué vale la pena invertir en diccionarios expertos (creados en el momento del análisis de preguntas). Un diccionario que cubra un tema de manera exhaustiva no es sólo una forma de encontrar la respuesta cuando existe; es el mecanismo que permite al sistema demostrar que la respuesta no existe cuando no es así.

clase RetrievalResult(BaseModel): candidatos: lista[dict] métodos_usados: lista[str] not_found: bool not_found_reason: str | Ninguno = Ninguno confianza: flotante

Cuando not_found es True, la etapa de generación sabe producir "la respuesta no está en los documentos disponibles" con el motivo, en lugar de adivinar. El ladrillo generacional vuelve a esto.

clase RetrievalResult(BaseModel): candidatos: lista[dict] métodos_usados: lista[str] not_found: bool not_found_reason: str | Ninguno = Ninguno confianza: flotante # Una pregunta del lector cuya respuesta NO se encuentra en este documento: el cumplimiento del RGPD no tiene nada que ver con # la arquitectura Transformer. Las palabras clave a continuación son muy distintivas: se verificaron 0 visitas en el artículo. absent_question = "¿Qué dice este documento sobre el cumplimiento del RGPD y la protección de datos personales?" absent_keywords = ["gdpr", "datos personales", "cumplimiento", "oficial de protección de datos"] # Lado de la palabra clave: la búsqueda exhaustiva devuelve 0 resultados -> defendible "no encontrado". title_hits = match_titles(toc_df, absent_keywords) content_hits = line_df[line_df["text"].str.lower().apply( lambda t: cualquiera(kw en t para kw en absent_keywords) )] palabra clave_envelope = RetrievalResult( candidatos=[], métodos_used=["title_keyword_match", "content_keyword_match"], not_found=title_hits.empty y content_hits.empty, not_found_reason="no_keyword_match" si title_hits.empty y content_hits.empty else Ninguno, confianza=0.0 si title_hits.empty y content_hits.empty else 0.8,) print("Resultado de KEYWORD en ausente topic:") print(f" not_found = {keyword_envelope.not_found}, Reason = {keyword_envelope.not_found_reason}") # Lado de incrustación: aún devuelve top-k con similitudes distintas de cero. Ningún umbral nos dice "no en el documento". emb_hits, _ = retrieve_pages_by_similarity(page_df, line_df, absent_question, top_k=3, client=client) print(f"\nEMBEDDING resultado en el mismo tema ausente:") for _, r en emb_hits.iterrows(): print(f" page {int(r['page_num']):>2} sim={r['similitud']:.3f} (sería devuelto como candidato)") print(" -> la incrustación no puede decir 'no encontrado': siempre devuelve top-k con puntuaciones continuas.")

El contraste entre los dos métodos sobre un tema ausente hace que el esquema sea valioso. Al preguntarle al documento de atención sobre el cumplimiento del RGPD (un tema que nunca cubre), la recuperación de palabras clave marca not_found=True limpiamente, mientras que la recuperación incrustada aún devuelve tres páginas con puntuaciones de coseno no triviales:

El mismo tema ausente: las palabras clave dicen que no, las incrustaciones aún se ubican tres páginas por encima de cero – Imagen del autor

3.2 Ninguna respuesta es mejor que una incorrecta

En un chatbot de consumo, una respuesta incorrecta resulta molesta. En un sistema de control de calidad de documentos empresariales, es costoso.

Algunos ejemplos:

Un responsable de cumplimiento pregunta si un contrato tiene una cláusula de no competencia. El sistema devuelve “sí, con una duración de 3 años” basándose en un pasaje incorrecto. El oficial se basa en esto en una reunión. La cláusula resulta ser de 1 año. Se tomaron decisiones basándose en información errónea. Un equipo de finanzas pregunta sobre el límite de responsabilidad en un contrato de proveedor. El sistema devuelve el límite de seguro del proveedor (un número diferente, en una sección diferente) porque la recuperación los confundió. Las negociaciones se desarrollan sobre una suposición equivocada. El error se detecta un mes después. Un equipo jurídico pregunta si un contrato cubre las violaciones de datos. El sistema devuelve un pasaje sobre daños generales que menciona datos tangencialmente. El equipo asume que existe cobertura. La empresa se enfrenta a un incidente sin seguro.

En todos ellos, ninguna respuesta habría sido mejor que una respuesta incorrecta. Si no hay respuesta, el usuario debe profundizar más, preguntarle a un colega y leer el documento. Una respuesta incorrecta con un tono confiado provoca un cortocircuito en ese proceso y crea una falsa sensación de certeza.

Un canal de recuperación que sabe decir "No encontré nada" es más valioso que uno con una tasa de recuperación más alta pero sin humildad. Esta no es una preferencia técnica, es un requisito comercial.

4. El resultado de la recuperación: un JSON listo para generar

Todo en este artículo alimenta un propósito: producir los candidatos adecuados para pasar a la etapa de generación. Los métodos, el árbitro LLM, el despachador, el manejo de "no encontrado", todo converge en un solo objeto escrito.

Esta sección lo nombra explícitamente. Retrieval entrega un RetrievalResult por par (documento, pregunta). El ladrillo generacional lo lee, produce la respuesta y nunca más tiene que pedir nada más para recuperarlo. Un ciclo de orquestación de seguimiento puede extender el mismo RetrievalResult si la generación necesita más contexto, pero la forma del contrato sigue siendo la misma.

4.1 Un candidato, dos alcances

Según el marco de anclaje y contexto del Artículo 7A (recuperación como filtrado), cada candidato lleva ambos. El ancla es donde aterrizó la coincidencia: una línea estrecha en una página específica, con la sección a la que pertenece. El contexto es la ventana extendida de recuperación que se decide pasar hacia adelante: un párrafo, una sección o una ventana de N líneas, nuevamente como un rango de líneas con límites de página.

¿Por qué ambos? La generación necesita el ancla para las citas (resalte las líneas coincidentes en el PDF, vincule la respuesta a las líneas precisas) y el contexto para la conexión a tierra (lea el párrafo o sección circundante en la que se encuentra la respuesta). Al almacenar solo el contexto se pierde la cita precisa. Al almacenar solo el ancla se pierde la evidencia circundante que hace que la respuesta sea interpretable.

La convención: el rango del contexto siempre contiene el rango del ancla. El ancho del contexto proviene del análisis de preguntas, impulsado por los dos ejes ortogonales que las etiquetas del analizador: respuesta_forma (cardinalidad) y respuesta_tipo (valor), más respuesta_contexto en StructuralHints (cuánto texto circundante se debe leer). Una pregunta (única, cantidad) con respuesta_contexto=línea obtiene un contexto limitado (el ancla más algunas líneas de margen de seguridad). Una pregunta (listado, texto) con respuesta_contexto=sección obtiene la sección completa.

4.2 El esquema unificado

class CandidateScope(BaseModel): """Un rango en line_df: página + límites de línea, más la sección a la que pertenece.""" page_start: int page_end: int line_start: int line_end: int section_id: str | Ninguno = Ninguno class RetrievedCandidate(BaseModel): """Un pasaje recuperado como posible fuente de respuesta.""" candidato_id: str anclaje: CandidateScope # dónde aterrizó la coincidencia (estrecho) contexto: CandidateScope # qué se pasa al fragmento de generación (extendido): str # breve, para el texto breve del árbitro de LLM: str # texto de contexto completo, para métodos de generación: list[str] # qué métodos aparecieron palabras clave_coincidentes: lista[cadena] # qué palabras clave llegaron al ancla título_sección: cadena | Ninguno = Ninguno rol: Literal["primario", "de apoyo", "tangencial"] | Ninguno = Ninguno motivo: str | Ninguno = Ninguno # Clase de justificación del árbitro LLM RetrievalResult(BaseModel): """El resultado de recuperación completo, entregado a la generación como un único JSON.""" doc_id: str pregunta: str candidatos: lista[RetrievedCandidate] # ordenado: primario primero métodos_usados: lista[cadena] not_found: bool not_found_reason: str | Ninguno = Ninguno confianza: flotante

El doc_id y la pregunta en la parte superior son los que hacen que el resultado se pueda reproducir. Conserve este objeto y podrá volver a ejecutar la generación más tarde, en la misma recuperación, sin pagar por la recuperación nuevamente. Esa es la base para el seguimiento de auditoría (Sección 1.3), para la evaluación de lotes y para cualquier orquestador que tenga que comparar dos indicaciones de generación sobre la misma evidencia recuperada.

4.3 Un JSON en vivo en el documento en ejecución

La misma pregunta que utilizamos en la Sección 1 para demostrar el árbitro LLM: "¿Qué codificación posicional utiliza el documento?". Sobreviven dos candidatos principales, ambos anclados en la sección Codificación posicional 3.5, con contextos que se extienden al cuerpo completo de la sección.

{ "doc_id": "1706.03762v7", "question": "¿Qué codificación posicional utiliza el papel?", "candidates": [ { "candidate_id": "p5_l12_l14", "anchor": { "page_start": 5, "page_end": 5, "line_start": 12, "line_end": 14, "section_id": "11" }, "context": { "page_start": 5, "page_end": 6, "line_start": 1, "line_end": 40, "section_id": "11" }, "snippet": "Usamos funciones seno y coseno de diferentes frecuencias…", "text": "3.5 Codificación posicional [… cuerpo de sección completo, 40 líneas, omitido para la impresión…]", "methods": [ "title_keyword_match", "content_keyword_match" ], "matched_keywords": [ "positional", "encoding", "sinusoidal" ], "section_title": "3.5 Codificación posicional", "role": "primary", "reason": "Define la codificación posicional sinusoidal utilizada por el papel". }, { "candidate_id": "p6_l4_l5", "anchor": { "page_start": 6, "page_end": 6, "line_start": 4, "line_end": 5, "section_id": "11" }, "context": { "page_start": 5, "page_end": 6, "line_start": 1, "line_end": 40, "section_id": "11" }, "snippet": "También experimentamos con incrustaciones posicionales aprendidas…", "text": "3.5 Codificación posicional [… mismo cuerpo de sección completo que el candidato 1, contexto compartido…]", "methods": [ "title_keyword_match", "content_keyword_match" ], "matched_keywords": [ "positional", "encoding", "learned" ], "section_title": "3.5 Posicional Codificación", "role": "primary", "reason": "Menciona la variante aprendida que el artículo también probó". } ], "methods_used": [ "title_keyword_match", "content_keyword_match", "llm_arbiter" ], "not_found": false, "not_found_reason": nulo, "confianza": 0,95 }

Cuando crea la recuperación como un servicio o un paso por lotes, este es el artefacto que ingresa a la base de datos, se versiona y admite la reproducción. También es lo que lee su auditor cuando le pregunta por qué se le mostró un pasaje al usuario.

Se aplica la misma convención de caché que instala el bloque de análisis. save_retrieved_pages(pdf_path, question, retrieved_pages_df) escribe el resultado a nivel de página (con las columnas match_count, matched_keywords intactas) en la salida ///questions//retrieved_pages.xlsx. Filtered_line_df no se almacena en caché intencionalmente. La generación lo vuelve a derivar de line_df + números de página en el acto. Los ID de página persistentes son el valor predeterminado económico; persistir el DataFrame filtrado pesado sería un estado duplicado. Esto mantiene limpios los límites de cada ladrillo: la recuperación escribe las selecciones de páginas, la generación las lee y ninguno necesita nada más.

5. Conclusión

Recuperar no es buscar. Está filtrando en tablas estructuradas. Una vez que el análisis ha producido line_df y toc_df, cada método de recuperación es una forma de seleccionar filas de uno o ambos. La detección de anclajes se ejecuta en tres etapas: detección de palabras clave en line_df y toc_df (siempre, gratis) y similitud de incrustación opcional en paralelo, agregación a una unidad estructural (sección, página, fragmento) y luego una llamada de árbitro de LLM que clasifica a los candidatos con sus razones. Las palabras clave permanecen siempre activas porque son auditables y dan un "no encontrado" defendible; las incrustaciones son opcionales y útiles para las discrepancias de vocabulario; BM25 tiene un rendimiento inferior al filtrado de palabras clave codificadas en cuestiones empresariales. Ancla pequeña (línea, título), expande para contexto (párrafo, sección); colapsar las dos granularidades es el error más común en las tuberías.

El artículo 8 (generación) toma el relevo a partir de aquí, el cuarto ladrillo. Con los candidatos correctos recuperados con la granularidad adecuada, el LLM todavía tiene trabajo real: extraer la respuesta, formatearla, citarla, negarse a inventar cuando la respuesta no está ahí. Ese paso debe ejecutarse como una ejecución controlada en lugar de una predicción de forma libre.

Este artículo forma parte de la serie Enterprise Document Intelligence. La canalización RAG mínima muestra la generación de alimentación de resultados de recuperación de principio a fin en un PDF real.

6. Fuentes y lecturas adicionales

La base BM25 en la que se basa el artículo es Robertson y Zaragoza (The Probabilistic Relevance Framework: BM25 and Beyond, FnT IR 2009). La mecánica de fusión de rangos cuando se combinan palabras clave y puntuadores integrados es la fusión de rangos recíproca (Cormack et al., SIGIR 2009). La afirmación empírica de que BM25 sigue siendo una base sólida fuera de distribución está fundamentada en Thakur et al. (BEIR, NeurIPS 2021). El artículo va en la misma dirección que el consenso de búsqueda híbrida que ahora respaldan Anthropic's Contextual Retrieval (septiembre de 2024), Elastic y Vespa. El equivalente publicado más cercano al patrón TOC como recuperador es RAPTOR (Sarthi et al., ICLR 2024); el artículo explota el TOC existente del documento en lugar de agrupar el suyo propio.

Misma dirección que el artículo:

Robertson & Zaragoza, El marco de relevancia probabilística: BM25 y más allá, FnT IR 2009. Referencia canónica de BM25; La afirmación del artículo de que BM25 mide la frecuencia en la que la recuperación de negocios necesita coexistencia se basa en esto. Cormack, Clarke, Buettcher, Reciprocal Rank Fusion supera a Condorcet, SIGIR 2009. La mecánica de fusión que utiliza el artículo al combinar palabras clave e incorporar puntuaciones. Thakur et al., BEIR: Un punto de referencia heterogéneo para la evaluación de disparo cero de modelos de recuperación de información, NeurIPS 2021 (arXiv:2104.08663). Hay apoyo empírico para la afirmación del artículo de que BM25 es una base sólida que los perros perdigueros densos no siempre superan. Sarthi et al., RAPTOR: Procesamiento abstracto recursivo para la recuperación organizada en árboles, ICLR 2024 (arXiv:2401.18059). Recuperación jerárquica; el equivalente publicado más cercano al patrón TOC como recuperador. RAPTOR agrupa el documento en un árbol; Este artículo utiliza el TOC que el documento ya declara. Recuperación antrópica y contextual (puesto de ingeniería de septiembre de 2024). Consenso de búsqueda híbrida de que el artículo va en la misma dirección que.

Ángulo diferente, contexto diferente:

Karpukhin et al., Recuperación de pasajes densos para respuesta a preguntas de dominio abierto (DPR), EMNLP 2020 (arXiv:2004.04906). Recuperación densa como valor predeterminado de producción. El contexto es control de calidad abierto en el dominio; este artículo coloca BM25/palabras clave en primer lugar y las incrustaciones en último lugar en los corpus empresariales. Khattab & Zaharia, ColBERT: Búsqueda de pasajes eficiente y eficaz mediante interacción tardía contextualizada sobre BERT, SIGIR 2020 (arXiv:2004.12832). Interacción tardía a nivel de token como primitiva de recuperación. Contexto diferente al de la separación ancla versus contexto de este artículo. Izacard et al., Recuperación de información densa no supervisada con aprendizaje contrastivo (Contriever), 2022 (arXiv:2112.09118). Recuperación densa no supervisada que alcanza un rendimiento fuera de distribución de grado BM25. Útil contraste con la primera postura del artículo del BM25 sobre los corpus empresariales. Izacard et al., Atlas: Aprendizaje de pocas posibilidades con modelos de lenguaje aumentados de recuperación, 2022 (arXiv:2208.03299). Recuperación densa a gran escala + estudio RAG; contexto útil para la familia de enfoques de integración primero.

Al principio de la serie:

Document Intelligence: introducción a la serie. Qué construye la serie, ladrillo a ladrillo y en qué orden. Baseline Enterprise RAG, desde PDF hasta respuesta resaltada. El canal de cuatro ladrillos de un extremo a otro: PDF de entrada, respuesta resaltada de salida. Las incrustaciones no son mágicas: los modos de falla predecibles de la recuperación de RAG. Dónde gana la incorporación de similitud (sinónimos, errores tipográficos, paráfrasis), dónde predeciblemente se rompe (términos desconocidos, negación, relevancia de término versus respuesta) y cómo usarlo de todos modos. Los rerankers tampoco son mágicos: cuando la capa de codificador cruzado vale la pena. Lo que agrega un codificador cruzado sobre las incorporaciones de codificador doble, medido y cuándo vale la pena la latencia. RAG no es aprendizaje automático y el conjunto de herramientas de ML resuelve el problema equivocado. Por qué los barridos y ajustes de tamaño de fragmentos optimizan lo incorrecto; en su lugar, ruta por tipo de pregunta. De expresiones regulares a modelos de visión: qué técnica RAG se adapta a qué problema. Dos ejes, complejidad documental y control de preguntas, que seleccionan la técnica para cada caso. 10 errores comunes de RAG que seguimos viendo en producción. Diez errores de producción, organizados ladrillo por ladrillo, con la solución para cada uno. Más allá de extract_text: las dos capas de un PDF que impulsan la calidad RAG. La primera mitad del bloque de análisis: la naturaleza del documento, las señales y el resumen. Deje de devolver texto plano desde un PDF: la forma relacional que RAG necesita. La segunda mitad del bloque de análisis: las tablas relacionales que lee cada bloque posterior.