From 9b1824bc48dd10fbb2a8af77b58ef5aa6a7d2afc Mon Sep 17 00:00:00 2001 From: PAVEL PALMA Date: Wed, 10 Sep 2025 10:24:55 -0600 Subject: [PATCH] UPDATE 10-sept --- .../base/DetectIntentRequestDTO.java | 11 + .../conversation/ConversationEntryDTO.java | 34 ++ .../conversation/ConversationEntryEntity.java | 3 +- .../conversation/ConversationEntryType.java | 3 +- .../conversation/QueryParamsDTO.java | 6 + .../FirestoreConversationMapper.java | 1 + .../ExternalNotRequestMapper.java | 3 + .../base/NotificationContextResolver.java | 114 ++++ .../ConversationManagerService.java | 522 ++++++++++-------- .../FirestoreConversationService.java | 4 +- .../MemoryStoreConversationService.java | 8 + .../MemoryStoreNotificationService.java | 13 +- .../prompts/notification_context_resolver.txt | 41 ++ .../MessageEntryFilterIntegrationTest.java | 2 +- .../NotificationContextResolverLiveTest.java | 51 ++ 15 files changed, 582 insertions(+), 234 deletions(-) create mode 100644 src/main/java/com/example/service/base/NotificationContextResolver.java create mode 100644 src/main/resources/prompts/notification_context_resolver.txt create mode 100644 src/test/java/com/example/service/integration_testing/NotificationContextResolverLiveTest.java diff --git a/src/main/java/com/example/dto/dialogflow/base/DetectIntentRequestDTO.java b/src/main/java/com/example/dto/dialogflow/base/DetectIntentRequestDTO.java index f26c861..a4b6b8f 100644 --- a/src/main/java/com/example/dto/dialogflow/base/DetectIntentRequestDTO.java +++ b/src/main/java/com/example/dto/dialogflow/base/DetectIntentRequestDTO.java @@ -26,4 +26,15 @@ public DetectIntentRequestDTO withParameter(String key, Object value) { updatedQueryParams ); } + +public DetectIntentRequestDTO withParameters(java.util.Map parameters) { + // Create a new QueryParamsDTO with the updated session parameters + QueryParamsDTO updatedQueryParams = this.queryParams().withSessionParameters(parameters); + + // Return a new DetectIntentRequestDTO instance with the updated QueryParamsDTO + return new DetectIntentRequestDTO( + this.queryInput(), + updatedQueryParams + ); +} } \ No newline at end of file diff --git a/src/main/java/com/example/dto/dialogflow/conversation/ConversationEntryDTO.java b/src/main/java/com/example/dto/dialogflow/conversation/ConversationEntryDTO.java index c0a82a3..df3a0d1 100644 --- a/src/main/java/com/example/dto/dialogflow/conversation/ConversationEntryDTO.java +++ b/src/main/java/com/example/dto/dialogflow/conversation/ConversationEntryDTO.java @@ -28,6 +28,16 @@ public record ConversationEntryDTO( null); } + public static ConversationEntryDTO forUser(String text, Map parameters) { + return new ConversationEntryDTO( + ConversationEntryEntity.USUARIO, + ConversationEntryType.CONVERSACION, + Instant.now(), + text, + parameters, + null); + } + public static ConversationEntryDTO forAgent(QueryResultDTO agentQueryResult) { String fulfillmentText = (agentQueryResult != null && agentQueryResult.responseText() != null) ? agentQueryResult.responseText() : ""; Map parameters = (agentQueryResult != null) ? agentQueryResult.parameters() : null; @@ -63,6 +73,8 @@ public record ConversationEntryDTO( ); } + + public static ConversationEntryDTO forSystem(String text, Map parameters) { return new ConversationEntryDTO( ConversationEntryEntity.SISTEMA, @@ -73,4 +85,26 @@ public record ConversationEntryDTO( null ); } + + public static ConversationEntryDTO forLlmConversation(String text) { + return new ConversationEntryDTO( + ConversationEntryEntity.LLM, + ConversationEntryType.CONVERSACION, + Instant.now(), + text, + null, + null + ); + } + + public static ConversationEntryDTO forLlmConversation(String text, Map parameters) { + return new ConversationEntryDTO( + ConversationEntryEntity.LLM, + ConversationEntryType.CONVERSACION, + Instant.now(), + text, + parameters, + null + ); + } } \ No newline at end of file diff --git a/src/main/java/com/example/dto/dialogflow/conversation/ConversationEntryEntity.java b/src/main/java/com/example/dto/dialogflow/conversation/ConversationEntryEntity.java index 17f442a..e79a62a 100644 --- a/src/main/java/com/example/dto/dialogflow/conversation/ConversationEntryEntity.java +++ b/src/main/java/com/example/dto/dialogflow/conversation/ConversationEntryEntity.java @@ -8,5 +8,6 @@ package com.example.dto.dialogflow.conversation; public enum ConversationEntryEntity { USUARIO, AGENTE, - SISTEMA + SISTEMA, + LLM } \ No newline at end of file diff --git a/src/main/java/com/example/dto/dialogflow/conversation/ConversationEntryType.java b/src/main/java/com/example/dto/dialogflow/conversation/ConversationEntryType.java index 0f1efdd..5ef10cf 100644 --- a/src/main/java/com/example/dto/dialogflow/conversation/ConversationEntryType.java +++ b/src/main/java/com/example/dto/dialogflow/conversation/ConversationEntryType.java @@ -7,5 +7,6 @@ package com.example.dto.dialogflow.conversation; public enum ConversationEntryType { INICIO, - CONVERSACION + CONVERSACION, + LLM } \ No newline at end of file diff --git a/src/main/java/com/example/dto/dialogflow/conversation/QueryParamsDTO.java b/src/main/java/com/example/dto/dialogflow/conversation/QueryParamsDTO.java index 8ab4b6d..cefceda 100644 --- a/src/main/java/com/example/dto/dialogflow/conversation/QueryParamsDTO.java +++ b/src/main/java/com/example/dto/dialogflow/conversation/QueryParamsDTO.java @@ -25,4 +25,10 @@ public record QueryParamsDTO( updatedParams.put(key, value); return new QueryParamsDTO(updatedParams); } + + public QueryParamsDTO withSessionParameters(Map parameters) { + Map updatedParams = new HashMap<>(this.parameters()); + updatedParams.putAll(parameters); + return new QueryParamsDTO(updatedParams); + } } \ No newline at end of file diff --git a/src/main/java/com/example/mapper/conversation/FirestoreConversationMapper.java b/src/main/java/com/example/mapper/conversation/FirestoreConversationMapper.java index a35a285..064c4a9 100644 --- a/src/main/java/com/example/mapper/conversation/FirestoreConversationMapper.java +++ b/src/main/java/com/example/mapper/conversation/FirestoreConversationMapper.java @@ -103,6 +103,7 @@ public class FirestoreConversationMapper { if (entry.canal() != null) { entryMap.put(FIELD_MESSAGE_CHANNEL, entry.canal()); } + logger.debug("Created Firestore entry map: {}", entryMap); return entryMap; } diff --git a/src/main/java/com/example/mapper/notification/ExternalNotRequestMapper.java b/src/main/java/com/example/mapper/notification/ExternalNotRequestMapper.java index b852b6f..39077b6 100644 --- a/src/main/java/com/example/mapper/notification/ExternalNotRequestMapper.java +++ b/src/main/java/com/example/mapper/notification/ExternalNotRequestMapper.java @@ -49,9 +49,12 @@ public class ExternalNotRequestMapper { if (request.hiddenParameters() != null && !request.hiddenParameters().isEmpty()) { + StringBuilder poBuilder = new StringBuilder(); request.hiddenParameters().forEach((key, value) -> { parameters.put(PREFIX_PO_PARAM + key, value); + poBuilder.append(key).append(": ").append(value).append("\n"); }); + parameters.put("po", poBuilder.toString()); } TextInputDTO textInput = new TextInputDTO(NOTIFICATION_LABEL); diff --git a/src/main/java/com/example/service/base/NotificationContextResolver.java b/src/main/java/com/example/service/base/NotificationContextResolver.java new file mode 100644 index 0000000..7ae1440 --- /dev/null +++ b/src/main/java/com/example/service/base/NotificationContextResolver.java @@ -0,0 +1,114 @@ +/* +* Copyright 2025 Google. This software is provided as-is, without warranty or representation for any use or purpose. +* Your use of it is subject to your agreement with Google. +*/ +package com.example.service.base; + +import com.example.service.notification.MemoryStoreNotificationService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.ClassPathResource; +import org.springframework.stereotype.Service; +import org.springframework.util.FileCopyUtils; +import jakarta.annotation.PostConstruct; +import java.io.IOException; +import java.io.Reader; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Objects; + +@Service +public class NotificationContextResolver { + + private static final Logger logger = LoggerFactory.getLogger(NotificationContextResolver.class); + private final GeminiClientService geminiService; + + @Value("${notificationcontext.geminimodel:gemini-2.0-flash-001}") + private String geminiModelNameResolver; + + @Value("${notificationcontext.temperature:0.1f}") + private Float resolverTemperature; + + @Value("${notificationcontext.maxOutputTokens:1024}") + private Integer resolverMaxOutputTokens; + + @Value("${notificationcontext.topP:0.1f}") + private Float resolverTopP; + + @Value("${notificationcontext.prompt:prompts/notification_context_resolver.txt}") + private String promptFilePath; + + public static final String CATEGORY_DIALOGFLOW = "DIALOGFLOW"; + + private String promptTemplate; + + public NotificationContextResolver(GeminiClientService geminiService, MemoryStoreNotificationService memoryStoreNotificationService) { + this.geminiService = Objects.requireNonNull(geminiService, + "GeminiClientService cannot be null for NotificationContextResolver."); + } + + @PostConstruct + public void loadPromptTemplate() { + try { + ClassPathResource resource = new ClassPathResource(promptFilePath); + try (Reader reader = new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8)) { + this.promptTemplate = FileCopyUtils.copyToString(reader); + } + logger.info("Successfully loaded prompt template from '{}'.", promptFilePath); + } catch (IOException e) { + logger.error("Failed to load prompt template from '{}'. Please ensure the file exists.", promptFilePath, e); + throw new IllegalStateException("Could not load prompt template.", e); + } + } + + public String resolveContext(String queryInputText, String notificationsJson, String conversationJson, + String metadata, String userId, String sessionId, String userPhoneNumber) { + if (queryInputText == null || queryInputText.isBlank()) { + logger.warn("Query input text for context resolution is null or blank. Returning {}.", CATEGORY_DIALOGFLOW); + return CATEGORY_DIALOGFLOW; + } + + String notificationContent = (notificationsJson != null && !notificationsJson.isBlank()) ? notificationsJson + : "No metadata in notification."; + + String conversationHistory = (conversationJson != null && !conversationJson.isBlank()) ? conversationJson + : "No conversation history."; + + String contextPrompt = String.format( + this.promptTemplate, + conversationHistory, + notificationContent, + metadata, + queryInputText); + + logger.info("Sending context resolution request to Gemini for input (first 100 chars): '{}'...", + queryInputText.substring(0, Math.min(queryInputText.length(), 100))); + + try { + String geminiResponse = geminiService.generateContent( + contextPrompt, + resolverTemperature, + resolverMaxOutputTokens, + geminiModelNameResolver, + resolverTopP); + + if (geminiResponse != null && !geminiResponse.isBlank()) { + if (geminiResponse.trim().equalsIgnoreCase(CATEGORY_DIALOGFLOW)) { + logger.info("Resolved to {}. Input: '{}'", CATEGORY_DIALOGFLOW, queryInputText); + return CATEGORY_DIALOGFLOW; + } else { + logger.info("Resolved to a specific response. Input: '{}'", queryInputText); + return geminiResponse; + } + } else { + logger.warn("Gemini returned a null or blank response. Input: '{}'. Returning {}.", + queryInputText, CATEGORY_DIALOGFLOW); + return CATEGORY_DIALOGFLOW; + } + } catch (Exception e) { + logger.error("An error occurred during Gemini content generation for context resolution.", e); + return CATEGORY_DIALOGFLOW; + } + } +} diff --git a/src/main/java/com/example/service/conversation/ConversationManagerService.java b/src/main/java/com/example/service/conversation/ConversationManagerService.java index ccbd165..9d6dfbc 100644 --- a/src/main/java/com/example/service/conversation/ConversationManagerService.java +++ b/src/main/java/com/example/service/conversation/ConversationManagerService.java @@ -3,18 +3,21 @@ * Your use of it is subject to your agreement with Google. */ package com.example.service.conversation; + import com.example.dto.dialogflow.base.DetectIntentRequestDTO; import com.example.dto.dialogflow.base.DetectIntentResponseDTO; import com.example.dto.dialogflow.conversation.ConversationContext; import com.example.dto.dialogflow.conversation.ConversationEntryDTO; import com.example.dto.dialogflow.conversation.ConversationSessionDTO; import com.example.dto.dialogflow.conversation.ExternalConvRequestDTO; +import com.example.dto.dialogflow.conversation.QueryResultDTO; import com.example.dto.dialogflow.notification.NotificationDTO; import com.example.mapper.conversation.ExternalConvRequestMapper; import com.example.mapper.messagefilter.ConversationContextMapper; import com.example.mapper.messagefilter.NotificationContextMapper; import com.example.service.base.DialogflowClientService; import com.example.service.base.MessageEntryFilter; +import com.example.service.base.NotificationContextResolver; import com.example.service.notification.MemoryStoreNotificationService; import com.example.service.quickreplies.QuickRepliesManagerService; import com.example.util.SessionIdGenerator; @@ -29,37 +32,41 @@ import java.util.Collections; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.stream.Collectors; @Service public class ConversationManagerService { - private static final Logger logger = LoggerFactory.getLogger(ConversationManagerService.class); - - private static final long SESSION_RESET_THRESHOLD_MINUTES = 30; - private static final String CONV_HISTORY_PARAM = "conversation_history"; - private final ExternalConvRequestMapper externalRequestToDialogflowMapper; - private final DialogflowClientService dialogflowServiceClient; - private final FirestoreConversationService firestoreConversationService; - private final MemoryStoreConversationService memoryStoreConversationService; - private final QuickRepliesManagerService quickRepliesManagerService; - private final MessageEntryFilter messageEntryFilter; - private final MemoryStoreNotificationService memoryStoreNotificationService; - private final NotificationContextMapper notificationContextMapper; - private final ConversationContextMapper conversationContextMapper; - private final DataLossPrevention dataLossPrevention; - private final String dlpTemplateCompleteFlow; + private static final Logger logger = LoggerFactory.getLogger(ConversationManagerService.class); - public ConversationManagerService( - DialogflowClientService dialogflowServiceClient, - FirestoreConversationService firestoreConversationService, - MemoryStoreConversationService memoryStoreConversationService, - ExternalConvRequestMapper externalRequestToDialogflowMapper, - QuickRepliesManagerService quickRepliesManagerService, - MessageEntryFilter messageEntryFilter, - MemoryStoreNotificationService memoryStoreNotificationService, - NotificationContextMapper notificationContextMapper, - ConversationContextMapper conversationContextMapper, - DataLossPrevention dataLossPrevention, - @Value("${google.cloud.dlp.dlpTemplateCompleteFlow}") String dlpTemplateCompleteFlow) { + private static final long SESSION_RESET_THRESHOLD_MINUTES = 30; + private static final String CONV_HISTORY_PARAM = "conversation_history"; + private final ExternalConvRequestMapper externalRequestToDialogflowMapper; + private final DialogflowClientService dialogflowServiceClient; + private final FirestoreConversationService firestoreConversationService; + private final MemoryStoreConversationService memoryStoreConversationService; + private final QuickRepliesManagerService quickRepliesManagerService; + private final MessageEntryFilter messageEntryFilter; + private final MemoryStoreNotificationService memoryStoreNotificationService; + private final NotificationContextMapper notificationContextMapper; + private final ConversationContextMapper conversationContextMapper; + private final DataLossPrevention dataLossPrevention; + private final String dlpTemplateCompleteFlow; + + private final NotificationContextResolver notificationContextResolver; + + public ConversationManagerService( + DialogflowClientService dialogflowServiceClient, + FirestoreConversationService firestoreConversationService, + MemoryStoreConversationService memoryStoreConversationService, + ExternalConvRequestMapper externalRequestToDialogflowMapper, + QuickRepliesManagerService quickRepliesManagerService, + MessageEntryFilter messageEntryFilter, + MemoryStoreNotificationService memoryStoreNotificationService, + NotificationContextMapper notificationContextMapper, + ConversationContextMapper conversationContextMapper, + DataLossPrevention dataLossPrevention, + NotificationContextResolver notificationContextResolver, + @Value("${google.cloud.dlp.dlpTemplateCompleteFlow}") String dlpTemplateCompleteFlow) { this.dialogflowServiceClient = dialogflowServiceClient; this.firestoreConversationService = firestoreConversationService; this.memoryStoreConversationService = memoryStoreConversationService; @@ -71,204 +78,269 @@ public class ConversationManagerService { this.conversationContextMapper = conversationContextMapper; this.dataLossPrevention = dataLossPrevention; this.dlpTemplateCompleteFlow = dlpTemplateCompleteFlow; -} - public Mono manageConversation(ExternalConvRequestDTO externalrequest) { - return dataLossPrevention.getObfuscatedString(externalrequest.message(), dlpTemplateCompleteFlow) - .flatMap(obfuscatedMessage -> { - ExternalConvRequestDTO obfuscatedRequest = new ExternalConvRequestDTO( - obfuscatedMessage, - externalrequest.user(), - externalrequest.channel(), - externalrequest.tipo(), - externalrequest.pantallaContexto() - ); - return memoryStoreConversationService.getSessionByTelefono(externalrequest.user().telefono()) - .flatMap(session -> { - - if (session != null && session.pantallaContexto() != null && !session.pantallaContexto().isBlank()) { - logger.info("Detected 'pantallaContexto' in session. Delegating to QuickRepliesManagerService."); - return quickRepliesManagerService.manageConversation(obfuscatedRequest); + this.notificationContextResolver = notificationContextResolver; + + } + + public Mono manageConversation(ExternalConvRequestDTO externalrequest) { + return dataLossPrevention.getObfuscatedString(externalrequest.message(), dlpTemplateCompleteFlow) + .flatMap(obfuscatedMessage -> { + ExternalConvRequestDTO obfuscatedRequest = new ExternalConvRequestDTO( + obfuscatedMessage, + externalrequest.user(), + externalrequest.channel(), + externalrequest.tipo(), + externalrequest.pantallaContexto()); + return memoryStoreConversationService.getSessionByTelefono(externalrequest.user().telefono()) + .flatMap(session -> { + + if (session != null && session.pantallaContexto() != null + && !session.pantallaContexto().isBlank()) { + logger.info( + "Detected 'pantallaContexto' in session. Delegating to QuickRepliesManagerService."); + return quickRepliesManagerService.manageConversation(obfuscatedRequest); } return continueManagingConversation(obfuscatedRequest); - }) - .switchIfEmpty(continueManagingConversation(obfuscatedRequest)); + }) + .switchIfEmpty(continueManagingConversation(obfuscatedRequest)); }); + } + + private Mono continueManagingConversation(ExternalConvRequestDTO externalrequest) { + final DetectIntentRequestDTO request; + try { + request = externalRequestToDialogflowMapper.mapExternalRequestToDetectIntentRequest(externalrequest); + logger.debug("Successfully pre-mapped ExternalRequestDTO to DetectIntentRequestDTO"); + } catch (IllegalArgumentException e) { + logger.error("Error during pre-mapping: {}", e.getMessage()); + return Mono.error(new IllegalArgumentException( + "Failed to process external request due to mapping error: " + e.getMessage(), e)); } - private Mono continueManagingConversation(ExternalConvRequestDTO externalrequest) { - final DetectIntentRequestDTO request; - try { - request = externalRequestToDialogflowMapper.mapExternalRequestToDetectIntentRequest(externalrequest); - logger.debug("Successfully pre-mapped ExternalRequestDTO to DetectIntentRequestDTO"); - } catch (IllegalArgumentException e) { - logger.error("Error during pre-mapping: {}", e.getMessage()); - return Mono.error(new IllegalArgumentException( - "Failed to process external request due to mapping error: " + e.getMessage(), e)); - } - - final ConversationContext context; - try { - context = resolveAndValidateRequest(request); - } catch (IllegalArgumentException e) { - logger.error("Validation error for incoming request: {}", e.getMessage()); - return Mono.error(e); - } - - return handleMessageClassification(context, request); - } - private Mono handleMessageClassification(ConversationContext context, DetectIntentRequestDTO request) { - final String userPhoneNumber = context.primaryPhoneNumber(); - final String userMessageText = context.userMessageText(); - - return memoryStoreConversationService.getSessionByTelefono(userPhoneNumber) - .map(conversationContextMapper::toText) - .defaultIfEmpty("") - .flatMap(conversationHistory -> { - return memoryStoreNotificationService.getNotificationIdForPhone(userPhoneNumber) - .flatMap(notificationId -> memoryStoreNotificationService.getCachedNotificationSession(notificationId)) - .map(notificationSession -> notificationSession.notificaciones().stream() - .filter(notification -> "active".equalsIgnoreCase(notification.status())) - .max(java.util.Comparator.comparing(NotificationDTO::timestampCreacion)) - .orElse(null)) - .filter(Objects::nonNull) - .flatMap((NotificationDTO notification) -> { - String notificationText = notificationContextMapper.toText(notification); - String classification = messageEntryFilter.classifyMessage(userMessageText, notificationText, conversationHistory); - if (MessageEntryFilter.CATEGORY_NOTIFICATION.equals(classification)) { - return startNotificationConversation(context, request, notification); - } else { - return continueConversationFlow(context, request); - } - }) - .switchIfEmpty(continueConversationFlow(context, request)); - }); - } - private Mono continueConversationFlow(ConversationContext context, DetectIntentRequestDTO request) { - final String userId = context.userId(); - final String userMessageText = context.userMessageText(); - final String userPhoneNumber = context.primaryPhoneNumber(); - - if (userPhoneNumber == null || userPhoneNumber.isBlank()) { - logger.warn("No phone number provided in request. Cannot manage conversation session without it."); - return Mono.error(new IllegalArgumentException("Phone number is required to manage conversation sessions.")); - } - - logger.info("Primary Check (MemoryStore): Looking up session for phone number: {}", userPhoneNumber); - return memoryStoreConversationService.getSessionByTelefono(userPhoneNumber) - .flatMap(session -> handleMessageClassification(context, request, session)) - .switchIfEmpty(Mono.defer(() -> { - logger.info("No session found in MemoryStore. Performing full lookup to Firestore."); - return fullLookupAndProcess(null, request, userId, userMessageText, userPhoneNumber); - })) - .onErrorResume(e -> { - logger.error("Overall error handling conversation in ConversationManagerService: {}", e.getMessage(), e); - return Mono.error(new RuntimeException("Failed to process conversation due to an internal error.", e)); - }); + final ConversationContext context; + try { + context = resolveAndValidateRequest(request); + } catch (IllegalArgumentException e) { + logger.error("Validation error for incoming request: {}", e.getMessage()); + return Mono.error(e); } - private Mono handleMessageClassification(ConversationContext context, DetectIntentRequestDTO request, ConversationSessionDTO session) { - final String userPhoneNumber = context.primaryPhoneNumber(); - final String userMessageText = context.userMessageText(); + return handleMessageClassification(context, request); + } - return memoryStoreNotificationService.getNotificationIdForPhone(userPhoneNumber) - .flatMap(notificationId -> memoryStoreNotificationService.getCachedNotificationSession(notificationId)) - .map(notificationSession -> notificationSession.notificaciones().stream() - .filter(notification -> "active".equalsIgnoreCase(notification.status())) - .max(java.util.Comparator.comparing(NotificationDTO::timestampCreacion)) - .orElse(null)) - .filter(Objects::nonNull) - .flatMap((NotificationDTO notification) -> { - String conversationHistory = conversationContextMapper.toText(session); + private Mono handleMessageClassification(ConversationContext context, + DetectIntentRequestDTO request) { + final String userPhoneNumber = context.primaryPhoneNumber(); + final String userMessageText = context.userMessageText(); + + return memoryStoreConversationService.getSessionByTelefono(userPhoneNumber) + .map(conversationContextMapper::toText) + .defaultIfEmpty("") + .flatMap(conversationHistory -> { + return memoryStoreNotificationService.getNotificationIdForPhone(userPhoneNumber) + .flatMap(notificationId -> memoryStoreNotificationService + .getCachedNotificationSession(notificationId)) + .map(notificationSession -> notificationSession.notificaciones().stream() + .filter(notification -> "active".equalsIgnoreCase(notification.status())) + .max(java.util.Comparator.comparing(NotificationDTO::timestampCreacion)) + .orElse(null)) + .filter(Objects::nonNull) + .flatMap((NotificationDTO notification) -> { String notificationText = notificationContextMapper.toText(notification); - String classification = messageEntryFilter.classifyMessage(userMessageText, notificationText, conversationHistory); + String classification = messageEntryFilter.classifyMessage(userMessageText, + notificationText, conversationHistory); if (MessageEntryFilter.CATEGORY_NOTIFICATION.equals(classification)) { - return startNotificationConversation(context, request, notification); + return startNotificationConversation(context, request, notification); } else { - return proceedWithConversation(context, request, session); + return continueConversationFlow(context, request); } - }) - .switchIfEmpty(proceedWithConversation(context, request, session)); - } + }) + .switchIfEmpty(continueConversationFlow(context, request)); + }); + } - private Mono proceedWithConversation(ConversationContext context, DetectIntentRequestDTO request, ConversationSessionDTO session) { - Instant now = Instant.now(); - if (Duration.between(session.lastModified(), now).toMinutes() < SESSION_RESET_THRESHOLD_MINUTES) { - logger.info("Recent Session Found: Session {} is within the 10-minute threshold. Proceeding to Dialogflow.", session.sessionId()); - return processDialogflowRequest(session, request, context.userId(), context.userMessageText(), context.primaryPhoneNumber(), false); - } else { - logger.info("Old Session Found: Session {} is older than the threshold. Fetching history and continuing with same session.", session.sessionId()); - String conversationHistory = conversationContextMapper.toTextWithLimits(session); - DetectIntentRequestDTO newRequest = request.withParameter(CONV_HISTORY_PARAM, conversationHistory); - return processDialogflowRequest(session, newRequest, context.userId(), context.userMessageText(), context.primaryPhoneNumber(), false); - } - } - - private Mono fullLookupAndProcess(ConversationSessionDTO oldSession, DetectIntentRequestDTO request, String userId, String userMessageText, String userPhoneNumber) { - return firestoreConversationService.getSessionByTelefono(userPhoneNumber) - .map(conversationContextMapper::toTextWithLimits) - .defaultIfEmpty("") - .flatMap(conversationHistory -> { - String newSessionId = SessionIdGenerator.generateStandardSessionId(); - logger.info("Creating new session {} after full lookup.", newSessionId); - ConversationSessionDTO newSession = ConversationSessionDTO.create(newSessionId, userId, userPhoneNumber); - DetectIntentRequestDTO newRequest = request.withParameter(CONV_HISTORY_PARAM, conversationHistory); - return processDialogflowRequest(newSession, newRequest, userId, userMessageText, userPhoneNumber, true); - }); - } - - private Mono processDialogflowRequest(ConversationSessionDTO session, DetectIntentRequestDTO request, String userId, String userMessageText, String userPhoneNumber, boolean newSession) { - final String finalSessionId = session.sessionId(); - - ConversationEntryDTO userEntry = ConversationEntryDTO.forUser(userMessageText); - - return this.persistConversationTurn(userId, finalSessionId, userEntry, userPhoneNumber) - .doOnSuccess(v -> logger.debug("User entry successfully persisted for session {}. Proceeding to Dialogflow...", finalSessionId)) - .doOnError(e -> logger.error("Error during user entry persistence for session {}: {}", finalSessionId, e.getMessage(), e)) - .then(Mono.defer(() -> dialogflowServiceClient.detectIntent(finalSessionId, request) - .flatMap(response -> { - logger.debug("Received Dialogflow CX response for session {}. Initiating agent response persistence.", finalSessionId); - ConversationEntryDTO agentEntry = ConversationEntryDTO.forAgent(response.queryResult()); - return persistConversationTurn(userId, finalSessionId, agentEntry, userPhoneNumber) - .thenReturn(response); - }) - .doOnError(error -> logger.error("Overall error during conversation management for session {}: {}", finalSessionId, error.getMessage(), error)) - )); - } - private Mono startNotificationConversation(ConversationContext context, DetectIntentRequestDTO request, NotificationDTO notification) { + private Mono continueConversationFlow(ConversationContext context, + DetectIntentRequestDTO request) { final String userId = context.userId(); final String userMessageText = context.userMessageText(); final String userPhoneNumber = context.primaryPhoneNumber(); + + if (userPhoneNumber == null || userPhoneNumber.isBlank()) { + logger.warn("No phone number provided in request. Cannot manage conversation session without it."); + return Mono + .error(new IllegalArgumentException("Phone number is required to manage conversation sessions.")); + } + + logger.info("Primary Check (MemoryStore): Looking up session for phone number: {}", userPhoneNumber); + return memoryStoreConversationService.getSessionByTelefono(userPhoneNumber) + .flatMap(session -> handleMessageClassification(context, request, session)) + .switchIfEmpty(Mono.defer(() -> { + logger.info("No session found in MemoryStore. Performing full lookup to Firestore."); + return fullLookupAndProcess(null, request, userId, userMessageText, userPhoneNumber); + })) + .onErrorResume(e -> { + logger.error("Overall error handling conversation in ConversationManagerService: {}", + e.getMessage(), e); + return Mono + .error(new RuntimeException("Failed to process conversation due to an internal error.", e)); + }); + } + + private Mono handleMessageClassification(ConversationContext context, + DetectIntentRequestDTO request, ConversationSessionDTO session) { + final String userPhoneNumber = context.primaryPhoneNumber(); + final String userMessageText = context.userMessageText(); + + return memoryStoreNotificationService.getNotificationIdForPhone(userPhoneNumber) + .flatMap(notificationId -> memoryStoreNotificationService.getCachedNotificationSession(notificationId)) + .map(notificationSession -> notificationSession.notificaciones().stream() + .filter(notification -> "active".equalsIgnoreCase(notification.status())) + .max(java.util.Comparator.comparing(NotificationDTO::timestampCreacion)) + .orElse(null)) + .filter(Objects::nonNull) + .flatMap((NotificationDTO notification) -> { + String conversationHistory = conversationContextMapper.toText(session); + String notificationText = notificationContextMapper.toText(notification); + String classification = messageEntryFilter.classifyMessage(userMessageText, notificationText, + conversationHistory); + if (MessageEntryFilter.CATEGORY_NOTIFICATION.equals(classification)) { + return startNotificationConversation(context, request, notification); + } else { + return proceedWithConversation(context, request, session); + } + }) + .switchIfEmpty(proceedWithConversation(context, request, session)); + } + + private Mono proceedWithConversation(ConversationContext context, + DetectIntentRequestDTO request, ConversationSessionDTO session) { + Instant now = Instant.now(); + if (Duration.between(session.lastModified(), now).toMinutes() < SESSION_RESET_THRESHOLD_MINUTES) { + logger.info("Recent Session Found: Session {} is within the 10-minute threshold. Proceeding to Dialogflow.", + session.sessionId()); + return processDialogflowRequest(session, request, context.userId(), context.userMessageText(), + context.primaryPhoneNumber(), false); + } else { + logger.info( + "Old Session Found: Session {} is older than the threshold. Fetching history and continuing with same session.", + session.sessionId()); + String conversationHistory = conversationContextMapper.toTextWithLimits(session); + DetectIntentRequestDTO newRequest = request.withParameter(CONV_HISTORY_PARAM, conversationHistory); + return processDialogflowRequest(session, newRequest, context.userId(), context.userMessageText(), + context.primaryPhoneNumber(), false); + } + } + + private Mono fullLookupAndProcess(ConversationSessionDTO oldSession, + DetectIntentRequestDTO request, String userId, String userMessageText, String userPhoneNumber) { + return firestoreConversationService.getSessionByTelefono(userPhoneNumber) + .map(conversationContextMapper::toTextWithLimits) + .defaultIfEmpty("") + .flatMap(conversationHistory -> { + String newSessionId = SessionIdGenerator.generateStandardSessionId(); + logger.info("Creating new session {} after full lookup.", newSessionId); + ConversationSessionDTO newSession = ConversationSessionDTO.create(newSessionId, userId, + userPhoneNumber); + DetectIntentRequestDTO newRequest = request.withParameter(CONV_HISTORY_PARAM, conversationHistory); + return processDialogflowRequest(newSession, newRequest, userId, userMessageText, userPhoneNumber, + true); + }); + } + + private Mono processDialogflowRequest(ConversationSessionDTO session, + DetectIntentRequestDTO request, String userId, String userMessageText, String userPhoneNumber, + boolean newSession) { + final String finalSessionId = session.sessionId(); + + ConversationEntryDTO userEntry = ConversationEntryDTO.forUser(userMessageText); + + return this.persistConversationTurn(userId, finalSessionId, userEntry, userPhoneNumber) + .doOnSuccess(v -> logger.debug( + "User entry successfully persisted for session {}. Proceeding to Dialogflow...", + finalSessionId)) + .doOnError(e -> logger.error("Error during user entry persistence for session {}: {}", finalSessionId, + e.getMessage(), e)) + .then(Mono.defer(() -> dialogflowServiceClient.detectIntent(finalSessionId, request) + .flatMap(response -> { + logger.debug( + "Received Dialogflow CX response for session {}. Initiating agent response persistence.", + finalSessionId); + ConversationEntryDTO agentEntry = ConversationEntryDTO.forAgent(response.queryResult()); + return persistConversationTurn(userId, finalSessionId, agentEntry, userPhoneNumber) + .thenReturn(response); + }) + .doOnError( + error -> logger.error("Overall error during conversation management for session {}: {}", + finalSessionId, error.getMessage(), error)))); + } + + private Mono startNotificationConversation(ConversationContext context, + DetectIntentRequestDTO request, NotificationDTO notification) { + final String userId = context.userId(); + final String userMessageText = context.userMessageText(); + final String userPhoneNumber = context.primaryPhoneNumber(); + return memoryStoreNotificationService.getSessionByTelefono(userPhoneNumber) .switchIfEmpty(Mono.defer(() -> { - String newSessionId = SessionIdGenerator.generateStandardSessionId(); - logger.info("No existing notification session found for phone number {}. Creating new session: {}", - userPhoneNumber, newSessionId); - return Mono.just(ConversationSessionDTO.create(newSessionId, userId, userPhoneNumber)); + String newSessionId = SessionIdGenerator.generateStandardSessionId(); + logger.info("No existing notification session found for phone number {}. Creating new session: {}", + userPhoneNumber, newSessionId); + return Mono.just(ConversationSessionDTO.create(newSessionId, userId, userPhoneNumber)); })) .flatMap(session -> { - final String sessionId = session.sessionId(); - ConversationEntryDTO userEntry = ConversationEntryDTO.forUser(userMessageText); - Instant now = Instant.now(); - if (Duration.between(session.lastModified(), now).toMinutes() < SESSION_RESET_THRESHOLD_MINUTES) { - return memoryStoreNotificationService.saveEntry(userId, sessionId, userEntry, userPhoneNumber) - .then(dialogflowServiceClient.detectIntent(sessionId, request) - .doOnSuccess(response -> { - ConversationEntryDTO agentEntry = ConversationEntryDTO.forAgent(response.queryResult()); - memoryStoreNotificationService.saveEntry(userId, sessionId, agentEntry, userPhoneNumber).subscribe(); + final String sessionId = session.sessionId(); + String conversationHistory = conversationContextMapper.toTextWithLimits(session); + String notificationText = notificationContextMapper.toText(notification); + + Map filteredParams = notification.parametros().entrySet().stream() + .filter(entry -> entry.getKey().startsWith("po_")) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + String resolvedContext = notificationContextResolver.resolveContext(userMessageText, + notificationText, conversationHistory, filteredParams.toString(), userId, sessionId, + userPhoneNumber); + + if (!NotificationContextResolver.CATEGORY_DIALOGFLOW.equals(resolvedContext)) { + ConversationEntryDTO userEntry = ConversationEntryDTO.forUser(userMessageText, + notification.parametros()); + ConversationEntryDTO llmEntry = ConversationEntryDTO.forLlmConversation(resolvedContext, + notification.parametros()); + + return persistNotificationTurn(userId, sessionId, userEntry, userPhoneNumber) + .then(persistNotificationTurn(userId, sessionId, llmEntry, userPhoneNumber)) + .then(Mono.fromCallable(() -> { + QueryResultDTO queryResult = new QueryResultDTO(resolvedContext, + java.util.Collections.emptyMap()); + return new DetectIntentResponseDTO(null, queryResult); + })); + } + + ConversationEntryDTO userEntry = ConversationEntryDTO.forUser(userMessageText, + notification.parametros()); + + DetectIntentRequestDTO finalRequest; + Instant now = Instant.now(); + if (Duration.between(session.lastModified(), now).toMinutes() < SESSION_RESET_THRESHOLD_MINUTES) { + finalRequest = request.withParameters(notification.parametros()); + } else { + finalRequest = request.withParameter(CONV_HISTORY_PARAM, conversationHistory) + .withParameters(notification.parametros()); + } + + return memoryStoreNotificationService.saveEntry(userId, sessionId, userEntry, userPhoneNumber) + .then(dialogflowServiceClient.detectIntent(sessionId, finalRequest) + .flatMap(response -> { + ConversationEntryDTO agentEntry = ConversationEntryDTO + .forAgent(response.queryResult()); + return memoryStoreNotificationService + .saveEntry(userId, sessionId, agentEntry, userPhoneNumber) + .thenReturn(response); })); - } else { - String conversationHistory = conversationContextMapper.toTextWithLimits(session); - DetectIntentRequestDTO newRequest = request.withParameter(CONV_HISTORY_PARAM, conversationHistory); - return memoryStoreNotificationService.saveEntry(userId, sessionId, userEntry, userPhoneNumber) - .then(dialogflowServiceClient.detectIntent(sessionId, newRequest) - .doOnSuccess(response -> { - ConversationEntryDTO agentEntry = ConversationEntryDTO.forAgent(response.queryResult()); - memoryStoreNotificationService.saveEntry(userId, sessionId, agentEntry, userPhoneNumber).subscribe(); - })); - } }); - } - private Mono persistConversationTurn(String userId, String sessionId, ConversationEntryDTO entry,String userPhoneNumber) { + } + + private Mono persistConversationTurn(String userId, String sessionId, ConversationEntryDTO entry, + String userPhoneNumber) { logger.debug("Starting Write-Back persistence for session {}. Type: {}. Writing to Redis first.", sessionId, entry.type().name()); return memoryStoreConversationService.saveEntry(userId, sessionId, entry, userPhoneNumber) @@ -284,42 +356,54 @@ public class ConversationManagerService { sessionId, entry.type().name(), fsError.getMessage(), fsError))) .doOnError(e -> logger.error("Error during primary Redis write for session {}. Type: {}: {}", sessionId, entry.type().name(), e.getMessage(), e)); - } - - private ConversationContext resolveAndValidateRequest(DetectIntentRequestDTO request) { + } + + 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, + 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, + entry.type().name(), e.getMessage(), e)); + } + + private ConversationContext resolveAndValidateRequest(DetectIntentRequestDTO request) { Map params = Optional.ofNullable(request.queryParams()) .map(queryParamsDTO -> queryParamsDTO.parameters()) .orElse(Collections.emptyMap()); String primaryPhoneNumber = null; Object telefonoObj = params.get("telefono"); // Get from map if (telefonoObj instanceof String) { - primaryPhoneNumber = (String) telefonoObj; + primaryPhoneNumber = (String) telefonoObj; } else if (telefonoObj != null) { - logger.warn("Parameter 'telefono' in queryParams is not a String (type: {}). Expected String.", - telefonoObj.getClass().getName()); + logger.warn("Parameter 'telefono' in queryParams is not a String (type: {}). Expected String.", + telefonoObj.getClass().getName()); } if (primaryPhoneNumber == null || primaryPhoneNumber.trim().isEmpty()) { - throw new IllegalArgumentException( - "Phone number (telefono) is required in query parameters for conversation management."); + throw new IllegalArgumentException( + "Phone number (telefono) is required in query parameters for conversation management."); } String resolvedUserId = null; Object userIdObj = params.get("usuario_id"); if (userIdObj instanceof String) { - resolvedUserId = (String) userIdObj; + resolvedUserId = (String) userIdObj; } else if (userIdObj != null) { - logger.warn("Parameter 'userId' in query_params is not a String (type: {}). Expected String.", - userIdObj.getClass().getName()); + logger.warn("Parameter 'userId' in query_params is not a String (type: {}). Expected String.", + userIdObj.getClass().getName()); } if (resolvedUserId == null || resolvedUserId.trim().isEmpty()) { - resolvedUserId = "user_by_phone_" + primaryPhoneNumber.replaceAll("[^0-9]", ""); - logger.warn("User ID not provided in query parameters. Using derived ID from phone number: {}", - resolvedUserId); + resolvedUserId = "user_by_phone_" + primaryPhoneNumber.replaceAll("[^0-9]", ""); + logger.warn("User ID not provided in query parameters. Using derived ID from phone number: {}", + resolvedUserId); } if (request.queryInput() == null || request.queryInput().text() == null || request.queryInput().text().text() == null || request.queryInput().text().text().trim().isEmpty()) { - throw new IllegalArgumentException("Dialogflow query input text is required."); + throw new IllegalArgumentException("Dialogflow query input text is required."); } String userMessageText = request.queryInput().text().text(); return new ConversationContext(resolvedUserId, null, userMessageText, primaryPhoneNumber); -} + } } \ No newline at end of file diff --git a/src/main/java/com/example/service/conversation/FirestoreConversationService.java b/src/main/java/com/example/service/conversation/FirestoreConversationService.java index 5ddc7b5..89c6f03 100644 --- a/src/main/java/com/example/service/conversation/FirestoreConversationService.java +++ b/src/main/java/com/example/service/conversation/FirestoreConversationService.java @@ -53,7 +53,9 @@ public class FirestoreConversationService { WriteBatch batch = firestoreBaseRepository.createBatch(); try { - if (firestoreBaseRepository.documentExists(sessionDocRef)) { + DocumentSnapshot documentSnapshot = firestoreBaseRepository.getDocumentSnapshot(sessionDocRef); + + if (documentSnapshot != null && documentSnapshot.exists()) { // Update: Append the new entry using arrayUnion and update lastModified Map updates = firestoreConversationMapper.createUpdateMapForSingleEntry(newEntry); if (pantallaContexto != null) { diff --git a/src/main/java/com/example/service/conversation/MemoryStoreConversationService.java b/src/main/java/com/example/service/conversation/MemoryStoreConversationService.java index 3c53a72..d5ab7d6 100644 --- a/src/main/java/com/example/service/conversation/MemoryStoreConversationService.java +++ b/src/main/java/com/example/service/conversation/MemoryStoreConversationService.java @@ -52,6 +52,13 @@ public class MemoryStoreConversationService { logger.info("Attempting to save entry to Redis 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); + } else { + logger.info("No session found in Redis for key: {}", sessionKey); + } + }) .switchIfEmpty(Mono.defer(() -> { logger.info("Creating new session {} in Redis with TTL.", sessionId); ConversationSessionDTO newSession = ConversationSessionDTO.create(sessionId, userId, userPhoneNumber); @@ -64,6 +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.info("Attempting to set updated session {} with new entry entity {} in Redis.", sessionId, newEntry.entity().name()); return redisTemplate.opsForValue().set(sessionKey, updatedSession) diff --git a/src/main/java/com/example/service/notification/MemoryStoreNotificationService.java b/src/main/java/com/example/service/notification/MemoryStoreNotificationService.java index 1277772..29e6fe1 100644 --- a/src/main/java/com/example/service/notification/MemoryStoreNotificationService.java +++ b/src/main/java/com/example/service/notification/MemoryStoreNotificationService.java @@ -134,17 +134,8 @@ public class MemoryStoreNotificationService { .then(stringRedisTemplate.opsForValue().set(phoneToSessionKey, sessionId, notificationTtl)) .then(); }) - .doOnSuccess(success ->{ - logger.info("Successfully saved updated session and phone mapping to Redis for session {}. Entity Type: {}", sessionId, newEntry.entity().name()); - firestoreNotificationConvService.saveEntry(userId, sessionId, newEntry, userPhoneNumber) - .subscribe( - fsVoid -> logger.debug( - "Asynchronously (Write-Back): Entry successfully saved to Firestore for session {}. Type: {}.", - sessionId, newEntry.type().name()), - fsError -> logger.error( - "Asynchronously (Write-Back): Failed to save entry to Firestore for session {}. Type: {}: {}", - sessionId, newEntry.type().name(), fsError.getMessage(), fsError)); - }) + .doOnSuccess(success -> logger.info("Successfully saved updated session and phone mapping to Redis for session {}. Entity Type: {}", sessionId, newEntry.entity().name())) + .then(firestoreNotificationConvService.saveEntry(userId, sessionId, newEntry, userPhoneNumber)) .doOnError(e -> logger.error("Error appending entry to Redis for session {}: {}", sessionId, e.getMessage(), e)); } diff --git a/src/main/resources/prompts/notification_context_resolver.txt b/src/main/resources/prompts/notification_context_resolver.txt new file mode 100644 index 0000000..7e6f356 --- /dev/null +++ b/src/main/resources/prompts/notification_context_resolver.txt @@ -0,0 +1,41 @@ +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" +} + +**Examples (Few-Shot Learning):** +Ejemplo 2: Información faltante + +Notificación: Tu cita para el servicio de mantenimiento ha sido confirmada. Por favor, llega 15 minutos antes. +Metadatos_ Notificación: { + "tipo_servicio": "mantenimiento rutinario", + "ubicacion": "Sucursal Centro" +} + +Historial de Conversación: +%s + +Notificación: +%s + +Metadatos de la Notificación: +%s + +Pregunta del Usuario: +%s \ No newline at end of file diff --git a/src/test/java/com/example/service/integration_testing/MessageEntryFilterIntegrationTest.java b/src/test/java/com/example/service/integration_testing/MessageEntryFilterIntegrationTest.java index a44a682..890f03b 100644 --- a/src/test/java/com/example/service/integration_testing/MessageEntryFilterIntegrationTest.java +++ b/src/test/java/com/example/service/integration_testing/MessageEntryFilterIntegrationTest.java @@ -18,7 +18,7 @@ import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; @SpringBootTest -@ActiveProfiles("test") +@ActiveProfiles("dev") @DisplayName("MessageEntryFilter Integration Tests") public class MessageEntryFilterIntegrationTest { diff --git a/src/test/java/com/example/service/integration_testing/NotificationContextResolverLiveTest.java b/src/test/java/com/example/service/integration_testing/NotificationContextResolverLiveTest.java new file mode 100644 index 0000000..728aa8b --- /dev/null +++ b/src/test/java/com/example/service/integration_testing/NotificationContextResolverLiveTest.java @@ -0,0 +1,51 @@ +package com.example.service.integration_testing; + +import com.example.service.base.NotificationContextResolver; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@SpringBootTest +@ActiveProfiles("dev") +@DisplayName("NotificationContextResolver Live Tests") +public class NotificationContextResolverLiveTest { + + private String notificationsJson; + private String conversationJson; + private String queryInputText; + private String metadataJson; + + @Autowired + private NotificationContextResolver notificationContextResolver; + + @BeforeEach + void setUp() { + notificationsJson = "Hola :\n" + + "Pasó algo con la captura de tu INE y no se completó tu *solicitud de tarjeta de crédito con folio *.\n" + + + "¡Reinténtalo cuando quieras! Solo toma en cuenta estos consejos:\n" + + "🪪 Presenta tu INE original (no copias ni escaneos).\n" + + "📅Revisa que esté vigente y sin tachaduras.\n" + + "📷 Confirma que la fotografía sea clara.\n" + + "🏠 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"; + metadataJson = "{\"contexto\":\"campañaprueba\",\"id_aplicacion\":\"TestSigma\",\"id_campaña\":\"campaña01\",\"id_notificacion\":\"Prueba2\",\"vigencia\":\"30/09/2025\"}"; + } + + @Test + @DisplayName("Should get live response from LLM and print it") + public void shouldGetLiveResponseFromLlmAndPrintIt() { + String result = notificationContextResolver.resolveContext(queryInputText, notificationsJson, conversationJson, + metadataJson, "test_user", "test_session", "1234567890"); + System.out.println("Live LLM Response: " + result); + assertNotNull(result); + } +}