Esta guía explica cómo probar la API del chatbot y resume los cambios de diseño realizados (patrones de diseño y principios SOLID) con un lenguaje claro y directo.
- Backend expuesto vía Nginx en
http://localhost:8080. - Endpoints principales:
GET /api/health— estado y métricas.POST /api/chat— recibe la consulta del usuario.
Usa cuerpo de formulario para evitar problemas de codificación de JSON en PowerShell 5.1:
Invoke-RestMethod -Method Post -Uri 'http://localhost:8080/api/chat' -ContentType 'application/x-www-form-urlencoded' -Body 'query=hola'
Invoke-RestMethod -Method Post -Uri 'http://localhost:8080/api/chat' -ContentType 'application/x-www-form-urlencoded' -Body 'query=dictadura militar'Si prefieres JSON, envía el cuerpo como bytes UTF-8 con cabeceras explícitas:
$json = '{"query":"fotografias 1975"}';
$bytes = [System.Text.Encoding]::UTF8.GetBytes($json);
Invoke-RestMethod -Method Post -Uri 'http://localhost:8080/api/chat' -Headers @{ 'Content-Type'='application/json; charset=utf-8'; 'Accept'='application/json' } -Body $bytescurl -s -X POST 'http://localhost:8080/api/chat' -H 'Content-Type: application/json' -d '{"query":"fotografias 1975"}'Actualización reciente: Se mejoró el sistema de búsqueda con:
El chatbot ahora analiza todas las búsquedas de forma automática y ofrece ayuda contextual cuando los resultados pueden no ser suficientes:
-
Detección automática de consultas genéricas
- Si escribes términos muy amplios (ej: "dictadura", "fotografías", "gobierno", "historia"), el sistema lo detecta automáticamente
- Te sugiere refinamientos específicos: añadir años, contexto, o términos relacionados
- Funciona con cualquier búsqueda, no solo con palabras específicas
-
Análisis de documentos encontrados
- Extrae automáticamente temas comunes de los títulos de resultados
- Detecta años mencionados (1973, 1974, 1980, etc.)
- Identifica palabras clave frecuentes que puedes usar para refinar
-
Sugerencias contextuales personalizadas
- Si buscas "dictadura" → sugiere: "dictadura años 70", "dictadura 1973", "dictadura documentos"
- Si buscas "derechos humanos 1980" → sugiere temas encontrados: "solicita", "casos", "violaciones"
- Si buscas "MIR" → sugiere años o contextos detectados en los resultados
- Las sugerencias cambian según tu consulta y los documentos encontrados
-
Búsqueda por keywords (respaldo automático)
- Si la API de Gemini no está disponible, el sistema usa búsqueda por coincidencia de palabras en títulos
- Funciona con cualquier término sin necesidad de embeddings
- Calcula relevancia por número de palabras coincidentes
Objetivo: Si los 6 documentos sugeridos no son exactamente lo que buscabas, el chatbot te ayuda a refinar automáticamente sin necesidad de adivinar qué más buscar.
Se reforzó el backend del chatbot (chatbot/api_chatbot.py) con tres patrones de diseño y dos principios SOLID. El objetivo: mejorar orden, seguridad y mantenibilidad sin cambiar el comportamiento.
-
Abstract Factory —
chatbot/services/factory.py- ¿Qué hace? Centraliza cómo se crean las funciones de “embedding” (para búsqueda) y la de “respuesta” (IA), dependiendo si la API de Gemini está disponible o no.
- ¿Por qué aquí? Permite cambiar la estrategia (usar Gemini o un reemplazo básico) sin tocar el resto del código. Esto reduce el acoplamiento y hace el sistema más flexible.
-
Proxy —
chatbot/services/llm_proxy.py- ¿Qué hace? Envuelve las llamadas a Gemini para manejarlas con seguridad (errores, indisponibilidad) y devolver valores controlados en caso de fallo.
- ¿Por qué aquí? Evita que errores externos (API) rompan el flujo del servidor. El Proxy es perfecto para poner “una capa de seguridad” sin reescribir la lógica de negocio.
-
Observer —
chatbot/services/events.py- ¿Qué hace? Implementa un bus de eventos simple (publicar/suscribir) y un observador de logging (
LoggingObserver). - ¿Por qué aquí? Permite registrar lo que ocurre (recibir consultas, tipo detectado, búsqueda hecha, respuesta generada) sin mezclar logs con la lógica central. Así podemos añadir métricas o auditoría sin tocar el flujo principal.
- ¿Qué hace? Implementa un bus de eventos simple (publicar/suscribir) y un observador de logging (
-
Strategy (Explícito) —
chatbot/services/conversation.py- ¿Qué hace? Define abstracciones base (
IntentionStrategy,EntityStrategy,SimilarityStrategy) que permiten múltiples implementaciones intercambiables. - ¿Por qué aquí? El chatbot necesita ser extensible: hoy usamos regex para detección, mañana queremos Gemini o ML. Las estrategias permitenSwitch sin tocar el código existente.
- ¿Qué hace? Define abstracciones base (
-
SRP (Single Responsibility Principle)
- ¿Qué significa? Cada módulo hace una sola cosa.
- Aplicación: Separar creación de servicios (Factory), llamadas a IA (Proxy) y eventos (Observer) del controlador Flask (
api_chatbot.py). Resultado: archivos más simples y fáciles de mantener. - En conversation.py: Cada clase (
ConversationSession,IntentionDetector,EntityExtractor,DocumentComparator) tiene UNA responsabilidad específica. Ninguna mezcla lógicas.
-
OCP (Open/Closed Principle) — Mejorado en conversation.py
- ¿Qué significa? Las clases deben ser abiertas para EXTENSIÓN, cerradas para MODIFICACIÓN.
- Aplicación en conversation.py:
- Abstracciones base:
IntentionStrategy,EntityStrategy,SimilarityStrategy - Nuevas implementaciones heredan sin tocar código existente
- Ejemplos de extensión futura:
class GeminiIntentionDetector(IntentionStrategy): """Detección mejorada con IA (sin modificar código actual)""" def detect(self, message): # Usa Gemini en lugar de regex return genai.detect_intention(message) class EmbeddingComparator(SimilarityStrategy): """Similitud con embeddings (sin modificar código actual)""" def calculate_topic_similarity(self, docs1, docs2): # Usa embeddings en lugar de palabras return embedding_based_similarity(docs1, docs2)
- El resto del código sigue funcionando sin cambios
- Abstracciones base:
-
DIP (Dependency Inversion Principle) — Mejorado en conversation.py
- ¿Qué significa? Depender de abstracciones, no de implementaciones concretas.
- Aplicación en conversation.py:
- Inyección de dependencias en constructores
- Patrones personalizables sin modificar la clase
- Ejemplo:
# Uso por defecto (regex) detector = IntentionDetector() # Uso personalizado (patrones custom) custom_patterns = { 'satisfied': [r'...'], 'unsatisfied': [r'...'] } detector = IntentionDetector(patterns=custom_patterns) # Uso futuro (estrategia diferente completamente) detector = GeminiIntentionDetector() # Otro proveedor, mismo interfaz
- En
api_chatbot.py: instancias de estrategias inyectadas como globalesintention_detector = IntentionDetector() # Intercambiable entity_extractor = EntityExtractorImpl() # Intercambiable document_comparator = DocumentComparator() # Intercambiable
-
DIP (Dependency Inversion Principle) — Clásico
- ¿Qué significa? El código debe depender de abstracciones, no de detalles concretos.
- Aplicación:
api_chatbot.pyahora pide "servicios" alServiceFactory(abstracción). Si cambia Gemini o si no hay conexión, el resto del código sigue funcionando sin cambios.
chatbot/api_chatbot.py— usa la fábrica, bus de eventos y estrategias inyectadas; mantiene endpoints y comportamiento.chatbot/services/factory.py— crea funciones de embedding y respuesta (IA).chatbot/services/llm_proxy.py— protege llamadas a Gemini.chatbot/services/events.py— EventBus y LoggingObserver para registro desacoplado.chatbot/services/conversation.py— ⭐ NUEVO: Gestión multi-turno, estrategias base (OCP), inyección (DIP)ConversationSession— gestiona historial por usuario (SRP)IntentionStrategy(abstracta) /IntentionDetector(regex) — detecta intención (OCP+DIP)EntityStrategy(abstracta) /EntityExtractorImpl(regex) — extrae entidades (OCP+DIP)SimilarityStrategy(abstracta) /DocumentComparator— compara documentos (OCP+DIP)
El chatbot ahora mantiene contexto entre múltiples mensajes y adapta su lógica según la intención del usuario:
-
Usuario satisfecho
User: "dictadura 1973" Bot: [5 documentos relevantes] User: "Gracias, perfecto" Bot: "¡Excelente! ¿Hay algo más que quieras explorar?" -
Usuario insatisfecho → pide detalles
User: "derechos humanos" Bot: [6 documentos] User: "No encuentro lo que buscaba" Bot: "¿Puedes ser más específico? ¿Años? ¿Tipo de documento? ¿Tema?" -
Usuario insatisfecho + proporciona detalles → re-búsqueda
User: "No encuentro" Bot: "¿Puedes ser más específico?" User: "Quiero de 1975 a 1980" Bot: [Nueva búsqueda refinada con años] -
Refinamiento (cambio de tema)
User: "dictadura" Bot: [documentos sobre dictadura] User: "En realidad quiero derechos humanos 1980" Bot: [Nueva búsqueda adaptada]
session_iden cada request identifica al usuario y mantiene historialIntentionDetectorclasifica el mensaje: satisfied / unsatisfied / refinementEntityExtractorobtiene contexto: años, tipos de doc, tópicosDocumentComparatormarca documentos como repetidos (🔄) o nuevos (✨)- Ramificación inteligente: El endpoint
/api/chatcambia comportamiento según intención
Todas las estrategias son intercambiables sin modificar el código:
# Hoy: regex (rápido, local)
detector = IntentionDetector()
# Mañana: Gemini (más sofisticado)
detector = GeminiIntentionDetector()
# El resto del código sigue igual (polimorfismo)
intention = detector.detect(message) # Funciona con ambos- Singleton: Evitado para no introducir estados globales difíciles de testear. La configuración ya se maneja claramente con variables de entorno (p. ej.,
GEMINI_API_KEY). - Decorator: Útil para añadir comportamiento dinámico, pero el objetivo aquí era separar responsabilidades y proteger llamadas externas; el Proxy satisface mejor esa necesidad.
- Strategy “pura”: La fábrica ya selecciona estrategias (con o sin GENAI). Usar Strategy adicional habría duplicado estructuras sin aportar claridad.
- Facade: Nginx y Flask ya sirven como “fachada” de entrada. Añadir otra fachada no resolvía un problema concreto.
- La clave de Gemini ahora se lee desde
.envydocker-compose.yml(variableGEMINI_API_KEY). - Importante: Si ves errores
403 Your API key was reported as leaked, necesitas generar una nueva clave en Google AI Studio y actualizar tu.env. - El sistema funciona en modo degradado (búsqueda por keywords) si Gemini no está disponible.
- Para evitar exponer secretos o binarios grandes,
.gitignoreincluye:.env,chatbot/.env*.pkl,chatbot/embeddings_cache.pklatom/vendor/,atom/cache/,atom/log/
Si clonas este proyecto desde GitHub, necesitarás recrear algunos archivos que no se suben por seguridad o tamaño:
-
Crear archivo
.enven la raíz del proyecto:GEMINI_API_KEY=tu_clave_aqui
Obtén tu clave en Google AI Studio
-
Instalar dependencias PHP de AtoM (opcional, solo si usas AtoM):
cd atom composer install -
Iniciar los contenedores Docker:
docker compose up -d
-
El sistema generará automáticamente:
chatbot/embeddings_cache.pkl— se crea en el primer arranque si GENAI está disponibleatom/cache/— cache de Symfony (se regenera automáticamente)
GET /api/healthdevuelve el estado (documentos cargados, embeddings disponibles y si la IA está activa).- Si la IA no está disponible, el sistema sigue funcionando: muestra documentos relevantes y enlaces sin detener el servicio.
- "No veo resultados de búsqueda": El sistema ahora usa búsqueda por palabras clave como respaldo. Si no aparece nada, reformula con términos más específicos (ej.: "derechos humanos años 80", "MIR", "fotografías 1975").
- "Mi POST JSON falla en PowerShell": Usa el método de formulario o el envío de bytes UTF-8 con cabeceras (ver arriba).
- "Veo sugerencias debajo de los resultados": Esto es nuevo. El chatbot analiza los documentos encontrados y te sugiere cómo refinar la búsqueda si es muy amplia.
- "¿Por qué dice 'Tu búsqueda es amplia'?": Consultas como "dictadura", "gobierno", "fotografías" solas son muy genéricas. El sistema te pide que añadas más contexto (años, temas específicos, etc.).
Si quieres, puedo añadir ejemplos de métricas con el EventBus (tiempos de respuesta) o una pequeña batería de pruebas para el servicio de búsqueda.