Skip to main content
Glama

RAG · Chatbot experto en NTC CDMX (2004 / 2017 / 2023)

Sistema de Retrieval-Augmented Generation sobre las Normas Técnicas Complementarias del Reglamento de Construcciones de la Ciudad de México, con búsqueda híbrida (BM25 + embeddings multilingües + RRF) y citas por edición y numeral.

Estructura

RAG/
├── src/
│   ├── config.py        # rutas y mapeo PDF → (edición, norma)
│   ├── extract.py       # PDF → páginas de texto por norma (temp/extracted_text/)
│   ├── structure.py     # páginas → secciones X.Y.Z (data/corpus/*.json)
│   ├── index_build.py   # secciones → catálogo + BM25 + embeddings (data/index/)
│   ├── retrieve.py      # retriever híbrido (BM25 + embeddings + RRF + numeral)
│   ├── answer.py        # generador de respuestas con LLM (DeepSeek V4 Flash)
│   ├── calc.py          # cálculos validados (viento, sismo, combinaciones)
│   ├── evaluate.py      # evaluación recall@k con el dataset de 21k Q&A
│   └── finetune_gen.py  # genera dataset RAG-formateado para fine-tune del generador
├── app/app.py           # interfaz web (Streamlit)
└── scripts/run_all.py   # orquesta el pipeline completo

Servidor MCP (para opencode, codex, Claude Desktop, etc.)

El proyecto se expone como un servidor MCP con tres tools:

Tool

Qué hace

answer_ntc(query)

Responde con RAG + LLM (DeepSeek V4 Flash) citando edición, norma y numeral; también resuelve cálculos validados

search_ntc(query, edition, norm, top_k)

Devuelve las secciones relevantes en bruto

get_section(edition, norm, numeral)

Devuelve el texto completo de un numeral concreto

Instalación automática (registra el servidor en opencode y/o codex):

.venv\Scripts\python.exe scripts\install_mcp.py            # opencode + codex
.venv\Scripts\python.exe scripts\install_mcp.py --opencode # solo opencode
.venv\Scripts\python.exe scripts\install_mcp.py --codex    # solo codex

Reinicia opencode/codex y el RAG estará disponible como tools (answer_ntc, etc.). El servidor lee la API key del proveedor de RAG/.env, la variable de entorno correspondiente o ~/.config/ntc-cdmx/.env.

Instalación con un solo comando (GitHub + uv)

uvx --from git+https://github.com/Sobrio25/ntc-cdmx-mcp ntc-cdmx-install

Ese comando instala y registra el MCP en opencode, Codex, Command Code y Kilo Code. Reinicia los clientes y answer_ntc, search_ntc y get_section estarán disponibles. El índice (BM25 + embeddings) viaja dentro del paquete; la API key del proveedor se configura en ~/.config/ntc-cdmx/.env.

Para instalar solamente el ejecutable:

uv tool install git+https://github.com/Sobrio25/ntc-cdmx-mcp

Rendimiento al arrancar (evita timeouts del cliente)

El servidor MCP responde el handshake y las tools al instante (~1 s). El índice y el modelo de embeddings (intfloat/multilingual-e5-small, ~130 MB) se cargan en segundo plano; la primera llamada responde rápido usando solo BM25 y pasa a la búsqueda híbrida completa en cuanto el modelo está listo. No se bloquea la conexión del cliente, así que opencode/codex no marcan el servidor como timeout.

Solo la primera vez en una máquina nueva hay una espera adicional inevitable: uvx --from git+... construye el paquete (~15 s) y descarga el modelo (~130 MB). Instalar una sola vez con uv tool install evita la reconstrucción en cada arranque.

Probar el servidor manualmente:

ntc-cdmx                                     # stdio (modo instalado)
.venv\Scripts\python.exe src\mcp_server.py   # stdio (modo desarrollo)

Pipeline

# 1) Extraer y estructurar e indexar
.venv/Scripts/python.exe scripts/run_all.py --steps extract structure index

# 2) Evaluar recall del retriever (muestra 400 preguntas del dataset de 21k)
.venv/Scripts/python.exe scripts/run_all.py --steps eval

# 3) Interfaz web
.venv/Scripts/python.exe -m streamlit run app/app.py

Configurar el LLM

Las respuestas usan DeepSeek V4 Flash. Configura el proveedor/API key en src/answer.py (LLM_MODEL, LLM_BASE_URL). Sin clave, el chatbot responde con las secciones recuperadas (sin LLM), útil para depurar.

Cálculos validados (src/calc.py)

Si la pregunta pide un cálculo (p. ej. "calcula la presión de viento para Vz=35 m/s"), el motor lo detecta y usa una fórmula verificada contra el texto de la norma, sin pasar por el LLM. Calculadoras incluidas:

Cálculo

Fórmula

Fuente

Presión dinámica de viento

qz = 0.52·Vz² (m/s → Pa)

NTC-Viento 2023, §5.1.3

Presión de diseño por viento

pz = 0.47·Cp·VD²

NTC-Viento 2017/2004, §3.2

Fuerza de arrastre de viento

F = 0.47·CD·VD²·A

NTC-Viento 2017/2004, §3.3

Cortante basal mínimo sísmico

Vo,min = amin·Wo

NTC-Sismo 2023, §7.5

Combinación de cargas

Grupo B: 1.3·CM+1.5·CV · Grupo A: 1.5·CM+1.7·CV

NTC-Criterios 2023, §3.4.1

Si faltan datos, el chatbot los pide explícitamente.

Fine-tune del generador (src/finetune_gen.py)

Genera un dataset en formato chat donde cada ejemplo incluye el contexto recuperado (para que el generador aprenda a responder desde el contexto, en vez de memorizar las normas):

.venv/Scripts/python.exe src/finetune_gen.py --max 2000 --top_k 8 --require_all

Filtra automáticamente los ejemplos cuya respuesta "gold" NO está sustentada por el contexto recuperado (numerales citados ausentes → se descartan).

Evaluación

El módulo evaluate.py usa tu dataset de Documents\Fine_Tunning\NTC_CDMX\dataset.jsonl: para cada pregunta con numerales citados en la respuesta "gold", verifica que el numeral aparezca entre las secciones recuperadas.

Resultado de referencia (muestra 164 preguntas con cita, top-6): recall@q ≈ 0.58. Aproximadamente 18 % de los numerales citados por el dataset no existen en el corpus de su edición (posibles citas erróneas del dataset o huecos de extracción).

Notas técnicas

  • Las ediciones 2004 y 2017 vienen en gacetas (varios documentos por PDF); las fronteras de cada norma están mapeadas en src/config.py.

  • El troceo es por sección numerada (nunca por párrafo), preservando fórmulas/tablas.

  • Cada sección lleva metadata {edición, norma, numeral, página} para citar con precisión.

  • Los PDFs 2023 tienen nombres con caracteres corruptos en disco; el extractor los resuelve por prefijo numérico.