ntc-cdmx-mcp
by Sobrio25
README.md
# 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):
```bash
.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`)
```bash
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:
```bash
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:
```bash
ntc-cdmx # stdio (modo instalado)
.venv\Scripts\python.exe src\mcp_server.py # stdio (modo desarrollo)
```
## Pipeline
```bash
# 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):
```bash
.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.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessSyncing