🏗️Arquitectura General

8 servicios Docker + nginx proxy. 3 capas: acceso, backend, LLM. Datos persistentes en bind mounts.

Diagrama de arquitectura

📦Servicios

🖥️ Frontend Web nginx

:8088
  • Sirve HTML/CSS/JS
  • Proxy inverso a APIs
  • Chat + Voz + Upload
  • Panel Debug 🐛

🧠 Orquestador FastAPI

:8000
  • Arquitectura multi-agente
  • OrquestadorAgent (+ plan narrativo)
  • RelacionesAgent / EventosAgent
  • CuantificadorAgent / GeneralAgent
  • Extrae parámetros con LLM
  • Búsqueda dual RAG + cruce
  • Post-verifica respuestas

📝 Entrada Recuerdos FastAPI

:8001
  • Procesa texto libre
  • Extrae eventos vía LLM
  • Distribuye a backends
  • POST /procesar

🔍 Backend RAG FastAPI

  • Qdrant embebido
  • Embeddings 384d
  • Búsqueda semántica
  • Memoria episódica

🗄️ Backend BBDD FastAPI

  • PostgreSQL 16
  • Eventos / Personas
  • Visitas terapéuticas
  • Tags y keywords

📂 Backend Ficheros FastAPI

  • JSON: árbol familiar
  • JSON: relaciones
  • JSON: reglas/fotos
  • API CRUD básica

🤖 Backend LLM FastAPI

  • Proxy a Ollama
  • Genera respuesta empática
  • Perfil emocional
  • qwen2.5:7b

🐘 PostgreSQL 16-alpine

5432
  • Base de datos principal
  • Schema en schema.sql
  • Init automático

🔄Flujo de una Pregunta

Diagrama de flujo de pregunta

Paso a paso

Clasificar pregunta

LLM clasificador (salamandra-7b / hf.co/cstr/…) clasifica en: perfil_familiar / relacion / evento / cuantificador / conversacion_general.

Si la pregunta menciona más|menos + hijos|nietos + dos nombres, fuerza perfil_familiar por keyword.

Generar plan narrativo

OrquestadorAgent genera un plan con 5 fases: clasificar → extraer parámetros → ejecutar agentes → cruzar nombres → generar respuesta. El plan se muestra en debug.

Extraer parámetros con LLM

LLM clasificador extrae JSON estructurado: {actividad, lugar, persona_objetivo, parentesco, palabra_clave}. Regex de respaldo si LLM falla.

Ej: "¿Quién fue al concierto de Iván Ferreiro en el Movistar Arena?" → actividad="concierto", lugar="Movistar Arena", palabra_clave="Iván Ferreiro".

Ejecutar agentes

Según el tipo, se ejecutan: RelacionesAgent (árbol familiar), EventosAgent (RAG + BBDD), CuantificadorAgent (conteos), GeneralAgent (conversación).

Búsqueda dual RAG

Dos llamadas a RAG: (1) pregunta completa → top_k=15, (2) palabra_clave extraída → top_k=50, score_threshold=0.0. Resultados fusionados por ID único.

Cruzar nombres con resultados

Valida que los nombres del árbol familiar aparezcan en el texto de los recuerdos RAG/BBDD. Si ninguno coincide, responde "ninguna de esas personas ha realizado esa actividad".

Generar respuesta LLM

Prompt con contexto familiar, recuerdos RAG fusionados, perfil emocional, historial. qwen2.5:7b genera respuesta empática. Para preguntas de conteo, se inyecta el número exacto de nombres confirmados.

Post-verificar

Comprueba que todos los nombres esperados aparecen en la respuesta. Si falta alguno, añade nota automática. Se salta para preguntas de conteo con >3 nombres.

Ejemplo 1: ¿Quién tuvo más hijos, Gumersindo o Rogelio?

PasoQué ocurre
ClasificadorKeyword override → perfil_familiar
FetchFamilia completa (198 pers.) + relaciones + RAG
Filtro nombres"Gumersindo" + "Rogelio" → 2 personas encontradas
PlanificadorPatrón "hijos" → depth=1. Comparación "X o Y" → procesa ambos
DescendientesGumersindo: 3 hijos (Eduardo, Manuel, Sergio) | Rogelio: 2 (Gustavo, Alejandro)
LLMGenera respuesta con nombres correctos
Post-verificación✅ OK (no aplica porque es comparación)

Ejemplo 2: ¿Cuántos nietos de Avelina Peláez han ido a la piscina?

PasoQué ocurre
Clasificadorcuantificador
Extraer parámetrosLLM → {actividad: "piscina", persona_objetivo: "Avelina Peláez", parentesco: "nietos"}
Sub-agentesRelacionesAgent: 16 nietos. EventosAgent: 19 recuerdos RAG sobre piscina.
Búsqueda dual RAGKeyword "piscina" → top_k=50 + full query → top_k=15, fusionados
Cross-reference16 nombres de nietos cotejados con texto RAG → 16 confirmados
LLMInyecta len(nombres)=16 + ejemplos → respuesta: "16 nietos"
Post-verificación⏭️ Saltado (>3 nombres)

Ejemplo 3: ¿Quién fue al concierto de Iván Ferreiro en el Movistar Arena?

PasoQué ocurre
Clasificadorevento
Extraer parámetrosLLM → {actividad: "concierto", lugar: "Movistar Arena", palabra_clave: "Iván Ferreiro"}
Búsqueda dual RAGFull query → 15 resultados. Keyword "Iván Ferreiro" → 50 resultados adicionales. Fusionados: 65 únicos.
Cross-referenceNombres del árbol cotejados → 35 recuerdos con personas confirmadas
LLMResponde con las personas que fueron al concierto
Post-verificación✅ Nombres verificados en respuesta

🗄️Estructuras de Datos

Diagrama de estructuras de datos

IDs jerárquicos del árbol

IDSignificado
1Raíz (Pedro Legido)
1.11er hijo de Pedro
1.1.11er hijo de 1.1
1.1.1.1.55º hijo de 1.1.1.1 (Isidoro)
1.1.1.1.5pPareja de Isidoro (Teresa)
1.1.1.1.5.22º hijo de Isidoro (Tere)

Totales

MétricaValor
Personas en árbol198
Sin fecha de nacimiento104 (53%)
Sin lugar de nacimiento124 (63%)
Modelo embeddingsparaphrase-multilingual-MiniLM-L12-v2 (384d)
Motor RAGQdrant embebido (local, sin servidor)
Cache TTL300 segundos (5 min)
MAX_CONTEXT_CHARS25000

⚙️Configuración

.env

TZ=Europe/Madrid
LLM_URL=http://spark-ac6f:11434
LLM_MODEL=qwen2.5:7b
CLASSIFIER_MODEL=hf.co/cstr/...GGUF
LOG_LEVEL=INFO
MAX_CONTEXT_CHARS=25000

config/

models.yaml   → modelos LLM
agents.yaml   → tools y backends
prompts.yaml  → prompts de sistema

Los valores {VAR:-default} se resuelven desde .env via config_loader.py.

Resolución jerárquica de endpoints

LLM_URL base → se sobreescribe por CLASSIFIER_LLM_ENDPOINT, ENTRADA_LLM_ENDPOINT, LLM_ENDPOINT por sección.

🖥️Frontend Web

Acceso: http://localhost:8088

💬 Chat

Envía preguntas por texto, recibe respuestas con tipo y fuentes. Indicador de escritura mientras el LLM procesa.

🎤 Voz

Web Speech API (SpeechRecognition). Solo Chrome/Edge. Graba, transcribe al campo de texto y envía automáticamente.

📎 Subir archivos

.txt / .md / .json → se envía a entrada-recuerdos para extraer eventos. Muestra resultado en chat.

🐛 Debug

Toggle que activa ?debug=true. Muestra panel lateral con fases: pregunta → plan → ejecucion → validacion → respuesta → tiempo.

📖 Documentación

Pestaña /documentacion/ con diagramas SVG interactivos, estructura de datos, flujo de pregunta paso a paso y configuración.

💾Volúmenes Bind Mount

VolumenHostContenedor
datos_logs./datos/logs/logs
datos_familia./datos/familia/datos/familia
datos_recuerdos./datos/recuerdos/datos/recuerdos
datos_fotos./datos/fotos/datos/fotos
datos_reglas./datos/reglas_terapeuticas/datos/reglas_terapeuticas
qdrant_storage./datos/qdrant_storage/qdrant_data
postgresql_data./datos/postgresql/var/lib/postgresql/data

🌐Red y Puertos

Puertos al host

:8088Frontend web
:8000Orquestador
:8001Entrada recuerdos

Red interna

Docker network: alzheimer-net (bridge). Los servicios se descubren por nombre de contenedor.

Ollama externo: spark-ac6f:11434 (resuelto a 100.66.93.60 vía Tailscale, mapeado con extra_hosts).