🏗️Arquitectura General
8 servicios Docker + nginx proxy. 3 capas: acceso, backend, LLM. Datos persistentes en bind mounts.
📦Servicios
🖥️ Frontend Web nginx
- Sirve HTML/CSS/JS
- Proxy inverso a APIs
- Chat + Voz + Upload
- Panel Debug 🐛
🧠 Orquestador FastAPI
- 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
- 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
- Base de datos principal
- Schema en schema.sql
- Init automático
🔄Flujo de una 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?
| Paso | Qué ocurre |
|---|---|
| Clasificador | Keyword override → perfil_familiar |
| Fetch | Familia completa (198 pers.) + relaciones + RAG |
| Filtro nombres | "Gumersindo" + "Rogelio" → 2 personas encontradas |
| Planificador | Patrón "hijos" → depth=1. Comparación "X o Y" → procesa ambos |
| Descendientes | Gumersindo: 3 hijos (Eduardo, Manuel, Sergio) | Rogelio: 2 (Gustavo, Alejandro) |
| LLM | Genera 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?
| Paso | Qué ocurre |
|---|---|
| Clasificador | cuantificador |
| Extraer parámetros | LLM → {actividad: "piscina", persona_objetivo: "Avelina Peláez", parentesco: "nietos"} |
| Sub-agentes | RelacionesAgent: 16 nietos. EventosAgent: 19 recuerdos RAG sobre piscina. |
| Búsqueda dual RAG | Keyword "piscina" → top_k=50 + full query → top_k=15, fusionados |
| Cross-reference | 16 nombres de nietos cotejados con texto RAG → 16 confirmados |
| LLM | Inyecta 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?
| Paso | Qué ocurre |
|---|---|
| Clasificador | evento |
| Extraer parámetros | LLM → {actividad: "concierto", lugar: "Movistar Arena", palabra_clave: "Iván Ferreiro"} |
| Búsqueda dual RAG | Full query → 15 resultados. Keyword "Iván Ferreiro" → 50 resultados adicionales. Fusionados: 65 únicos. |
| Cross-reference | Nombres del árbol cotejados → 35 recuerdos con personas confirmadas |
| LLM | Responde con las personas que fueron al concierto |
| Post-verificación | ✅ Nombres verificados en respuesta |
🗄️Estructuras de Datos
IDs jerárquicos del árbol
| ID | Significado |
|---|---|
1 | Raíz (Pedro Legido) |
1.1 | 1er hijo de Pedro |
1.1.1 | 1er hijo de 1.1 |
1.1.1.1.5 | 5º hijo de 1.1.1.1 (Isidoro) |
1.1.1.1.5p | Pareja de Isidoro (Teresa) |
1.1.1.1.5.2 | 2º hijo de Isidoro (Tere) |
Totales
| Métrica | Valor |
|---|---|
| Personas en árbol | 198 |
| Sin fecha de nacimiento | 104 (53%) |
| Sin lugar de nacimiento | 124 (63%) |
| Modelo embeddings | paraphrase-multilingual-MiniLM-L12-v2 (384d) |
| Motor RAG | Qdrant embebido (local, sin servidor) |
| Cache TTL | 300 segundos (5 min) |
| MAX_CONTEXT_CHARS | 25000 |
⚙️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
| Volumen | Host | Contenedor |
|---|---|---|
| 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
:8088 | Frontend web |
:8000 | Orquestador |
:8001 | Entrada 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).