From 62c09be67d91d2bdb9f28066002841560d571ab3 Mon Sep 17 00:00:00 2001 From: PAVEL PALMA Date: Wed, 10 Sep 2025 16:40:31 -0600 Subject: [PATCH] UPDATE 2 10-sept --- .../service/base/MessageEntryFilter.java | 6 +- .../base/NotificationContextResolver.java | 3 +- .../ConversationManagerService.java | 8 +- .../MemoryStoreConversationService.java | 16 ++-- .../prompts/notification_context_resolver.txt | 87 ++++++++++++++----- .../NotificationContextResolverLiveTest.java | 5 +- 6 files changed, 87 insertions(+), 38 deletions(-) diff --git a/src/main/java/com/example/service/base/MessageEntryFilter.java b/src/main/java/com/example/service/base/MessageEntryFilter.java index 47f3cbb..3fd25ad 100644 --- a/src/main/java/com/example/service/base/MessageEntryFilter.java +++ b/src/main/java/com/example/service/base/MessageEntryFilter.java @@ -103,11 +103,13 @@ public class MessageEntryFilter { String resultCategory = switch (geminiResponse != null ? geminiResponse.trim().toUpperCase() : "") { case CATEGORY_CONVERSATION -> { - logger.info("Classified as {}. Input: '{}'", CATEGORY_CONVERSATION, queryInputText); + logger.info("Classified as {}. Input: '{}'", CATEGORY_CONVERSATION); + logger.debug("Classified as {}. Input: '{}'", CATEGORY_CONVERSATION, queryInputText); yield CATEGORY_CONVERSATION; } case CATEGORY_NOTIFICATION -> { - logger.info("Classified as {}. Input: '{}'", CATEGORY_NOTIFICATION, queryInputText); + logger.info("Classified as {}. Input: '{}'", CATEGORY_NOTIFICATION); + logger.debug("Classified as {}. Input: '{}'", CATEGORY_NOTIFICATION, queryInputText); yield CATEGORY_NOTIFICATION; } default -> { diff --git a/src/main/java/com/example/service/base/NotificationContextResolver.java b/src/main/java/com/example/service/base/NotificationContextResolver.java index 7ae1440..ac9c198 100644 --- a/src/main/java/com/example/service/base/NotificationContextResolver.java +++ b/src/main/java/com/example/service/base/NotificationContextResolver.java @@ -64,6 +64,7 @@ public class NotificationContextResolver { public String resolveContext(String queryInputText, String notificationsJson, String conversationJson, String metadata, String userId, String sessionId, String userPhoneNumber) { + logger.debug("resolveContext -> queryInputText: {}, notificationsJson: {}, conversationJson: {}, metadata: {}", queryInputText, notificationsJson, conversationJson, metadata); if (queryInputText == null || queryInputText.isBlank()) { logger.warn("Query input text for context resolution is null or blank. Returning {}.", CATEGORY_DIALOGFLOW); return CATEGORY_DIALOGFLOW; @@ -82,7 +83,7 @@ public class NotificationContextResolver { metadata, queryInputText); - logger.info("Sending context resolution request to Gemini for input (first 100 chars): '{}'...", + logger.debug("Sending context resolution request to Gemini for input (first 100 chars): '{}'...", queryInputText.substring(0, Math.min(queryInputText.length(), 100))); try { diff --git a/src/main/java/com/example/service/conversation/ConversationManagerService.java b/src/main/java/com/example/service/conversation/ConversationManagerService.java index 9d6dfbc..d04236d 100644 --- a/src/main/java/com/example/service/conversation/ConversationManagerService.java +++ b/src/main/java/com/example/service/conversation/ConversationManagerService.java @@ -293,7 +293,7 @@ public class ConversationManagerService { String notificationText = notificationContextMapper.toText(notification); Map filteredParams = notification.parametros().entrySet().stream() - .filter(entry -> entry.getKey().startsWith("po_")) + .filter(entry -> entry.getKey().startsWith("notification_po_")) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); String resolvedContext = notificationContextResolver.resolveContext(userMessageText, @@ -360,13 +360,15 @@ public class ConversationManagerService { private Mono persistNotificationTurn(String userId, String sessionId, ConversationEntryDTO entry, String userPhoneNumber) { - logger.debug("Starting Write-Back persistence for notification session {}. Type: {}. Writing to Redis first.", sessionId, + logger.debug("Starting Write-Back persistence for notification session {}. Type: {}. Writing to Redis first.", + sessionId, entry.type().name()); return memoryStoreNotificationService.saveEntry(userId, sessionId, entry, userPhoneNumber) .doOnSuccess(v -> logger.info( "Entry saved to Redis for notification session {}. Type: {}. Kicking off async Firestore write-back.", sessionId, entry.type().name())) - .doOnError(e -> logger.error("Error during primary Redis write for notification session {}. Type: {}: {}", sessionId, + .doOnError(e -> logger.error( + "Error during primary Redis write for notification session {}. Type: {}: {}", sessionId, entry.type().name(), e.getMessage(), e)); } diff --git a/src/main/java/com/example/service/conversation/MemoryStoreConversationService.java b/src/main/java/com/example/service/conversation/MemoryStoreConversationService.java index d5ab7d6..4a989fd 100644 --- a/src/main/java/com/example/service/conversation/MemoryStoreConversationService.java +++ b/src/main/java/com/example/service/conversation/MemoryStoreConversationService.java @@ -49,18 +49,18 @@ public class MemoryStoreConversationService { String sessionKey = SESSION_KEY_PREFIX + sessionId; String phoneToSessionKey = PHONE_TO_SESSION_KEY_PREFIX + userPhoneNumber; - logger.info("Attempting to save entry to Redis for session {}. Entity: {}", sessionId, newEntry.entity().name()); + logger.info("Attempting to save entry to Memorystore for session {}. Entity: {}", sessionId, newEntry.entity().name()); return redisTemplate.opsForValue().get(sessionKey) .doOnSuccess(session -> { if (session != null) { - logger.info("Found existing session in Redis: {}", session); + logger.info("Found existing session in Memorystore: {}", session); } else { - logger.info("No session found in Redis for key: {}", sessionKey); + logger.info("No session found in Memorystore for key: {}", sessionKey); } }) .switchIfEmpty(Mono.defer(() -> { - logger.info("Creating new session {} in Redis with TTL.", sessionId); + logger.info("Creating new session {} in Memorystore with TTL.", sessionId); ConversationSessionDTO newSession = ConversationSessionDTO.create(sessionId, userId, userPhoneNumber); return redisTemplate.opsForValue().set(sessionKey, newSession, SESSION_TTL) .then(stringRedisTemplate.opsForValue().set(phoneToSessionKey, sessionId, SESSION_TTL)) @@ -71,7 +71,7 @@ public class MemoryStoreConversationService { ConversationSessionDTO sessionWithPantallaContexto = (pantallaContexto != null) ? sessionWithUpdatedTelefono.withPantallaContexto(pantallaContexto) : sessionWithUpdatedTelefono; ConversationSessionDTO updatedSession = sessionWithPantallaContexto.withAddedEntry(newEntry); - logger.info("Updated session to be saved in Redis: {}", updatedSession); + logger.debug("Updated session to be saved in Memorystore: {}", updatedSession); logger.info("Attempting to set updated session {} with new entry entity {} in Redis.", sessionId, newEntry.entity().name()); return redisTemplate.opsForValue().set(sessionKey, updatedSession) @@ -81,14 +81,14 @@ public class MemoryStoreConversationService { .doOnSuccess(success -> { logger.info("Successfully saved updated session and phone mapping to Redis for session {}. Entity Type: {}", sessionId, newEntry.entity().name()); }) - .doOnError(e -> logger.error("Error appending entry to Redis for session {}: {}", sessionId, e.getMessage(), e)); + .doOnError(e -> logger.error("Error appending entry to Memorystore for session {}: {}", sessionId, e.getMessage(), e)); } public Mono getSessionByTelefono(String telefono) { if (telefono == null || telefono.isBlank()) { return Mono.empty(); } String phoneToSessionKey = PHONE_TO_SESSION_KEY_PREFIX + telefono; - logger.debug("Attempting to retrieve session ID for phone number {} from Redis.", telefono); + logger.debug("Attempting to retrieve session ID for phone number {} from Memorystore.", telefono); return stringRedisTemplate.opsForValue().get(phoneToSessionKey) .flatMap(sessionId -> { logger.debug("Found session ID {} for phone number {}. Retrieving session data.", sessionId, telefono); @@ -106,7 +106,7 @@ public class MemoryStoreConversationService { public Mono updateSession(ConversationSessionDTO session) { String sessionKey = SESSION_KEY_PREFIX + session.sessionId(); - logger.info("Attempting to update session {} in Redis.", session.sessionId()); + logger.info("Attempting to update session {} in Memorystore.", session.sessionId()); return redisTemplate.opsForValue().set(sessionKey, session).then(); } } \ No newline at end of file diff --git a/src/main/resources/prompts/notification_context_resolver.txt b/src/main/resources/prompts/notification_context_resolver.txt index 7e6f356..ba83e22 100644 --- a/src/main/resources/prompts/notification_context_resolver.txt +++ b/src/main/resources/prompts/notification_context_resolver.txt @@ -1,32 +1,75 @@ Eres un agente conversacional de soporte al usuario, amable, servicial y conciso. -Tu principal objetivo es responder a la pregunta específica del usuario de manera clara y directa, utilizando la información proporcionada en la notificación, sus metadatos y el historial de conversación. -Instrucciones Clave: -Prioridad: La información de la Notificación y sus Metadatos tienen la máxima prioridad. Si la pregunta del usuario se refiere a un dato que está en los metadatos, debes usarlo para responder. -El Historial de Conversación sirve para entender el contexto previo y evitar repeticiones. -Manejo de JSON (Metadatos y Conversación):Los campos de los metadatos y el historial pueden variar o no estar presentes. -Deberás analizar e inferir la información relevante de estos objetos JSON, incluso si la estructura no es idéntica en cada interacción.Si un dato es crucial y no está presente, -indícalo educadamente. -Claridad y Concisión: Proporciona la respuesta más directa y útil posible. Evita la verbosidad. -Tono: Mantén un tono profesional, amable y servicial. -Idioma: Responde en el mismo idioma en el que el usuario hizo la pregunta. -Información Insuficiente: Si la pregunta del usuario no puede ser respondida con la información provista, devuelve la cadena DIALOGFLOW. -Ejemplo 1: Pregunta directa, sin historial -Notificación: Hola Usuario, ya puedes revisar tu documentación en el sitio web. Deberás presentar una identificación oficial (INE, pasaporte) para acceder. -Metadatos de la notificación: { - "tipo_documentacion": "verificacion de identidad", - "documentos_permitidos_adicionales": ["licencia de conducir", "cedula profesional"], - "fecha_limite_revision": "2024-12-31" +Recibirás cuatro piezas de información: +1. HISTORIAL_CONVERSACION: El diálogo previo con el usuario. Úsalo para entender el contexto y evitar repetir información. +2. NOTIFICACION: El texto del mensaje que el usuario acaba de recibir. +3. METADATOS_NOTIFICACION: Un objeto JSON con datos estructurados relacionados con la notificación. Esta es tu fuente de verdad principal. +4. PREGUNTA_USUARIO: La pregunta específica del usuario que debes responder. + +Tu objetivo es sintetizar la información de estas fuentes para dar la respuesta más directa y útil posible. + +**Reglas de Comportamiento:** + +**Proceso Lógico:** Debes seguir este orden de prioridad para encontrar la respuesta: + 1. Autoridad Principal: Busca la respuesta primero en el objeto METADATOS_NOTIFICACION. Los datos aquí tienen la máxima autoridad. + 2. Fuente Alternativa: Si la respuesta no está en el objeto METADATOS_NOTIFICACION, busca como alternativa en el texto de HISTORIAL_CONVERSACION los datos que empiecen con el prefijo notification_po_. + 3. Contexto: Utiliza el HISTORIAL_CONVERSACION únicamente para dar contexto y asegurarte de no repetir algo que ya se dijo + +**Manejo de Datos Faltantes:** Si la respuesta a la PREGUNTA_USUARIO no se encuentra METADATOS_NOTIFICACION ni en el HISTORIAL_CONVERSACION (con el prefijo notification_po_) entonces debes responder exactamente con la palabra DIALOGFLOW.No intentes adivinar ni disculparte +**Concisión y Tono:** Tu respuesta debe ser directa, clara y resolver la pregunta. Mantén un tono profesional, amable y servicial. +**Idioma:** Responde siempre en el mismo idioma de la PREGUNTA_USUARIO. + +Manejo de Datos Faltantes: Si la respuesta a la PREGUNTA_USUARIO no se encuentra ni en METADATOS_NOTIFICACION ni en el HISTORIAL_CONVERSACION (con el prefijo notification_po_), +entonces debes responder exactamente con la palabra DIALOGFLOW. +No intentes adivinar ni disculparte. + +Estrategia de Respuesta: +Siempre sintetiza la información encontrada en una respuesta completa y conversacional. No devuelvas solo el dato. Utiliza el dato para construir una frase que sea útil y siga el tono. Por ejemplo, si encuentras el dato "30/09/2025", tu respuesta debe ser una frase como "La vigencia de tu solicitud es hasta el 30 de septiembre de 2025." o similar. + +**Ejemplos (Few-Shot Learning):** + +**Ejemplo 1: La respuesta está en los Metadatos** +HISTORIAL_CONVERSACION: +Usuario: Hola, necesito ayuda con una documentación. +Agente: Claro, ¿en qué puedo ayudarte? +NOTIFICACION: Hola :Pasó algo con la captura de tu INE y no se completó tu solicitud de tarjeta de crédito con folio ###.¡Reinténtalo cuando quieras! Solo toma en cuenta estos consejos: +Presenta tu INE original (no copias ni escaneos).📅Revisa que esté vigente y sin tachaduras.📷 Confirma que la fotografía sea clara.🏠 Asegúrate de que la dirección sea legible. +Estamos listos para recibirte. +METADATOS_NOTIFICACION: { + "parametrosOcultos": { + "vigencia": "30/09/2025" + } } +PREGUNTA_USUARIO: ¿Hasta cuando esta disponible esta solicitud? +Respuesta: Tienes hasta el 30 de septiembre de 2025 para revisarlos. -**Examples (Few-Shot Learning):** -Ejemplo 2: Información faltante +**Ejemplo 2: Poca Información encontrada en texto de Notificacion * +HISTORIAL_CONVERSACION: +Usuario: Hola. +Agente: ¡Qué onda! Soy Beto, tu asistente virtual de Sigma. ¿Como te puedo ayudar hoy? 🧐 +NOTIFICACION: Hola :Pasó algo con la captura de tu INE y no se completó tu *solicitud de tarjeta de crédito con folio ###*. +¡Reinténtalo cuando quieras! Solo toma en cuenta estos consejos: Presenta tu INE original (no copias ni escaneos)... +Estamos listos para recibirte. +METADATOS_NOTIFICACION: { + "parametrosOcultos": { + "vigencia": "30/09/2025" + } +} +PREGUNTA_USUARIO: Mi INE tiene algunas tachaduras y en general esta en mal estado +Respuesta: DIALOGFLOW -Notificación: Tu cita para el servicio de mantenimiento ha sido confirmada. Por favor, llega 15 minutos antes. -Metadatos_ Notificación: { +**Ejemplo 3: Información no encontrada en ninguna fuente** +HISTORIAL_CONVERSACION: +Usuario: ¿Cómo van mis trámites? +Agente: Veo que tienes una cita de mantenimiento programada. +NOTIFICACION: Tu cita para el servicio de mantenimiento ha sido confirmada. Por favor, llega 15 minutos antes. +METADATOS_NOTIFICACION: { "tipo_servicio": "mantenimiento rutinario", - "ubicacion": "Sucursal Centro" + "ubicacion": "Sucursal Centro", + "id_cita": "C-182736" } +PREGUNTA_USUARIO: Perfecto, ¿cuál será el costo del mantenimiento? +Respuesta: DIALOGFLOW Historial de Conversación: %s diff --git a/src/test/java/com/example/service/integration_testing/NotificationContextResolverLiveTest.java b/src/test/java/com/example/service/integration_testing/NotificationContextResolverLiveTest.java index 728aa8b..0beb414 100644 --- a/src/test/java/com/example/service/integration_testing/NotificationContextResolverLiveTest.java +++ b/src/test/java/com/example/service/integration_testing/NotificationContextResolverLiveTest.java @@ -35,9 +35,10 @@ public class NotificationContextResolverLiveTest { "🏠 Asegúrate de que la dirección sea legible.\n" + "Estamos listos para recibirte.\n"; - conversationJson = "{}"; - queryInputText = "necesito saber el id de la campaña"; + conversationJson = "System: Hola :Pasó algo con la captura de tu INE y no se completó tu *solicitud de tarjeta de crédito con folio *.¡Reinténtalo cuando quieras! Solo toma en cuenta estos consejos:🪪 Presenta tu INE original (no copias ni escaneos).📅Revisa que esté vigente y sin tachaduras.📷 Confirma que la fotografía sea clara.🏠 Asegúrate de que la dirección sea legible.Estamos listos para recibirte.notification_po_contexto=campañaprueba, notification_po_id_campaña=campaña01, notification_po_id_aplicacion=TestSigma, notification_po_id_notificacion=Prueba2"; + queryInputText = "cual es el id de la notificaion?"; metadataJson = "{\"contexto\":\"campañaprueba\",\"id_aplicacion\":\"TestSigma\",\"id_campaña\":\"campaña01\",\"id_notificacion\":\"Prueba2\",\"vigencia\":\"30/09/2025\"}"; + //metadataJson = "{}"; } @Test