RAG financiero de grado institucional
Chunking consciente de estructura, hybrid search con reranking, la división texto/números y evaluación RAGAS.
Objetivos de aprendizaje
- Cuantificar, con benchmarks públicos (FinanceBench, FinQA, ConvFinQA, TAT-QA), por qué un pipeline RAG de tutorial no alcanza el estándar del buy-side y qué palancas de ingeniería cierran la brecha.
- Implementar chunking consciente de estructura: 10-K por Items regulatorios con linaje de sección, earnings calls por turnos de hablante y textos regulatorios con jerarquía preservada.
- Construir retrieval avanzado con LangChain v1.x: búsqueda híbrida BM25+denso con
EnsembleRetriever, reranking conCohereRerank, filtrado de metadata por ticker/fecha/tipo yParentDocumentRetriever. - Aplicar el principio de división texto/números: prosa al vector store, cifras a herramientas estructuradas (XBRL, SQL), con verificación verbatim de toda cifra citada.
- Evaluar un pipeline RAG financiero con RAGAS (faithfulness, context precision/recall, answer relevancy), construir un golden dataset propio estilo FinanceBench y fijar umbrales de aceptación para producción.
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.

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
| Benchmark | Tamaño | Qué mide | Mejor resultado documentado | Humano experto | Brecha |
|---|---|---|---|---|---|
| FinanceBench (arXiv 2311.11944, 2023) | 10.231 preg. (150 abiertas) | QA open-book sobre SEC filings; exactitud de cifra con cita | 83,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 QA | Razonamiento numérico multi-paso sobre tabla + texto | GPT-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 cifras | GPT-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 QA | QA híbrido tabular + textual; 42% aritmético | TAGOP: 58,0 F1 | 90,8 F1 | ~33 pp |
Interpretación. La tabla ordena los benchmarks por la habilidad que certifican, y esa lectura es la que importa a un desk. FinanceBench certifica fidelidad documental — la cifra correcta, en el filing correcto, del periodo correcto: la tarea exacta del analista de research o del equipo de riesgo que valida un disclosure; por eso es la plantilla del golden dataset propio (§5.5). FinQA y TAT-QA certifican cómputo: la brecha de ~28-33 pp no la cierra mejor retrieval sino la delegación del cálculo a herramientas (§5.4) — de ahí los +42 puntos de XBRL-Agent con RAG + calculadora simbólica. (getvocal.ai) ConvFinQA certifica estado conversacional, crítico en copilotos de analista donde "¿y al año siguiente?" es la norma; su cascada de errores exige evaluar la conversación completa, no el turno aislado. (🦜️🔗 LangChain) Conclusión de gestión: ningún benchmark muestra el problema resuelto; la brecha humano-máquina persiste y justifica el veto humano y los umbrales de §5.5.
Nota de riesgo — alucinación de cifras. El modo de fallo peligroso no es la abstención sino la cifra inventada con confianza. En FinanceBench, Llama2 con vector store produjo respuestas incorrectas en el 70% de los casos con store compartido y el 54% con store por documento, frente a GPT-4-Turbo, que tendía a abstenerse (67%/38% de failed-to-answer) con solo 13%/11% de incorrectas; Patronus AI concluye que "todos los modelos examinados exhiben debilidades, como alucinaciones, que limitan su idoneidad para uso empresarial". (API EvangelistAPI Evangelist) Un estudio de 2024 citado por FailSafeQA estima alucinación en hasta el 41% de consultas financieras (博客园) , y en extracción de filings la cifra fabricada supone ~10% de los fallos, con otro 15% de errores de escala (leer "millones" como "miles"). (🦜️🔗 LangChain) Para un risk manager, "NO_DISPONIBLE" es un resultado operativo válido; una cifra alucinada en un informe de riesgo es un incidente. El pipeline debe premiar la abstención y verificar cada cifra contra la fuente (§5.4), no solo maximizar cobertura.
En palabras llanas: del 19% al 83,7% sin cambiar de modelo
FinanceBench hace algo incómodo para quien vende modelos: mantiene fijos el examen (150 preguntas sobre SEC filings) y el modelo, y solo cambia la ingeniería que rodea a ambos. El GPT-4-Turbo que acierta el 19% con un vector store compartido es exactamente el mismo que acierta el 50% con un store por documento. La diferencia no es inteligencia: es archivo.
Piénsalo como una sala de research. El RAG de tutorial es un becario que tritura los 10-K de las 40 empresas en tiras de 1.000 tokens y las echa todas al mismo saco; preguntado por una cifra, saca un puñado de tiras «parecidas» — a veces de la empresa equivocada, casi siempre del periodo equivocado. El pipeline institucional es el analista que archiva cada filing en su carpeta, con separadores por Item, una ficha que dice empresa, año y sección, una calculadora sobre la mesa y la obligación de cotejar cada cifra antes de escribirla. Toda la trayectoria 9% → 19% → 50% → 76% → 83,7% del módulo es esa disciplina de archivo convertida en código. Por eso el curso insiste: en comprensión documental, la palanca es el pipeline, no el modelo.
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)
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)
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 documento | Unidad de chunking | Metadata obligatoria | Técnica / splitter | Riesgo del chunking genérico |
|---|---|---|---|---|
| 10-K / 10-Q (EDGAR) | Item regulatorio (1, 1A, 7, 8) y subsección titulada | ticker, fiscal_year, item, section_path |
Parseo HTML/XBRL + regex de Items; re-split recursivo propagando metadata | Factor de riesgo partido; cifra sin sección que la contextualiza (🦜️🔗 LangChain) |
| Earnings call | Turno de hablante (≥10 tokens, sin operador) | speaker, role, section (prepared_remarks/qa), quarter |
Segmentación por turnos del transcript XML | Q&A mezclado con discurso: se pierde el +34% de precisión de retrieval que aporta el chunking por turnos (🦜️🔗 LangChain) |
| Texto regulatorio | Artículo / apartado jerárquico | regulation, article_path, cross_refs resueltas |
Split por encabezados (MarkdownHeaderTextSplitter) + resolución de referencias |
Referencias cruzadas huérfanas; norma incompleta en el contexto (stackademic.com) |
| Tablas financieras | Fila/hecho XBRL como unidad de primera clase | concept, period, currency, scale |
Docling/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-side | Párrafo temático con encabezado | analyst, date, rating, sector |
Doble división: headers → recursivo 400-1.000 tokens | Tesis y condiciones mezcladas entre chunks |
Interpretación. La tabla hace explícito que no existe "el mejor chunk_size" financiero: existe la mejor unidad por documento, y el tamaño es un parámetro secundario que solo opera dentro de ella. Tres decisiones transversales se derivan. Primera, la metadata no es un adorno: ticker, fiscal_year y section_path habilitan el filtrado previo a la similitud (§5.3) y la citación auditable; un chunk sin linaje no puede defenderse ante compliance. Segunda, las tablas exigen salir del paradigma de texto — la fila con concepto-periodo-moneda es la unidad —, lo que conecta con la división texto/números de §5.4. (csdn.net) Tercera, el coste es asimétrico: parsear Items o turnos son ~50 líneas con beneficios medibles (el +34% reportado), mientras que ignorar la estructura produce fallos silenciosos que ninguna métrica agregada de embedding revela. Criterio de aceptación práctico: si al inspeccionar diez chunks aleatorios alguno no se atribuye a una sección concreta, el chunking no es de grado institucional.
Trampa común: ajustar chunk_size en lugar de cambiar la unidad
El error de laboratorio más repetido: el equipo pasa dos semanas probando 500, 800, 1.200 tokens con RecursiveCharacterTextSplitter, gana medio punto de recall y concluye que «el chunking está optimizado». No lo está: el fallo es estructural y ningún tamaño lo corrige. Un factor de riesgo partido a mitad sigue partido con 800 y con 1.200 tokens; una cifra separada de la sección que la contextualiza sigue huérfana; el Q&A mezclado con el discurso preparado sigue contaminado. Se está afinando el segundo parámetro mientras el primero —la unidad de chunking— está mal elegido.
La tabla 5.2 del módulo lo dice sin rodeos: no existe «el mejor chunk_size» financiero, existe la mejor unidad por documento (Item, turno de hablante, artículo, fila XBRL), y el tamaño solo subdivide dentro de ella. El coste es asimétrico a favor de hacerlo bien: parsear Items o turnos son ~50 líneas con beneficios medibles (el +34% de precisión de retrieval reportado en calls), mientras que el chunking ingenuo produce fallos silenciosos que ninguna métrica agregada de embeddings revela. Aplica el criterio de aceptación del módulo antes de tocar ningún parámetro: si al inspeccionar diez chunks aleatorios alguno no se atribuye a una sección concreta, el problema no es el tamaño.
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)
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)
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:
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.
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)
donde \(R\) es el conjunto de candidatos, \(S\) el de documentos ya seleccionados, \(q\) 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.
Ejemplo trabajado: el embudo de retrieval, con cifras
Tomemos el caso aplicado del módulo —40 bancos de cobertura, corpus indexado por Items y turnos— y sigamos una consulta real por el embudo: «¿Cuál fue el CET1 ratio de JPM en 2024 y qué dijo el CFO sobre él?». Las cifras son ilustrativas pero de escala fiel al caso:
| Etapa | Acción | Candidatos |
|---|---|---|
| 0. Corpus | 40 bancos × filings y calls, chunkeados por estructura | 48.000 chunks |
| 1. Filtro de metadata | ticker=JPM, fiscal_year=2024 |
1.150 chunks |
| 2. Híbrido RRF 0,6/0,4 | denso k=10 + BM25 k=10, fusión | 20 candidatos únicos |
3. CohereRerank top_n=5 |
reordena leyendo query + documento | 5 chunks hijos |
4. ParentDocumentRetriever |
hijo 400 tok → padre 2.000 tok | ~10.000 tokens de contexto |
5. create_retrieval_chain |
prompt de citación doc+sección+periodo | respuesta fundamentada |
Tres lecturas de estos números. Primera, el filtro de metadata hace el trabajo pesado: reduce el espacio de búsqueda un 98% antes de gastar un solo embedding, y es gratis — por eso va primero en el stack. Segunda, el híbrido cuesta ~6 ms de fusión RRF y es la etapa que captura «CET1», un identificador exacto donde la búsqueda densa flaquea: la referencia del módulo es 91% de recall@10 frente a 78% solo denso. Tercera, el reranker y el contexto padre no añaden candidatos nuevos: reordenan y re-contextualizan los que ya tienes, recuperando la narrativa de gestión que el chunking hijo sacrificó por precisión.

Fuente: cifras de vendor 2026 y benchmark de retrieval financiero citados en §5.3.1 · Elaboración propia a 29-jul-2026.
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 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)
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.
}
En la práctica institucional — Kensho (S&P Global). Kensho, la división de IA de S&P Global, opera un framework multi-agente sobre LangGraph para retrieval de datos financieros, y su práctica de evaluación es la referencia del sector: evaluación multi-etapa con exact-match de routing y de tool-calling — no juicio de LLM sobre la salida final — porque un juicio generativo sobre una cifra de riesgo es circular. (🦜️🔗 LangChain) El pipeline documentado en la industria replica esa disciplina: golden dataset estilo FinanceBench → evaluación de trayectoria (¿la herramienta correcta con los argumentos correctos?) → exact-match numérico → replay en CI. Es el mismo principio que la división texto/números: lo verificable determinísticamente no se evalúa con un LLM. Grounding (anclaje): la propiedad de que cada afirmación de la salida sea trazable a una fuente recuperada con proveniencia (documento, sección, periodo); no se declara en el prompt — se demuestra con la verificación verbatim y se mide con faithfulness (§5.5) (el caso completo de Kensho se presenta en §1.1.2).
En palabras llanas: la biblioteca y la calculadora
Un vector store es una biblioteca magnífica para encontrar dónde se habla de algo, pero el bibliotecario no hace cuentas. El embedding de «158.104» queda cerca del de «158.014» aunque entre ambos haya 90 millones de diferencia, porque el embedding codifica contexto distributivo —dónde suele aparecer ese número— y no su magnitud. Pedirle al vector store que «entienda» una cifra es pedirle al bibliotecario que audite el balance.
La división texto/números del módulo es, en el fondo, un reparto de oficina: la prosa —riesgos, narrativa de gestión, tono del CFO— va a la biblioteca con su ficha de sección; las cifras van al contable, es decir, a hechos XBRL y tablas SQL donde concepto, periodo, moneda y escala son campos explícitos; y la aritmética va a la calculadora, una herramienta determinista. El LLM coordina la visita: formula la pregunta, recupera la fuente, invoca la herramienta y redacta la respuesta citando ambas. Lo que nunca hace es la cuenta «de cabeza».
Trampa común: dejar que el LLM haga la cuenta
Escena real: el retrieval trajo el fragmento correcto —ingresos de 158.104 y 177.798 millones en años consecutivos— y se pide «la variación interanual». El LLM responde, con prosa impecable, «≈14%». La cifra correcta es 12,5% (\((177.798-158.104)/158.104 = 0{,}1246\)). El error no es de retrieval ni de comprensión: es aritmética improvisada, el modo de fallo dominante del razonamiento LLM que documentó PAL —16 de 25 cadenas de pensamiento eran idénticas salvo en los números, como recuerda el módulo (§6.2 lo desarrolla).
La defensa tiene dos capas y ninguna es prompting. Primera, la cuenta se delega: xbrl_lookup para los hechos y la calculadora de ratios para el cociente — los +42 puntos de XBRL-Agent en razonamiento numérico miden exactamente esta decisión. Segunda, la verificación verbatim como código: si una cifra de la salida no existe literalmente en la fuente citada, la respuesta se abstiene («NO_DISPONIBLE») o va a cola humana. Ojo con la escala: «millones leídos como miles» es el 15% de los fallos de extracción, y un regex de literales no lo detecta si el texto dice «$158 billion» y la fuente «158.104 million» — por eso el campo scale explícito de los hechos XBRL es parte de la verificación, no un adorno.
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 \(R_q\) de pasajes relevantes para la consulta \(q\) y los \(k\) pasajes recuperados \(D_k\),
Es decir, la fracción de la evidencia necesaria que aparece entre los \(k\) 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)
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 \(R_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)
Ejemplo trabajado: una pregunta de golden dataset, campo a campo
El golden dataset estilo FinanceBench se construye pregunta a pregunta, y cada una es un registro con tres campos obligados. Este es un ítem real de la mesa de research del caso aplicado (§5.5.1: 150 preguntas escritas por los propios analistas sobre sus filings de cobertura):
{
"question": "¿Cuál fue el ratio CET1 de JPMorgan a cierre de 2024?",
# (a) Respuesta dorada con tolerancia (±2,5 % en numéricas, como FinanceBench)
"reference": "15,7 % (aceptable: 15,3 %–16,1 %)",
# (b) Pasajes de evidencia: definen R_q para recall@k
"evidence": [
{"doc": "JPM 10-K FY2024", "section_path": "10-K > Item 8 > Note 27",
"page": 312}
],
# (c) Tipo de pregunta, para desglosar fallos por categoría
"type": "extractiva", # extractiva | comparativa | multi-hop | con_cálculo
}
Tres decisiones prácticas. La tolerancia del ±2,5% existe porque el modelo puede citar la cifra con distinto redondeo y seguir siendo correcta; sin ella, el scoring binario penaliza respuestas buenas. Los pasajes de evidencia no son decoración: son el \(R_q\) contra el que se mide context recall — sin ellos, recall@k no es computable y el diagnóstico retrieval-vs-generación se cae. Y el campo type es el que permite descubrir que «el 40% de los fallos se concentra en consultas con términos exactos», como en el caso aplicado: agregado, un recall 0,62 no dice dónde actuar; desglosado por tipo, sí. Con 100–200 preguntas así sobre 20–40 filings se cubre el 80% de los modos de consulta de un desk — escribe las primeras 30 copiando las preguntas que los analistas ya hacen por correo.
Ejemplo trabajado: leer la matriz de diagnóstico antes de tocar nada
El caso aplicado del módulo es la demostración del procedimiento: context recall 0,62 diagnosticó retrieval (y se arregló con peso BM25 y filtro de año, no con prompts); faithfulness 0,79 diagnosticó después generación (y se arregló moviendo el cálculo a herramientas y activando la verificación verbatim). Ninguna de las dos mejoras fue de prompting. El diagrama operativo:
Dos detalles prácticos. El orden de las preguntas importa: medir faithfulness con recall bajo es juzgar al redactor por libros que nunca le llegaron — primero se arregla el retrieval, luego la generación. Y los dos gates finales son de otra naturaleza que las métricas RAGAS: la verificación verbatim es determinista (no admite «0,99») y la tasa de invención en preguntas sin evidencia es un veto binario. Por eso el gate de CI bloquea el merge aunque las cuatro métricas estén en verde.
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
- 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) consection_pathcompleto. Verifique que Item 1A e Item 7 quedan separados, re-divida Items >2.000 tokens propagando metadata y escriba un test: ningún chunk sinitemyfiscal_year. - Hybrid search medido. Sobre el corpus del ejercicio 1, construya un retriever denso solo y un
EnsembleRetriever0,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%. - Reranking y contexto padre. Añada
CohereRerank(o cross-encoder localms-marco-MiniLM-L-6-v2) yParentDocumentRetriever(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. - 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. Apliqueverificacion_verbatimy reporte la tasa de cifras no soportadas antes y después. - 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. - 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.
Recursos de élite para seguir profundizando
- FinanceBench — paper original (arXiv 2311.11944): la radiografía del RAG naive en finanzas; lee la tabla de configuraciones (store compartido vs. por documento vs. oracle) antes que el abstract.
- FinanceBench — repositorio de Patronus AI: el dataset abierto de 150 preguntas con respuestas doradas y pasajes de evidencia; es la plantilla directa para tu golden dataset propio de §5.5.
- RAGAS — paper (arXiv 2309.15217): la definición formal de faithfulness, answer relevancy y context precision/recall que el módulo resume operativamente.
- RAGAS — documentación oficial: API actual de
evaluate(), métricas con y sin referencia y generación de testsets sintéticos; imprescindible porque la librería cambia rápido. - Cohere Rerank — documentación: cómo funciona el reranking cross-encoder, límites de entrada y por qué reordena lo que la similitud de embeddings no puede.
- Voyage AI — voyage-finance-2: el caso de los embeddings de dominio financiero que el módulo cifra en +8 puntos sobre FinanceBench; evalúa siempre contra tu corpus.
- SEC EDGAR — APIs oficiales (data.sec.gov): company facts XBRL y submissions JSON, la fuente estructurada de la división texto/números; respeta el user-agent declarado que exige la SEC.
- LangChain v1 — Context engineering: el marco oficial de qué entra en el contexto y por qué; conecta el chunking estructural con el retrieval de este módulo.
Comprueba lo aprendido
Autoevaluación con feedback inmediato. No se guarda ninguna puntuación: es solo para ti.