Sistema de preguntas y respuestas sobre documentos PDF utilizando Retrieval-Augmented Generation (RAG) con FastAPI.
Esta API permite ingestar documentos PDF, procesarlos en chunks, almacenarlos en una base de datos vectorial y realizar consultas en lenguaje natural sobre el contenido. Utiliza modelos de embedding, re-ranking y LLMs para proporcionar respuestas precisas basadas en el contexto de los documentos.
- Ingesta de PDFs: Carga y procesa documentos PDF con detección automática de duplicados
- Chunking inteligente: División del texto en fragmentos semánticamente coherentes
- Embeddings: Generación de vectores semánticos con modelos de HuggingFace
- Búsqueda Híbrida: Combinación de búsqueda vectorial semántica (Qdrant) y léxica (BM25) para máxima precisión
- Re-ranking: Mejora de la relevancia de los resultados antes de la generación
- Generación de respuestas: LLM local con Ollama para respuestas contextualizadas
- Timeout de seguridad: Límite de 5 minutos por consulta para evitar cuelgues
- API REST: Interfaz sencilla con FastAPI
- Logging estructurado: Seguimiento detallado de operaciones
- Deduplicación de documentos: Evita indexar documentos duplicados automáticamente
- FastAPI: Framework web moderno y rápido
- LangChain: Framework para aplicaciones con LLMs
- Qdrant: Base de datos vectorial con persistencia
- BM25: Búsqueda léxica eficiente (índice invertido)
- Sentence Transformers: Modelos de embedding
- Ollama: Servidor de LLMs local
- PDFPlumber: Extracción de texto de PDFs
- Uvicorn: Servidor ASGI
- Python 3.11+
- Ollama instalado y ejecutándose
- Clonar el repositorio
git clone <repository-url>
cd QA_demo- Crear entorno virtual
python -m venv venv
venv\Scripts\activate # Windows- Instalar dependencias
pip install -r requirements.txt- Configurar Ollama y descargar modelo en local
ollama pull gemma2:2bAccede a la documentación Swagger en: http://localhost:8000/docs
- Iniciar el servidor
python main.pyLa API estará disponible en http://localhost:8000
- Ingestar PDFs de la carpeta data/pdfs
Antes de poder realizar consultas, debes ingestar todos los PDFs disponibles en data/pdfs/. El sistema indexa automáticamente los documentos en la búsqueda semántica y léxica.
Puedes hacerlo desde la documentación interactiva (/docs) o con curl:
# Ejemplo: ingestar un PDF
curl -X POST "http://localhost:8000/ingest" \
-F "file=@data/pdfs/documento.pdf"
# Repite para cada PDF en la carpetaNota importante: El sistema detecta automáticamente si un documento ya está indexado y evita duplicados. Cada PDF ingresado amplía el índice vectorial e índice léxico (BM25) existente.
- Realizar consultas
Una vez ingresados los PDFs, ya puedes hacer consultas sobre el contenido desde /docs o con curl:
curl -X POST "http://localhost:8000/query" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "query=Tu pregunta aquí"POST /ingest
Sube un documento PDF para procesarlo e indexarlo en los índices semántico y léxico.
Parámetros:
file: Archivo PDF (multipart/form-data)
Ejemplo con curl:
curl -X POST "http://localhost:8000/ingest" \
-H "accept: application/json" \
-H "Content-Type: multipart/form-data" \
-F "file=@documento.pdf"Respuesta (documento nuevo):
{
"pages": 10,
"chunks": 42,
"status": "Indexado"
}Respuesta (documento ya existente):
{
"status": "Ya indexado",
"message": "El documento 'documento.pdf' ya existe en el índice.",
"pages": 0,
"chunks": 0
}POST /query
Realiza una consulta en lenguaje natural sobre los documentos indexados usando búsqueda híbrida (semántica + léxica).
Parámetros (form-data):
query: Consulta en lenguaje natural (requerido)
Ejemplo con curl:
curl -X POST "http://localhost:8000/query" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "query=¿Cuál es el tema principal del documento?"Respuesta:
{
"answer": "El tema principal del documento es...",
"sources": [
{
"text": "Fragmento relevante del documento...",
"source": "documento.pdf",
"page": 1
},
{
"text": "Otro fragmento relevante...",
"source": "documento.pdf",
"page": 5
}
]
}Notas:
- El timeout máximo para generar una respuesta es de 5 minutos
- La búsqueda híbrida combina resultados semánticos (vectoriales) y léxicos para mayor precisión
- Los chunks se reordenan automáticamente usando un modelo de re-ranking antes de generar la respuesta
QA_demo/
├── main.py # Punto de entrada de la aplicación
├── requirements.txt # Dependencias del proyecto
├── README.md # Este archivo
├── api/
│ └── endpoints/
│ ├── ingest.py # Endpoint para ingestar PDFs
│ └── query.py # Endpoint para consultas
├── core/
│ ├── constants.py # Constantes y configuración
│ ├── dependencies.py # Inyección de dependencias (servicios)
│ ├── exceptions.py # Excepciones personalizadas
│ ├── logging.py # Configuración de logging
│ └── prompts.py # Plantillas de prompts para LLM
├── services/
│ ├── chunking.py # Servicio de dividir texto en chunks
│ ├── embedding.py # Servicio de generación de embeddings
│ ├── ingestion.py # Servicio de ingesta de PDFs
│ ├── llm_service.py # Servicio de modelo de lenguaje (LLM)
│ ├── reranker.py # Servicio de re-ranking de resultados
│ └── search/
│ ├── hybrid.py # Búsqueda híbrida (semántica + léxica)
│ ├── lexical.py # Búsqueda léxica (BM25)
│ └── semantic.py # Búsqueda semántica vectorial (Qdrant)
├── data/
│ ├── pdfs/ # Directorio para almacenar PDFs
│ ├── qdrant_db/ # Base de datos vectorial Qdrant (persistencia)
│ └── eval/ # Datos de evaluación
│ ├── eval.jsonl # Conjunto de evaluación en formato JSONL
│ └── responses/ # Respuestas generadas por el RAG
└── tests/
└── dataset.py # Utilidades para trabajar con datasets
Por defecto, el proyecto utiliza modelos de Sentence Transformers. Puedes configurar el modelo en services/embedding.py.
Este proyecto utiliza gemma2:2b como modelo de lenguaje a través de Ollama.
Asegúrate de tener el modelo descargado:
ollama pull gemma2:2bSi deseas usar otro modelo, configúralo en services/llm_service.py. Otros modelos compatibles:
- llama2
- mistral
- phi
El proyecto utiliza Qdrant como base de datos vectorial. Los datos se persisten en data/qdrant_db/. La configuración se puede modificar en los servicios de búsqueda (services/search/).
Los logs se generan en formato JSON estructurado para facilitar su análisis. La configuración se encuentra en core/logging.py.
El sistema implementa un pipeline RAG completo que combina recuperación de información con generación de respuestas:
-
Ingesta y Procesamiento
- PDFPlumber para extracción de texto: Elegido por su precisión en la extracción de texto estructurado de PDFs
- Chunking estratégico: División del texto en fragmentos semánticamente coherentes para optimizar la recuperación
-
Embedding y Búsqueda Vectorial
- Sentence Transformers: Modelos pre-entrenados de HuggingFace para generar embeddings de alta calidad
- Qdrant: Base de datos vectorial eficiente con búsqueda por similitud, almacenamiento persistente en disco (
data/qdrant_db/) y bajo overhead de latencia - BM25 (Búsqueda Léxica): Índice de recuperación de información basado en frecuencia de términos para búsqueda exacta y por palabra clave
- Búsqueda Híbrida: Combinación inteligente de resultados de búsqueda semántica (Qdrant) y léxica (BM25) para maximizar precisión y recall en la recuperación de documentos
-
Re-ranking
- Capa adicional de refinamiento tras la recuperación inicial, ya que evalúa cada par (query, documento) de forma conjunta, mejorando la relevancia de los documentos seleccionados antes de la generación
- Reduce el ruido y falsos positivos en el contexto enviado al LLM
- Se aplica automáticamente con los 3 mejores documentos tras la búsqueda híbrida
-
Deduplicación de Documentos
- El sistema detecta automáticamente si un documento ya ha sido indexado en ambos servicios (semántico y léxico)
- Evita procesar y almacenar duplicados, ahorrando espacio y tiempo de indexación
- Mantiene la integridad del índice sin duplicados
-
Timeout de Seguridad
- Límite máximo de 5 minutos (300 segundos) por consulta para evitar que el proceso se cuelgue indefinidamente
- Devuelve error 504 (Gateway Timeout) si se excede el tiempo límite
- Essential para mantener la disponibilidad de la API en producción
-
Modelo de Lenguaje
- Ollama con gemma2:2b
- Ventaja: Modelo ligero (2B parámetros) ejecutado localmente sin dependencias externas
- Desventaja: Menor capacidad comparado con modelos más grandes (7B+)
-
Framework y API
- LangChain: Abstracción de alto nivel para pipelines LLM
- FastAPI: Framework moderno con validación automática (Pydantic), documentación OpenAPI y alto rendimiento
- Arquitectura modular: Separación clara entre servicios (ingesta, query, embeddings, etc.)
- Formato JSON para facilitar análisis y monitorización
- En este formato tiene una mejor integración con sistemas de observabilidad.
- Actual: Qdrant con persistencia local, escalable para datasets medianos
- Búsqueda Híbrida: Combinación de estrategias semánticas (vectoriales) y léxicas para mayor precisión
- Modular: Arquitectura desacoplada que permite reemplazar componentes (embeddings, LLM, retriever) fácilmente