RAG Pipelines y Bases de Datos Vectoriales: Generación Aumentada por Recuperación en Producción
La guía definitiva para construir sistemas RAG de producción -- desde ingestión de documentos, estrategias de chunking y modelos de embeddings hasta selección de bases de datos vectoriales (Pinecone, Weaviate, Qdrant, ChromaDB, pgvector, Milvus), estrategias de recuperación (búsqueda híbrida, HyDE, multi-query), reranking (Cohere, ColBERT, cross-encoders), patrones avanzados (RAG agéntico, graph RAG, RAG correctivo), evaluación RAGAS y despliegue en producción con caching, streaming y optimización de costos.
Índice de Contenidos
1. Arquitectura RAG: Visión General
La Generación Aumentada por Recuperación (RAG) es el patrón dominante para fundamentar las respuestas de LLMs en conocimiento externo. En vez de depender únicamente de la memoria paramétrica del modelo (que está congelada al momento del entrenamiento y propensa a alucinaciones), RAG recupera documentos relevantes de una base de conocimiento en tiempo de inferencia y los incluye en el contexto del prompt. Esto le da al modelo acceso a información actual, específica del dominio y verificable -- la diferencia entre un LLM que adivina y uno que cita fuentes.
Un sistema RAG de producción tiene tres fases: ingestión (procesar documentos en chunks buscables con embeddings almacenados en una base de datos vectorial), recuperación (encontrar los chunks más relevantes para una consulta usando similaridad vectorial, búsqueda por keywords o enfoques híbridos), y generación (alimentar el contexto recuperado a un LLM para producir una respuesta fundamentada). Cada fase tiene su propio conjunto de decisiones de ingeniería que se acumulan en la calidad general del sistema.
Pipeline de Ingestión
El pipeline offline que procesa documentos crudos en conocimiento consultable. Pasos: (1) cargar documentos de fuentes (PDFs, páginas web, bases de datos, APIs), (2) extraer y limpiar texto con metadata, (3) dividir texto en chunks usando una estrategia elegida, (4) generar vectores de embedding para cada chunk, (5) almacenar chunks + embeddings + metadata en una base de datos vectorial. Este pipeline corre en schedule o al cambiar un documento, no en tiempo de consulta.
Pipeline de Recuperación
El pipeline online que corre en tiempo de consulta. Pasos: (1) embeber la consulta del usuario usando el mismo modelo de ingestión, (2) buscar en la base vectorial los top-k chunks similares, (3) opcionalmente aplicar búsqueda híbrida (combinando scores de vector + keyword BM25), (4) reranking de resultados usando un cross-encoder o API de reranking, (5) filtrar por metadata (fecha, fuente, permisos). El presupuesto de latencia es típicamente 200-500ms para todo el paso de recuperación.
Pipeline de Generación
El paso final: construir un prompt con el contexto recuperado y la consulta del usuario, luego llamar al LLM. La plantilla del prompt controla cómo el modelo usa el contexto -- si debe citar fuentes, negarse a responder cuando el contexto es insuficiente, o sintetizar a través de múltiples documentos. El post-procesamiento incluye extracción de citas, validación de respuestas y detección de alucinaciones.
Loop de Retroalimentación
Los sistemas RAG de producción necesitan mejora continua. Rastrea la calidad de recuperación (¿se recuperaron los documentos correctos?), la calidad de respuesta (¿fue la respuesta precisa y relevante?), y la satisfacción del usuario (pulgar arriba/abajo, preguntas de seguimiento). Alimenta estos datos de vuelta en el refinamiento de la estrategia de chunking, la selección de modelo de embedding y la ingeniería de prompts. Sin un loop de retroalimentación, la calidad RAG se degrada silenciosamente a medida que la base de conocimiento crece.
Gestión de Ventana de Contexto
Los LLMs modernos ofrecen ventanas de contexto de 128K-1M tokens, pero meter más contexto no siempre mejora las respuestas. Los modelos de contexto largo muestran un efecto de "perdido en el medio" donde la información en el centro del contexto se recuerda menos confiablemente. Los sistemas RAG de producción recuperan 5-20 chunks altamente relevantes en vez de 100 marginalmente relevantes, priorizando precisión sobre recall para mantener alta la calidad de generación y bajo el costo.
Arquitectura Multi-Índice
El RAG empresarial frecuentemente abarca múltiples fuentes de conocimiento: docs internos, Confluence, Slack, JIRA, repositorios de código y fuentes externas. Cada fuente obtiene su propio pipeline de ingestión y potencialmente su propio índice con diferentes estrategias de chunking y embedding. En tiempo de consulta, un router determina qué índices buscar, y los resultados se fusionan y reranquean entre fuentes. Esto evita que una fuente ruidosa ahogue los resultados de alta calidad de otra.
2. Procesamiento de Documentos
La calidad de tu sistema RAG está limitada por la calidad de tu procesamiento de documentos. Basura entra, basura sale aplica doblemente aquí: texto mal extraído lleva a chunks malos, que llevan a recuperaciones irrelevantes, que llevan a respuestas alucinadas. Invierte fuertemente en el procesamiento de documentos -- es la mejora de mayor apalancamiento que puedes hacer a un pipeline RAG.
Procesamiento de PDF
Los PDFs son el formato más difícil de procesar confiablemente. Usa PyMuPDF (fitz) para extracción rápida de texto con preservación de layout. Para PDFs escaneados, usa Tesseract OCR o APIs cloud (Google Document AI, Azure Form Recognizer). Unstructured maneja PDFs de contenido mixto con tablas, imágenes y layouts multi-columna. Siempre preserva la estructura de tablas como markdown o HTML -- aplanar las tablas en párrafos destruye la información relacional que el LLM necesita.
HTML y Contenido Web
Usa BeautifulSoup4 o trafilatura para extracción de contenido web. Trafilatura está construido específicamente para extracción de artículos y maneja la eliminación de boilerplate, la extracción de fechas y la detección de autor automáticamente. Para páginas renderizadas con JavaScript, usa Playwright o Selenium para renderizar antes de extraer. Elimina navegación, pies de página, anuncios y banners de cookies -- agregan ruido a los embeddings sin aportar valor informativo.
Markdown y Texto Estructurado
Markdown es el formato de entrada ideal para RAG porque los headers proveen límites naturales de chunk y la estructura es explícita. Usa división basada en encabezados para crear chunks que respeten la jerarquía del documento. Preserva los bloques de código como unidades atómicas. Para repositorios de GitHub, procesa los archivos README, la documentación y los comentarios de código por separado con estrategias específicas por formato. MarkItDown de Microsoft convierte documentos Office (DOCX, PPTX, XLSX) a Markdown limpio.
Extracción de Metadata
Cada chunk debe llevar metadata: URL fuente, título del documento, encabezado de sección, número de página, fecha de creación, autor y tipo de documento. La metadata habilita recuperación filtrada (buscar solo docs legales, solo de 2025+, solo del equipo de ingeniería). Usa LLMs para generar metadata sintética: resúmenes, entidades clave, clasificación de temas y tags de relevancia. Este enfoque enriquecido con metadata supera consistentemente a los chunks de texto crudo en precisión de recuperación.
Tablas y Datos Estructurados
Las tablas son densas en información y pierden significado cuando se chunkean ingenuamente. Extrae las tablas como unidades completas usando Camelot o Tabula para PDFs. Almacena cada tabla como un solo chunk con su título y contexto circundante. Para tablas muy grandes, divide por grupos de filas repitiendo los encabezados. Considera generar un resumen en lenguaje natural de cada tabla como chunk adicional -- a los LLMs les resulta más fácil razonar sobre descripciones narrativas que sobre datos tabulares crudos.
Contenido Multi-modal
Los documentos con imágenes, diagramas y gráficos requieren procesamiento multi-modal. Usa LLMs de visión (GPT-5.6, Claude Sonnet 5) para generar descripciones textuales de imágenes y diagramas. Almacena tanto la referencia de imagen como su descripción generada como chunk. Para diagramas arquitectónicos y diagramas de flujo, extrae las relaciones como texto estructurado. El RAG multi-modal con ColPali embebe páginas enteras de documentos como imágenes, evitando por completo la extracción de texto para documentos visualmente ricos.
3. Estrategias de Chunking
El chunking determina la granularidad de tu base de conocimiento. Chunks muy grandes diluyen el embedding con información irrelevante y desperdician tokens de ventana de contexto. Chunks muy pequeños pierden el contexto necesario para que el LLM produzca respuestas coherentes. El tamaño óptimo de chunk depende de tu tipo de documento, modelo de embedding y caso de uso -- pero la mayoría de sistemas de producción caen entre 256 y 1024 tokens por chunk.
Chunking de Tamaño Fijo
Divide el texto en chunks de un conteo fijo de tokens con overlap configurable. Simple, predecible y funciona como baseline. Configuración típica: 512 tokens con 50-100 tokens de overlap. El overlap asegura que la información que abarca un límite de chunk aparezca en al menos un chunk. Usa tiktoken para conteo preciso de tokens con modelos OpenAI. Desventaja: corta a mitad de oración y párrafo, rompiendo la coherencia semántica.
División Recursiva por Caracteres
El RecursiveCharacterTextSplitter de LangChain divide en una jerarquía de separadores: primero por doble salto de línea (párrafos), luego salto simple, luego oraciones, luego palabras. Esto respeta los límites naturales del texto mientras mantiene los chunks dentro del límite de tamaño. La estrategia de chunking más usada en sistemas RAG de producción. Supera significativamente al chunking fijo para documentos narrativos con estructura clara de párrafos.
Chunking Semántico
Usa la similaridad de embeddings para determinar los límites de chunk. Computa embeddings para cada oración, luego divide donde la similaridad coseno entre oraciones consecutivas cae por debajo de un umbral. Esto crea chunks donde todas las oraciones están relacionadas semánticamente. El SemanticChunker de LangChain y el SemanticSplitterNodeParser de LlamaIndex implementan esto. Mayor calidad que la división recursiva pero 10-50x más lento debido al cómputo de embeddings por oración durante la ingestión.
Chunking Padre-Hijo (Jerárquico)
Crea dos niveles de chunks: chunks hijo pequeños (128-256 tokens) para recuperación precisa, y chunks padre más grandes (1024-2048 tokens) para contexto. Embebe y busca sobre los chunks hijo, pero retorna el chunk padre al LLM. Esto te da la precisión de recuperación de los chunks pequeños con la completitud contextual de los chunks grandes. El AutoMergingRetriever de LlamaIndex implementa esto: cuando múltiples chunks hijo del mismo padre son recuperados, se fusionan en el padre automáticamente.
Ventana Deslizante
Una variante del chunking fijo con alto overlap (50%+). Cada chunk se superpone significativamente con sus vecinos, asegurando que ninguna información caiga entre las grietas. Funciona bien para contenido técnico denso donde el contexto del texto circundante es crítico. El tradeoff es un índice más grande (2-3x más chunks) y mayores costos de embedding. Considera esto cuando la precisión de recuperación importa más que los costos de almacenamiento y cómputo.
Chunking Consciente del Documento
Divide por estructura del documento: headers, secciones, subsecciones y límites lógicos. Los headers de Markdown, los tags heading de HTML y las secciones de LaTeX proveen límites naturales. Cada chunk hereda su jerarquía de sección como metadata (por ejemplo, "Capítulo 3 > Sección 3.2 > Subsección 3.2.1"). Esto preserva la estructura lógica del documento y habilita recuperación jerárquica donde el LLM sabe exactamente de dónde viene cada pieza de información.
Late Chunking (Embeddings Contextuales)
Propuesto por Jina AI: embeber el documento completo primero usando un modelo de embedding de contexto largo, luego dividir los embeddings de salida en los límites de chunk. Cada embedding de chunk retiene contexto del documento completo porque la atención se computó a través de todo el documento antes de dividir. Esto aborda la limitación fundamental del chunking tradicional donde cada chunk se embebe de forma aislada. Soportado por jina-embeddings-v3 y el enfoque de recuperación contextual de Anthropic.
4. Modelos de Embeddings
Los modelos de embeddings convierten texto en vectores densos que capturan significado semántico. La elección del modelo de embedding determina la calidad de recuperación: establece el techo de qué tan bien tu sistema RAG puede emparejar consultas con documentos relevantes. A junio de 2026, el panorama incluye modelos API propietarios (OpenAI, Cohere, Google, Voyage) y modelos open-source (BGE, E5, GTE, Jina) que rivalizan o superan a las opciones propietarias en benchmarks como MTEB.
OpenAI text-embedding-3-large (Modelo Grande)
3072 dimensiones, 8191 tokens de contexto. El modelo de embedding de mayor calidad de OpenAI. Soporta el parámetro dimensions para acortar embeddings (por ejemplo, 1536 o 256) con pérdida mínima de calidad vía Matryoshka Representation Learning. Precio: $0.13 por millón de tokens. Usa las 3072 dimensiones completas para máxima calidad, o 1536 para un balance de calidad y costo. La variante text-embedding-3-small (1536 dims, $0.02/M tokens) es un buen default para aplicaciones sensibles al costo.
Cohere embed-v4 (Multimodal)
El último modelo de embedding de Cohere (2026) con soporte multi-modal nativo para texto e imágenes. 1024 dimensiones, 128K tokens de contexto. Soporta el parámetro input_type (search_document vs search_query) para embeddings asimétricos -- una característica crucial para RAG donde los documentos y las consultas tienen distribuciones diferentes. Ofrece cuantización int8 y binaria para reducción de almacenamiento 4-32x. Top-3 en el leaderboard MTEB. Soporte integrado para 100+ idiomas.
Google gemini-embedding-001 (Multilingüe)
3072 dimensiones (Matryoshka: truncable a 1536 o 768 con pérdida mínima de calidad), 2048 tokens de contexto. Reemplazó a los modelos legacy text-embedding-004/005 como el modelo de embedding de texto recomendado de Google. Tier gratis vía la Gemini API (con límite de tasa), $0.15/M tokens pago, también disponible en Vertex AI. Soporta el parámetro task_type para optimizar embeddings para recuperación, clasificación, clustering o similaridad, con rendimiento multilingüe de primer nivel en 100+ idiomas. El nuevo gemini-embedding-2 multimodal mapea texto, imágenes, video y audio en un mismo espacio, pero su espacio de embeddings es incompatible con -001, así que migrar requiere re-embeber tu corpus. Buena elección para stacks nativos de GCP.
Voyage AI voyage-4-large (Modelo Grande)
1024 dimensiones por defecto (Matryoshka: 256, 512 o 2048), 32K tokens de contexto, $0.12/M tokens. Voyage AI (ahora parte de MongoDB) lanzó la serie Voyage 4 en enero de 2026 con un espacio de embeddings compartido pionero en la industria: voyage-4-large (arquitectura MoE, mejor calidad), voyage-4, voyage-4-lite y el open-weight voyage-4-nano producen embeddings compatibles entre sí, así que puedes embeber consultas con un modelo barato y documentos con uno de mayor calidad. Respaldado por Anthropic para usar con Claude y sobresale en recuperación de código (variante voyage-code-3). Lidera el leaderboard de recuperación RTEB manteniendo costos de servicio cerca de 40% por debajo de modelos densos comparables.
BGE / E5 / GTE (Código Abierto)
BGE-M3 de BAAI: modelo multi-lingüe, multi-granularidad y multi-funcionalidad que genera embeddings densos, sparse y ColBERT simultáneamente. 1024 dims, 8192 tokens. E5-mistral-7b-instruct: instruction-tuned, top scorer en MTEB, 4096 dims. GTE-Qwen2: modelo de 1.5B parámetros con contexto de 8192 tokens, fuerte soporte multilingüe. Ejecútalos localmente con sentence-transformers u Ollama. Cero costo de API, privacidad total de datos, pero requiere GPU para throughput de producción.
Jina Embeddings v3 (Multilingüe)
1024 dimensiones, 8192 tokens de contexto. Soporta adaptadores LoRA específicos por tarea para recuperación, clasificación y similaridad. Innovación clave: soporte de late chunking donde el modelo procesa el documento completo y produce embeddings de chunk conscientes del contexto. Precio competitivo a $0.02/M tokens. Variante open-weights disponible para self-hosting. Excelente para RAG multilingüe con 89 idiomas soportados.
| Model | Dims | Max Tokens | MTEB Avg | Price / M tokens |
|---|---|---|---|---|
| text-embedding-3-large | 3072 | 8,191 | 64.6 | $0.13 |
| text-embedding-3-small | 1536 | 8,191 | 62.3 | $0.02 |
| Cohere embed-v4 | 1024 | 128,000 | 66.4 | $0.10 |
| voyage-4-large | 1024 | 32,000 | n/a (RTEB #1) | $0.12 |
| gemini-embedding-001 | 3072 | 2,048 | 68.3 | Free / $0.15 |
| BGE-M3 | 1024 | 8,192 | 65.1 | Self-hosted |
| jina-embeddings-v3 | 1024 | 8,192 | 65.5 | $0.02 |
5. Comparación de Bases de Datos Vectoriales
Las bases de datos vectoriales almacenan embeddings y habilitan búsqueda de similaridad rápida a escala. La elección depende de tu modelo de despliegue (gestionado vs self-hosted), requisitos de escala (miles vs miles de millones de vectores), patrones de consulta (vector puro vs búsqueda híbrida) e infraestructura existente. Todas las bases vectoriales de grado producción soportan indexación HNSW, filtrado por metadata y latencia de consulta sub-100ms para colecciones de millones de vectores.
Pinecone
Base de datos vectorial completamente gestionada y serverless. Cero infraestructura que gestionar -- crea un índice y empieza a insertar vectores. Escalado, replicación y backups automáticos. Soporta namespaces para aislamiento multi-tenant, búsqueda híbrida sparse-dense y filtrado por metadata. Precio serverless: pago por consulta y almacenamiento ($0.04/GB/mes de almacenamiento, $8/1M de consultas). La opción de menor fricción para equipos que quieren RAG sin operaciones de base de datos. Limitación: dependencia del proveedor y sin opción self-hosted.
Weaviate
Base de datos vectorial open-source con módulos de vectorización integrados. Característica única: integra la inferencia del modelo de embedding directamente -- le pasas texto crudo y Weaviate maneja el embedding vía los proveedores de modelo configurados. Búsqueda híbrida nativa combinando BM25 y scoring vectorial. API GraphQL para consultas expresivas. Soporta multi-tenancy, RBAC y backup/restore. Disponible como Weaviate Cloud (gestionado) o self-hosted vía Docker/Kubernetes. Fuerte elección para equipos que quieren tanto búsqueda vectorial como por keywords en un solo sistema.
Qdrant
Escrito en Rust para máximo rendimiento. Soporta cuantización escalar, binaria y de producto para reducción de memoria 4-32x. Filtrado avanzado con índices de payload que se aplican antes de la búsqueda vectorial (no como post-filtro), asegurando que siempre obtengas k resultados incluso con filtros estrictos. Soporta puntos multi-vector (por ejemplo, embeddings de título + cuerpo por documento). Soporte de vectores sparse para búsqueda híbrida. Qdrant Cloud (gestionado) o self-hosted. La elección orientada a rendimiento para RAG sensible a latencia a escala.
ChromaDB
Base de datos vectorial liviana y amigable para desarrolladores, diseñada para aplicaciones AI. Corre en-proceso (modo embebido) con cero configuración -- pip install chromadb y empieza a indexar. Soporta almacenamiento persistente y modo cliente-servidor para producción. Funciones de embedding integradas para OpenAI, Cohere y sentence-transformers. Ideal para prototipado, notebooks y aplicaciones RAG de una sola máquina. No diseñada para cargas de producción a escala de miles de millones -- gradúate a Pinecone, Qdrant o pgvector cuando la superes.
pgvector
Extensión de PostgreSQL que agrega búsqueda de similaridad vectorial a tu base de datos existente. Almacena embeddings junto con datos relacionales con acceso SQL completo, transacciones ACID, joins y tu stack de backup/monitoreo existente. Soporta índices HNSW e IVFFlat. pgvecto.rs (de Tensorchord) ofrece mejor rendimiento con indexación basada en Rust. La elección pragmática cuando ya corres PostgreSQL y quieres evitar agregar otra base de datos a tu stack. Escala a ~10M de vectores en una sola instancia.
Milvus
Construido específicamente para búsqueda vectorial a escala de miles de millones. Arquitectura distribuida con separación de almacenamiento y cómputo. Soporta 10+ tipos de índice incluyendo IVF acelerado por GPU y DiskANN para datasets más grandes que la memoria. Búsqueda híbrida nativa con BM25. Ejecútalo como Milvus Lite (embebido), Milvus Standalone (nodo único) o Milvus Distributed (Kubernetes). Zilliz Cloud ofrece una versión completamente gestionada. La elección para equipos que indexan 100M+ de vectores y necesitan escalado horizontal con latencia sub-100ms consistente.
| Database | Deployment | Hybrid Search | Max Scale | Best For |
|---|---|---|---|---|
| Pinecone | Managed only | Sparse-dense | Billions | Zero-ops teams |
| Weaviate | Cloud + self-hosted | BM25 + vector | Billions | Built-in vectorization |
| Qdrant | Cloud + self-hosted | Sparse vectors | Billions | Low-latency, filtering |
| ChromaDB | Embedded + server | No native | Millions | Prototyping, notebooks |
| pgvector | Self-hosted (PG ext) | Via SQL + tsvector | ~10M per node | Existing PostgreSQL stack |
| Milvus | Cloud + self-hosted | BM25 + vector | 10B+ | Massive scale |
6. Estrategias de Recuperación
La recuperación es donde se originan la mayoría de los problemas de calidad RAG. Un modelo de embedding perfecto con una estrategia naive de top-k tendrá peor rendimiento que un modelo decente con ingeniería de recuperación bien pensada. Las estrategias a continuación progresan de simples a avanzadas, y los sistemas de producción típicamente combinan múltiples enfoques.
Búsqueda por Similaridad Vectorial
La estrategia de recuperación más simple: embeber la consulta, encontrar los top-k vectores más cercanos por similaridad coseno (o producto punto / distancia L2). Funciona bien cuando el lenguaje de la consulta y el del documento coinciden de cerca. Valores típicos de k: 5-20. Problemas: retorna resultados redundantes cuando múltiples chunks cubren el mismo tema, y pierde documentos relevantes cuando la consulta usa terminología diferente a la de los documentos fuente.
Máxima Relevancia Marginal (MMR)
Balancea relevancia y diversidad en los resultados recuperados. MMR selecciona iterativamente documentos que son tanto similares a la consulta como disímiles a los documentos ya seleccionados. Controlado por un parámetro lambda (1.0 = relevancia pura, 0.0 = diversidad pura). Esto previene el modo de falla común donde 5 de 5 chunks recuperados dicen lo mismo. Esencial para tareas de síntesis multi-documento. Integrado en los retrievers de LangChain y LlamaIndex.
Búsqueda Híbrida (BM25 + Vector)
Combina el scoring por keywords BM25 con la similaridad vectorial. BM25 sobresale en la coincidencia exacta de términos (nombres de producto, códigos de error, acrónimos) donde la búsqueda vectorial batalla. Los vectores sobresalen en la coincidencia semántica donde BM25 falla (por ejemplo, "costo" coincidiendo con "precios"). La búsqueda híbrida captura ambos. Estrategias de fusión: reciprocal rank fusion (RRF), combinación lineal ponderada o fusión de scores aprendida. Weaviate, Qdrant y Pinecone soportan búsqueda híbrida nativamente. Para pgvector, combina con la búsqueda de texto completo tsvector de PostgreSQL.
HyDE (Embeddings de Documentos Hipotéticos)
Usa el LLM para generar una respuesta hipotética a la consulta, luego embebe esa respuesta y busca documentos similares. La intuición: una respuesta hipotética está en el mismo "lenguaje" que los documentos (detallada, técnica, completa), mientras que la consulta es corta e informal. Esto cierra la brecha de distribución entre consulta y documento. Efectivo para preguntas complejas donde la consulta por sí sola no contiene suficiente señal semántica. Agrega una llamada LLM de latencia. Implementado en LangChain como HypotheticalDocumentEmbedder.
Recuperación Multi-Query
Usa el LLM para generar 3-5 reformulaciones alternativas de la consulta del usuario, luego recupera documentos para cada reformulación y deduplica los resultados. Esto aumenta el recall al capturar diferentes aspectos de la consulta que un solo embedding podría perder. Ejemplo: "¿Cómo despliego a producción?" genera variantes como "pasos de despliegue en producción", "configuración de pipeline CI/CD", "proceso de gestión de releases". El MultiQueryRetriever de LangChain implementa este patrón.
Compresión Contextual
Después de recuperar chunks, usa un LLM para extraer solo las porciones relevantes de cada chunk relativas a la consulta. Un chunk de 1000 tokens podría contener solo 100 tokens de información relevante. La compresión contextual reduce el ruido en el contexto de generación y te permite recuperar más chunks dentro del mismo presupuesto de ventana de contexto. El ContextualCompressionRetriever de LangChain encadena un retriever base con un compresor (basado en LLM o cross-encoder).
Enrutamiento de Consultas
No todas las consultas deberían buscar en el mismo índice o usar la misma estrategia de recuperación. Un router (basado en LLM o en clasificador) analiza la consulta y la enruta al índice, colección o estrategia apropiada. Ejemplo: las preguntas factuales usan recuperación densa, las consultas cargadas de keywords usan BM25, las preguntas analíticas usan un knowledge graph. El RouterChain de LangChain y el RouterQueryEngine de LlamaIndex implementan esto. Crítico para arquitecturas RAG multi-índice.
7. Reclasificación (Reranking)
El reranking es la mejora post-recuperación de mayor apalancamiento. La recuperación inicial (similaridad de embeddings) es una primera pasada rápida pero imprecisa. Los rerankers usan modelos más costosos para re-puntuar y reordenar los top-k resultados basándose en la interacción profunda consulta-documento. Un bi-encoder (modelo de embedding) procesa la consulta y el documento de forma independiente; un cross-encoder los procesa conjuntamente, atendiendo a las interacciones entre los tokens de la consulta y del documento. Este procesamiento conjunto captura señales de relevancia que la codificación independiente pierde.
Cohere Rerank
La API de reranking más usada. rerank-v4.0-pro y rerank-v4.0-fast (2026) son los últimos modelos multilingües para documentos y JSON semi-estructurado: pro apunta a máxima calidad en consultas complejas, fast a baja latencia y alto throughput; rerank-v3.5 (contexto de 4096 tokens) sigue disponible. Pásale tu consulta y los top-k documentos; recibes de vuelta scores de relevancia y el orden reordenado. Precio: $2 por 1000 unidades de búsqueda. Agrega consistentemente 5-15% de mejora en precisión de recuperación sobre la recuperación solo por embeddings. Soporta inputs de documento estructurados (campos JSON) para reranking multi-campo. Integración drop-in con LangChain y LlamaIndex.
Modelos Cross-Encoder
Modelos cross-encoder open-source para reranking self-hosted. BAAI/bge-reranker-v2-m3 soporta reranking multilingüe. cross-encoder/ms-marco-MiniLM-L-12-v2 es liviano y rápido. Ejecútalos vía sentence-transformers con GPU para throughput de producción. Los cross-encoders procesan cada par consulta-documento de forma independiente, así que la latencia escala linealmente con el número de documentos. Reordena los top 20-50 documentos de la recuperación inicial, no el corpus entero.
ColBERT (Interacción Tardía)
ColBERT computa embeddings a nivel de token para la consulta y el documento, luego puntúa vía MaxSim (máxima similaridad entre cada token de la consulta y todos los tokens del documento). Esta arquitectura de interacción tardía es más rápida que los cross-encoders (los embeddings de documento pueden precomputarse) mientras captura señales de relevancia de grano fino que los bi-encoders pierden. ColBERTv2 y RAGatouille proveen implementaciones Python fáciles de usar. Ideal para reranking de alto throughput donde la latencia del cross-encoder es prohibitiva.
Fusión de Rango Recíproco (RRF)
Un método simple y efectivo para fusionar listas ranqueadas de múltiples estrategias de recuperación. Para cada documento, computa RRF(d) = sum(1 / (k + rank_i)) a través de todas las listas, donde k es una constante (típicamente 60). Esto da más peso a los documentos que aparecen alto en múltiples listas. No requiere modelo -- puramente basado en rango. Usa RRF para fusionar resultados BM25 con resultados vectoriales, o para combinar resultados de múltiples modelos de embedding. Implementado nativamente en Elasticsearch 8.x y Weaviate.
FlashRank y Rerankers Livianos
FlashRank provee reranking sub-50ms en CPU usando modelos destilados (14-86M de parámetros). mixedbread-ai/mxbai-rerank-large-v1 ofrece fuerte calidad con inferencia rápida. Estos rerankers livianos son prácticos para entornos con restricciones de latencia donde las llamadas a la API de Cohere o los cross-encoders grandes agregan demasiada latencia. Ejecútalos en la misma máquina que tu servidor de aplicación -- no se requiere GPU para modelos menores a 100M de parámetros.
LLM como Reranker
Usa un LLM para puntuar la relevancia de cada documento recuperado respecto a la consulta. El LLM evalúa relevancia semántica, alineación factual y completitud de maneras que la similaridad de embeddings no puede. Prompt: "Califica qué tan relevante es este pasaje para la pregunta en una escala de 1-5." Costoso (una llamada LLM por documento) pero de máxima calidad para aplicaciones críticas. Úsalo con un modelo rápido y barato (Claude Haiku 4.5, GPT-5.6 Luna) para mantener el costo manejable. Resérvalo para consultas de alto valor o como fallback cuando otros rerankers muestran baja confianza.
8. Patrones RAG Avanzados
El RAG estándar (recuperar-luego-generar) alcanza un techo para preguntas complejas que requieren razonamiento multi-paso, auto-corrección o fuentes de datos heterogéneas. Los patrones RAG avanzados extienden la arquitectura básica con comportamiento agéntico, conocimiento basado en grafos, recuperación multi-modal y loops de auto-evaluación. Estos patrones aumentan la complejidad del sistema pero desbloquean capacidades que el RAG simple no puede lograr.
RAG Agéntico
Envuelve el pipeline RAG dentro de un agente AI que puede decidir cuándo recuperar, qué recuperar y si recuperar de nuevo. El agente formula las consultas de búsqueda, evalúa los resultados recuperados, reformula si los resultados son pobres y sintetiza respuestas a través de múltiples rondas de recuperación. Construido con LangGraph o el Claude Agent SDK, donde la recuperación es una herramienta que el agente invoca según se necesite. Este es el patrón dominante de producción para 2025-2026: recuperación como llamada a herramienta, no un paso fijo del pipeline.
RAG de Grafos
Construye un knowledge graph a partir de documentos usando extracción de entidades y relaciones basada en LLM, luego usa recorrido de grafo para la recuperación junto con la búsqueda vectorial. Sobresale en preguntas multi-hop ("¿Qué empresas fueron fundadas por personas que estudiaron en Stanford y trabajaron en Google?") donde la búsqueda vectorial falla porque la respuesta requiere conectar múltiples hechos. La librería GraphRAG de Microsoft implementa detección de comunidades y resumen sobre el grafo para recuperación temática. Combínala con Neo4j o una base de datos de grafo de propiedades para el almacenamiento.
RAG Correctivo (CRAG)
Agrega un paso de auto-evaluación después de la recuperación: un LLM califica cada documento recuperado como "relevante", "ambiguo" o "irrelevante". Si los documentos son relevantes, procede a la generación. Si son ambiguos, recupera de fuentes adicionales (por ejemplo, búsqueda web). Si son irrelevantes, salta la búsqueda vectorial por completo y recurre a la búsqueda web o se niega a responder. Esto previene el modo de falla común donde el LLM genera respuestas con confianza a partir de contexto recuperado irrelevante. Implementado como un workflow de LangGraph con enrutamiento condicional.
RAG Multi-Modal
Extiende RAG para manejar imágenes, tablas, gráficos y diagramas junto con texto. Dos enfoques: (1) generar descripciones textuales del contenido visual y embeberlas como chunks de texto. (2) usar modelos de embedding multi-modales (CLIP, Cohere embed-v4, ColPali) para embeber imágenes directamente junto con el texto en el mismo espacio vectorial. ColPali embebe páginas enteras de documentos como imágenes, evitando por completo la extracción de texto. Usa LLMs de visión (GPT-5.6, Claude) para la generación sobre el contenido visual recuperado.
Auto-RAG (Self-RAG)
Un modelo fine-tuned que decide dinámicamente si recuperar, genera tokens de reflexión (IsRel, IsSup, IsUse) para evaluar la calidad de recuperación y la calidad de respuesta en cada paso, y puede regenerar si la auto-evaluación falla. A diferencia del RAG estándar donde la recuperación siempre se dispara, Self-RAG salta la recuperación para preguntas que el modelo puede responder desde su memoria paramétrica y activa la recuperación solo cuando se necesita. Reduce la latencia para preguntas simples mientras mantiene la precisión para las intensivas en conocimiento.
Prompting de Retroceso (Step-Back)
Antes de la recuperación, el LLM genera una versión de nivel más alto y más abstracta de la consulta. Ejemplo: "¿Cuál es la solubilidad del hidróxido de calcio a 25C?" se convierte en "¿Cuáles son las propiedades químicas del hidróxido de calcio?" La consulta step-back recupera contexto más amplio que incluye la respuesta específica. Esto ayuda con preguntas altamente específicas donde la formulación exacta podría no coincidir con ningún documento. Combínala con la consulta original para un enfoque multi-query.
RAPTOR (Recuperación Basada en Árbol)
Construye un árbol de resúmenes de documentos a múltiples niveles de abstracción. Los nodos hoja son los chunks originales, los nodos intermedios son resúmenes de clusters de chunks, y la raíz es un resumen del corpus entero. La recuperación busca a través de todos los niveles del árbol, emparejando las preguntas a nivel de detalle con los chunks hoja y las preguntas temáticas con los nodos de resumen. Particularmente efectivo para documentos largos y contenido de longitud de libro donde tanto los hechos específicos como los temas amplios necesitan ser recuperables.
Recuperación Contextual (Anthropic)
El enfoque de Anthropic: anteponer una breve explicación de contexto a cada chunk antes de embeber. Usa el LLM para generar un contexto situacional de 50-100 tokens: "Este chunk es del reporte de resultados del Q2 2025, específicamente la sección de desglose de ingresos." Este contexto se embebe con el chunk, mejorando la precisión de recuperación en 49% en los benchmarks de Anthropic. Cuando se combina con búsqueda híbrida BM25 y reranking, la recuperación contextual reduce las recuperaciones fallidas en 67% comparado con el RAG estándar.
9. Evaluación (Framework RAGAS)
No puedes mejorar lo que no mides. La evaluación RAG es más difícil que la evaluación ML estándar porque necesitas evaluar tanto la calidad de recuperación como la calidad de generación de forma independiente, y su interacción. El framework RAGAS (Retrieval Augmented Generation Assessment) es el toolkit de evaluación más adoptado, proveyendo métricas automatizadas que correlacionan bien con los juicios humanos sin requerir etiquetas de ground-truth.
Fidelidad
Mide si la respuesta generada está fundamentada en el contexto recuperado. El LLM extrae los claims individuales de la respuesta, luego verifica cada claim contra los documentos recuperados. Fidelidad = (claims soportados) / (claims totales). Un score de fidelidad por debajo de 0.8 indica alucinación -- el modelo está generando información no presente en el contexto. Esta es la métrica más crítica: un sistema RAG que alucina es peor que uno que se niega a responder.
Relevancia de Respuesta
Mide si la respuesta aborda la pregunta. El LLM evaluador genera preguntas que la respuesta abordaría, luego computa la similaridad coseno entre estas preguntas generadas y la pregunta original. Alta similaridad = respuesta relevante. Baja similaridad = la respuesta se fue del tema. Esto captura el modo de falla donde el modelo genera una respuesta factualmente correcta pero irrelevante porque el contexto recuperado lo desvió.
Precisión de Contexto
Mide si los documentos recuperados son relevantes a la pregunta. Para cada chunk recuperado, el evaluador determina si contiene la información necesaria para responder la consulta. Precisión = (chunks relevantes) / (chunks recuperados totales). Baja precisión significa que tu recuperación está trayendo ruido -- chunks irrelevantes que desperdician ventana de contexto y pueden confundir al modelo. Objetivo: por encima de 0.7 para sistemas de producción.
Recall de Contexto
Mide si los documentos recuperados contienen toda la información necesaria para responder la pregunta. A diferencia de la precisión (que mide el ruido), el recall mide la cobertura. Requiere respuestas de ground-truth para su cómputo: el evaluador verifica si cada claim en la respuesta de ground-truth está soportado por el contexto recuperado. Bajo recall significa que tu recuperación está perdiendo documentos relevantes -- necesitas mejores embeddings, más chunks o expansión de consulta.
Framework RAGAS
Framework de evaluación open-source (pip install ragas). Computa fidelidad, relevancia de respuesta, precisión de contexto y recall de contexto usando LLM-as-judge. Soporta la generación de conjuntos de prueba a partir de tus documentos. Se integra con LangSmith, Weights & Biases y pipelines CI/CD para evaluación continua. Usa RAGAS para comparar estrategias de chunking, modelos de embedding y parámetros de recuperación sistemáticamente en vez de confiar en corazonadas. Ejecuta la evaluación en 100-500 consultas de prueba diversas para resultados estadísticamente significativos.
Dimensiones de Evaluación Custom
Más allá de las métricas de RAGAS, los sistemas de producción evalúan: latencia (tiempo de respuesta end-to-end bajo p95), costo por consulta (tokens de embedding + recuperación + reranking + generación), precisión de citas (¿apuntan las citas a la fuente correcta?), cobertura (¿qué porcentaje de consultas obtiene respuestas satisfactorias?) y frescura (¿se basan las respuestas en la versión más reciente de los documentos?). Construye un dashboard que rastree estas métricas a lo largo del tiempo para detectar regresiones temprano.
from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy, context_precision, context_recall
from datasets import Dataset
# Prepare evaluation dataset
eval_data = Dataset.from_dict({
"question": questions,
"answer": generated_answers,
"contexts": retrieved_contexts, # list[list[str]]
"ground_truth": reference_answers # for context_recall
})
# Run evaluation
results = evaluate(
dataset=eval_data,
metrics=[faithfulness, answer_relevancy, context_precision, context_recall],
llm=ChatOpenAI(model="gpt-5.5"), # evaluator LLM
embeddings=OpenAIEmbeddings()
)
print(results)
# {'faithfulness': 0.87, 'answer_relevancy': 0.91,
# 'context_precision': 0.78, 'context_recall': 0.82}
10. Despliegue en Producción
Mover RAG de un notebook a producción involucra caching, streaming, multi-tenancy, monitoreo y optimización de costos. La diferencia entre un demo y un sistema de producción no es el algoritmo de recuperación -- es la infraestructura que hace al algoritmo confiable, rápido y económico a escala.
Caching Semántico
Cachea pares consulta-respuesta indexados por la similaridad de embedding de la consulta. Cuando una nueva consulta es semánticamente similar (similaridad coseno > 0.95) a una consulta cacheada, retorna la respuesta cacheada sin golpear la base de datos vectorial ni el LLM. Redis con RedisVL o GPTCache provee lookups de caché sub-milisegundo. El caching semántico reduce los costos en 30-60% para cargas con consultas repetitivas (soporte al cliente, bots de FAQ). Configura el TTL según la frecuencia de actualización de la base de conocimiento para prevenir respuestas obsoletas.
Respuestas Streaming
Streamea la respuesta del LLM token por token usando Server-Sent Events (SSE) o WebSockets. El usuario ve el primer token dentro de 500ms en vez de esperar 3-5 segundos por la respuesta completa. Streamea las citas de fuentes junto con la respuesta para que los usuarios puedan verificar los claims en tiempo real. El astream_events de LangChain y el stream_chat de LlamaIndex soportan streaming con metadata de recuperación. Siempre usa streaming en producción -- la mejora en latencia percibida es dramática.
Arquitectura Multi-Tenant
Aísla la base de conocimiento de cada cliente para prevenir la fuga de datos. Estrategias: (1) colecciones separadas por tenant (aislamiento más fuerte, mayor costo), (2) namespace/partición dentro de una colección compartida (namespaces de Pinecone, filtrado de payload de Qdrant), (3) filtrado basado en metadata con seguridad a nivel de fila. Siempre aplica los filtros de tenant antes de la recuperación, nunca después. Prueba el aislamiento consultando como Tenant A y verificando cero resultados de los documentos del Tenant B. Los namespaces de Pinecone y el multi-tenancy de Weaviate proveen soporte nativo.
Monitoreo y Observabilidad
Rastrea cuatro categorías: métricas de recuperación (latencia de consulta, chunks recuperados, tasas de acierto de filtros), métricas de generación (uso de tokens, latencia del LLM, tasas de error), métricas de calidad (scores RAGAS sobre una muestra de consultas de producción) y métricas de negocio (satisfacción del usuario, tasa de escalamiento, cobertura de respuestas). Usa LangSmith o Langfuse para trazas end-to-end. Configura alertas sobre caídas del score de fidelidad -- indican degradación de recuperación o desactualización de la base de conocimiento.
Optimización de Costos
Los costos de RAG = costos de embedding + costos de base de datos vectorial + costos de reranking + costos de generación del LLM. Optimiza cada uno: (1) usa modelos de embedding más pequeños para aplicaciones de bajo riesgo (text-embedding-3-small), (2) aplica cuantización para reducir el almacenamiento de vectores 4-8x, (3) usa un reranker liviano (FlashRank) en vez de Cohere para cargas sensibles al costo, (4) usa LLMs más pequeños (Claude Haiku 4.5, GPT-5.6 Luna) para preguntas y respuestas directas, (5) cachea agresivamente. Un pipeline RAG bien optimizado cuesta $0.001-0.01 por consulta. Uno no optimizado cuesta $0.10-0.50.
Ingestión Incremental
Re-embeber toda tu base de conocimiento en cada actualización es costoso y lento. Implementa ingestión incremental: rastrea los checksums de documentos, solo re-procesa los documentos cambiados y haz upsert (actualizar o insertar) de los vectores por ID de documento. Usa un registro de documentos (tabla de PostgreSQL o almacén clave-valor) para rastrear el estado de ingestión. Para fuentes frecuentemente actualizadas (Confluence, Notion), configura webhooks para disparar la re-ingestión al guardar un documento. Agrupa las llamadas de embedding en lotes para maximizar el throughput y minimizar los costos de API.
Seguridad y Control de Acceso
Los sistemas RAG pueden filtrar información sensible a través de la recuperación. Implementa control de acceso a nivel de documento: almacena los permisos de usuario/grupo como metadata en cada chunk, y filtra por los permisos del usuario solicitante en tiempo de consulta. Nunca confíes en que el LLM respete los límites de acceso -- incluirá contenido restringido en su respuesta si aparece en el contexto. Sanitiza los inputs para prevenir la inyección de prompts que podría eludir los filtros de recuperación. Audita todas las consultas que tocan documentos sensibles.
11. Ejemplos de Código
Tres enfoques de implementación desde frameworks de alto nivel hasta llamadas directas a APIs. LangChain provee el camino más rápido a un sistema RAG funcional con la mayor cantidad de abstracciones. LlamaIndex está construido específicamente para RAG con primitivas de indexación más profundas. Las llamadas directas a API dan máximo control y mínimas dependencias para equipos que prefieren código explícito sobre la magia del framework.
Pipeline RAG con LangChain
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_community.vectorstores import Qdrant
from langchain_community.document_loaders import PyMuPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_core.prompts import ChatPromptTemplate
# 1. Load and chunk documents
loader = PyMuPDFLoader("technical_manual.pdf")
docs = loader.load()
splitter = RecursiveCharacterTextSplitter(
chunk_size=512, chunk_overlap=50,
separators=["\n\n", "\n", ". ", " ", ""]
)
chunks = splitter.split_documents(docs)
# 2. Embed and store in Qdrant
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
vectorstore = Qdrant.from_documents(
chunks, embeddings,
url="http://localhost:6333",
collection_name="tech_manual"
)
# 3. Create retrieval chain with reranking
retriever = vectorstore.as_retriever(
search_type="mmr", # Maximum Marginal Relevance
search_kwargs={"k": 10, "fetch_k": 25, "lambda_mult": 0.7}
)
prompt = ChatPromptTemplate.from_messages([
("system", """Answer based on the context below. Cite sources.
If the context doesn't contain the answer, say so.
Context: {context}"""),
("human", "{input}")
])
llm = ChatOpenAI(model="gpt-5.5", temperature=0)
chain = create_retrieval_chain(
retriever,
create_stuff_documents_chain(llm, prompt)
)
# 4. Query
result = chain.invoke({"input": "How do I configure failover?"})
print(result["answer"])
Pipeline RAG con LlamaIndex
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings
from llama_index.core.node_parser import SentenceSplitter
from llama_index.vector_stores.pinecone import PineconeVectorStore
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.llms.anthropic import Anthropic
from llama_index.core.postprocessor import SentenceTransformerRerank
from pinecone import Pinecone
# 1. Configure models
Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-large")
Settings.llm = Anthropic(model="claude-sonnet-4-6", temperature=0)
# 2. Load documents with metadata
documents = SimpleDirectoryReader(
input_dir="./knowledge_base",
recursive=True,
filename_as_id=True
).load_data()
# 3. Parse with sentence-aware splitting
parser = SentenceSplitter(chunk_size=512, chunk_overlap=50)
nodes = parser.get_nodes_from_documents(documents)
# 4. Create Pinecone index
pc = Pinecone(api_key="your-api-key")
pinecone_index = pc.Index("rag-production")
vector_store = PineconeVectorStore(pinecone_index=pinecone_index)
index = VectorStoreIndex.from_documents(
documents, vector_store=vector_store
)
# 5. Query with reranking
reranker = SentenceTransformerRerank(
model="BAAI/bge-reranker-v2-m3", top_n=5
)
query_engine = index.as_query_engine(
similarity_top_k=20,
node_postprocessors=[reranker],
response_mode="tree_summarize"
)
response = query_engine.query("Explain the backup recovery procedure")
print(response)
for node in response.source_nodes:
print(f" [{node.score:.3f}] {node.metadata['file_name']}")
API Directa: Pinecone + OpenAI
import openai
from pinecone import Pinecone
client = openai.OpenAI()
pc = Pinecone(api_key="your-api-key")
index = pc.Index("rag-production")
def embed(text: str, input_type: str = "search_query") -> list[float]:
"""Generate embedding for text."""
response = client.embeddings.create(
model="text-embedding-3-large",
input=text,
dimensions=1536 # Matryoshka shortening for cost savings
)
return response.data[0].embedding
def retrieve(query: str, top_k: int = 10, filters: dict = None) -> list[dict]:
"""Retrieve relevant chunks from Pinecone."""
query_vector = embed(query)
results = index.query(
vector=query_vector,
top_k=top_k,
include_metadata=True,
filter=filters # e.g., {"source": "engineering-docs", "year": {"$gte": 2025}}
)
return [
{"text": m.metadata["text"], "source": m.metadata["source"], "score": m.score}
for m in results.matches
]
def generate(query: str, contexts: list[dict]) -> str:
"""Generate answer grounded in retrieved context."""
context_str = "\n\n---\n\n".join(
f"[Source: {c['source']}] {c['text']}" for c in contexts
)
response = client.chat.completions.create(
model="gpt-5.5",
temperature=0,
messages=[
{"role": "system", "content": f"""Answer based on the context below.
Cite sources using [Source: ...] format.
If context is insufficient, say "I don't have enough information."
Context:
{context_str}"""},
{"role": "user", "content": query}
],
stream=True
)
chunks = []
for chunk in response:
if chunk.choices[0].delta.content:
chunks.append(chunk.choices[0].delta.content)
print(chunk.choices[0].delta.content, end="", flush=True)
return "".join(chunks)
# RAG query
query = "What are the SLA requirements for the payment service?"
contexts = retrieve(query, top_k=10, filters={"team": "payments"})
answer = generate(query, contexts)
pgvector con Python (SQL Directo)
import psycopg2
import openai
import json
client = openai.OpenAI()
conn = psycopg2.connect("postgresql://user:pass@localhost:5432/ragdb")
# Setup: create table with vector column
with conn.cursor() as cur:
cur.execute("CREATE EXTENSION IF NOT EXISTS vector")
cur.execute("""
CREATE TABLE IF NOT EXISTS documents (
id SERIAL PRIMARY KEY,
content TEXT NOT NULL,
source VARCHAR(255),
embedding vector(1536),
metadata JSONB DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ DEFAULT NOW()
)
""")
cur.execute("""
CREATE INDEX IF NOT EXISTS docs_embedding_idx
ON documents USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 200)
""")
conn.commit()
def ingest(content: str, source: str, metadata: dict = None):
"""Embed and store a document chunk."""
resp = client.embeddings.create(
model="text-embedding-3-large", input=content, dimensions=1536
)
embedding = resp.data[0].embedding
with conn.cursor() as cur:
cur.execute(
"""INSERT INTO documents (content, source, embedding, metadata)
VALUES (%s, %s, %s::vector, %s::jsonb)""",
(content, source, str(embedding), json.dumps(metadata or {}))
)
conn.commit()
def search(query: str, top_k: int = 5, source_filter: str = None) -> list:
"""Hybrid search: vector similarity + optional source filter."""
q_emb = client.embeddings.create(
model="text-embedding-3-large", input=query, dimensions=1536
).data[0].embedding
sql = """
SELECT content, source,
1 - (embedding <=> %s::vector) AS similarity
FROM documents
WHERE 1=1
"""
params = [str(q_emb)]
if source_filter:
sql += " AND source = %s"
params.append(source_filter)
sql += " ORDER BY embedding <=> %s::vector LIMIT %s"
params.extend([str(q_emb), top_k])
with conn.cursor() as cur:
cur.execute(sql, params)
return [
{"content": r[0], "source": r[1], "score": r[2]}
for r in cur.fetchall()
]