Skip to content

Repository files navigation

⚖️ RAG Jurídico

Sistema RAG (Retrieval-Augmented Generation) especializado en consultas sobre normativa jurídica española, con trazabilidad completa y citación de fuentes oficiales.

Python FastAPI License


🚀 Inicio Rápido

Important

El despliegue operativo actual se realiza con docker-compose.production.yml. La guía completa y la asignación de puertos están en DEPLOYMENT.md.

Producción actual

make up
make health

Para ver todos los comandos disponibles: make help

Servicio Puerto del host
Frontend 10100
API (solo localhost) 10101
PostgreSQL compartido 10110
Qdrant HTTP / gRPC 10111 / 10112

La aplicación reutiliza PostgreSQL y Qdrant mediante la red externa docker_iajuridica_network; este Compose no crea ni sustituye sus datos. El proveedor LLM se configura mediante .env y la aplicación no levanta Ollama.

Modo Desarrollo (recomendado para trabajar en el código)

# 1. Arrancar infraestructura (PostgreSQL + Qdrant)
make dev-infra

# 2. Arrancar aplicación con hot-reload
make dev

Acceso:

Ollama opcional para desarrollo local

  1. Arranque: docker compose up -d ollama.

  2. Descargar modelo (una sola vez): docker compose run --rm ollama ollama pull gpt-oss-20b

  3. Verificar salud: curl -f http://localhost:11434/api/tags

Notas:

  • En Docker Compose el backend usa GPT_OSS_BASE_URL=http://ollama:11434 (ya definido en docker-compose.yml).
  • El puerto 11434 queda publicado en el host para uso local (http://localhost:11434).

📋 Tabla de Contenidos


✨ Características

🎯 RAG Jurídico Especializado

  • Responde consultas sobre normativa española con contexto legal preciso
  • Citación automática de fuentes (BOE, DOGC, etc.)
  • Verificación de vigencia y estado de las normas

🔍 Búsqueda Semántica Avanzada

  • Qdrant con embeddings bge-m3-es-legal-tmp-6 (fine-tuned para derecho español)
  • Filtros críticos: estado_vigencia, ambito_codigo, diario_sigla, fecha_vigencia
  • Agregación de chunks por documento con scoring configurable (max/avg/sum/count_weighted)
  • PostgreSQL batch fetch optimizado (WHERE id_documento = ANY($1))

🤖 Generación con LLM

  • Respuestas contextualizadas usando modelos LLM de cualquier proveedor; se da preferencia a modelos alojados localmente cuando sea posible (también se admiten servicios gestionados en la nube).
  • Prompts versionados y optimizados
  • Safety filters y validación de respuestas

📊 Observabilidad Completa

  • Logging estructurado con structlog
  • Métricas de latencia (p50, p90, p99) por etapa
  • Trazabilidad E2E con request_id
  • SLOs y circuit breaker

🛡️ Robustez y Escalabilidad

  • Manejo de errores con reintentos
  • Caché LRU de documentos
  • Pool de conexiones async (PostgreSQL, Qdrant)
  • Rate limiting y validación de entrada

🏗️ Arquitectura

┌─────────────┐
│   Usuario   │
└──────┬──────┘
       │
       v
┌─────────────────────────────────────────────────────┐
│                  API (FastAPI)                      │
│  • /api/answer                                      │
│  • /api/docs/{id}                                   │
│  • /health, /metrics                                │
└─────────────────┬───────────────────────────────────┘
                  │
                  v
┌─────────────────────────────────────────────────────┐
│              RAG Orchestrator                       │
│  Coordina el flujo completo de retrieval → LLM      │
└─────────────────┬───────────────────────────────────┘
                  │
       ┌──────────┴──────────┐
       │                     │
       v                     v
┌──────────────┐      ┌──────────────┐
│  Retrieval   │      │   Context    │
│  • Qdrant    │      │   Builder    │
│  • PostgreSQL│      │  • Whole-doc │
│  • Aggregator│      │  • Budget    │
└──────────────┘      └──────────────┘
       │                     │
       └──────────┬──────────┘
                  │
                  v
         ┌────────────────┐
         │  Prompt Reg.   │
         │  • Templates   │
         │  • Versiones   │
         └────────┬───────┘
                  │
                  v
         ┌────────────────┐
         │  LLM           │
         │  • Retry       │
         │  • Timeout     │
         └────────┬───────┘
                  │
                  v
         ┌────────────────┐
         │  LLM           │
         │  • Streaming   │
         │  • Markdown    │
         └────────┬───────┘
                  │
                  v
         ┌────────────────┐
         │  Streaming API │
         │  • NDJSON      │
         │  • Eventos     │
         └────────────────┘

Proceso RAG Completo (Arquitectura Híbrida)

  1. Usuario pregunta: "What does the law say about vacations?" (en cualquier idioma)

  2. Intervención de Query (LLM con respuesta JSON):

    • LLM analiza la query y devuelve JSON estructurado:
      {"idiomaQuery": "Inglés", "canonical_es": "¿Cuál es la normativa vigente sobre el período vacacional?"}
  3. Embeddings: Convierte canonical_es a vector con bge-m3-es-legal-tmp-6 (1024-dim)

  4. Búsqueda vectorial masiva (Qdrant): Recupera RETRIEVAL_K_CHUNKS_PRIMARY chunks (default: 200) con score_threshold (0.70)

  5. Agregación por documento: Agrupa chunks por id_documento y calcula score_doc (max/avg/sum/count_weighted)

  6. Limitación a candidatos: Selecciona top RETRIEVAL_K_DOCS_CANDIDATES_MAX documentos únicos (default: 50) ordenados por score_doc

  7. Recuperación batch (PostgreSQL): Fetch de textos completos + metadatos + url_html_consolidada solo para los candidatos. Si un documento excede 500.000 caracteres, se reemplaza su texto por una instrucción con el enlace oficial para evitar saturar el contexto.

  8. Selección por presupuesto: WholeDocsBudgetPolicy incluye documentos completos hasta CONTEXT_MAX_CHARS. Si un documento no cabe, se incluye una versión resumida con enlace y relevancia para no perder contexto.

  9. Construcción de contexto: Formatea artículos completos con encabezados y metadatos. Se preparan las fuentes deterministas (extraídas de DB) antes de llamar al LLM.

  10. Generación Streaming (Markdown-First):

    • Template incluye contexto + idioma del usuario.
    • LLM genera la respuesta en Markdown directamente, permitiendo razonamiento jurídico fluido y visualización instantánea (streaming).
  11. Streaming Response (NDJSON): La API emite eventos en tiempo real:

    • source: Lista de fuentes oficiales recuperadas (antes de empezar el texto).
    • token: Chunks de texto del mensaje.
    • meta: Latencias, modelo usado y estadísticas finales.
  12. Respuesta final: El frontend renderiza el Markdown y muestra las tarjetas de fuentes (SourceCard) en cascada.

Ventajas de la arquitectura Híbrida

  • Sin alucinaciones en fuentes: Las citaciones no las inventa el LLM; vienen directamente de la base de datos (PostgreSQL).
  • Máxima rapidez (Streaming): El usuario ve la respuesta palabra a palabra sin esperar al final del parseo JSON.
  • Mejor razonamiento: El LLM no gasta tokens ni atención en mantener una estructura JSON compleja, centrándose en el análisis legal.
  • Fallback Web: Integración opcional con MCP (Tavily) si no hay normativa local que responda a la consulta.

Prefiltrado en Qdrant

Por defecto estado_vigencia="VIGENTE" (Filters.solo_vigentes=True), con filtros adicionales validados en retrieval/filters.py.

Intervención de Query (JSON-First)

Aunque el flujo de respuesta final es Markdown, la normalización de la consulta sigue un patrón JSON-First para garantizar que el motor de búsqueda reciba términos limpios:

class QueryInterventionResult(BaseModel):
  idiomaQuery: str  # Idioma detectado
  canonical_es: str # Query normalizada en español
  palabrasClave: list[str] # Refuerzo para embeddings

Frontend: Botones de Citaciones

El frontend renderiza botones verticales con las fuentes citadas:

┌─────────────────────────────────┐
│ Respuesta del RAG               │     [📄 BOE-A-2015-11430] ← Click abre BOE
│ According to Spanish labor...   │     [📄 BOE-A-2021-5123]  ← Click abre BOE
└─────────────────────────────────┘

Cada botón usa citation.url para window.open(citation.url, '_blank').

📦 Requisitos

Servicios Externos

  • Qdrant (≥1.7): Base de datos vectorial
    • Colección: articulos_juridicos
    • Embeddings: bge-m3-es-legal-tmp-6 (dim=1024, cosine distance)
    • HNSW: ef_construct≈200, m≈48, quantization int8
  • PostgreSQL (≥14): Base de datos relacional
    • Tabla: articles (26 columnas + JSONB metadatos_completos)
    • Pool async con asyncpg
  • LLM (proveedor o local): Credenciales/configuración según el proveedor (por ejemplo, API key, endpoint o configuración local).
    • Preferencia por modelos alojados localmente en el servidor cuando sea posible; también se soportan servicios gestionados en la nube.

Python

  • Python 3.11+
  • pip para gestión de dependencias

Modo Mock vs Real (conmutación sin cambiar código)

Arquitectura Mock-First para desarrollo offline y CI:

  • Desarrollo/CI (mocks):
    • USE_QDRANT_MOCK=true
    • USE_POSTGRES_MOCK=true
    • USE_LLM_MOCK=true
  • Integración/Producción (reales):
    • USE_QDRANT_MOCK=false, USE_POSTGRES_MOCK=false, USE_LLM_MOCK=false
    • Configurar QDRANT_URL, POSTGRES_*/POSTGRES_DSN, LLM_API_KEY (o las credenciales específicas del proveedor que uses)
  • Selección automática vía factories: retrieval/factory.py, llm/factory.py
  • Fixtures de prueba para mocks: tests/fixtures/

🎮 Uso

Inicio con menú interactivo (recomendado)

Para evitar scripts paralelos y facilitar la selección de entorno, inicia con el lanzador interactivo:

python run.py

Opciones disponibles:

    1. Real definitiva (desactiva mocks y arranca en production)
    1. Mock (activa todos los mocks para trabajo offline/CI)
    1. Personalizada (elige qué mocks activar y credenciales/URLs)

El menú aplica las flags USE_* en el proceso sin modificar tu .env.

Uso no interactivo (CLI) — ideal para agentes

Puedes usar el mismo menú sin interacción por línea de comandos:

# Modo mock completo
python run.py --mode mock

# Modo real (producción) con más workers
python run.py --mode real --api-workers 4 --log-level INFO --yes

# Modo personalizado: Qdrant real, Postgres mock, LLM real
python run.py --mode custom \
  --use-qdrant-mock false \
  --use-postgres-mock true \
  --use-llm-mock false \
  --qdrant-url http://localhost:10111 \
  --llm-api-key "$LLM_API_KEY" \
  --api-host 0.0.0.0 --api-port 8000 --api-workers 1 --log-level DEBUG --yes

Sugerencia: exporta credenciales como variables de entorno y pásalas con --yes para evitar prompts.

Iniciar servidor (desarrollo)

./scripts/run_dev.sh
# O manualmente:
uvicorn backend.api.server:app --reload

El servidor estará disponible en: http://localhost:8000

Acceder a la documentación

UI Demo

Abre en el navegador:

frontend/web/index.html

Ejemplo de consulta (curl)

curl -X POST "http://localhost:8000/api/answer" \
  -H "Content-Type: application/json" \
  -d '{
    "query": {
      "text": "¿Qué es el Estatuto de los Trabajadores?",
      "locale": "es"
    },
    "filters": {
      "solo_vigentes": true
    },
    "k": 10
  }'

Respuesta Esperada (Streaming Event: meta)

{
  "text": "El Estatuto de los Trabajadores (Real Decreto Legislativo 2/2015) es la norma fundamental que regula las relaciones laborales en España...",
  "citations": [
    {
      "id_documento": "BOE-A-2015-11430_a1",
      "titulo": "Real Decreto Legislativo 2/2015, de 23 de octubre...",
      "url": "https://www.boe.es/buscar/act.php?id=BOE-A-2015-11430",
      "estado_vigencia": "VIGENTE"
    }
  ],
  "meta": {
    "request_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "OK",
    "latency_ms": {
      "retrieval": 185,
      "total": 2362
    }
  }
}

Nota: Este ejemplo muestra datos ilustrativos. La política WholeDocsBudgetPolicy incluye documentos completos sin truncar (cuando CONTEXT_ALLOW_TRUNCATION=false), por lo que truncated_docs es siempre 0.


🛠️ Desarrollo

Estructura del Proyecto

📁 RAG Jurídico
.
├── 📄 pyproject.toml          # Configuración del proyecto y dependencias
├── 📄 README.md               # Documentación principal
├── 📄 ROADMAP.md              # Plan de implementación detallado
├── 📄 .env.example            # Template de configuración
├── 📄 .gitignore              # Exclusiones de git
│
├── 📁 backend/                # 🎯 Código fuente principal
│   │
│   ├── 📁 api/                # Capa API REST (FastAPI)
│   │   ├── __init__.py            # Módulo API
│   │   ├── server.py              # ✅ FastAPI app + middlewares
│   │   ├── routes_answer.py       # ✅ Endpoint POST /api/answer
│   │   ├── routes_docs.py         # ✅ Endpoint GET /api/docs/{id}
│   │   ├── routes_health.py       # ✅ Endpoints GET /health/*
│   │   ├── routes_suggest.py      # 📝 Archivo vacío (pendiente implementar)
│   │   └── 📁 middleware/         # Middlewares de seguridad
│   │       ├── rate_limit.py      # ✅ Rate limiting por IP
│   │       └── api_key.py         # ✅ Validación de API keys
│   │
│   ├── 📁 core/               # Núcleo del sistema
│   │   ├── __init__.py        # Módulo core
│   │   ├── config.py          # ✅ Settings con Pydantic (359 líneas)
│   │   ├── types.py           # ✅ DTOs (Query, Filters, Hit, Answer...)
│   │   ├── errors.py          # ✅ Excepciones personalizadas
│   │   ├── logging.py         # ✅ Structlog configurado
│   │   └── metrics.py         # ✅ Sistema de métricas p50/p90/p99
│   │
│   ├── 📁 retrieval/          # Búsqueda y recuperación
│   │   ├── __init__.py        # Módulo retrieval
│   │   ├── factory.py         # ✅ Factory pattern (mock/real)
│   │   ├── qdrant_client.py   # ✅ Cliente Qdrant (vector search)
│   │   ├── postgres_store.py  # ✅ Store PostgreSQL (batch fetch)
│   │   ├── aggregator.py      # ✅ Agregación/deduplicación
│   │   ├── embedding_service.py # ✅ Generación embeddings
│   │   ├── filters.py         # ✅ Constructor de filtros Qdrant
│   │   └── 📁 mocks/          # Implementaciones mock
│   │       ├── qdrant_mock.py
│   │       └── postgres_mock.py
│   │
│   ├── 📁 context/            # Construcción de contexto
│   │   ├── __init__.py        # Módulo context
│   │   ├── builder.py         # ✅ Context builder (Whole-doc)
│   │   └── policies.py        # ✅ Políticas de selección
│   │
│   ├── 📁 prompts/            # Gestión de prompts versionados
│   │   ├── __init__.py        # Módulo prompts
│   │   ├── registry.py        # ✅ Registry de templates
│   │   ├── 📁 templates/      # Plantillas de prompts
│   │   │   └── answer_v1.txt  # ✅ Prompt actual (único)
│   │   └── 📁 tests/          # Tests de regresión
│   │       └── test_prompts_regression.yaml
│   │
│   ├── 📁 llm/                # Integración con LLMs
│   │   ├── __init__.py        # Módulo LLM
│   │   ├── factory.py         # ✅ Factory pattern (mock/real)
│   │   ├── llm_client.py      # ✅ Cliente genérico LLM (con retry)
│   │   ├── safety.py          # 📝 Archivo vacío (pendiente)
│   │   └── 📁 mocks/          # Implementaciones mock
│   │       └── llm_mock.py
│   │
│   └── 📁 rag/                # Orquestación RAG
│       ├── __init__.py        # Módulo RAG
│       ├── orchestrator.py    # ✅ Orquestador E2E (Streaming + Determinismo)
│       └── query_processor.py # ✅ Intervención de query (JSON-First)
│
├── 📁 tests/                  # 🧪 Suite de tests
│   ├── __init__.py
│   ├── test_api.py            # ✅ Tests endpoints básicos
│   ├── test_routes_docs.py    # ✅ Tests /api/docs/{id}
│   ├── test_security.py       # ✅ Tests rate limiting + API keys + health
│   ├── test_retrieval.py      # ✅ Tests Qdrant/PostgreSQL
│   ├── test_context.py        # ✅ Tests builder/policies
│   ├── test_rag_flow.py       # ✅ Tests integración E2E (mocks)
│   ├── test_smoke_mock.py     # ✅ Smoke test rápido
│   └── test_integration_real.py # ✅ Tests con servicios reales
│
├── 📁 scripts/                # Scripts de utilidad
│   ├── run_dev.sh             # ✅ Ejecutar servidor en desarrollo
│   ├── load_test.http         # ✅ Ejemplos de peticiones HTTP
│   └── smoke.py               # ✅ Script de smoke test CLI
│
├── 📁 Informes/               # 📊 Documentación y análisis
│   ├── ROADMAP.md             # Plan detallado de implementación
│   └── analisis_variables_flujo.md  # Análisis del flujo de variables
│
├── 📄 run.py                  # ✅ Lanzador interactivo (mock/real/custom)
│
└── 📁 frontend/               # 🎨 UI web (demo)
    └── 📁 web/
        └── index.html         # ✅ Interfaz demo HTML/JS

Leyenda:

  • ✅ = Implementado
  • 🔜 = Pendiente / por mejorar
  • 📁 = Directorio
  • 📄 = Archivo

Nota: La sección muestra el estado actual de los módulos principales y ayudará a orientar el trabajo en F1–F5. Para cambios mayores en la arquitectura, actualiza este bloque con las nuevas rutas o estados.

Comandos Útiles

# === Desarrollo ===
# Ejecutar servidor en modo desarrollo
./scripts/run_dev.sh
# O manualmente: uvicorn backend.api.server:app --reload

# === Testing ===
# Todos los tests
pytest

# Tests con coverage
pytest --cov=backend --cov-report=html

# Suite específica
pytest tests/test_retrieval.py -v

# === Calidad de Código ===
# Linting
ruff check backend/

# Formateo (si usas black)
black backend/

# Type checking
mypy backend/

# === Base de Datos ===
# Verificar estructura PostgreSQL
sudo docker exec -it iajuridica_postgres psql -U iajuridica -d legislacion -c "\d+ articles"

# Contar documentos vigentes
sudo docker exec -it iajuridica_postgres psql -U iajuridica -d legislacion -c "SELECT COUNT(*) FROM articles WHERE estado_vigencia = 'VIGENTE';"

# === Qdrant ===
# Verificar colección
curl http://localhost:10111/collections/articulos_juridicos

# Inspeccionar vector específico
curl -X POST http://localhost:10111/collections/articulos_juridicos/points \
  -H "Content-Type: application/json" \
  -d '{"ids": ["BOA-d-2014-90375_a82_V20140910_c0"], "with_payload": true, "with_vector": false}'

🧪 Testing

# Todos los tests
pytest

# Con coverage (objetivo >80%)
pytest --cov=backend --cov-report=html
# Output HTML en htmlcov/index.html

# Tests por módulo
pytest tests/test_api.py -v           # API endpoints
pytest tests/test_retrieval.py -v     # Qdrant + PostgreSQL
pytest tests/test_context.py -v       # Context builder
pytest tests/test_rag_flow.py -v      # Flujo E2E

# Configuración en pyproject.toml
# - asyncio_mode = "auto" (para async tests)
# - Markers, fixtures y coverage configurados

Test Manual con REST Client

Ver scripts/load_test.http para ejemplos de peticiones HTTP.

Cobertura y htmlcov

Tras ejecutar tests con coverage, abre htmlcov/index.html para inspeccionar rutas de ejecución y líneas no cubiertas. La carpeta htmlcov/ se genera automáticamente como informe visual de cobertura.

Estructura de Tests

  • tests/test_api.py: Endpoints FastAPI con TestClient
  • tests/test_retrieval.py: Qdrant y PostgreSQL (requiere servicios activos)
  • tests/test_context.py: Context builder y políticas de truncado
  • tests/test_rag_flow.py: Integración E2E con mocks

🗺️ Roadmap

Ver Informes/ROADMAP.md para el plan detallado de implementación (924 líneas con estado actualizado al 17/11/2025).

Fases

  • F0: Esqueleto y configuración (COMPLETADO)
  • F1: Conectores Qdrant y PostgreSQL (COMPLETADO)
  • F2: Context builder y prompts (COMPLETADO)
  • F3: LLM y orquestación (COMPLETADO)
  • F4: API y UI (COMPLETADO)
  • F5: Optimización y benchmarks (PENDIENTE)

Estado actual: Sistema completamente funcional con arquitectura Híbrida (JSON-First para normalización, Markdown-First para respuesta). Streaming de tokens y fuentes deterministas habilitados. MCP / Búsqueda web integrada como fallback. Ready for production.


📊 Métricas y Observabilidad

SLOs Objetivo

  • Retrieval p90: < 300ms (búsqueda Qdrant + fetch PostgreSQL)
  • Context building p90: < 50ms
  • LLM generation p90: < 3s
  • Total p90: < 5s
  • Error rate: < 0.5%
  • Uptime: > 99%

Métricas Implementadas

  • Latencias por etapa: retrieval, context_building, llm_generation, postprocess
  • Percentiles: p50, p90, p99 (vía metrics_collector.timer())
  • Counters: total_requests, successful_requests, failed_requests
  • Gauges: active_connections, cache_size

Logging Estructurado

  • Formato: JSON en producción, colores en desarrollo
  • Request ID: UUID único por petición para trazabilidad E2E
  • Contexto automático: app, version, environment, request_id
  • Redacción: API keys, passwords automáticamente redactados

Monitoreo

# Health check
curl http://localhost:8000/health

# Métricas agregadas
curl http://localhost:8000/metrics

# Ver logs estructurados
# En producción: JSON parseables por ELK, Datadog, etc.
# En desarrollo: Logs con colores y formato legible

About

Ensamblador de procesos. Conectamos Qdrant, PostgreSQL, LLM, Prompts etc...

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages