Documentación técnica
Motor de Evaluación Normativa
Motor autónomo RAG que evalúa documentos contra normativas UNE, RGPD, DGA y Data Act. Devuelve informes de auditoría estructurados con score, gaps y acciones correctivas vía API HTTP/SSE.
Quickstart
Levanta el motor completo desde cero en cuatro pasos.
Requisitos previos
- Docker + Docker Compose
- Python 3.12 (para indexador y parser)
- API key del proveedor de embeddings (configurable)
- API key del proveedor LLM elegido
- Tesseract + poppler (solo si hay que reprocesar PDFs)
1 — Arranque del motor RAG
cd IA-data-x
cp .env.example .env # rellenar DATABASE_URL y las API keys del proveedor elegido
bash start.sh # levanta postgres + ia-service con healthchecks
curl http://localhost:8001/health # o el HOST:PORT configurado Respuesta esperada:
{
"status": "ok",
"db": "connected",
"chunks_indexados": 0,
"models": { "embed": "<id>", "llm": "<id>" },
"version": "0.3.0"
} 2 — Indexar normativas
cd IA-data-x
python3 indexar_criterios.py # interactivo: confirma y empieza
# o sin confirmación:
python3 indexar_criterios.py --yes Lee normativas-parser/output/eval_criteria.json y publica chunks vía POST /indexar.
3 — (Opcional) Reprocesar normativas desde PDFs
Solo si cambia el corpus normativo. Pipeline completo en normativas-parser/:
cd normativas-parser
bash setup.sh # venv + dependencias
python3 1.extract_pdfs.py
python3 2.clean_extracted.py
python3 3.pdf_parser.py
python3 4.llm_cleaner.py --model --vision-model
python3 5.generate_eval_json.py --model
cd ../IA-data-x && python3 indexar_criterios.py --yes 4 — Primera evaluación
curl -N -X POST $BASE_URL/validar-archivo \
-F "file=@mi_politica.pdf" \
-F "embed_api_key=$EMBED_API_KEY" \
-F "llm_api_key=$LLM_API_KEY" \
-F "proveedor=" resultado contiene el informe JSON completo.
Visión general
Dos módulos autocontenidos que cooperan vía API HTTP.
normativas-parser/
Pipeline de ingestión y enriquecimiento de normativas UNE desde PDFs. Produce eval_criteria.json.
IA-data-x/
Microservicio RAG (FastAPI :8001). Indexa criterios y evalúa documentos del usuario via PostgreSQL + pgvector.
Cadena de valor
FASE OFFLINE
FASE ONLINE
Casos de uso
- Auditor sube un SLA — informe evaluado contra UNE-0087 y DGA, cláusulas citadas textualmente, gaps tipificados y acciones correctivas.
- Responsable de gobierno del dato sube su Política de Datos — evaluación contra UNE-0077 y UNE-0085, score por dimensión, dictamen global.
- Equipo legal sube un contrato de tratamiento — detección automática de tipo (encargado RGPD), criterios filtrados al rol del firmante, recomendaciones priorizadas.
- Consultor sube diagnóstico de madurez — evaluación arquetipo "evidencia", verificación de que cada hallazgo es concreto, referenciado y verificable.
- Integración programática —
POST /validar-archivocon fichero → stream SSE + informe JSON consumible desde cualquier plataforma.
Módulo normativas-parser
Pipeline determinístico que convierte PDFs de normas UNE en el catálogo de criterios RAG.
Corpus de PDFs (ejemplo)
El parser acepta cualquier normativa en PDF con estructura de secciones. El siguiente corpus es un ejemplo de uso con normas UNE; cualquier otro conjunto de PDFs sigue el mismo pipeline.
| Archivo | Norma | Año |
|---|---|---|
Especificacion_UNE_0077.pdf | Gobierno del Dato | 2023 |
Especificacion_UNE_0078.pdf | Gestión del Dato | 2023 |
Especificacion_UNE_0079.pdf | Gestión de la Calidad del Dato | 2023 |
Especificacion_UNE_0080.pdf | Guía de evaluación del Gobierno, Gestión y Calidad | 2023 |
Especificacion_UNE_0081.pdf | Guía de evaluación de la Calidad de un Conjunto de Datos | 2023 |
Especificacion_UNE_0085.pdf | Guía de implantación del Gobierno del Dato | 2024 |
Especificacion_UNE_0087.pdf | Compartición y espacios de datos | 2025 |
Pipeline de 5 etapas
Cada etapa es reentrante: si se interrumpe, reanuda donde quedó detectando qué documentos ya tienen salida válida.
1.extract_pdfs.py Sin LLMExtracción PDF → TXT
Abre PDFs con pdfplumber. Detecta páginas escaneadas y aplica OCR (Tesseract, español). Extrae tablas como bloques [TABLA:CSV] e imágenes con placeholder [IMG:NNN]. Etiqueta ruido con [RUIDO:...].
→ output/*.txt
2.clean_extracted.py Sin LLMSaneamiento heurístico
Elimina líneas [RUIDO], normaliza codificación (NFC), elimina duplicados y páginas en blanco. Convierte tablas CSV a Markdown para mejorar procesamiento LLM posterior.
→ output/cleaned/*.txt
3.pdf_parser.py Sin LLMSegmentación a JSON estructurado
Convierte el TXT saneado en JSON con esquema fijo: id, numero, año, titulo y una lista plana de secciones con codigo, nivel, parent_codigo, contenido, es_anexo.
→ output/json/*.json
4.llm_cleaner.py LLM texto + visiónLimpieza con LLM y visión
Cuatro sub-tareas: A) limpieza de prosa (sin reescribir), B) reconstrucción de tablas Markdown deterioradas, C) clasificación de imágenes (relevante/decorativa), D) extracción de tablas desde imágenes. Flags: --dry-run, --skip-images, --doc <id>.
→ output/json/cleaned/*.json
5.generate_eval_json.py LLM + embeddingsGeneración de criterios RAG
Etapa crítica. Pre-computa embeddings de todas las secciones, extrae criterios con razonamiento LLM (detección de tablas TAREA/ENTRADAS/SALIDAS + lenguaje normativo prescriptivo), calcula referencias cruzadas por similitud coseno entre normas (sin LLM), y valida que cada criterio tenga ≥15 palabras y un verbo normativo.
→ output/eval_criteria.json
Modelos IA
El motor es agnóstico al proveedor: acepta cualquier modelo con interfaz Chat Completions. El modelo se pasa por parámetro en cada llamada — no hay proveedor fijo.
Criterios de selección
| Aspecto | Qué buscar |
|---|---|
| Contexto | Cuanto mayor, mejor: documentos normativos pueden ser extensos. Modelos con 100K+ tokens evitan truncaciones. |
| Visión (script 4) | Necesaria si el PDF contiene tablas como imagen. Cualquier modelo multimodal es compatible. |
| Calidad de seguimiento | El prompt exige JSON estructurado estricto. Modelos con bajo seguimiento de instrucciones generan JSON roto. |
| Coste | La evaluación puede suponer cientos de miles de tokens por documento. Considerar precio input/output según volumen. |
Recetas rápidas
# Pasar el modelo deseado como argumento
python3 4.llm_cleaner.py --model --vision-model
python3 5.generate_eval_json.py --model Formato eval_criteria.json
{
"version": "2.0",
"total_criterios": 42,
"criterios": [
{
"id": "77.1",
"une_fuente": "une-0077-2023-gobierno-del-dato",
"dimension_codigo": "5.1",
"dimension_nombre": "Política de gobierno del dato",
"criterio": "Aprobar y publicar una política formal de gobierno del dato",
"tipo_documento_afectado": "Política de Gobierno del Dato",
"descripcion": "La norma establece que la organización debe disponer…",
"evidencias_requeridas": "Política aprobada, acta de aprobación por dirección",
"resultado_esperado": "Documento de política publicado y accesible",
"posible_evidencia": "Política firmada, publicación en intranet, acta del comité",
"medidas_correctivas": "Redactar y aprobar la política con patrocinio directivo",
"origen": "LENGUAJE_NORMATIVO",
"referencias_normativas": [...],
"referencias": [...]
}
]
} descripcion, evidencias_requeridas y medidas_correctivas alimentan el prompt LLM. tipo_documento_afectado es la clave de filtrado contextual.
Puente parser → RAG: indexar_criterios.py
Único vínculo entre la fase offline y la online. Produce chunks RAG y los envía a POST /indexar.
Por cada criterio se construyen tres tipos de chunks:
Intro de la norma
Una sola vez por fuente. Incluye motivación, objeto, alcance y concepto base. Etiquetado con normative_force: "CONTEXT".
Chunk principal del criterio
Contiene norma, dimensión, criterio, evidencias, medidas correctivas. Se enriquece con keywords semánticos (LLM ligero) para potenciar BM25. La fuerza normativa se infiere por regex: MUST vs. SHOULD.
Chunks de referencia normativa
Uno por cada referencia con extracto no vacío. Permiten que el LLM cite el texto original de la norma ("Contextual Retrieval").
El script aplica un rate limit de 1.1 s entre peticiones y reintentos con backoff exponencial (MAX_REINTENTOS=3, BACKOFF_BASE=5.0). Tras el primer pase, hace una pasada de recuperación para chunks fallidos.
POST /indexar es idempotente: cada chunk se identifica por SHA-256 del contenido. Re-indexar produce action: "updated" sin duplicar.
Módulo IA-data-x
Stack y arquitectura
| Componente | Tecnología |
|---|---|
| Runtime | Python 3.12 |
| Framework HTTP | FastAPI 0.115.x + Uvicorn |
| Base de datos | PostgreSQL 16 + extensión pgvector |
| Pool de conexiones | asyncpg (min 2, max 10) |
| Embeddings | Vectores de 1024 dimensiones (modelo configurable) |
| Índice vectorial | HNSW coseno (m=16, ef_construction=64) |
| Índice léxico | GIN sobre tsvector español (BM25) |
| Modelos LLM | Configurables por variable de entorno o por petición |
| Contenedores | Docker Compose (postgres + ia-service) |
| Streaming | Server-Sent Events (SSE) |
El servicio escucha en el puerto 8001. La base de datos en el puerto 5432 del contenedor (mapeado a 5434 en algunos entornos host).
Modelo de datos
Base de datos: ia_normativas.
Tabla normativas — criterios indexados:
| Campo | Tipo | Descripción |
|---|---|---|
id | SERIAL PK | |
contenido | TEXT | Texto completo del chunk |
fuente | VARCHAR(200) | Norma (ej. UNE-0085-2024) |
seccion | VARCHAR(100) | Código de sección o id de criterio |
dimension | TEXT | Dimensión normativa |
hash | VARCHAR(64) UNIQUE | SHA-256 — clave de deduplicación |
embedding | VECTOR(1024) | Embedding semántico |
tsv | TSVECTOR GENERATED | Full-text search español |
tipo_documento_afectado | TEXT | Tipo de documento que evalúa este criterio |
keywords | TEXT | Keywords semánticos para BM25 mejorado |
normative_force | VARCHAR(10) | MUST / SHOULD / MAY / CONTEXT |
Tabla validaciones — log de auditoría:
| Campo | Tipo |
|---|---|
documento_hash | VARCHAR(64) — SHA-256 del documento evaluado |
fuente_norma | VARCHAR(200) |
resultado | JSONB — informe completo |
chunks_recuperados | JSONB — chunks RAG + scores (trazabilidad) |
tokens_usados | INT |
ts_validacion | TIMESTAMP |
Mapa de módulos core/
Variables de configuración clave
| Variable | Default | Significado |
|---|---|---|
DATABASE_URL | obligatoria | Conexión PostgreSQL |
EMBED_MODEL | configurable | Modelo de embeddings (debe generar vectores de 1024 dims por defecto) |
DOC_RAG_THRESHOLD | 15000 | Chars mínimos para activar RAG bidireccional |
MAX_UPLOAD_CHARS | 200000 | Límite de texto procesado (truncación inteligente) |
MODO_VALIDACION | rag | rag (pgvector) | checklist (XLSX) |
RAG_MODE | per_norma | full (todos los criterios) | per_norma (top-K) |
RAG_K_POR_FUENTE | 50 | Top-K cuando RAG_MODE=per_norma |
Sistema de arquetipos
El motor clasifica cada documento en un arquetipo. El arquetipo determina la regla fundamental que el LLM aplica para dictaminar cumplimiento — no es lo mismo evaluar un contrato que una política o una evidencia técnica.
| Arquetipo | Modalidades | Regla fundamental |
|---|---|---|
contractual | participante, operador, proveedor_servicio, sla, nda | CUMPLE si establece obligación jurídica (no se exige operatividad) |
tecnico | log_accesos, log_auditoria, config_infraestructura, captura_monitor | CUMPLE si evidencia técnica demuestra control activo con resultados reales |
politica | politica_datos, procedimiento, manual | MENCIÓN ≠ ACREDITACIÓN: solo CUMPLE si implantado operativamente |
estrategico | plan_estrategico, acta_comite, programa_gobierno | CUMPLE si establece qué + quién + cómo (requiere accionabilidad) |
evidencia | informe_auditoria, certificacion, diagnostico_madurez | CUMPLE si evidencia concreta, referenciada y verificable |
catalogo | catalogo_activos, diccionario_datos, inventario_sistemas | CUMPLE si elemento documentado con detalle operativamente útil |
arquitectura | arquitectura_tecnica, diseno_api, flujo_datos | CUMPLE si especifica componente con suficiente detalle implementable |
requisitos | dmp, especificacion, historia_usuario | CUMPLE si existe requisito + criterio de aceptación verificable |
Flujo completo de una evaluación
Lo que ocurre cuando un usuario sube un fichero a POST /validar-archivo.
5.1 — Carga y normalización del documento
Petición entrante: multipart/form-data.
| Campo | Obligatorio | Descripción |
|---|---|---|
file | Sí | PDF, DOCX o TXT — máx. 50 MB |
fuente_norma | No | Norma concreta (si se omite, se auto-detecta) |
fuentes_norma | No | Array JSON o CSV de fuentes — evalúa contra varias normas |
top_k | No | Top-K de pasajes RAG por criterio (default 10, clamp 1–50) |
contexto | No | Texto adicional del analista, se antepone al documento |
proveedor | No | Identificador del modelo LLM a usar |
embed_api_key | Sí | API key del proveedor de embeddings configurado |
llm_api_key | Sí | API key del proveedor LLM elegido |
modo_validacion | No | rag | checklist |
user_id | No | ID de usuario para logging por carpeta |
Pasos de preparación en main.py: validar_archivo:
- Control de tamaño → HTTP 413 si > 50 MB
- Extracción de texto (PDF:
pypdf; DOCX:python-docx; TXT: UTF-8 con fallback latin-1) - Deduplicación de párrafos (
_deduplicar_parrafos) - Detección de subdocumentos con inyección de marcadores
═══ SUBDOCUMENTO ═══ - Concatenación del contexto del analista
- Truncación inteligente si > 200 000 chars: 50% inicio + 30% centro + 20% final
- Validación mínima: si len < 50 → HTTP 422
- Logging completo del texto que entrará al pipeline
5.2 — Embedding y detección de norma
Se computa el embedding del documento con caché LRU (2048 entradas, clave MD5). Con ese vector se ejecuta la detección de norma aplicable si el usuario no especificó fuente_norma:
ORDER BY
0.7 * (1 - (embedding <=> $1::vector)) +
0.3 * ts_rank(tsv, plainto_tsquery('spanish', $2))
DESC LIMIT 40 Los scores se agregan por fuente y se normalizan a porcentaje de relevancia, devolviendo las N normativas más relevantes.
5.3 — Clasificación documental jerárquica
Tres niveles en cascada:
Nivel 1
Tipo de documento (LLM)
El LLM recibe un preview de 4-6 K chars y elige entre 21 tipos conocidos: Política de Gobierno del Dato, SLA, NDA, Catálogo de activos, Informe de auditoría, Diagnóstico de madurez, Arquitectura técnica…
Nivel 2
Arquetipo (heurístico)
Mapeo por overlap de keywords a uno de 8 arquetipos: contractual, tecnico, politica, estrategico, evidencia, catalogo, arquitectura, requisitos. Cada arquetipo define su regla fundamental de evaluación.
Nivel 3
Modalidad
Afina dentro del arquetipo (ej. dentro de contractual: sla, nda, participante). Ajusta excluir_dims y añade contexto específico al system prompt.
5.4 — Selección de criterios
Filtros en cascada, todos con fallback automático al conjunto previo si el resultado queda vacío:
_filtrar_criterios_por_norma— pre-filtra por prefijo de fuente UNE según tipo_filtrar_criterios_por_tipo— filtra portipo_documento_afectado_filtrar_dimensiones_por_arquetipo— elimina criterios enexcluir_dims_filtrar_dimensiones_por_modalidad— filtrado fino adicional
5.5 — RAG bidireccional
Activo si el documento supera DOC_RAG_THRESHOLD (15 000 chars). La clave: el score no es solo query→corpus, sino criterio↔chunks del documento:
- Chunking del documento: 1 500 chars, overlap 200, corte en saltos de línea
- Embeddings en batch (caché LRU compartida)
- Ajuste dinámico de top-K: >40 criterios → K=1; >20 → K=2
- BM25-lite en memoria (k1=1.5, b=0.75) con keywords del criterio
- Score híbrido: coseno ponderado + BM25-lite
- Top-K pasajes por criterio, recortados a 1 200 chars
- Fallback robusto: si fallan embeddings → primeros 15 chunks
5.6 — Evaluación LLM por batches
Batches de EVAL_BATCH_SIZE = 25 criterios por llamada LLM. Por cada batch:
- Construcción del prompt con regla del arquetipo + contexto de modalidad + criterios + pasajes RAG
- Llamada al LLM: max_tokens 32 K, timeout 180 s, temperatura 0.1–0.6 según modelo
- Reparación del JSON: extrae bloques markdown, corrige comas finales, cierra strings truncados, recupera objeto a objeto como último recurso
- Normalización de
resultadoa valores canónicos:Cumple/Parcial/No cumple/Ausencia - Segunda pasada de completion si quedan criterios sin respuesta válida
- Deduplicación por
criterio_codigo
Esquema JSON de respuesta por criterio:
{
"criterio_codigo": "77.1",
"criterio_nombre": "...",
"dimension": "...",
"fuente": "UNE-0077:2023",
"resultado": "Cumple" | "Parcial" | "No cumple" | "Ausencia",
"justificacion": "...",
"razonamiento": "...",
"evidencias": [{ "cita": "...", "ubicacion": "...", "comentario": "..." }],
"gap_type": "PARTIAL_COVERAGE" | "MISSING" | "MISALIGNED" | null,
"gap_detectado": "...",
"accion_requerida": "...",
"confianza": "alta" | "media" | "baja",
"lecturas_alternativas_descartadas": [...]
} 5.7 — Cálculo determinístico de campos derivados
Sin LLM. Pura aritmética sobre los resultados agregados:
Score ponderado = (suma de scores / nº criterios) × 100. Dictamen global:
| Dictamen | Condición |
|---|---|
| Conforme | Score ≥ 80 y cero dimensiones "No superada" |
| Parcialmente conforme | Score ≥ 50 |
| No conforme | Score < 50 |
5.8 — Persistencia, trazabilidad y SSE
Eventos SSE durante una evaluación típica:
event: paso data: {"paso":"embed","msg":"Generando embedding..."}
event: norma_inicio data: {"fuente":"UNE-0085-2024","idx":1,"total":1}
event: paso data: {"paso":"clasificar","msg":"Clasificando documento..."}
event: paso data: {"paso":"filtrar","msg":"Filtrando criterios..."}
event: paso data: {"paso":"rag","msg":"Recuperando pasajes RAG..."}
event: paso data: {"paso":"llm","msg":"Evaluando criterios 1–25 de 87..."}
event: paso data: {"paso":"llm","msg":"Evaluando criterios 26–50 de 87..."}
...
event: resultado data: { ...informe JSON completo... }
event: todo_fin data: {"total":1} Trazabilidad en tres niveles:
- BD (
validaciones.chunks_recuperados): IDs y scores de chunks RAG usados - Log de servicio (
ia-service.log): líneas[TEXTOCLIENTE],[EMBED],[SEARCH],[PROMPT],[LLM-RESPONSE] - Log por evaluación (
/app/logs/ia/evaluations/<user_id>/): JSON íntegro del informe
API Reference
Todos los endpoints del servicio. El host y puerto dependen de tu configuración de despliegue (por defecto localhost:8001 en desarrollo local).
| Método | Ruta | Función |
|---|---|---|
| GET | /health | Estado del servicio, conectividad DB, conteo de chunks, modelos en uso |
| GET | /proveedores | Lista de modelos LLM disponibles para la UI |
| GET | /normas | Normativas indexadas, totales por fuente y dimensiones |
| GET | /detectar-norma?texto=… | Ranking de normas más relevantes para un fragmento |
| GET | /explorar | Explorador paginado de criterios (filtros por fuente, dimensión, keyword) |
| POST | /indexar | Upsert de un chunk por hash SHA-256 |
| POST | /buscar | Búsqueda híbrida 70% coseno + 30% BM25 con filtros |
| POST | /consultar | Q&A conversacional sobre normativas (RAG sobre chunks) |
| POST | /validar | Pipeline RAG completo síncrono |
| POST | /validar-stream | Pipeline con streaming SSE (texto plano en body) |
| POST | /extraer-texto | Extrae texto plano de PDF/DOCX/TXT sin evaluar |
| POST | /validar-archivo | Endpoint principal. multipart/form-data con fichero + SSE |
| POST | /recomendar | Genera recomendaciones de mejora a partir de criterios fallados |
Ejemplos de uso (curl)
En los ejemplos siguientes, sustituye $BASE_URL por la dirección de tu instancia.
Health check
curl -s $BASE_URL/health | jq Listar normativas indexadas
curl -s $BASE_URL/normas | jq Buscar criterios por keyword
curl -s -X POST "$BASE_URL/buscar?query=catalogación+activos&top_k=5" | jq Detectar norma aplicable
curl -s "$BASE_URL/detectar-norma?texto=la%20organización%20designará%20un%20Data%20Owner" | jq Validación síncrona (texto plano)
curl -X POST $BASE_URL/validar \
-H 'Content-Type: application/json' \
-d '{
"texto": "La organización designa…",
"fuente_norma": "nombre-de-la-norma",
"proveedor": "tu-modelo",
"embed_api_key": "sk-…",
"llm_api_key": "sk-…"
}' | jq Validación de fichero con streaming SSE
curl -N -X POST $BASE_URL/validar-archivo \
-F "file=@politica_datos.pdf" \
-F 'fuentes_norma=["norma-1","norma-2"]' \
-F "proveedor=tu-modelo" \
-F "top_k=10" \
-F "contexto=Empresa del sector financiero, 2000 empleados" \
-F "embed_api_key=$EMBED_API_KEY" \
-F "llm_api_key=$LLM_API_KEY" \
-F "user_id=auditor-42" Generar recomendaciones
curl -X POST $BASE_URL/recomendar \
-H 'Content-Type: application/json' \
-d '{
"eval_name": "Evaluación Q1",
"criterios": [
{"codigo":"Gob.1","descripcion":"OGD no constituida","dimension":"Gobernanza"}
],
"top_k": 5
}' | jq Códigos de error HTTP
| Endpoint | Código | Cuándo |
|---|---|---|
/validar-archivo | 413 | Fichero > 50 MB |
/validar-archivo | 422 | No se pudo extraer texto / texto < 50 chars |
/validar-archivo | 401 | API key de embeddings o LLM ausente o inválida |
/indexar | 400 | contenido vacío |
| Cualquier endpoint LLM | 502 | Timeout o respuesta no parseable tras reintentos |
| Cualquier endpoint BD | 503 | Pool de conexiones caído (auto-reintenta) |
| Global | 500 | Error inesperado — revisar ia-service.log |
Glosario
UNE-0085-2024)contractual: SLA, NDA, encargo)Cumple / Parcial / No cumple / AusenciaPARTIAL_COVERAGE (cobertura incompleta) / MISSING (ausencia total) / MISALIGNED (presente pero erróneo)Rendimiento y costes
Latencias típicas
| Operación | Tiempo orientativo |
|---|---|
/indexar un chunk | 200–400 ms |
| Indexar eval_criteria.json completo (~300 chunks) | ~6–8 min (rate-limit 1.1 s) |
/health | < 50 ms |
/buscar | 200–500 ms |
| Embedding de documento 50K chars | 1–2 s |
| Detección de norma | 300–700 ms |
| RAG bidireccional (M chunks × N criterios) | 2–5 s |
| LLM por batch de 25 criterios | 15–40 s (según modelo) |
| Evaluación completa (1 norma, corpus estándar) | 60–180 s |
| Evaluación contra 3 normas | 3–7 min |
Caché
LRU de embeddings en proceso (max=2048). Hit-ratio típico tras precalentar: 60–80 % en cargas similares. cache_stats() expuesta en /health.
Costes orientativos
- Embedding de 1 documento de 50K chars → ~40K tokens (despreciable)
- Evaluación completa con LLM medio → ~120K–200K tokens input + ~30K output (varía con el número de criterios)
- Indexar el corpus completo de normativas → ~600K tokens (parser etapa 5, una sola vez)
Tuning
| Variable | Efecto |
|---|---|
RAG_MODE=per_norma + RAG_K_POR_FUENTE=25 | Prompts más cortos, evaluación más rápida y barata, menos cobertura |
EVAL_BATCH_SIZE (en pipeline.py) | Más grande = menos llamadas LLM pero más riesgo de truncación |
DOC_RAG_THRESHOLD bajo | Activa RAG incluso para docs cortos (más preciso pero más latencia) |
| Pool asyncpg min/max | Ampliar para concurrencia alta |
Seguridad y privacidad
| Aspecto | Implementación |
|---|---|
| API keys del usuario | Recibidas por petición, nunca persistidas en BD. El servicio no almacena claves entre peticiones. |
| Documento subido | Hasheado con SHA-256. El texto completo no se almacena en BD — solo el informe como JSONB. |
| Logs a disco | Carpeta por user_id en /app/logs/ia/evaluations/. Contiene el informe pero no la API key. |
| Logs de servicio | [TEXTOCLIENTE] registra el texto enviado al LLM. En producción: LOG_LEVEL=warning. |
| Validación de inputs | Tamaño 50 MB (anti-DoS), tipos MIME validados, JSON parseado defensivamente. |
| Inyección SQL | Todas las consultas usan parámetros asyncpg — sin concatenación. |
| CORS | Configurable vía CORS_ORIGINS. Default * — cambiar en producción. |
| TLS | El servicio escucha HTTP plano. TLS debe terminarse en reverse proxy (Caddy, nginx). |
Limitaciones conocidas
- Tamaño máximo de fichero: 50 MB (HTTP 413)
- Texto procesado por evaluación: 200 000 chars (configurable). Por encima → truncación inteligente 50/30/20 %
- Ventana de contexto LLM: depende del modelo elegido. El motor parte criterios en batches de 25 pero el documento + prompt deben caber
- Idioma: optimizado para español (
tsvector('spanish'), prompts en castellano). Documentos en otros idiomas funcionan pero la calidad de BM25 baja - PDFs escaneados:
/validar-archivousapypdfque requiere texto extraíble. Un PDF escaneado producirá texto vacío y HTTP 422 (el OCR solo está en el pipeline parser) - Dependencia de APIs externas: requiere conectividad al servicio de embeddings y al LLM. Sin red → sin evaluaciones
- Determinismo: la evaluación no es 100% reproducible entre ejecuciones (LLM con temperature > 0). Para auditoría → guardar
chunks_recuperados+documento_hash - Evaluación incremental: cada
/validar-archivoes independiente. Se puede simular pasando ambos ficheros concatenados o usandocontextopara anexos cortos
Troubleshooting
| Síntoma | Causa probable | Solución |
|---|---|---|
chunks_indexados: 0 | BD vacía | python3 indexar_criterios.py (tras generar eval_criteria.json) |
KeyError: DATABASE_URL | .env no cargado | Verificar .env en raíz del proyecto |
401 Unauthorized | API key inválida | Regenerar la clave del proveedor correspondiente |
Connection refused 5434 | PostgreSQL no arrancado | bash start.sh |
| BD muestra criterios pobres | eval_criteria.json no generado | Ejecutar scripts 1-5 en normativas-parser/ |
| Respuesta LLM cortada | Timeout o cuota agotada | Cambiar proveedor en selector de modelo |
| LLM devuelve JSON roto | Modelo débil o prompt demasiado largo | Cambiar a un modelo con mejor seguimiento de instrucciones; reducir EVAL_BATCH_SIZE |
| RAG no recupera pasajes relevantes | Criterios indexados sin contexto rico | Re-indexar con indexar_criterios.py (no indexar.py legacy) |
| Evaluación muy lenta | Modelo LLM lento o batches grandes | Reducir EVAL_BATCH_SIZE o cambiar a un modelo más rápido |
| 429 en embeddings al indexar | Rate limit del proveedor | Normal — el script gestiona automáticamente con backoff. Esperar o pasar a un tier superior. |
| Pocos criterios extraídos (script 5) | Tablas TAREA/ENTRADAS/SALIDAS mal formateadas | Reprocesar con un modelo de visión de mayor calidad: python3 4.llm_cleaner.py --vision-model <modelo> --doc nombre-* |
| Score inesperadamente bajo | Documento fuera de alcance normativo | Verificar arquetipo detectado en logs; ajustar fuente_norma |
Diagnóstico rápido
# Health completo (reemplaza HOST:PORT por tu endpoint)
curl -s http://HOST:PORT/health | python3 -m json.tool
# Logs en tiempo real
docker logs -f ia-service
# Chunks por fuente
curl -s http://HOST:PORT/normas | python3 -c "
import json,sys; d=json.load(sys.stdin)
[print(f'{x[\"fuente\"]}: {x[\"total_criterios\"]} criterios') for x in d]
"
# Re-indexar desde cero
cd IA-data-x && ./start-all.sh --reset && python3 indexar_criterios.py --yes ./start-all.sh --reset borra la BD y la recrea. Luego re-indexar con python3 indexar_criterios.py --yes.
FAQ
Sí. El motor expone una API HTTP completa; basta con POST /validar-archivo o POST /validar. La web es solo uno de sus clientes posibles.
Sí, la primera vez. Una vez generado eval_criteria.json (manual o con el parser), python3 indexar_criterios.py puebla la BD. Re-indexar solo si cambian las normas.
El motor es agnóstico. Modelos con ventana de contexto grande (≥1M tokens) y buen razonamiento funcionan mejor para evaluación normativa. Modelos rápidos sirven para tareas auxiliares (detección de tipo, keywords).
Sí. El campo criterios_json en /validar-archivo permite inyectar criterios externos. Saltan el filtrado y van directos al LLM.
rag: producción normal, criterios desde pgvector + JSON, flexible y enriquecido. checklist: catálogo fijo en XLSX, para auditorías estandarizadas con criterios cerrados, no usa pgvector.
Recomendado 1–3 por petición. Cada norma multiplica latencia y coste en tokens.
El motor detecta documentos "fuera de alcance" (_verificar_fuera_de_alcance) y devuelve respuesta sin evaluar criterios irrelevantes.
No en el motor en sí. Las API keys del usuario son las que limitan. Se recomienda un proxy con rate limit por delante en producción.