// RAG · MÓDULO 05/12

RAG financiero de grado institucional

Chunking consciente de estructura, hybrid search con reranking, la división texto/números y evaluación RAGAS.

Lectura ~31 minNivel intermedio-avanzadoEdición jul 2026Read in English

El módulo 4 dejó las fuentes documentales institucionales (EDGAR, transcripts, textos regulatorios) ingestadas y normalizadas. Este módulo las convierte en un sistema de comprensión: un pipeline de Retrieval-Augmented Generation (RAG — generación aumentada por recuperación: se recuperan fragmentos documentales y se inyectan como contexto al LLM para fundamentar su respuesta) al estándar de evidencia de una mesa de research o de riesgo. La tesis central, sostenida con datos en cada sección: en finanzas la estructura es el pipeline — el chunking, el retrieval y la evaluación genéricos destruyen exactamente la señal que el analista necesita, y la diferencia entre un 19% y un 83,7% de precisión es ingeniería, no modelo. (API EvangelistAPI Evangelist)


5.1 Por qué el RAG de tutorial falla en finanzas

El RAG de tutorial — cargar PDF, RecursiveCharacterTextSplitter con 1.000 tokens, embeddings genéricos, top-k por similitud coseno — parte de tres supuestos que el documento financiero viola sistemáticamente. Primero, que el texto es prosa plana: un 10-K es un artefacto estructurado (Items, notas, tablas, tags XBRL), y los chunks de tamaño fijo "aplanan disclosures estructurados en bloques arbitrarios de texto", partiendo factores de riesgo y separando cada cifra de la sección que le da significado. (🦜️🔗 LangChain) Segundo, que la similitud semántica basta: preguntar por el inventario a cierre de ejercicio puede recuperar prosa del periodo equivocado "porque el embedding parecía suficientemente cercano" — el RAG plano "confunde periodos fiscales, enruta mal hechos del balance o cita narrativa que no soporta la cifra de la respuesta". (csdn.net) Tercero, que recuperar el texto correcto implica responder correctamente: la respuesta financiera exige razonamiento numérico multi-paso sobre tabla y texto, donde el LLM solo es estructuralmente débil. (getvocal.ai)

5.1.1 Evidencia de benchmarks: qué miden y por qué importan al buy-side

La referencia empírica es FinanceBench (Patronus AI, arXiv 2311.11944, nov-2023): 10.231 preguntas escritas por expertos sobre SEC filings de 40 empresas estadounidenses, con un subconjunto abierto de 150 preguntas y scoring binario con tolerancia de ±2,5% en respuestas numéricas. (API EvangelistAPI Evangelist) Sus resultados originales son la radiografía del RAG naive: GPT-4-Turbo a libro cerrado acierta el 9%; con un vector store compartido para todos los documentos, el 19% — es decir, "respondió incorrectamente o se negó a responder el 81% de las preguntas"; con un vector store por documento, el 50%; con el filing completo en contexto (long-context), el 76%; y con las páginas de evidencia dadas (oracle), el 85%. (API EvangelistAPI Evangelist) En 2026, una evaluación independiente sobre el mismo benchmark (media de 10 corridas con LLM-juez) sitúa el RAG agéntico exhaustivo — retrieval híbrido, rerank, navegación por estructura y herramientas — en el 83,7%, por encima del full-context con modelos de 1M de tokens (76,0%). (langchain.js) La trayectoria 19% → 83,7% es la demostración cuantificada de que la ingeniería del pipeline mueve la aguja en comprensión documental, el dominio donde el ROI institucional de los LLM está probado.

Precisión en FinanceBench por arquitectura de pipeline RAGRAG naive (store compartido): 19,0 %; RAG naive (store por documento): 50,0 %; Long-context (filing completo): 76,0 %; RAG agéntico (híbrido + rerank + tools): 83,7 %0 %20 %40 %60 %80 %100 %Precisión de respuesta (accuracy)19,0 %RAG naive (storecompartido)50,0 %RAG naive (store pordocumento)76,0 %Long-context (filingcompleto)83,7 %RAG agéntico (híbrido +rerank + tools)

Fuente: resultados originales de FinanceBench (arXiv 2311.11944, 2023) (API EvangelistAPI Evangelist) y evaluación independiente de RAG agéntico (2026, media de 10 corridas) (langchain.js); elaboración propia, datos a julio de 2026.

El resto de benchmarks acotan el problema desde el razonamiento numérico. FinQA (arXiv 2109.00122): 8.281 pares QA anotados por once profesionales sobre earnings reports del S&P 500, con evidencia heterogénea (tabla + texto) y un programa de razonamiento numérico; GPT-4 alcanza 62,87 frente a 91,16 del experto humano. (Model Agent Platform) ConvFinQA (arXiv 2210.03849) lo extiende a 3.892 conversaciones multi-turno con anáfora sobre cifras: GPT-4 logra 76,48 frente a 89,44 humano, y los errores van en cascada — si un turno falla, "hay muy pocas posibilidades de que los turnos subsiguientes sean correctos". (🦜️🔗 LangChain) TAT-QA (ACL 2021): 16.552 preguntas híbridas tabular-textuales (42% aritméticas); el mejor modelo del paper, TAGOP, alcanza 58,0 F1 frente a 90,8 F1 humano. (博客园)

Tabla 5.1 — Benchmarks financieros de QA y razonamiento numérico: tamaño, mejor resultado documentado y brecha frente al experto humano

BenchmarkTamañoQué mideMejor resultado documentadoHumano expertoBrecha
FinanceBench (arXiv 2311.11944, 2023)10.231 preg. (150 abiertas)QA open-book sobre SEC filings; exactitud de cifra con cita83,7% (RAG agéntico, 2026); 19% (RAG naive, 2023)~100% (respuestas gold de expertos)El RAG naive falla el 81%; el agéntico aún deja ~16 pp
FinQA (arXiv 2109.00122, 2021)8.281 QARazonamiento numérico multi-paso sobre tabla + textoGPT-4: 62,87% accuracy (arXiv 2305.05862, 2023)91,16%~28 pp
ConvFinQA (arXiv 2210.03849, 2022)14.115 preg. / 3.892 conv.Numérico multi-turno con anáfora sobre cifrasGPT-4: 76,48% (arXiv 2305.05862, 2023)89,44%~13 pp; cascada de errores entre turnos
TAT-QA (ACL 2021.acl-long.254, 2021)16.552 QAQA híbrido tabular + textual; 42% aritméticoTAGOP: 58,0 F190,8 F1~33 pp

5.2 Chunking consciente de estructura

El chunking es la palanca número uno del RAG financiero porque decide qué unidades de significado sobreviven a la indexación. La regla operativa: la estructura del documento marca las fronteras; el tamaño solo subdivide dentro de ellas. LangChain ofrece el patrón de doble división — primero estructural (por ejemplo, MarkdownHeaderTextSplitter, que conserva el linaje de encabezados como metadata) y luego por tamaño con RecursiveCharacterTextSplitter, heredando la metadata estructural en cada sub-chunk. (stackademic.com)

5.2.1 10-K por Items, earnings calls por turnos, textos regulatorios con jerarquía

10-K por Items regulatorios. La unidad semántica de un 10-K es el Item (1 Business, 1A Risk Factors, 7 MD&A, 8 Financial Statements): un analista lee "seleccionando filing y periodo, inspeccionando la tabla de contenidos y navegando a Item 1A, 7 u 8", y el retrieval debe imitar ese recorrido. (🦜️🔗 LangChain) Cada chunk lleva un section_path de linaje ("10-K > Item 1A > Regulatory Risk > EU Operations") que permite citar con precisión de sección y filtrar antes de buscar. (🦜️🔗 LangChain)

python
import re
from langchain_core.documents import Document

ITEM_RE = re.compile(r"^\s*Item\s+(\d+[A-Z]?)\.?\s+(.+)$", re.IGNORECASE | re.MULTILINE)

def chunk_10k_by_items(text: str, ticker: str, fiscal_year: int) -> list[Document]:
    """Divide un 10-K en chunks por Item regulatorio, con metadata de linaje."""
    matches = list(ITEM_RE.finditer(text))
    docs = []
    for i, m in enumerate(matches):
        start = m.end()
        end = matches[i + 1].start() if i + 1 < len(matches) else len(text)
        docs.append(Document(
            page_content=text[start:end].strip(),
            metadata={
                "ticker": ticker,
                "fiscal_year": fiscal_year,
                "filing_type": "10-K",
                "item": f"Item {m.group(1).upper()}",
                "item_title": m.group(2).strip(),
                "section_path": f"10-K > Item {m.group(1).upper()} > {m.group(2).strip()}",
            },
        ))
    return docs
# Después: re-dividir Items largos con RecursiveCharacterTextSplitter,
# PROPAGANDO la metadata a cada sub-chunk.

Earnings calls por turnos de hablante. Los transcripts ya traen fronteras de turno y nombre del hablante (formato XML), y la literatura de NLP financiero valida el turno como unidad natural de procesamiento, eliminando turnos del operador y turnos de menos de 10 tokens (saludos sin señal). (LangChain Forum) Un practitioner del sector (FinTech Studios, feb-2026) reporta una mejora del 34% en precisión de retrieval para consultas sobre resultados con chunking por turnos frente a ventanas fijas de 512 tokens — cifra de vendor sin metodología publicada, pero cuya dirección (estructura > ventana fija) está corroborada por fuentes independientes. (🦜️🔗 LangChain)

python
from langchain_core.documents import Document

def chunk_earnings_call(turns: list[dict], ticker: str, quarter: str) -> list[Document]:
    """turns = [{'speaker': 'Jamie Dimon', 'role': 'CEO', 'section': 'prepared_remarks',
                'text': '...'}, ...]"""
    docs = []
    for t in turns:
        if t["role"] == "Operator" or len(t["text"].split()) < 10:
            continue  # ruido sin señal (cf. arXiv 1906.02868)
        docs.append(Document(
            page_content=f"[{t['speaker']} — {t['role']}] {t['text']}",
            metadata={
                "ticker": ticker, "quarter": quarter,
                "speaker": t["speaker"], "role": t["role"],
                "section": t["section"],  # prepared_remarks | qa
            },
        ))
    return docs

Textos regulatorios. Circulares, normas técnicas y rulebooks tienen jerarquía de artículos y cross-referencias ("según el artículo 12.3.b"). El chunking debe respetar la jerarquía capítulo-sección-artículo y resolver las cross-referencias materializando el texto referenciado o su identificador en metadata; de lo contrario, un chunk que dice "se aplicará lo dispuesto en el apartado anterior" llega al LLM sin el contenido que lo completa. La tabla siguiente sintetiza la técnica por tipo de documento.

Tabla 5.2 — Chunking consciente de estructura por tipo de documento financiero: unidad, metadata, técnica y riesgo del chunking genérico

Tipo de documentoUnidad de chunkingMetadata obligatoriaTécnica / splitterRiesgo del chunking genérico
10-K / 10-Q (EDGAR)Item regulatorio (1, 1A, 7, 8) y subsección tituladaticker, fiscal_year, item, section_pathParseo HTML/XBRL + regex de Items; re-split recursivo propagando metadataFactor de riesgo partido; cifra sin sección que la contextualiza (🦜️🔗 LangChain)
Earnings callTurno de hablante (≥10 tokens, sin operador)speaker, role, section (prepared_remarks/qa), quarterSegmentación por turnos del transcript XMLQ&A mezclado con discurso: se pierde el +34% de precisión de retrieval que aporta el chunking por turnos (🦜️🔗 LangChain)
Texto regulatorioArtículo / apartado jerárquicoregulation, article_path, cross_refs resueltasSplit por encabezados (MarkdownHeaderTextSplitter) + resolución de referenciasReferencias cruzadas huérfanas; norma incompleta en el contexto (stackademic.com)
Tablas financierasFila/hecho XBRL como unidad de primera claseconcept, period, currency, scaleDocling/unstructured: filas y hechos XBRL como nodos, nunca "sopa de palabras" (csdn.net)Números separados de cabecera y unidad; errores de escala (🦜️🔗 LangChain)
Research / notas de sell-sidePárrafo temático con encabezadoanalyst, date, rating, sectorDoble división: headers → recursivo 400-1.000 tokensTesis y condiciones mezcladas entre chunks

5.3 Retrieval avanzado

Con el corpus chunkeado por estructura, la capa de retrieval decide qué candidatos llegan al LLM. El stack de producción 2026 tiene cuatro componentes ordenados: filtro de metadata → búsqueda híbrida → reranking → expansión de contexto padre, todo ello enchufable como BaseRetriever a la cadena canónica de LangChain v1, create_retrieval_chain(retriever, create_stuff_documents_chain(llm, prompt)). (Github)

5.3.1 Hybrid search, reranking, metadata filtering y contexto padre

Metadata filtering primero. El error más común del RAG financiero es recuperar la cifra correcta del periodo equivocado; la defensa de primera línea es estrechar el subconjunto antes de la similitud vectorial con los campos de linaje del chunking. (LangChain Forum)

python
retriever = vectorstore.as_retriever(search_kwargs={
    "k": 6,
    "filter": {
        "$and": [
            {"ticker": {"$eq": "JPM"}},
            {"fiscal_year": {"$gte": 2023}},
            {"item": {"$in": ["Item 7", "Item 8"]}},
        ]
    },
})

Hybrid search BM25 + denso. La búsqueda densa falla en identificadores exactos (tickers, ISIN, nombres de instrumento, códigos de taxonomía) y BM25 falla en semántica; la combinación con Reciprocal Rank Fusion (RRF — fusión que puntúa cada documento por la suma de los inversos de sus rangos en cada lista) es el estándar de producción: cifras de vendor de 2026 reportan 91% de recall@10 frente a 78% (solo denso) y 65% (solo BM25), con ~6 ms de overhead de fusión. (LangChain) En LangChain, EnsembleRetriever implementa RRF con pesos configurables; el punto de partida típico es 0,6 semántico / 0,4 keyword, subiendo el peso BM25 cuando las consultas contienen términos exactos como tickers o códigos. (🦜️🔗 LangChain)

python
from langchain_classic.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever

bm25 = BM25Retriever.from_documents(chunks); bm25.k = 10
dense = vectorstore.as_retriever(search_kwargs={"k": 10})
hybrid = EnsembleRetriever(retrievers=[dense, bm25], weights=[0.6, 0.4])
# En finanzas, tickers/ISIN/nombres de instrumentos favorecen subir peso BM25.

El embedding también es una decisión de dominio: en un benchmark de retrieval financiero (HR@5), los embeddings adaptados a finanzas alcanzan 88% frente a 86% de OpenAI text-embedding-3-large, 85% de Cohere embed-english-v3.0 y 84% de Google text-embedding-004, (CSDN博客) y sustituir ada-002 por embeddings financieros en FinanceBench suma +8 puntos de precisión. (Towards AI) Presupuestar un embedding de dominio (p. ej., voyage-finance-2) es de las mejoras más baratas del pipeline.

Reranking. El retrieval híbrido devuelve candidatos; el reranker los reordena leyendo query y documento completos. Cohere Rerank 3 acepta hasta 4.000 tokens de entrada, lo que permite reranquear con contexto de documento completo en lugar del chunk aislado, recuperando relevancia que el chunking destruyó. (🦜️🔗 LangChain) Se integra como compresor sobre cualquier retriever base:

python
from langchain_classic.retrievers import ContextualCompressionRetriever
from langchain_cohere import CohereRerank

reranker = CohereRerank(model="rerank-v3.5", top_n=5)
final_retriever = ContextualCompressionRetriever(
    base_retriever=hybrid,      # ensemble BM25+denso, o multi-query
    base_compressor=reranker,
)

Contexto padre. Queda el dilema embeddings-precisos vs contexto-amplio: ParentDocumentRetriever lo resuelve indexando chunks hijos pequeños (~400 tokens, embeddings precisos) pero devolviendo el documento padre (~2.000 tokens) al recuperar. (arXiv.org) Aplicación financiera: el hijo es el párrafo con la cifra; el padre, la subsección completa del Item (toda la nota de "Liquidity and Capital Resources"), de modo que el LLM recibe la cifra con su narrativa de gestión.

python
from langchain_classic.retrievers import ParentDocumentRetriever
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain.storage import InMemoryStore

retriever = ParentDocumentRetriever(
    vectorstore=vectorstore,
    docstore=InMemoryStore(),
    child_splitter=RecursiveCharacterTextSplitter(chunk_size=400, chunk_overlap=50),
    parent_splitter=RecursiveCharacterTextSplitter(chunk_size=2000, chunk_overlap=200),
)
retriever.add_documents(filing_docs)  # indexa hijos en vectorstore, padres en docstore

MMR y multi-query. Cuando los top-k son casi duplicados (típico en filings anuales repetitivos), Maximal Marginal Relevance (MMR) equilibra relevancia y diversidad sobre un pool de fetch_k candidatos, con lambda_mult entre 1,0 (similitud pura) y 0,0 (diversidad pura): (🦜️🔗 LangChain)

MMR(d)=arg⁡max⁡di∈R∖S[λ⋅sim(di,q)−(1−λ)⋅max⁡dj∈Ssim(di,dj)]\mathrm{MMR}(d) = \arg\max_{d_i \in R \setminus S} \Big[ \lambda \cdot \mathrm{sim}(d_i, q) - (1 - \lambda) \cdot \max_{d_j \in S} \mathrm{sim}(d_i, d_j) \Big]

donde RR es el conjunto de candidatos, SS el de documentos ya seleccionados, qq la consulta y λ\lambda el peso de relevancia frente a diversidad. El multi-query retrieval genera varias paráfrasis de la consulta con un LLM, recupera para cada una y fusiona, mejorando recall y robustez ante la formulación; es patrón estándar de producción junto a MMR, al coste de llamadas LLM adicionales. (🦜️🔗 LangChain) La compresión contextual con LLM mejora precisión a costa de ~duplicar la latencia: reservarla para consultas de alto valor. (🦜️🔗 LangChain)

El pipeline completo resultante se resume en el diagrama siguiente.

Pipeline RAG en cinco etapas: ingesta, chunking estructurado, índices vectorial y BM25, retrieval con filtro y rerank, y generación con cita y verificación verbatim.EDGAR 10-K/10-Q/8-K → 10-K por Items + section_path; Earnings calls → Calls por turnos de hablante; Textos regulatorios → Jerarquía de artículos + cross-refs resueltas; EDGAR 10-K/10-Q/8-K → Tablas/XBRL: filas y hechos como nodos (Docling); 10-K por Items + section_path → Vector store (embedding financiero) + metadata: ticker, fecha, item; Calls por turnos de hablante → Vector store (embedding financiero) + metadata: ticker, fecha, item; Jerarquía de artículos + cross-refs resueltas → Vector store (embedding financiero) + metadata: ticker, fecha, item; 10-K por Items + section_path → Índice BM25 (términos exactos); Calls por turnos de hablante → Índice BM25 (términos exactos); Jerarquía de artículos + cross-refs resueltas → Índice BM25 (términos exactos); Vector store (embedding financiero) + metadata: ticker, fecha, item → Filtro de metadata (ticker/fecha/tipo); Índice BM25 (términos exactos) → Filtro de metadata (ticker/fecha/tipo); Filtro de metadata (ticker/fecha/tipo) → EnsembleRetriever RRF 0.6/0.4; EnsembleRetriever RRF 0.6/0.4 → CohereRerank top_n=5; CohereRerank top_n=5 → ParentDocumentRetriever hijo → padre; ParentDocumentRetriever hijo → padre → create_retrieval_chain + prompt de citación; create_retrieval_chain + prompt de citación → Verificación verbatim de cifras (§5.4); Verificación verbatim de cifras (§5.4) → Respuesta con cita doc + sección + periodoIngesta (Módulo 4)Chunking estructuradoÍndices híbridosRetrievalGeneración con groundingEDGAR 10-K/10-Q/8-KEarnings callsTextos regulatorios10-K por Items+ section_pathCalls por turnosde hablanteJerarquía de artículos+ cross-refs resueltasTablas/XBRL: filas y hechoscomo nodos (Docling)Vector store(embedding financiero)+ metadata: ticker, fecha, itemÍndice BM25(términos exactos)Filtro de metadata(ticker/fecha/tipo)EnsembleRetrieverRRF 0.6/0.4CohereReranktop_n=5ParentDocumentRetrieverhijo → padrecreate_retrieval_chain+ prompt de citaciónVerificación verbatimde cifras (§5.4)Respuesta con citadoc + sección + periodo
Pipeline RAG para documentos financieros. El chunking respeta la estructura de cada documento, la búsqueda híbrida combina vectores y BM25 con rerank, y la respuesta sale citada y verificada verbatim.

5.4 La división texto/números

5.4.1 Principio: el texto va al vector store, las cifras a herramientas estructuradas

El embedding de un número no preserva su valor: codifica contexto distributivo, no magnitud. De ahí la regla de primer orden del RAG financiero —la regla de oro de los datos del Módulo 4 (§4.4.1) aplicada al retrieval—: la prosa va al vector store con linaje de sección; las cifras van a bases estructuradas (hechos XBRL de la SEC, tablas SQL) accesibles por tool-calling; y toda cifra citada debe existir verbatim en la fuente. Tres evidencias la sostienen. Las tablas "son donde viven las cifras exactas", y el chunking estándar "convierte las tablas en sopa de palabras y separa los números de las secciones que les dan significado": la solución documentada (Red Hat, jul-2026) mantiene filas y hechos XBRL como nodos de primera clase con metadata de concepto, periodo y moneda. (csdn.net) XBRL-Agent cuantifica el beneficio: RAG + calculadoras simbólicas produjo +17 puntos en consultas de taxonomía y +42 en razonamiento numérico frente a LLM base, que "alucinan o malinterpretan contenido financiero" sobre XBRL. (getvocal.ai) Y los errores de escala (millones leídos como miles) son el 15% de los fallos de extracción: un campo scale explícito y la lectura desde la fuente estructurada los eliminan por construcción. (🦜️🔗 LangChain)

La arquitectura resultante divide la consulta en dos planos:

La cita del CFO sale del vector store y el margen de un cálculo determinista; si una cifra no aparece verbatim, va a revisión humana.Consulta del analista: &apos;¿Cuál fue el margen operativo de JPM en 2024 y qué dijo el CFO sobre él?&apos; → Router de la consulta; Router de la consulta → Vector store (hybrid + rerank) → turno del CFO con cita speaker/quarter/sección (componente textual: &apos;qué dijo el CFO&apos;); Router de la consulta → Herramientas estructuradas (componente numérica: &apos;margen operativo 2024&apos;); Herramientas estructuradas → XBRL company facts (concepto, periodo, moneda); Herramientas estructuradas → SQL sobre tabla financiera (filas como hechos); Herramientas estructuradas → Calculator / ratio tool EL LLM NUNCA CALCULA; XBRL company facts (concepto, periodo, moneda) → Cálculo determinista margen = op. income / revenue; SQL sobre tabla financiera (filas como hechos) → Cálculo determinista margen = op. income / revenue; Calculator / ratio tool EL LLM NUNCA CALCULA → Cálculo determinista margen = op. income / revenue; Vector store (hybrid + rerank) → turno del CFO con cita speaker/quarter/sección → Fusión en el contexto con metadatos de proveniencia; Cálculo determinista margen = op. income / revenue → Fusión en el contexto con metadatos de proveniencia; Fusión en el contexto con metadatos de proveniencia → LLM genera respuesta con cita doc+sección+periodo; LLM genera respuesta con cita doc+sección+periodo → Verificador: ¿cada cifra existe VERBATIM en la fuente?; Verificador: ¿cada cifra existe VERBATIM en la fuente? → Respuesta auditable (sí); Verificador: ¿cada cifra existe VERBATIM en la fuente? → NO_DISPONIBLE / cola de revisión humana (no)componente textual: &apos;qué dijo el CFO&apos;componente numérica: &apos;margen operativo 2024&apos;sínoConsulta del analista:&apos;¿Cuál fue el margen operativo de JPM en 2024y qué dijo el CFO sobre él?&apos;Router de la consultaVector store(hybrid + rerank)→ turno del CFO con citaspeaker/quarter/secciónHerramientas estructuradasXBRL company facts(concepto, periodo, moneda)SQL sobre tabla financiera(filas como hechos)Calculator / ratio toolEL LLM NUNCA CALCULACálculo deterministamargen = op. income / revenueFusión en el contextocon metadatos de provenienciaLLM genera respuestacon cita doc+sección+periodoVerificador: ¿cada cifraexiste VERBATIM en la fuente?Respuesta auditableNO_DISPONIBLE /cola de revisión humana
Consulta dividida en texto y cifras. El router envía lo textual al vector store y lo numérico a herramientas estructuradas; el LLM nunca calcula y cada cifra se verifica verbatim antes de publicarse.

La verificación verbatim es código, no prompting: un segundo pase comprueba que cada cifra de la salida aparece literalmente en el extracto citado (el patrón de verificación en dos pasos; el modo de fallo dominante del razonamiento LLM es la aritmética, no la lógica, como demostró PAL: 16 de 25 cadenas de pensamiento eran idénticas salvo en los números; el desarrollo completo de esta evidencia está en §6.2). (Financial Wisdom TV)

python
import re

def cifras_en_respuesta(texto: str) -> list[str]:
    """Extrae literales numéricos de una respuesta (soporta escala y separadores)."""
    return re.findall(r"\$?\d[\d,.]*\s*(?:million|billion|thousand|%)?", texto)

def verificacion_verbatim(respuesta: str, contextos: list[str]) -> dict:
    """Toda cifra citada debe existir VERBATIM en algún contexto recuperado."""
    corpus = " ".join(contextos).replace(",", "")
    no_soportadas = [
        c for c in cifras_en_respuesta(respuesta)
        if c.replace(",", "").strip() not in corpus
    ]
    return {
        "ok": not no_soportadas,
        "cifras_no_soportadas": no_soportadas,
        # En producción: si ok=False -> abstenerse ("NO_DISPONIBLE")
        # o enrutar a cola de revisión humana, NUNCA publicar la cifra.
    }

5.5 Evaluación de RAG

Un pipeline RAG no evaluado es un pipeline que ya falló en silencio. La evaluación institucional se hace por separado para retrieval y generación, y luego end-to-end sobre un golden dataset: (towardsai.net) el retrieval se mide con métricas de ranking (context precision/recall), la generación con faithfulness y answer relevancy. (ActiveWizards: data science and engineering lab)

5.5.1 RAGAS, golden dataset propio y umbrales de aceptación

Las cuatro métricas de RAGAS (definiciones operativas de arXiv 2409.09046): (ActiveWizards: data science and engineering lab)

  • Faithfulness (fidelidad): fracción de afirmaciones atómicas de la respuesta inferibles del contexto recuperado — el detector de alucinación. Un LLM-juez descompone la respuesta en claims y verifica cada uno contra el contexto.
  • Answer Relevancy: similitud coseno media entre la pregunta original y preguntas regeneradas a partir de la respuesta; detecta respuestas que no contestan lo preguntado.
  • Context Recall: fracción de afirmaciones del ground truth atribuibles al contexto recuperado — mide si el retrieval trajo la evidencia necesaria.
  • Context Precision: precision@k ponderada por rango — premia que los chunks relevantes aparezcan arriba en la lista.

La métrica de retrieval subyacente a context recall es recall@k: dado el conjunto RqR_q de pasajes relevantes para la consulta qq y los kk pasajes recuperados DkD_k,

recall@k=∣Dk∩Rq∣∣Rq∣\mathrm{recall@k} = \frac{|D_k \cap R_q|}{|R_q|}

Es decir, la fracción de la evidencia necesaria que aparece entre los kk primeros resultados; es la métrica que justifica el hybrid search (91% vs 78% de recall@10 reportado) (LangChain) y la que un golden dataset propio mide contra los pasajes de evidencia anotados.

El valor diagnóstico clave de RAGAS es la separación de fallos: faithfulness baja con context recall alta indica un problema de generación (el modelo tenía los documentos pero los distorsionó); context recall baja indica un problema de retrieval (los documentos nunca se recuperaron). Faithfulness y Answer Relevancy son reference-free (no requieren respuesta dorada), lo que las hace escalables a tráfico real. (shafiqulai.github.io)

python
from datasets import Dataset
from ragas import evaluate
from ragas.metrics import (
    Faithfulness, AnswerRelevancy,
    LLMContextPrecisionWithReference, LLMContextRecall,
)
from ragas.llms import LangchainLLMWrapper
from ragas.embeddings import LangchainEmbeddingsWrapper
from langchain_openai import ChatOpenAI, OpenAIEmbeddings

judge_llm = LangchainLLMWrapper(ChatOpenAI(model="gpt-4o-mini", temperature=0))
judge_emb = LangchainEmbeddingsWrapper(OpenAIEmbeddings(model="text-embedding-3-small"))

rows = {"question": [], "answer": [], "contexts": [], "reference": []}
for item in EVAL_QUESTIONS:  # p.ej. preguntas tipo FinanceBench
    result = rag.invoke({"input": item["q"]})
    rows["question"].append(item["q"])
    rows["answer"].append(result["answer"])
    rows["contexts"].append([d.page_content for d in result["context"]])
    rows["reference"].append(item["ref"])

result = evaluate(
    Dataset.from_dict(rows),
    metrics=[Faithfulness(), AnswerRelevancy(),
             LLMContextPrecisionWithReference(), LLMContextRecall()],
    llm=judge_llm, embeddings=judge_emb,
)
print(result.to_pandas().mean(numeric_only=True))

Golden dataset propio estilo FinanceBench. La plantilla: preguntas escritas por expertos sobre los filings propios de la firma, cada una con (a) respuesta dorada con la cifra exacta y tolerancia (±2,5% en numéricas, como FinanceBench), (b) pasajes de evidencia anotados (doc, sección, página) que definen RqR_q para recall@k, y (c) tipo de pregunta (extractiva, comparativa entre periodos, multi-hop, con cálculo) para desglosar fallos. (API EvangelistAPI Evangelist) Unas 100-200 preguntas sobre 20-40 filings de la cartera cubren el 80% de los modos de consulta de un desk. LangSmith complementa con un grader de correctness de salida estructurada ("califica SOLO por exactitud factual respecto al ground truth; es aceptable información adicional si no contradice") y drill-down por traza. (matt-harrison.com)

Umbrales de aceptación para producción. La guía RAGAS 2026 con gating en CI propone como punto de partida: faithfulness ≥ 0,85, answer relevancy ≥ 0,80, context precision ≥ 0,75 y context recall ≥ 0,80; (shafiqulai.github.io) pipelines financieros reales sobre FinanceBench reportan faithfulness en torno a 0,86-0,87 como referencia alcanzable. (CallSphere) A estos umbrales se añaden dos gates específicos de finanzas: (1) tasa de verificación verbatim = 100% de las cifras de la muestra de evaluación (§5.4) — es determinista, no negociable; (2) tasa de abstención adecuada: en preguntas sin evidencia en el corpus, la respuesta correcta es "NO_DISPONIBLE", y una tasa de invención >0% en ese subconjunto bloquea el despliegue. El control de costes del juez se resuelve con un modelo barato (gpt-4o-mini), temperatura 0 y muestreo en PR. (shafiqulai.github.io)


Caso aplicado: mesa de research sobre filings de banca

Un equipo de research de banca despliega el pipeline del módulo sobre 10-K/10-Q y earnings calls de sus 40 bancos de cobertura: chunking por Items con section_path y turnos con rol; embedding financiero; Qdrant con filtro de metadata; EnsembleRetriever (0,6/0,4) → CohereRerank (top 5) → ParentDocumentRetriever; hechos XBRL y ratios en herramientas SQL/calculadora; verificación verbatim obligatoria. Golden dataset: 150 preguntas tipo FinanceBench escritas por los propios analistas.

La primera medición RAGAS muestra context recall 0,62 — problema de retrieval: el 40% de los fallos se concentra en consultas con tickers y códigos de instrumento exactos. Subir el peso BM25 a 0,5 y añadir el filtro de fiscal_year eleva recall a 0,84. La segunda medición muestra faithfulness 0,79 — ahora problema de generación: el LLM derivaba variaciones interanuales en texto. Se mueve todo cálculo a la herramienta de ratios y se activa la verificación verbatim: faithfulness sube a 0,88 y las cifras no soportadas caen a cero. El gate de producción (faithfulness ≥ 0,85, recall ≥ 0,80, verbatim 100%, invención 0% en preguntas sin evidencia) se supera y el sistema entra en piloto con revisión humana de toda cifra que alimenta notas a clientes. La lección transferible es el bucle diagnóstico: cada métrica señaló una capa distinta del pipeline, y ninguna mejora fue de prompting.


Ejercicios

  1. Chunking de un 10-K por Items. Descargue el 10-K más reciente de JPMorgan de EDGAR (Módulo 4) e implemente chunk_10k_by_items (§5.2.1) con section_path completo. Verifique que Item 1A e Item 7 quedan separados, re-divida Items >2.000 tokens propagando metadata y escriba un test: ningún chunk sin item y fiscal_year.
  2. Hybrid search medido. Sobre el corpus del ejercicio 1, construya un retriever denso solo y un EnsembleRetriever 0,6/0,4. Con 20 preguntas que mezclen consultas semánticas ("riesgos de liquidez") y exactas ("Common Equity Tier 1 ratio", tickers), mida recall@10 contra evidencia anotada y compare con la referencia 78% vs 91%.
  3. Reranking y contexto padre. Añada CohereRerank (o cross-encoder local ms-marco-MiniLM-L-6-v2) y ParentDocumentRetriever (hijo 400 / padre 2.000). Mida context precision de RAGAS antes y después sobre las mismas 20 preguntas y cuantifique la latencia añadida por consulta.
  4. División texto/números. Implemente xbrl_lookup (SEC company facts) y la calculadora de ratios; reescriba cinco preguntas con cálculo ("variación interanual del margen neto") para resolverlas por tool-calling. Aplique verificacion_verbatim y reporte la tasa de cifras no soportadas antes y después.
  5. Evaluación RAGAS con gate de CI. Ejecute evaluate() (§5.5.1) con un golden dataset propio de ≥30 preguntas estilo FinanceBench (respuesta dorada + pasajes de evidencia). Diagnostique retrieval-vs-generación con la matriz faithfulness × context recall y fije los umbrales 0,85/0,80/0,75/0,80 como job de CI que bloquee el merge.
  6. Abstención controlada. Añada 10 preguntas sin respuesta en el corpus (otro periodo u otra empresa). Mida la tasa de invención y ajuste prompt y verificador hasta 0% de cifras inventadas con abstención "NO_DISPONIBLE". Discuta por qué este gate pesa más que un punto de accuracy para un risk manager.

Módulo 5 de 15 — LangChain para Trading Cuantitativo. Datos y versiones verificados a julio de 2026; precios y cifras de mercado sujetos a cambio. LangChain 1.3.14 / langchain-core 1.5.2.


// check

Comprueba lo aprendido

Autoevaluación con respuesta inmediata. No se guarda ninguna puntuación.

  1. En FinanceBench, GPT-4-Turbo pasa de 19% (vector store compartido) a 50% (store por documento) y el RAG agéntico alcanza 83,7%. ¿Cuál es la lectura correcta para un desk?

    Ver respuesta

    B. El benchmark mantiene fijos el examen y el modelo: lo que cambia entre filas es la arquitectura de retrieval y las herramientas. Es la demostración cuantificada del módulo de que la diferencia entre 19% y 83,7% es ingeniería. Y aun así quedan ~16 puntos de brecha, lo que justifica el veto humano de §5.5.

  2. Un equipo trocea 10-K con RecursiveCharacterTextSplitter a 1.000 tokens y los factores de riesgo llegan partidos al LLM. ¿Qué acción corrige el fallo estructural?

    Ver respuesta

    C. El fallo es de unidad, no de tamaño: la estructura del documento marca las fronteras y el tamaño solo subdivide dentro (§5.2). Además, la metadata de linaje habilita el filtrado previo a la similitud y la citación auditable ante compliance.

  3. Las consultas de un desk contienen tickers, ISIN y códigos de taxonomía exactos, y el retriever denso pierde esos documentos. ¿Qué cambio ataca directamente ese modo de fallo?

    Ver respuesta

    B. La búsqueda densa falla precisamente en identificadores exactos; BM25 los captura y la fusión RRF eleva el recall@10 del 78% (solo denso) al 91% reportado (§5.3.1). El filtro de metadata evita además recuperar la cifra correcta del periodo equivocado. MMR y multi-query ayudan a diversidad y robustez, pero no a términos exactos.

  4. Para responder «¿cuál fue la variación interanual del margen neto?», ¿qué diseño es el institucionalmente aceptable?

    Ver respuesta

    C. El modo de fallo dominante del razonamiento LLM es la aritmética, no la lógica (evidencia de PAL citada en §5.4). La división texto/números exige que el cálculo sea determinista — los +42 puntos de XBRL-Agent miden esa decisión — y que cada cifra citada exista verbatim en la fuente.

  5. Una medición RAGAS arroja context recall 0,86 y faithfulness 0,71. ¿Dónde está el problema y qué se arregla primero?

    Ver respuesta

    B. Es la matriz de diagnóstico del módulo: recall alto significa que la evidencia está en el contexto, así que faithfulness bajo señala distorsión en la generación. El caso aplicado lo resolvió delegando el cálculo y verificando cifras, no con prompting.

  6. ¿Por qué la tasa de invención en preguntas sin evidencia en el corpus es un gate binario (0% o no se despliega) y no una métrica más a optimizar?

    Ver respuesta

    B. La nota de riesgo de §5.1.1 distingue abstención (resultado válido) de cifra alucinada (incidente en un informe de riesgo). El pipeline debe premiar la abstención y verificar cada cifra contra la fuente, no maximizar cobertura: por eso invención >0% bloquea el despliegue aunque las demás métricas estén en verde.

  7. ¿Qué problema resuelve ParentDocumentRetriever (hijo ~400 tokens, padre ~2.000 tokens) en el pipeline financiero?

    Ver respuesta

    B. El hijo pequeño da embeddings precisos para el matching; el padre devuelve la nota o subsección completa (p. ej. toda «Liquidity and Capital Resources») para que el LLM reciba la cifra con su contexto (§5.3.1). No sustituye ni al reranker ni al chunking estructural: se apila sobre ellos.