GAISE · UPV/EHU 2026

TFG Knowledge Assistant

Un chatbot RAG que responde preguntas sobre cómo fue construido el chatbot RAG del TFG. Consulta la arquitectura, decisiones técnicas y diseño del sistema usando las propias notas del proyecto.

Autor: Martin Etxeberria

Becario en Xabet, donde trabajo sobre el sistema RAG real de DWall que inspira este proyecto. 4º de Ingeniería Informática de Gestión y Sistemas de Información — UPV/EHU.

linkedin.com/in/martin-etxeberria-zubeldia →

Sobre este proyecto

Un chatbot RAG que responde preguntas sobre su propio TFG — usando las notas con las que ese TFG fue escrito como base de conocimiento. Entrega final de la asignatura GAISE (4º Grado, UPV/EHU); el TFG real, desarrollado en paralelo en Xabet, documenta el diseño e industrialización de un sistema RAG para la plataforma DWall. Sus apuntes Obsidian — wikilinks, callouts, diagramas Mermaid e imágenes embebidas — son el corpus que este asistente ingiere, embebe y sirve con citas clicables.

Pregúntale "¿por qué pgvector y no FAISS?" o "¿qué es contextual retrieval?" y obtendrás una respuesta citada, con enlaces de vuelta a la nota exacta donde vive la justificación. El propio chat habla el mismo Markdown que el visor: bloques de código resaltados, diagramas Mermaid renderizados al vuelo, callouts y wikilinks navegables.

Arquitectura a vista de pájaro

Cómo funciona

Una pipeline clásica de Retrieval-Augmented Generation, montada a mano de principio a fin:

  1. Las notas Markdown se trocean por headings y a cada chunk se le añade un prefijo contextual [Documento > H1 > H2] que lo localiza sin contaminar el vector.
  2. Gemini genera los embeddings en lotes, con failover automático entre dos claves de API cuando se agota la cuota free-tier.
  3. Los vectores se guardan en PostgreSQL + pgvector, junto a tablas relacionales (chapter, file) que permiten consultas exactas además de búsqueda semántica.
  4. El agente, escrito sobre LangChain, dispone de 5 herramientas — una búsqueda semántica como default y dos pares complementarios (list_* para descubrir IDs, get_* para recuperar el contenido entero) para archivos y capítulos.
  5. La respuesta se devuelve en streaming desde FastAPI, con citas inline en formato Obsidian ([Nombre](/docs/ruta)) que el frontend convierte en enlaces clicables a las notas originales.

Stack técnico

Frontend

  • Next.js 16 + React 19 — App Router con Server Components para el visor de notas.
  • Tailwind CSS v4 + shadcn/ui — utilities y primitivos accesibles sobre Radix.
  • react-markdown + remark-gfm — pipeline base de Markdown (tablas, listas, GFM).
  • Mermaid — diagramas vivos a partir de bloques ```mermaid, con paleta Dracula en modo oscuro.
  • react-syntax-highlighter — resaltado de código por lenguaje (Dracula / OneLight).
  • react-force-graph-2d — grafo interactivo de notas y wikilinks (la misma librería que Obsidian).
  • Vercel AI SDK (useChat) — estado de mensajes y streaming en el cliente.
  • next-themes — modo claro / oscuro reflejado en Mermaid y el syntax highlighting.

Backend

  • Python 3 + FastAPI — API asíncrona con StreamingResponse para tokens.
  • LangChain — bucle de tool-calling, document loading y chunking por heading.
  • Google Geminigemini-2.5-flash como LLM, gemini-embedding-001 para los vectores.
  • PostgreSQL 16 + pgvector — único almacén para vectores, metadatos relacionales y usuarios.
  • psycopg 3 + SQLAlchemy — drivers y modelos.
  • bcrypt + python-jose (JWT) — autenticación.

Pipeline de ingestión

Un único script (backend/scripts/ingest.py) recorre la vault, upserta capítulos y archivos en sus tablas, trocea cada nota por headings, llama a Gemini por lotes y vuelca los vectores en pgvector con su file_id como FK indexada. Eso es lo que hace que get_file y get_chapter sean lookups directos sin pasar por búsqueda vectorial.

Las 5 herramientas del agente

ToolPropósito
search_chunksBúsqueda semántica sobre todos los chunks (la herramienta por defecto).
list_chaptersÍndice de capítulos del TFG — paso previo a get_chapter.
list_filesListado y filtrado de archivos por capítulo — paso previo a get_file.
get_fileDevuelve el contenido completo de una nota, en orden, dado su UUID.
get_chapterDevuelve el contenido completo de un capítulo entero.

Los dos list_* son complementarios y se usan en pareja con los dos get_*: el agente nunca se inventa un UUID, primero lo descubre con un list_* y después lo consume con su get_* correspondiente. El system prompt y los docstrings de cada herramienta guían al modelo: la búsqueda semántica es siempre el default, y las otras cuatro se reservan para preguntas explícitas del tipo "qué capítulos hay", "muéstrame el archivo X" o "resúmeme todo el capítulo Y".