Asistente que responde preguntas sobre trámites del Estado peruano citando la ficha oficial exacta. Corpus real de 116 trámites de gob.pe.
Lo que casi ningún RAG de portafolio trae: una evaluación que mide si el sistema realmente encuentra la respuesta, en vez de una demo que responde bonito.
tramites-rag preguntar "me robaron el brevete como obtengo otro"50 preguntas escritas en el lenguaje que usaría un ciudadano, cada una con la ficha que debería responderla. Las preguntas no repiten el título de la ficha — hay un test que lo verifica — porque si lo repitieran, recuperar sería trivial y la métrica no mediría nada.
| Recuperador | recall@1 | recall@3 | recall@5 | MRR |
|---|---|---|---|---|
| BM25 | 0,340 | 0,540 | 0,620 | 0,449 |
| TF-IDF | 0,320 | 0,520 | 0,580 | 0,427 |
| híbrido (BM25 + TF-IDF) | 0,360 | 0,500 | 0,600 | 0,449 |
| expandido con sinónimos | 0,260 | 0,520 | 0,680 | 0,408 |
recall@5 es la métrica que manda: si la ficha correcta no está entre las 5 recuperadas, el generador no tiene con qué responder y va a inventar.
BM25 arrancó en 0,620. Al mirar las preguntas que fallaban — que el proyecto publica, no esconde — el patrón era claro: el ciudadano escribe "me robaron el brevete" y la ficha dice "licencia de conducir"; escribe "mi documento" y la ficha dice "DNI". Desajuste de vocabulario, no falla del algoritmo.
La expansión con un diccionario de sinónimos del dominio sube recall@5 a 0,680. Pero hay un costo honesto: recall@1 baja de 0,340 a 0,260. Traer más candidatos correctos al top 5 también ensucia el primer puesto. Si el sistema mostrara una sola respuesta convendría BM25; como muestra cinco fragmentos al generador, conviene la expansión.
Esa es toda la idea del proyecto: medir, diagnosticar con los fallos, corregir, volver a medir — y reportar también lo que empeoró.
Varios "fallos" son aciertos disfrazados. A "quiero poner un negocio por dónde empiezo" el sistema responde con la ficha "Abrir o hacer negocio" cuando el
ground truth dice "Abrir un negocio en Perú". Son fichas hermanas casi
idénticas del propio gob.pe. El recall real es mejor que el reportado, y aun así
la cifra se publica sin ajustar: inflar una métrica corrigiendo a mano los casos
que no gustan es la forma más rápida de que deje de significar algo.
gob.pe ──barrido de ids──▶ corpus.json (116 fichas, versionado)
│
troceado (700 chars, 150 de solapamiento)
│
720 fragmentos
│
┌─────────────────┼─────────────────┐
BM25 TF-IDF expansión
└─────────────────┼─────────────────┘
evaluación (50 preguntas)
│
respuesta con cita de la fuente
Sin embeddings neuronales, a propósito. sentence-transformers arrastra
torch (más de 500 MB): volvería el CI lento y el repositorio imposible de correr
en una máquina modesta. BM25 bien implementado sobre español, con tokenización
que quita tildes y palabras vacías, es un punto de partida fuerte y —lo que
importa aquí— completamente auditable: la fórmula está a la vista en
indice.py, en treinta líneas.
La respuesta no puede alucinar por defecto. Sin GROQ_API_KEY el modo es
extractivo: devuelve las oraciones del corpus tal cual, y un test verifica que
cada oración devuelta exista textualmente en las fichas. Con la clave, redacta
con Groq bajo la instrucción explícita de usar solo los fragmentos y de admitir
cuando no sabe. El modo sin clave hace que el repositorio funcione para
cualquiera y que el CI no necesite secretos.
gob.pe no publica sitemap y su buscador se arma por JavaScript, así que no hay
una lista de fichas para pedir. Lo que sí funciona: cada ficha vive en
gob.pe/<id> y responde 200 solo si existe. El corpus se construye barriendo un
rango de ids y quedándose con lo que responde.
Dos detalles que costaron encontrar:
- gob.pe sirve UTF-8 pero el autodetector de
requestsse equivoca y deja todo el texto con mojibake (Dóndeen vez deDónde). Hay que forzar el encoding. Un test verifica que el corpus publicado esté limpio. - Varios ids distintos llevan al mismo trámite — "obtener licencia de conducir" aparecía seis veces. Dejarlos rompe la evaluación: si el mismo contenido está en seis documentos, no existe una respuesta correcta única contra la cual medir. Se deduplica por título conservando la versión más completa.
pip install -e ".[dev]"El corpus va versionado, así que se puede preguntar de inmediato:
tramites-rag preguntar "acabo de tener un bebe donde lo inscribo"tramites-rag evaluar --fallosPara redactar con IA en vez de extraer (opcional):
export GROQ_API_KEY=tu_clavePara reconstruir el corpus desde cero:
tramites-rag corpus- 116 trámites, no todo gob.pe. El barrido cubrió un rango de ids acotado.
- El corpus es una foto: si gob.pe actualiza requisitos o costos, hay que
volver a correr
tramites-rag corpus. - No sustituye a la fuente oficial. El asistente orienta y cita el enlace para que la persona verifique en gob.pe.
Python 3.11+ · scikit-learn (TF-IDF) · BM25 propio · Groq (opcional) · pytest (23 tests) · ruff · GitHub Actions
MIT — ver LICENSE.