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.
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.
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:
- 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. - Gemini genera los embeddings en lotes, con failover automático entre dos claves de API cuando se agota la cuota free-tier.
- Los vectores se guardan en PostgreSQL + pgvector, junto a tablas relacionales (
chapter,file) que permiten consultas exactas además de búsqueda semántica. - 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. - 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
StreamingResponsepara tokens. - LangChain — bucle de tool-calling, document loading y chunking por heading.
- Google Gemini —
gemini-2.5-flashcomo LLM,gemini-embedding-001para 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
| Tool | Propósito |
|---|---|
search_chunks | Bú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_files | Listado y filtrado de archivos por capítulo — paso previo a get_file. |
get_file | Devuelve el contenido completo de una nota, en orden, dado su UUID. |
get_chapter | Devuelve 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".