Trazabilidad de evidencia y Control Anti-alucinación para convocatorias IEEE. Sistema multiagente que abstrae convocatorias IEEE (URL, PDF o HTML) y produce entregables verificables: informe PDF, deck Marp, ideas de proyectos y correo ejecutivo.
- Resumen
- Características clave
- Arquitectura
- Stack tecnológico
- Estructura del repositorio
- Requisitos previos
- Instalación
- Configuración
- Uso
- Endpoints de la API
- Pruebas
- Verificación de calidad (lint y tipos)
- Modelos LLM y presupuestos de tokens
- Reglas de negocio no negociables
- Roadmap por fases
- Contribución
- Licencia
TRACE-IEEE es un sistema multiagente construido sobre LangGraph que recibe una convocatoria IEEE (URL, PDF o HTML), la rastrea, canonicaliza, le aplica OCR si es necesario, extrae evidencia trazable desde las fuentes oficiales, valida reclamos con un estricto modo anti-alucinación, y genera cuatro entregables:
- Informe PDF (desde Markdown vía WeasyPrint).
- Deck de presentación (Marp).
- Ideas de proyectos alineadas a la convocatoria (modo híbrido creativo + endurecedor conservador).
- Correo ejecutivo resumiendo la viabilidad.
Cada reclamo (Claim) de tipo fact debe estar respaldado por evidence_ids que apunten a una fuente official verificada. El sistema se detiene en una puerta de aprobación humana antes del empaquetado final: la revisión es por archivo, no por paquete, y solo se conserva la última versión activa de cada entregable.
Diseñado para presentación en el Congreso Colombiano de Computación (20CCC).
- Multiagente orquestado con LangGraph (
app/graphs/orchestrator.py): 14 nodos en flujo lineal con puerta de aprobación interactiva (interrupt_before). - Trazabilidad de evidencia: cada
EvidenceincluyeSpan(start, end),category,confidence,verifierysource_id. ElSourceManifestdiferencia fuentesofficialvsexternal_contextual. - Anti-alucinación por diseño: hooks de validación que rechazan
factsin evidencia oficial, y unprior_errorsSUMMARY que se inyecta en cada reintento del LLM. - Modos por agente (
NODE_PHASEenapp/schemas/models.py):conservative→ analistas y generadoreshybrid→ soloideas_generator(creativo)neutral→ infraestructura (intake, crawler, ocr, packager)
- Human-in-the-loop: parada obligatoria en
approval_gatecon aprobación/rechazo por archivo, persistencia del historial de revisiones y errores previos. - Stack 100% OSS local: PostgreSQL 16, tesseract + ocrmypdf, WeasyPrint, Marp, Streamlit. Sin nube para archivos, sin dependencias propietarias cerradas.
- LLM NVIDIA free tier con fallback automático a
MockLLMClientsi no hay API key configurada. - MCP servers por dominio para aislar reglas deterministas del razonamiento LLM.
El grafo maestro se construye en app/graphs/orchestrator.py:26 y se compila con
workflow.compile(checkpointer=checkpointer, interrupt_before=["approval_gate"])START
│
▼
intake ──▶ crawler ──▶ canonicalizer ──▶ ocr
│ │
└─ pre_job_policy (hook) ▼
evidence_extractor ──▶ analyst ──▶ compliance_verifier
│
▼
ideas_generator (HÍBRIDO)
│
▼
ideas_hardener (conservador)
│
▼
report_generator ──▶ deck_generator ──▶ email_generator
│
▼
approval_gate ◀── interrupt_before
│
┌───────────────────┴────────────────────┐
│ has_rejections? │
▼ ▼
analyst packager ──▶ END
(reintento)
Los 14 nodos (Agent enum en app/schemas/models.py:85):
| # | Nodo | Fase | Rol |
|---|---|---|---|
| 1 | intake |
neutral | Descarga la entrada, crea SourceRef seed, aplica pre_job_policy |
| 2 | crawler |
neutral | Recursión de enlaces hasta crawl_max_depth |
| 3 | canonicalizer |
neutral | Normaliza HTML/PDF a CanonicalDoc con Sections |
| 4 | ocr |
neutral | Aplica tesseract + ocrmypdf si enable_ocr=True |
| 5 | evidence_extractor |
conservative | Extrae evidencia determinista (fechas, presupuestos) |
| 6 | analyst |
conservative | Genera Claims tipo fact con retry anti-alucinación |
| 7 | compliance_verifier |
conservative | Verifica deadlines, topes presupuestales, elegibilidad |
| 8 | ideas_generator |
hybrid | LLM creativo para propuestas alineadas |
| 9 | ideas_hardener |
conservative | Endurece ideas con validación presupuestal vía MCP |
| 10 | report_generator |
conservative | Informe PDF desde templates/report.md |
| 11 | deck_generator |
conservative | Deck Marp desde templates/marp.md |
| 12 | email_generator |
conservative | Correo ejecutivo (templates/email.txt) |
| 13 | approval_gate |
neutral | Pausa el grafo para revisión humana |
| 14 | packager |
neutral | Empaqueta entregables finales |
Middleware en app/hooks/ que se ejecuta antes/después de cada llamada al LLM:
| Hook | Archivo | Responsabilidad |
|---|---|---|
pre_job_policy |
pre_job_policy.py |
Valida la solicitud antes de iniciar el job |
evidence_register |
evidence_register.py |
Indexa evidencias en evidence_index |
claim_validator |
claim_validator.py |
Rechaza fact sin evidence_ids apuntando a official |
grounding_guard |
grounding_guard.py |
Inyecta contexto de evidencia permitido al prompt |
budget_guard |
budget_guard.py |
Valida topes presupuestales contra reglas IEEE |
language_guard |
language_guard.py |
Refuerza idioma español |
semantic_diff_guard |
semantic_diff_guard.py |
Evita regenerar entregables duplicados |
artifact_pre_validator |
artifact_pre_validator.py |
Valida artefactos antes de guardarlos |
prior_error_recorder |
prior_error_recorder.py |
Resume errores previos; se inyecta en el prompt siguiente |
app/mcps/ expone servicios deterministas (sin LLM) accesibles por los agentes:
documents— parsing y resumen de documentos.ieee_knowledge— glosario oficial (HAC,SIGHT,CS,R9,MGA), validación de temáticas prioritarias IEEE.budget_rules— reglas matemáticas rígidas de presupuesto IEEE (ej. SIGHT ≤ $5,000 USD).templates— plantillas de entregables (report.md,marp.md,email.txt,ideas.md).audit— registro de auditoría por job.
- PostgreSQL 16 (
app/persistence/init.sql):jobs(job_id, status, payload JSBON, created_at, updated_at)— estado completo delJobStateserializado como JSON.job_events(event_id, job_id, node, verdict, tokens_in, tokens_out, detail)— auditoría por nodo.- Índice
idx_job_events_job(job_id, created_at). - Extensión
pg_trgmpara matching fuzzy.
- Checkpoint LangGraph:
CheckpointPostgres(app/services/checkpoint_postgres.py) conupsert,load,iter_jobseinsert_event. - Almacenamiento de archivos: Docker volume
storage/con subcarpetassources/,output/,logos/,uploads/. Sin nube.
| Capa | Tecnología |
|---|---|
| Lenguaje | Python 3.11 |
| Orquestación de agentes | LangGraph + langchain-core |
| API | FastAPI 0.115 + uvicorn |
| UI prototipo | Streamlit ≥ 1.36 |
| Validación | Pydantic v2 + pydantic-settings |
| Base de datos | PostgreSQL 16 (Docker) |
| OCR local | tesseract + ocrmypdf + poppler |
| Generación PDF | WeasyPrint |
| Generación deck | Marp CLI |
| LLM | NVIDIA free tier (langchain-nvidia-ai-endpoints), Llama 3.1 8B/70B |
| Gestor de paquetes | uv (astral) |
| Lint/tipos | ruff + mypy strict |
| Pruebas | pytest + pytest-asyncio + respx |
| Cliente HTTP | httpx + curl-cffi |
abstraccion-convocatorias-ieee/
├── app/
│ ├── agents/prompts/ # Prompts de los agentes
│ ├── api/ # FastAPI: routes/{jobs,approval,artifacts,health}
│ ├── docker/ # Dockerfile (multistage) por servicio
│ ├── graphs/
│ │ ├── orchestrator.py # Grafo maestro LangGraph
│ │ └── subgraphs/ # 14 nodos (uno por agente)
│ ├── hooks/ # Middleware anti-alucinación
│ ├── mcps/ # MCP servers por dominio
│ ├── persistence/ # init.sql + migraciones alembic
│ ├── schemas/models.py # JobState + entidades (FromData, Claim, evidence...)
│ ├── services/ # llm_nvidia, storage, checkpoint, settings, worker, audit
│ ├── templates/ # Plantillas report.md, marp.md, email.txt, ideas.md + CSS
│ ├── tools/ # Tools deterministas (fetch, ocr, pdf, html_parse, etc.)
│ └── ui/ # Streamlit app + pages
├── tests/
│ ├── unit/ # test_schemas.py, test_phase2_nodes.py
│ ├── integration/ # test_api.py (requiere Docker)
│ ├── regression/
│ └── fixtures/
├── storage/ # Volume local (sources, output, logos)
├── docker-compose.yml # postgres + api + ui + worker
├── pyproject.toml # Dependencias + ruff + mypy + pytest
├── Makefile # atajos: install, test, lint, start, up, down
└── AGENTS.md # Memoria de operación del repo
- Docker 24+ y Docker Compose v2 (recomendado)
- NVIDIA API key (opcional pero recomendado). Sin ella el sistema usa
MockLLMClienty produce output simulado.- Solicítala gratis en: https://build.nvidia.com
- Para desarrollo sin Docker:
- Python 3.11
uvinstalado (pip install uv)- tesseract + poppler instalados en el sistema (solo si usas OCR fuera de Docker)
git clone <repo-url> trace-ieee
cd trace-ieee
# Copiar variables de entorno
cp .env.example .env
# Editar .env y rellenar NVIDIA_API_KEY (opcional pero recomendado)
# $EDITOR .envVariables clave en .env (ver .env.example para el listado completo):
| Variable | Default | Descripción |
|---|---|---|
POSTGRES_USER |
ieee |
Usuario PostgreSQL |
POSTGRES_PASSWORD |
ieee_local |
Contraseña (solo desarrollo local) |
POSTGRES_DB |
ieee_calls |
Base de datos |
DATABASE_URL |
postgresql+psycopg://... |
DSN completa (sobreescribe las anteriores) |
NVIDIA_API_KEY |
(vacío) | API key NVIDIA free tier. Si está vacío, se usa MockLLMClient |
NVIDIA_MODEL_SMALL |
meta/llama-3.1-8b-instruct |
Modelo para tareas rápidas |
NVIDIA_MODEL_MEDIUM |
meta/llama-3.1-70b-instruct |
Modelo para análisis |
NVIDIA_MODEL_LARGE |
meta/llama-3.1-70b-instruct |
Modelo para el analista |
NVIDIA_MAX_TOKENS_PER_JOB |
500000 |
Tope de tokens por job |
CRAWL_MAX_DEPTH |
1 |
Profundidad del crawler (0–3) |
CRAWL_REQUEST_TIMEOUT |
20 |
Timeout HTTP del crawler (segundos) |
STORAGE_ROOT |
/app/storage |
Raíz del almacenamiento local |
COVERAGE_MIN |
0.70 |
Cobertura mínima exigida al analyst |
LANG_EXPECTED |
es |
Idioma esperado para los outputs |
Importante: en el contenedor, STORAGE_ROOT debe apuntar a un volumen Docker persistente (configurado en docker-compose.yml).
# Levanta postgres, api, ui y worker en segundo plano
make start
# Equivalente: docker compose up -dEsto expone:
| Servicio | URL | Descripción |
|---|---|---|
| UI Streamlit | http://localhost:8501 | Interfaz web para crear, monitorear y aprobar jobs |
| API FastAPI | http://localhost:8000 | Endpoints REST /jobs, /healthz |
| Docs OpenAPI | http://localhost:8000/docs | Swagger UI autogenerada |
| PostgreSQL | localhost:5432 |
Solo si necesitas inspeccionar la BD |
Para detener y limpiar:
make down# 1. Instalar dependencias
make install
# Equivalente: uv sync --all-extras
# 2. Levantar PostgreSQL (alternativa: contenedor suelto)
docker run -d --name trace-ieee-pg \
-e POSTGRES_USER=ieee -e POSTGRES_PASSWORD=ieee_local \
-e POSTGRES_DB=ieee_calls -p 5432:5432 postgres:16-alpine
# 3. Apuntar DATABASE_URL al host local en .env:
# DATABASE_URL=postgresql+psycopg://ieee:ieee_local@localhost:5432/ieee_calls
# 4. Arrancar API, UI y worker en terminales separadas:
make run-api # uvicorn en 0.0.0.0:8000
make run-ui # streamlit en 0.0.0.0:8501
make run-worker # worker asíncrono que procesa jobs pendientes- En la barra lateral izquierda, elige tipo de entrada:
url,pdfohtml. - Pega la URL de la convocatoria IEEE o sube el archivo PDF/HTML.
- Ajusta la profundidad del crawler (0–3) y si quieres OCR habilitado.
- Pulsa Iniciar Trabajo. Se asigna un
job_idy el worker arranca el pipeline. - Monitorea el progreso en las pestañas:
- Progreso — estado actual y eventos (
JobEvent) por nodo. - Fuentes —
SourceManifestcon hashes y tipos. - Evidencias — tabla con
Evidenceextraída (categoría, confidence, verifier). - Entregables — links de descarga a
report.pdf,deck.marp,ideas.md,email.txt. - Revisión — aprueba o solicita corrección (observaciones requeridas si corriges).
- Errores previos — historial de
PriorErrorSummaryusados por el LLM.
- Progreso — estado actual y eventos (
- En Revisión, si apruebas →
packagerempaqueta final y el job quedapackaged. Si rechazas → el flujo vuelve aanalystcon tu observación registrada enprior_errors.
Para crear un job por URL:
curl -X POST http://localhost:8000/jobs \
-H "Content-Type: application/json" \
-d '{
"source_type": "url",
"source_uri": "https://www.ieee.org/about/sight.html",
"options": {"crawl_max_depth": 1, "enable_ocr": true}
}'
# => {"job_id": "job_a1b2c3...", "status": "intake"}Para subir un PDF local:
curl -X POST http://localhost:8000/jobs/upload \
-F "file=@./sight_call.pdf" \
-F "source_type=pdf" \
-F 'options={"crawl_max_depth": 0, "enable_ocr": true}'Para consultar estado:
curl http://localhost:8000/jobs/<job_id>/statusPara aprobar/rechazar:
curl -X POST http://localhost:8000/jobs/<job_id>/approve/all \
-H "Content-Type: application/json" \
-d '{"decision": "approve", "observations": "Revisado y conforme"}'
# decisiones válidas: approve | correct | retryPara descargar un artefacto:
curl -OJ http://localhost:8000/jobs/<job_id>/artifacts/report.pdf| Método | Ruta | Descripción |
|---|---|---|
GET |
/healthz |
Health check (verifica conexión a PostgreSQL) |
POST |
/jobs |
Crea un job nueva (URL o local://...) |
POST |
/jobs/upload |
Crea un job subiendo un archivo PDF/HTML |
GET |
/jobs/{job_id}/status |
Devuelve el JobState completo serializado |
POST |
/jobs/{job_id}/approve/{kind} |
Registra revisión humana (approve/correct/retry) |
GET |
/jobs/{job_id}/artifacts/{kind} |
Descarga el artefacto report/deck/ideas/email |
kind en el endpoint de descarga corresponde a los valores del enum DeliverableKind: report, deck, ideas, email.
make test
# Equivalente: uv run pytest -q tests/Marquer de pytest estándar:
@pytest.mark.integration— requiere Docker levantado (postgres + api).
Para correr solo unitarios:
uv run pytest tests/unit -qPara correr integración (requiere make start):
uv run pytest tests/integration -m integration -qObligatorios antes de cualquier commit (definidos en AGENTS.md):
make lint
# Equivalente:
# uv run ruff check .
# uv run mypy app/schemas app/services app/hooks app/tools app/graphs app/apiConfiguración relevante en pyproject.toml:
ruffcon reglasE, F, I, B, UP, N, RUF, SIM, TID, PTyline-length=100.mypy --strictcon pluginpydantic.mypy.- Exclusiones:
app/persistence/alembic/versions/ytests/fixtures/.
app/services/llm_nvidia.py define tres tiers seleccionables por agente:
| Tier | Modelo default | Uso típico |
|---|---|---|
small |
meta/llama-3.1-8b-instruct |
Tareas rápidas y barreras deterministas |
medium |
meta/llama-3.1-70b-instruct |
Generadores de entregables |
large |
meta/llama-3.1-70b-instruct |
analyst_node (reasoning más exigente) |
- Fallback automático: si
NVIDIA_API_KEYestá vacío o la API falla,NvidiaLLMClientdelega aMockLLMClient(output simulado). El sistema nunca crashea por falta de LLM. - Tope por job:
nvidia_max_tokens_per_job = 500_000(JobConfig). - Reintentos anti-alucinación:
analyst_nodereintenta hasta 3 veces, inyectandoprior_errorsacumulados en cada prompt.
Tomadas de AGENTS.md:
- Ningún
Claimde tipofactsinevidence_idsapuntando a fuenteofficial._forzada porclaim_validator.py. - Modos por agente:
conservativepara analistas y todos los generadores.hybridsolo paraideas_generator.
- Aprobación por archivo, no por paquete. La
approval_gatepausa el grafo (interrupt_before) y el usuario decide por cada entregable. - Solo se conserva la última versión activa de cada entregable (
Deliverable.active = Truepara la última; las previas quedansuperseded). - Resumen de errores previos consultado antes de regenerar (
prior_error_recorder+format_prior_errors).
- Fase 0 ✅ Esqueleto del repo,
JobState, MCPs básicos, Dockerfile multistage. - Fase 1 🚧 En progreso (estado actual): pipeline lineal completo, hooks anti-alucinación, UI con revisión por job.
- Fase 2 🔜
structured_outputreal paraClaim/AnalysisResultdesde el LLM (actualmente el analyst devuelve texto envuelto en un único claim). - Fase 3 🔜 AsyncPostgresSaver para escalar workers;
semantic_diff_guardfuncional; integración full de los MCPs con tools reales del crawler. - Fase 4 🔜 Empaquetado del paper para 20CCC con resultados de trazabilidad.
-
Lee
AGENTS.mdantes de tocar código — es la memoria operativa del repo. -
No cambies el stack sin confirmación del usuario (Python 3.11, FastAPI + LangGraph, PostgreSQL, OCR OSS).
-
Respeta convenciones del repositorio:
- Sin comentarios explicativos salvo petición explícita.
- Tipado estricto (
mypy --strict). - Imports absolutos desde
app.*. - Nombres en inglés para código; literales de salida en español, sin emoticones.
-
Antes de abrir un PR, ejecuta y que pasen:
make lint make test -
Commits atómicos: un cambio lógico por commit, mensaje en presente imperativo.
(Por definir — el autor debe agregar SPDX. Se recomienda MIT para máximo alcance académico.)