UPDATE 10-sept

This commit is contained in:
PAVEL PALMA
2025-09-10 10:24:55 -06:00
parent 11888d2632
commit 9b1824bc48
15 changed files with 582 additions and 234 deletions

View File

@@ -26,4 +26,15 @@ public DetectIntentRequestDTO withParameter(String key, Object value) {
updatedQueryParams updatedQueryParams
); );
} }
public DetectIntentRequestDTO withParameters(java.util.Map<String, Object> 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
);
}
} }

View File

@@ -28,6 +28,16 @@ public record ConversationEntryDTO(
null); null);
} }
public static ConversationEntryDTO forUser(String text, Map<String, Object> parameters) {
return new ConversationEntryDTO(
ConversationEntryEntity.USUARIO,
ConversationEntryType.CONVERSACION,
Instant.now(),
text,
parameters,
null);
}
public static ConversationEntryDTO forAgent(QueryResultDTO agentQueryResult) { public static ConversationEntryDTO forAgent(QueryResultDTO agentQueryResult) {
String fulfillmentText = (agentQueryResult != null && agentQueryResult.responseText() != null) ? agentQueryResult.responseText() : ""; String fulfillmentText = (agentQueryResult != null && agentQueryResult.responseText() != null) ? agentQueryResult.responseText() : "";
Map<String, Object> parameters = (agentQueryResult != null) ? agentQueryResult.parameters() : null; Map<String, Object> parameters = (agentQueryResult != null) ? agentQueryResult.parameters() : null;
@@ -63,6 +73,8 @@ public record ConversationEntryDTO(
); );
} }
public static ConversationEntryDTO forSystem(String text, Map<String, Object> parameters) { public static ConversationEntryDTO forSystem(String text, Map<String, Object> parameters) {
return new ConversationEntryDTO( return new ConversationEntryDTO(
ConversationEntryEntity.SISTEMA, ConversationEntryEntity.SISTEMA,
@@ -73,4 +85,26 @@ public record ConversationEntryDTO(
null 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<String, Object> parameters) {
return new ConversationEntryDTO(
ConversationEntryEntity.LLM,
ConversationEntryType.CONVERSACION,
Instant.now(),
text,
parameters,
null
);
}
} }

View File

@@ -8,5 +8,6 @@ package com.example.dto.dialogflow.conversation;
public enum ConversationEntryEntity { public enum ConversationEntryEntity {
USUARIO, USUARIO,
AGENTE, AGENTE,
SISTEMA SISTEMA,
LLM
} }

View File

@@ -7,5 +7,6 @@ package com.example.dto.dialogflow.conversation;
public enum ConversationEntryType { public enum ConversationEntryType {
INICIO, INICIO,
CONVERSACION CONVERSACION,
LLM
} }

View File

@@ -25,4 +25,10 @@ public record QueryParamsDTO(
updatedParams.put(key, value); updatedParams.put(key, value);
return new QueryParamsDTO(updatedParams); return new QueryParamsDTO(updatedParams);
} }
public QueryParamsDTO withSessionParameters(Map<String, Object> parameters) {
Map<String, Object> updatedParams = new HashMap<>(this.parameters());
updatedParams.putAll(parameters);
return new QueryParamsDTO(updatedParams);
}
} }

View File

@@ -103,6 +103,7 @@ public class FirestoreConversationMapper {
if (entry.canal() != null) { if (entry.canal() != null) {
entryMap.put(FIELD_MESSAGE_CHANNEL, entry.canal()); entryMap.put(FIELD_MESSAGE_CHANNEL, entry.canal());
} }
logger.debug("Created Firestore entry map: {}", entryMap);
return entryMap; return entryMap;
} }

View File

@@ -49,9 +49,12 @@ public class ExternalNotRequestMapper {
if (request.hiddenParameters() != null && !request.hiddenParameters().isEmpty()) { if (request.hiddenParameters() != null && !request.hiddenParameters().isEmpty()) {
StringBuilder poBuilder = new StringBuilder();
request.hiddenParameters().forEach((key, value) -> { request.hiddenParameters().forEach((key, value) -> {
parameters.put(PREFIX_PO_PARAM + 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); TextInputDTO textInput = new TextInputDTO(NOTIFICATION_LABEL);

View File

@@ -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;
}
}
}

View File

@@ -3,18 +3,21 @@
* Your use of it is subject to your agreement with Google. * Your use of it is subject to your agreement with Google.
*/ */
package com.example.service.conversation; package com.example.service.conversation;
import com.example.dto.dialogflow.base.DetectIntentRequestDTO; import com.example.dto.dialogflow.base.DetectIntentRequestDTO;
import com.example.dto.dialogflow.base.DetectIntentResponseDTO; import com.example.dto.dialogflow.base.DetectIntentResponseDTO;
import com.example.dto.dialogflow.conversation.ConversationContext; import com.example.dto.dialogflow.conversation.ConversationContext;
import com.example.dto.dialogflow.conversation.ConversationEntryDTO; import com.example.dto.dialogflow.conversation.ConversationEntryDTO;
import com.example.dto.dialogflow.conversation.ConversationSessionDTO; import com.example.dto.dialogflow.conversation.ConversationSessionDTO;
import com.example.dto.dialogflow.conversation.ExternalConvRequestDTO; import com.example.dto.dialogflow.conversation.ExternalConvRequestDTO;
import com.example.dto.dialogflow.conversation.QueryResultDTO;
import com.example.dto.dialogflow.notification.NotificationDTO; import com.example.dto.dialogflow.notification.NotificationDTO;
import com.example.mapper.conversation.ExternalConvRequestMapper; import com.example.mapper.conversation.ExternalConvRequestMapper;
import com.example.mapper.messagefilter.ConversationContextMapper; import com.example.mapper.messagefilter.ConversationContextMapper;
import com.example.mapper.messagefilter.NotificationContextMapper; import com.example.mapper.messagefilter.NotificationContextMapper;
import com.example.service.base.DialogflowClientService; import com.example.service.base.DialogflowClientService;
import com.example.service.base.MessageEntryFilter; import com.example.service.base.MessageEntryFilter;
import com.example.service.base.NotificationContextResolver;
import com.example.service.notification.MemoryStoreNotificationService; import com.example.service.notification.MemoryStoreNotificationService;
import com.example.service.quickreplies.QuickRepliesManagerService; import com.example.service.quickreplies.QuickRepliesManagerService;
import com.example.util.SessionIdGenerator; import com.example.util.SessionIdGenerator;
@@ -29,37 +32,41 @@ import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
@Service @Service
public class ConversationManagerService { public class ConversationManagerService {
private static final Logger logger = LoggerFactory.getLogger(ConversationManagerService.class); private static final Logger logger = LoggerFactory.getLogger(ConversationManagerService.class);
private static final long SESSION_RESET_THRESHOLD_MINUTES = 30; private static final long SESSION_RESET_THRESHOLD_MINUTES = 30;
private static final String CONV_HISTORY_PARAM = "conversation_history"; private static final String CONV_HISTORY_PARAM = "conversation_history";
private final ExternalConvRequestMapper externalRequestToDialogflowMapper; private final ExternalConvRequestMapper externalRequestToDialogflowMapper;
private final DialogflowClientService dialogflowServiceClient; private final DialogflowClientService dialogflowServiceClient;
private final FirestoreConversationService firestoreConversationService; private final FirestoreConversationService firestoreConversationService;
private final MemoryStoreConversationService memoryStoreConversationService; private final MemoryStoreConversationService memoryStoreConversationService;
private final QuickRepliesManagerService quickRepliesManagerService; private final QuickRepliesManagerService quickRepliesManagerService;
private final MessageEntryFilter messageEntryFilter; private final MessageEntryFilter messageEntryFilter;
private final MemoryStoreNotificationService memoryStoreNotificationService; private final MemoryStoreNotificationService memoryStoreNotificationService;
private final NotificationContextMapper notificationContextMapper; private final NotificationContextMapper notificationContextMapper;
private final ConversationContextMapper conversationContextMapper; private final ConversationContextMapper conversationContextMapper;
private final DataLossPrevention dataLossPrevention; private final DataLossPrevention dataLossPrevention;
private final String dlpTemplateCompleteFlow; private final String dlpTemplateCompleteFlow;
public ConversationManagerService( private final NotificationContextResolver notificationContextResolver;
DialogflowClientService dialogflowServiceClient,
FirestoreConversationService firestoreConversationService, public ConversationManagerService(
MemoryStoreConversationService memoryStoreConversationService, DialogflowClientService dialogflowServiceClient,
ExternalConvRequestMapper externalRequestToDialogflowMapper, FirestoreConversationService firestoreConversationService,
QuickRepliesManagerService quickRepliesManagerService, MemoryStoreConversationService memoryStoreConversationService,
MessageEntryFilter messageEntryFilter, ExternalConvRequestMapper externalRequestToDialogflowMapper,
MemoryStoreNotificationService memoryStoreNotificationService, QuickRepliesManagerService quickRepliesManagerService,
NotificationContextMapper notificationContextMapper, MessageEntryFilter messageEntryFilter,
ConversationContextMapper conversationContextMapper, MemoryStoreNotificationService memoryStoreNotificationService,
DataLossPrevention dataLossPrevention, NotificationContextMapper notificationContextMapper,
@Value("${google.cloud.dlp.dlpTemplateCompleteFlow}") String dlpTemplateCompleteFlow) { ConversationContextMapper conversationContextMapper,
DataLossPrevention dataLossPrevention,
NotificationContextResolver notificationContextResolver,
@Value("${google.cloud.dlp.dlpTemplateCompleteFlow}") String dlpTemplateCompleteFlow) {
this.dialogflowServiceClient = dialogflowServiceClient; this.dialogflowServiceClient = dialogflowServiceClient;
this.firestoreConversationService = firestoreConversationService; this.firestoreConversationService = firestoreConversationService;
this.memoryStoreConversationService = memoryStoreConversationService; this.memoryStoreConversationService = memoryStoreConversationService;
@@ -71,204 +78,269 @@ public class ConversationManagerService {
this.conversationContextMapper = conversationContextMapper; this.conversationContextMapper = conversationContextMapper;
this.dataLossPrevention = dataLossPrevention; this.dataLossPrevention = dataLossPrevention;
this.dlpTemplateCompleteFlow = dlpTemplateCompleteFlow; this.dlpTemplateCompleteFlow = dlpTemplateCompleteFlow;
} this.notificationContextResolver = notificationContextResolver;
public Mono<DetectIntentResponseDTO> 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); public Mono<DetectIntentResponseDTO> 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); return continueManagingConversation(obfuscatedRequest);
}) })
.switchIfEmpty(continueManagingConversation(obfuscatedRequest)); .switchIfEmpty(continueManagingConversation(obfuscatedRequest));
}); });
}
private Mono<DetectIntentResponseDTO> 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<DetectIntentResponseDTO> continueManagingConversation(ExternalConvRequestDTO externalrequest) { final ConversationContext context;
final DetectIntentRequestDTO request; try {
try { context = resolveAndValidateRequest(request);
request = externalRequestToDialogflowMapper.mapExternalRequestToDetectIntentRequest(externalrequest); } catch (IllegalArgumentException e) {
logger.debug("Successfully pre-mapped ExternalRequestDTO to DetectIntentRequestDTO"); logger.error("Validation error for incoming request: {}", e.getMessage());
} catch (IllegalArgumentException e) { return Mono.error(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<DetectIntentResponseDTO> 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<DetectIntentResponseDTO> 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<DetectIntentResponseDTO> handleMessageClassification(ConversationContext context, DetectIntentRequestDTO request, ConversationSessionDTO session) { return handleMessageClassification(context, request);
final String userPhoneNumber = context.primaryPhoneNumber(); }
final String userMessageText = context.userMessageText();
return memoryStoreNotificationService.getNotificationIdForPhone(userPhoneNumber) private Mono<DetectIntentResponseDTO> handleMessageClassification(ConversationContext context,
.flatMap(notificationId -> memoryStoreNotificationService.getCachedNotificationSession(notificationId)) DetectIntentRequestDTO request) {
.map(notificationSession -> notificationSession.notificaciones().stream() final String userPhoneNumber = context.primaryPhoneNumber();
.filter(notification -> "active".equalsIgnoreCase(notification.status())) final String userMessageText = context.userMessageText();
.max(java.util.Comparator.comparing(NotificationDTO::timestampCreacion))
.orElse(null)) return memoryStoreConversationService.getSessionByTelefono(userPhoneNumber)
.filter(Objects::nonNull) .map(conversationContextMapper::toText)
.flatMap((NotificationDTO notification) -> { .defaultIfEmpty("")
String conversationHistory = conversationContextMapper.toText(session); .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 notificationText = notificationContextMapper.toText(notification);
String classification = messageEntryFilter.classifyMessage(userMessageText, notificationText, conversationHistory); String classification = messageEntryFilter.classifyMessage(userMessageText,
notificationText, conversationHistory);
if (MessageEntryFilter.CATEGORY_NOTIFICATION.equals(classification)) { if (MessageEntryFilter.CATEGORY_NOTIFICATION.equals(classification)) {
return startNotificationConversation(context, request, notification); return startNotificationConversation(context, request, notification);
} else { } else {
return proceedWithConversation(context, request, session); return continueConversationFlow(context, request);
} }
}) })
.switchIfEmpty(proceedWithConversation(context, request, session)); .switchIfEmpty(continueConversationFlow(context, request));
} });
}
private Mono<DetectIntentResponseDTO> proceedWithConversation(ConversationContext context, DetectIntentRequestDTO request, ConversationSessionDTO session) { private Mono<DetectIntentResponseDTO> continueConversationFlow(ConversationContext context,
Instant now = Instant.now(); DetectIntentRequestDTO request) {
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<DetectIntentResponseDTO> 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<DetectIntentResponseDTO> 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<DetectIntentResponseDTO> startNotificationConversation(ConversationContext context, DetectIntentRequestDTO request, NotificationDTO notification) {
final String userId = context.userId(); final String userId = context.userId();
final String userMessageText = context.userMessageText(); final String userMessageText = context.userMessageText();
final String userPhoneNumber = context.primaryPhoneNumber(); 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<DetectIntentResponseDTO> 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<DetectIntentResponseDTO> 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<DetectIntentResponseDTO> 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<DetectIntentResponseDTO> 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<DetectIntentResponseDTO> 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) return memoryStoreNotificationService.getSessionByTelefono(userPhoneNumber)
.switchIfEmpty(Mono.defer(() -> { .switchIfEmpty(Mono.defer(() -> {
String newSessionId = SessionIdGenerator.generateStandardSessionId(); String newSessionId = SessionIdGenerator.generateStandardSessionId();
logger.info("No existing notification session found for phone number {}. Creating new session: {}", logger.info("No existing notification session found for phone number {}. Creating new session: {}",
userPhoneNumber, newSessionId); userPhoneNumber, newSessionId);
return Mono.just(ConversationSessionDTO.create(newSessionId, userId, userPhoneNumber)); return Mono.just(ConversationSessionDTO.create(newSessionId, userId, userPhoneNumber));
})) }))
.flatMap(session -> { .flatMap(session -> {
final String sessionId = session.sessionId(); final String sessionId = session.sessionId();
ConversationEntryDTO userEntry = ConversationEntryDTO.forUser(userMessageText); String conversationHistory = conversationContextMapper.toTextWithLimits(session);
Instant now = Instant.now(); String notificationText = notificationContextMapper.toText(notification);
if (Duration.between(session.lastModified(), now).toMinutes() < SESSION_RESET_THRESHOLD_MINUTES) {
return memoryStoreNotificationService.saveEntry(userId, sessionId, userEntry, userPhoneNumber) Map<String, Object> filteredParams = notification.parametros().entrySet().stream()
.then(dialogflowServiceClient.detectIntent(sessionId, request) .filter(entry -> entry.getKey().startsWith("po_"))
.doOnSuccess(response -> { .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
ConversationEntryDTO agentEntry = ConversationEntryDTO.forAgent(response.queryResult());
memoryStoreNotificationService.saveEntry(userId, sessionId, agentEntry, userPhoneNumber).subscribe(); 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<Void> persistConversationTurn(String userId, String sessionId, ConversationEntryDTO entry,String userPhoneNumber) {
private Mono<Void> persistConversationTurn(String userId, String sessionId, ConversationEntryDTO entry,
String userPhoneNumber) {
logger.debug("Starting Write-Back persistence for session {}. Type: {}. Writing to Redis first.", sessionId, logger.debug("Starting Write-Back persistence for session {}. Type: {}. Writing to Redis first.", sessionId,
entry.type().name()); entry.type().name());
return memoryStoreConversationService.saveEntry(userId, sessionId, entry, userPhoneNumber) return memoryStoreConversationService.saveEntry(userId, sessionId, entry, userPhoneNumber)
@@ -284,42 +356,54 @@ public class ConversationManagerService {
sessionId, entry.type().name(), fsError.getMessage(), fsError))) sessionId, entry.type().name(), fsError.getMessage(), fsError)))
.doOnError(e -> logger.error("Error during primary Redis write for session {}. Type: {}: {}", sessionId, .doOnError(e -> logger.error("Error during primary Redis write for session {}. Type: {}: {}", sessionId,
entry.type().name(), e.getMessage(), e)); entry.type().name(), e.getMessage(), e));
} }
private ConversationContext resolveAndValidateRequest(DetectIntentRequestDTO request) { private Mono<Void> 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<String, Object> params = Optional.ofNullable(request.queryParams()) Map<String, Object> params = Optional.ofNullable(request.queryParams())
.map(queryParamsDTO -> queryParamsDTO.parameters()) .map(queryParamsDTO -> queryParamsDTO.parameters())
.orElse(Collections.emptyMap()); .orElse(Collections.emptyMap());
String primaryPhoneNumber = null; String primaryPhoneNumber = null;
Object telefonoObj = params.get("telefono"); // Get from map Object telefonoObj = params.get("telefono"); // Get from map
if (telefonoObj instanceof String) { if (telefonoObj instanceof String) {
primaryPhoneNumber = (String) telefonoObj; primaryPhoneNumber = (String) telefonoObj;
} else if (telefonoObj != null) { } else if (telefonoObj != null) {
logger.warn("Parameter 'telefono' in queryParams is not a String (type: {}). Expected String.", logger.warn("Parameter 'telefono' in queryParams is not a String (type: {}). Expected String.",
telefonoObj.getClass().getName()); telefonoObj.getClass().getName());
} }
if (primaryPhoneNumber == null || primaryPhoneNumber.trim().isEmpty()) { if (primaryPhoneNumber == null || primaryPhoneNumber.trim().isEmpty()) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Phone number (telefono) is required in query parameters for conversation management."); "Phone number (telefono) is required in query parameters for conversation management.");
} }
String resolvedUserId = null; String resolvedUserId = null;
Object userIdObj = params.get("usuario_id"); Object userIdObj = params.get("usuario_id");
if (userIdObj instanceof String) { if (userIdObj instanceof String) {
resolvedUserId = (String) userIdObj; resolvedUserId = (String) userIdObj;
} else if (userIdObj != null) { } else if (userIdObj != null) {
logger.warn("Parameter 'userId' in query_params is not a String (type: {}). Expected String.", logger.warn("Parameter 'userId' in query_params is not a String (type: {}). Expected String.",
userIdObj.getClass().getName()); userIdObj.getClass().getName());
} }
if (resolvedUserId == null || resolvedUserId.trim().isEmpty()) { if (resolvedUserId == null || resolvedUserId.trim().isEmpty()) {
resolvedUserId = "user_by_phone_" + primaryPhoneNumber.replaceAll("[^0-9]", ""); resolvedUserId = "user_by_phone_" + primaryPhoneNumber.replaceAll("[^0-9]", "");
logger.warn("User ID not provided in query parameters. Using derived ID from phone number: {}", logger.warn("User ID not provided in query parameters. Using derived ID from phone number: {}",
resolvedUserId); resolvedUserId);
} }
if (request.queryInput() == null || request.queryInput().text() == null || if (request.queryInput() == null || request.queryInput().text() == null ||
request.queryInput().text().text() == null || request.queryInput().text().text().trim().isEmpty()) { 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(); String userMessageText = request.queryInput().text().text();
return new ConversationContext(resolvedUserId, null, userMessageText, primaryPhoneNumber); return new ConversationContext(resolvedUserId, null, userMessageText, primaryPhoneNumber);
} }
} }

View File

@@ -53,7 +53,9 @@ public class FirestoreConversationService {
WriteBatch batch = firestoreBaseRepository.createBatch(); WriteBatch batch = firestoreBaseRepository.createBatch();
try { 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 // Update: Append the new entry using arrayUnion and update lastModified
Map<String, Object> updates = firestoreConversationMapper.createUpdateMapForSingleEntry(newEntry); Map<String, Object> updates = firestoreConversationMapper.createUpdateMapForSingleEntry(newEntry);
if (pantallaContexto != null) { if (pantallaContexto != null) {

View File

@@ -52,6 +52,13 @@ public class MemoryStoreConversationService {
logger.info("Attempting to save entry to Redis for session {}. Entity: {}", sessionId, newEntry.entity().name()); logger.info("Attempting to save entry to Redis for session {}. Entity: {}", sessionId, newEntry.entity().name());
return redisTemplate.opsForValue().get(sessionKey) 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(() -> { .switchIfEmpty(Mono.defer(() -> {
logger.info("Creating new session {} in Redis with TTL.", sessionId); logger.info("Creating new session {} in Redis with TTL.", sessionId);
ConversationSessionDTO newSession = ConversationSessionDTO.create(sessionId, userId, userPhoneNumber); ConversationSessionDTO newSession = ConversationSessionDTO.create(sessionId, userId, userPhoneNumber);
@@ -64,6 +71,7 @@ public class MemoryStoreConversationService {
ConversationSessionDTO sessionWithPantallaContexto = (pantallaContexto != null) ? sessionWithUpdatedTelefono.withPantallaContexto(pantallaContexto) : sessionWithUpdatedTelefono; ConversationSessionDTO sessionWithPantallaContexto = (pantallaContexto != null) ? sessionWithUpdatedTelefono.withPantallaContexto(pantallaContexto) : sessionWithUpdatedTelefono;
ConversationSessionDTO updatedSession = sessionWithPantallaContexto.withAddedEntry(newEntry); 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()); logger.info("Attempting to set updated session {} with new entry entity {} in Redis.", sessionId, newEntry.entity().name());
return redisTemplate.opsForValue().set(sessionKey, updatedSession) return redisTemplate.opsForValue().set(sessionKey, updatedSession)

View File

@@ -134,17 +134,8 @@ public class MemoryStoreNotificationService {
.then(stringRedisTemplate.opsForValue().set(phoneToSessionKey, sessionId, notificationTtl)) .then(stringRedisTemplate.opsForValue().set(phoneToSessionKey, sessionId, notificationTtl))
.then(); .then();
}) })
.doOnSuccess(success ->{ .doOnSuccess(success -> logger.info("Successfully saved updated session and phone mapping to Redis for session {}. Entity Type: {}", sessionId, newEntry.entity().name()))
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))
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));
})
.doOnError(e -> logger.error("Error appending entry to Redis for session {}: {}", sessionId, e.getMessage(), e)); .doOnError(e -> logger.error("Error appending entry to Redis for session {}: {}", sessionId, e.getMessage(), e));
} }

View File

@@ -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

View File

@@ -18,7 +18,7 @@ import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
@SpringBootTest @SpringBootTest
@ActiveProfiles("test") @ActiveProfiles("dev")
@DisplayName("MessageEntryFilter Integration Tests") @DisplayName("MessageEntryFilter Integration Tests")
public class MessageEntryFilterIntegrationTest { public class MessageEntryFilterIntegrationTest {

View File

@@ -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);
}
}