Un chatbot de preguntas sobre normativa pública que cita sus fuentes en lugar de adivinar
Cómo un chatbot RAG construido con LangChain, pgvector y Express responde preguntas ciudadanas a partir de documentos oficiales, con citas y un umbral de confianza.
Un chatbot que responde preguntas sobre políticas públicas es fácil de construir y difícil de creer. Aquí el objetivo no es un texto fluido. El objetivo es una respuesta que un ciudadano pueda contrastar con un documento oficial. Este proyecto, un chatbot RAG de código abierto llamado policy-qa-system, gira en torno a esa única idea.
Público, pero ilegible
Las administraciones publican enormes volúmenes de normativa: cuantías de prestaciones, requisitos de solicitud, reglas de acceso. Técnicamente todo está en internet. En la práctica, muy poco le sirve a una persona normal. Quien solo quiere saber a qué tiene derecho o qué documentos llevar acaba delante de un texto de 30 páginas escrito para funcionarios.
Lo que viene después es previsible. La gente llama a una línea de atención para preguntar algo que ya está publicado, o se rinde y lo hace a ojo. En los grandes servicios públicos cargados de documentación, este hueco aparece constantemente. La información existe. Lo que falta es una forma de hacerle una pregunta.
El sistema cubre ese hueco con generación aumentada por recuperación. La gente hace preguntas cotidianas en lenguaje llano y recibe una respuesta basada en un documento fuente real, con la cita adjunta. No es un chatbot genérico que improvisa sobre normativa. Solo habla a partir de los documentos que se le han dado.
Recuperar, leer, responder, citar
Cada pregunta pasa por cuatro pasos:
- Buscar en los documentos indexados los pasajes más relevantes para la pregunta.
- Leer esos pasajes junto con la pregunta.
- Redactar una respuesta en lenguaje cotidiano en vez de jerga administrativa.
- Adjuntar la fuente exacta, para que la respuesta pueda verificarse y no solo creerse.
La mayoría de los proyectos de chatbots se saltan el último paso, y aquí es el que más importa. Una respuesta sobre prestaciones solo sirve si alguien puede confirmarla. Por eso cada respuesta lleva una referencia, y cuando no aparece nada relevante, el sistema dice que no está seguro en lugar de adivinar.
Un ejemplo en lenguaje llano
Tomemos la pregunta «¿Cuánto es el pago semanal por dos hijos?». La respuesta tiene este aspecto:
For 2 children: first child gets the standard rate, second child
gets the additional-child rate (combined weekly and yearly totals
shown together). An income-based charge may apply above a set
household income threshold.
Source: Official rates document, section on child payments
Las cifras dependen de qué documentos de qué país estén indexados, porque nada en el sistema está atado a un único conjunto de reglas. Es un patrón para preguntas ciudadanas, no un conjunto de datos fijo.
Pila tecnológica
- Frontend: React 18, TypeScript y Tailwind CSS
- Backend: Node.js con Express
- IA y RAG: LangChain y LangGraph
- Almacén vectorial: PostgreSQL con pgvector
- Despliegue: Docker
Nada exótico. Un equipo que lo use de verdad cambiaría los documentos de ejemplo por su propia normativa oficial, mientras la recuperación, las citas y la generación en lenguaje llano se quedan como están.
Arquitectura
La petición va desde la pregunta del usuario, a través de la interfaz en React, hasta la API de Express, que la pasa a la cadena de recuperación. Este es el esquema:
User question
│
▼
React UI ──► Express API
│
▼
LangChain retrieval ──► PostgreSQL + pgvector (indexed policy docs)
│
▼
LLM answer generation (grounded in retrieved sections)
│
▼
Answer + citation + confidence score
La puntuación de confianza merece tanta atención como la respuesta. Cuando la recuperación no encuentra nada lo bastante cercano a la pregunta, el sistema lo dice, y el modelo nunca tiene ocasión de rellenar el hueco con algo verosímil.
El código que importa
Primero, la cadena no produce texto libre sin más. Debe devolver una fuente junto con la respuesta:
const response = await chain.invoke({
question,
context: retrievedChunks,
});
return {
answer: response.answer,
sources: retrievedChunks.map(c => ({
document: c.metadata.document,
section: c.metadata.section,
})),
confidence: response.confidence,
};
Segundo, un umbral de confianza decide entre responder o admitir que no se encontró nada:
if (topMatchScore < CONFIDENCE_THRESHOLD) {
return {
answer: "I couldn't find a reliable answer to that in the indexed documents.",
sources: [],
};
}
Esa comprobación es mínima, pero separa un chatbot en el que la gente puede confiar de uno que solo suena seguro de sí mismo.
Lo que funcionó
- Hacer obligatorias las citas mejoró la recuperación desde el principio. Una coincidencia vaga salta a la vista en cuanto tienes que mostrar de dónde sale.
- Incluso un umbral de confianza tosco redujo mucho las respuestas erróneas dadas con aplomo. Parecía una función pequeña y fue de las que más pesaron en la confianza.
- Mantener intercambiable el conjunto de documentos permite que el mismo sistema sirva a áreas de política muy distintas con solo volver a indexar.
Lo que costó
- Simplificar sin distorsionar. La redacción normativa es precisa por algo, y hacerla más amable sin perder una condición como un límite de ingresos o una regla de acceso exige verdadero cuidado. No está resuelto del todo.
- Saber cuándo decir «no lo sé». Con un umbral demasiado bajo, el sistema responde cosas que no debería, y con uno demasiado alto rechaza preguntas que podría resolver bien. La solución práctica fue ajustarlo a mano con una pequeña colección de preguntas reales.
- Madurez. Es una base sobre la que construir, no un producto listo para desplegar. Antes de que lo vean usuarios reales necesita una canalización de ingesta en condiciones, una revisión continua de respuestas de muestra y pruebas con casos límite.
Dónde encaja
Forma parte de una pequeña familia de proyectos de IA de código abierto construidos sobre la orquestación de LLM con LangChain y LangGraph: un sistema de preguntas y respuestas sobre documentos, un agente de revisión de código y una herramienta que convierte notas de voz en historias de usuario de Agile. La inclinación común es hacia respuestas que se pueden comprobar, no respuestas que solo suenan bien.
Pruébalo
El repositorio se arranca rápido en local:
git clone https://github.com/Srameshgitnow/policy-qa-system.git
cd policy-qa-system
npm install
npm run dev
El README explica la configuración, las variables de entorno y cómo indexar tus propios documentos. Los fallos en la lógica de citas y las ideas sobre el umbral de confianza son bienvenidos como issues o pull requests.