
XXVII. Fuentes visibles: que se vea de qué documento sale cada respuesta
Ya hemos escrito varias veces en este blog sobre el mismo límite incómodo: un modelo de 3B parámetros, corriendo en una CPU sin GPU, puede sonar convincente y aun así equivocarse en un detalle concreto — incluso citando bien una fuente real (lo vimos con la búsqueda en internet, entrada XXIV). Ninguna instrucción de prompt lo garantiza al cien por cien. Eso no tiene arreglo fácil. Pero hay algo que sí podemos hacer, y que no depende de que el modelo se comporte bien: enseñar de qué documento real viene la información, para que el alumno (o el profesor) pueda comprobarlo por su cuenta en vez de fiarse a ciegas.
Esta idea salió de un documento de brainstorm que me pasaron con varias propuestas para el proyecto. La mayoría de esas ideas eran, o bien cosas que ya habíamos construido ese mismo día (la ficha de repaso, el verificador de sintaxis), o bien proyectos demasiado grandes para un rato de tarde (integración con Moodle, nuevos modos de personalidad). “Fuentes visibles” fue la única que recomendé sin dudar: riesgo mínimo, beneficio inmediato, y — lo más importante — no le pide nada nuevo al modelo.
La solución: Python ya lo sabe, no hay que preguntárselo a la IA
Mismo principio que la calculadora, el tiempo, la búsqueda en internet o el verificador de sintaxis: si hay un dato que Python puede conocer con certeza, no se le delega al modelo. Y este es un caso claro. Cuando el RAG busca fragmentos relevantes para una pregunta, ya sabe exactamente de qué documento sale cada uno — ese dato existe antes incluso de llamar a LM Studio. No hace falta pedirle al modelo que “declare sus fuentes” (con el riesgo de que se las invente, las mezcle o se olvide de alguna): basta con que la propia API se acuerde de lo que ya calculó, y lo mande tal cual a la interfaz.
Así que ahora, cuando una respuesta se ha apoyado en algún documento real del centro (o de los apuntes, o de las programaciones, según el corpus elegido), debajo de la respuesta aparece una línea como:
Fuentes: 📄 normas_convivencia.pdf 📄 horario_general.pdf
Sin inventos, sin que el modelo tenga que acertar el nombre exacto del archivo: es la lista real que ya usó el buscador BM25 antes de escribirle nada al modelo, deduplicada y en el mismo orden de relevancia con que se encontraron.
Cómo se usa
No hace falta activar nada ni pedirlo explícitamente: funciona solo, en el chat normal, con cualquier corpus seleccionado. Si la pregunta encuentra fragmentos relevantes de verdad, la línea de fuentes aparece sola debajo de la respuesta. Si no encuentra ninguno, no aparece nada — de momento no hay un aviso de “no lo encontré en los documentos del centro” (eso queda para un paso posterior, ver “Siguiente paso”).
Qué cubre esta primera versión, y qué queda fuera a propósito
- Solo se muestran fuentes del RAG documental (los PDF,
.docxy.odtde los tres corpus). Las fuentes de la búsqueda en internet (Wikipedia, Vikidia) no están incluidas todavía — no por olvido, sino para no acumular varios cambios el mismo día, justo después de la lección de ir paso a paso que nos dejó el intento con Mermaid.js. - Las fuentes se muestran como texto plano, con un icono 📄, sin enlace en el que se pueda pinchar. No existe (todavía) ningún endpoint en la API que sirva los documentos originales, así que un enlace no llevaría a ningún sitio.
- Si un fragmento no tiene el campo de origen bien rellenado, simplemente no se cuenta — mejor omitir una fuente que mostrar un hueco vacío o un “None” sin sentido en la interfaz.
Cómo funciona por dentro (resumen técnico)
_construir_system_prompt(), la función que ya se encargaba de montar el system prompt con la personalidad, el conocimiento del centro y el contexto del RAG, ahora además calcula y devuelve la lista de documentos usados. En /preguntar (la respuesta llega en streaming, trozo a trozo) esa lista viaja en una cabecera HTTP nueva, X-Fuentes, como JSON — se puede fijar así porque la búsqueda en el RAG ya ha terminado antes de que empiece a llegar la respuesta del modelo. En /preguntar_simple (pensado para apps externas, sin streaming) es más sencillo: un campo más en el JSON de respuesta.
Casos de prueba
Antes de subir esto a ROCKY se probó con una batería de 6 comprobaciones:
- Varios fragmentos relevantes, algunos del mismo documento repetido → la lista de fuentes sale sin duplicados, en el mismo orden de relevancia en que aparecieron los fragmentos.
- Ninguna coincidencia en el RAG → la lista de fuentes es vacía, y (comprobación extra) ni siquiera se vuelve a consultar al buscador una segunda vez para nada: si ya se sabe que no hay contexto útil, no tiene sentido gastar ese trabajo.
- Un fragmento con el campo de origen vacío o sin rellenar, mezclado con otros que sí lo tienen → el fragmento raro no aparece en la lista final, ni como hueco ni como “None”.
Siguiente paso
Que se pruebe en vivo con una pregunta real que sí tenga coincidencia en el RAG, para confirmar que la línea de fuentes se ve bien en la interfaz de verdad, no solo en las pruebas automáticas. Más adelante, y sin prisa: sumar también las fuentes de la búsqueda en internet, y estudiar si merece la pena un aviso honesto de “no lo he encontrado en los documentos del centro” cuando no aparece ninguna fuente, para que quede claro que la respuesta viene solo del conocimiento general del modelo y no de nada verificado.

Etiqueta:fuentes visibles, IA educativa, ies monterroso, PINSAPIA, RAG, transparencia, verificación


