UPDATE 10-sept
This commit is contained in:
@@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -8,5 +8,6 @@ package com.example.dto.dialogflow.conversation;
|
|||||||
public enum ConversationEntryEntity {
|
public enum ConversationEntryEntity {
|
||||||
USUARIO,
|
USUARIO,
|
||||||
AGENTE,
|
AGENTE,
|
||||||
SISTEMA
|
SISTEMA,
|
||||||
|
LLM
|
||||||
}
|
}
|
||||||
@@ -7,5 +7,6 @@ package com.example.dto.dialogflow.conversation;
|
|||||||
|
|
||||||
public enum ConversationEntryType {
|
public enum ConversationEntryType {
|
||||||
INICIO,
|
INICIO,
|
||||||
CONVERSACION
|
CONVERSACION,
|
||||||
|
LLM
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,6 +32,7 @@ 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 {
|
||||||
@@ -48,6 +52,8 @@ public class ConversationManagerService {
|
|||||||
private final DataLossPrevention dataLossPrevention;
|
private final DataLossPrevention dataLossPrevention;
|
||||||
private final String dlpTemplateCompleteFlow;
|
private final String dlpTemplateCompleteFlow;
|
||||||
|
|
||||||
|
private final NotificationContextResolver notificationContextResolver;
|
||||||
|
|
||||||
public ConversationManagerService(
|
public ConversationManagerService(
|
||||||
DialogflowClientService dialogflowServiceClient,
|
DialogflowClientService dialogflowServiceClient,
|
||||||
FirestoreConversationService firestoreConversationService,
|
FirestoreConversationService firestoreConversationService,
|
||||||
@@ -59,6 +65,7 @@ public class ConversationManagerService {
|
|||||||
NotificationContextMapper notificationContextMapper,
|
NotificationContextMapper notificationContextMapper,
|
||||||
ConversationContextMapper conversationContextMapper,
|
ConversationContextMapper conversationContextMapper,
|
||||||
DataLossPrevention dataLossPrevention,
|
DataLossPrevention dataLossPrevention,
|
||||||
|
NotificationContextResolver notificationContextResolver,
|
||||||
@Value("${google.cloud.dlp.dlpTemplateCompleteFlow}") String dlpTemplateCompleteFlow) {
|
@Value("${google.cloud.dlp.dlpTemplateCompleteFlow}") String dlpTemplateCompleteFlow) {
|
||||||
this.dialogflowServiceClient = dialogflowServiceClient;
|
this.dialogflowServiceClient = dialogflowServiceClient;
|
||||||
this.firestoreConversationService = firestoreConversationService;
|
this.firestoreConversationService = firestoreConversationService;
|
||||||
@@ -71,7 +78,10 @@ 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) {
|
public Mono<DetectIntentResponseDTO> manageConversation(ExternalConvRequestDTO externalrequest) {
|
||||||
return dataLossPrevention.getObfuscatedString(externalrequest.message(), dlpTemplateCompleteFlow)
|
return dataLossPrevention.getObfuscatedString(externalrequest.message(), dlpTemplateCompleteFlow)
|
||||||
.flatMap(obfuscatedMessage -> {
|
.flatMap(obfuscatedMessage -> {
|
||||||
@@ -80,13 +90,14 @@ public class ConversationManagerService {
|
|||||||
externalrequest.user(),
|
externalrequest.user(),
|
||||||
externalrequest.channel(),
|
externalrequest.channel(),
|
||||||
externalrequest.tipo(),
|
externalrequest.tipo(),
|
||||||
externalrequest.pantallaContexto()
|
externalrequest.pantallaContexto());
|
||||||
);
|
|
||||||
return memoryStoreConversationService.getSessionByTelefono(externalrequest.user().telefono())
|
return memoryStoreConversationService.getSessionByTelefono(externalrequest.user().telefono())
|
||||||
.flatMap(session -> {
|
.flatMap(session -> {
|
||||||
|
|
||||||
if (session != null && session.pantallaContexto() != null && !session.pantallaContexto().isBlank()) {
|
if (session != null && session.pantallaContexto() != null
|
||||||
logger.info("Detected 'pantallaContexto' in session. Delegating to QuickRepliesManagerService.");
|
&& !session.pantallaContexto().isBlank()) {
|
||||||
|
logger.info(
|
||||||
|
"Detected 'pantallaContexto' in session. Delegating to QuickRepliesManagerService.");
|
||||||
return quickRepliesManagerService.manageConversation(obfuscatedRequest);
|
return quickRepliesManagerService.manageConversation(obfuscatedRequest);
|
||||||
}
|
}
|
||||||
return continueManagingConversation(obfuscatedRequest);
|
return continueManagingConversation(obfuscatedRequest);
|
||||||
@@ -116,7 +127,9 @@ public class ConversationManagerService {
|
|||||||
|
|
||||||
return handleMessageClassification(context, request);
|
return handleMessageClassification(context, request);
|
||||||
}
|
}
|
||||||
private Mono<DetectIntentResponseDTO> handleMessageClassification(ConversationContext context, DetectIntentRequestDTO request) {
|
|
||||||
|
private Mono<DetectIntentResponseDTO> handleMessageClassification(ConversationContext context,
|
||||||
|
DetectIntentRequestDTO request) {
|
||||||
final String userPhoneNumber = context.primaryPhoneNumber();
|
final String userPhoneNumber = context.primaryPhoneNumber();
|
||||||
final String userMessageText = context.userMessageText();
|
final String userMessageText = context.userMessageText();
|
||||||
|
|
||||||
@@ -125,7 +138,8 @@ public class ConversationManagerService {
|
|||||||
.defaultIfEmpty("")
|
.defaultIfEmpty("")
|
||||||
.flatMap(conversationHistory -> {
|
.flatMap(conversationHistory -> {
|
||||||
return memoryStoreNotificationService.getNotificationIdForPhone(userPhoneNumber)
|
return memoryStoreNotificationService.getNotificationIdForPhone(userPhoneNumber)
|
||||||
.flatMap(notificationId -> memoryStoreNotificationService.getCachedNotificationSession(notificationId))
|
.flatMap(notificationId -> memoryStoreNotificationService
|
||||||
|
.getCachedNotificationSession(notificationId))
|
||||||
.map(notificationSession -> notificationSession.notificaciones().stream()
|
.map(notificationSession -> notificationSession.notificaciones().stream()
|
||||||
.filter(notification -> "active".equalsIgnoreCase(notification.status()))
|
.filter(notification -> "active".equalsIgnoreCase(notification.status()))
|
||||||
.max(java.util.Comparator.comparing(NotificationDTO::timestampCreacion))
|
.max(java.util.Comparator.comparing(NotificationDTO::timestampCreacion))
|
||||||
@@ -133,7 +147,8 @@ public class ConversationManagerService {
|
|||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.flatMap((NotificationDTO notification) -> {
|
.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 {
|
||||||
@@ -143,14 +158,17 @@ public class ConversationManagerService {
|
|||||||
.switchIfEmpty(continueConversationFlow(context, request));
|
.switchIfEmpty(continueConversationFlow(context, request));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
private Mono<DetectIntentResponseDTO> continueConversationFlow(ConversationContext context, DetectIntentRequestDTO request) {
|
|
||||||
|
private Mono<DetectIntentResponseDTO> continueConversationFlow(ConversationContext context,
|
||||||
|
DetectIntentRequestDTO request) {
|
||||||
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()) {
|
if (userPhoneNumber == null || userPhoneNumber.isBlank()) {
|
||||||
logger.warn("No phone number provided in request. Cannot manage conversation session without it.");
|
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."));
|
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);
|
logger.info("Primary Check (MemoryStore): Looking up session for phone number: {}", userPhoneNumber);
|
||||||
@@ -161,12 +179,15 @@ public class ConversationManagerService {
|
|||||||
return fullLookupAndProcess(null, request, userId, userMessageText, userPhoneNumber);
|
return fullLookupAndProcess(null, request, userId, userMessageText, userPhoneNumber);
|
||||||
}))
|
}))
|
||||||
.onErrorResume(e -> {
|
.onErrorResume(e -> {
|
||||||
logger.error("Overall error handling conversation in ConversationManagerService: {}", e.getMessage(), e);
|
logger.error("Overall error handling conversation in ConversationManagerService: {}",
|
||||||
return Mono.error(new RuntimeException("Failed to process conversation due to an internal error.", e));
|
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) {
|
private Mono<DetectIntentResponseDTO> handleMessageClassification(ConversationContext context,
|
||||||
|
DetectIntentRequestDTO request, ConversationSessionDTO session) {
|
||||||
final String userPhoneNumber = context.primaryPhoneNumber();
|
final String userPhoneNumber = context.primaryPhoneNumber();
|
||||||
final String userMessageText = context.userMessageText();
|
final String userMessageText = context.userMessageText();
|
||||||
|
|
||||||
@@ -180,7 +201,8 @@ public class ConversationManagerService {
|
|||||||
.flatMap((NotificationDTO notification) -> {
|
.flatMap((NotificationDTO notification) -> {
|
||||||
String conversationHistory = conversationContextMapper.toText(session);
|
String conversationHistory = conversationContextMapper.toText(session);
|
||||||
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 {
|
||||||
@@ -190,54 +212,74 @@ public class ConversationManagerService {
|
|||||||
.switchIfEmpty(proceedWithConversation(context, request, session));
|
.switchIfEmpty(proceedWithConversation(context, request, session));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Mono<DetectIntentResponseDTO> proceedWithConversation(ConversationContext context, DetectIntentRequestDTO request, ConversationSessionDTO session) {
|
private Mono<DetectIntentResponseDTO> proceedWithConversation(ConversationContext context,
|
||||||
|
DetectIntentRequestDTO request, ConversationSessionDTO session) {
|
||||||
Instant now = Instant.now();
|
Instant now = Instant.now();
|
||||||
if (Duration.between(session.lastModified(), now).toMinutes() < SESSION_RESET_THRESHOLD_MINUTES) {
|
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());
|
logger.info("Recent Session Found: Session {} is within the 10-minute threshold. Proceeding to Dialogflow.",
|
||||||
return processDialogflowRequest(session, request, context.userId(), context.userMessageText(), context.primaryPhoneNumber(), false);
|
session.sessionId());
|
||||||
|
return processDialogflowRequest(session, request, context.userId(), context.userMessageText(),
|
||||||
|
context.primaryPhoneNumber(), false);
|
||||||
} else {
|
} else {
|
||||||
logger.info("Old Session Found: Session {} is older than the threshold. Fetching history and continuing with same session.", session.sessionId());
|
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);
|
String conversationHistory = conversationContextMapper.toTextWithLimits(session);
|
||||||
DetectIntentRequestDTO newRequest = request.withParameter(CONV_HISTORY_PARAM, conversationHistory);
|
DetectIntentRequestDTO newRequest = request.withParameter(CONV_HISTORY_PARAM, conversationHistory);
|
||||||
return processDialogflowRequest(session, newRequest, context.userId(), context.userMessageText(), context.primaryPhoneNumber(), false);
|
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) {
|
private Mono<DetectIntentResponseDTO> fullLookupAndProcess(ConversationSessionDTO oldSession,
|
||||||
|
DetectIntentRequestDTO request, String userId, String userMessageText, String userPhoneNumber) {
|
||||||
return firestoreConversationService.getSessionByTelefono(userPhoneNumber)
|
return firestoreConversationService.getSessionByTelefono(userPhoneNumber)
|
||||||
.map(conversationContextMapper::toTextWithLimits)
|
.map(conversationContextMapper::toTextWithLimits)
|
||||||
.defaultIfEmpty("")
|
.defaultIfEmpty("")
|
||||||
.flatMap(conversationHistory -> {
|
.flatMap(conversationHistory -> {
|
||||||
String newSessionId = SessionIdGenerator.generateStandardSessionId();
|
String newSessionId = SessionIdGenerator.generateStandardSessionId();
|
||||||
logger.info("Creating new session {} after full lookup.", newSessionId);
|
logger.info("Creating new session {} after full lookup.", newSessionId);
|
||||||
ConversationSessionDTO newSession = ConversationSessionDTO.create(newSessionId, userId, userPhoneNumber);
|
ConversationSessionDTO newSession = ConversationSessionDTO.create(newSessionId, userId,
|
||||||
|
userPhoneNumber);
|
||||||
DetectIntentRequestDTO newRequest = request.withParameter(CONV_HISTORY_PARAM, conversationHistory);
|
DetectIntentRequestDTO newRequest = request.withParameter(CONV_HISTORY_PARAM, conversationHistory);
|
||||||
return processDialogflowRequest(newSession, newRequest, userId, userMessageText, userPhoneNumber, true);
|
return processDialogflowRequest(newSession, newRequest, userId, userMessageText, userPhoneNumber,
|
||||||
|
true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private Mono<DetectIntentResponseDTO> processDialogflowRequest(ConversationSessionDTO session, DetectIntentRequestDTO request, String userId, String userMessageText, String userPhoneNumber, boolean newSession) {
|
private Mono<DetectIntentResponseDTO> processDialogflowRequest(ConversationSessionDTO session,
|
||||||
|
DetectIntentRequestDTO request, String userId, String userMessageText, String userPhoneNumber,
|
||||||
|
boolean newSession) {
|
||||||
final String finalSessionId = session.sessionId();
|
final String finalSessionId = session.sessionId();
|
||||||
|
|
||||||
ConversationEntryDTO userEntry = ConversationEntryDTO.forUser(userMessageText);
|
ConversationEntryDTO userEntry = ConversationEntryDTO.forUser(userMessageText);
|
||||||
|
|
||||||
return this.persistConversationTurn(userId, finalSessionId, userEntry, userPhoneNumber)
|
return this.persistConversationTurn(userId, finalSessionId, userEntry, userPhoneNumber)
|
||||||
.doOnSuccess(v -> logger.debug("User entry successfully persisted for session {}. Proceeding to Dialogflow...", finalSessionId))
|
.doOnSuccess(v -> logger.debug(
|
||||||
.doOnError(e -> logger.error("Error during user entry persistence for session {}: {}", finalSessionId, e.getMessage(), e))
|
"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)
|
.then(Mono.defer(() -> dialogflowServiceClient.detectIntent(finalSessionId, request)
|
||||||
.flatMap(response -> {
|
.flatMap(response -> {
|
||||||
logger.debug("Received Dialogflow CX response for session {}. Initiating agent response persistence.", finalSessionId);
|
logger.debug(
|
||||||
|
"Received Dialogflow CX response for session {}. Initiating agent response persistence.",
|
||||||
|
finalSessionId);
|
||||||
ConversationEntryDTO agentEntry = ConversationEntryDTO.forAgent(response.queryResult());
|
ConversationEntryDTO agentEntry = ConversationEntryDTO.forAgent(response.queryResult());
|
||||||
return persistConversationTurn(userId, finalSessionId, agentEntry, userPhoneNumber)
|
return persistConversationTurn(userId, finalSessionId, agentEntry, userPhoneNumber)
|
||||||
.thenReturn(response);
|
.thenReturn(response);
|
||||||
})
|
})
|
||||||
.doOnError(error -> logger.error("Overall error during conversation management for session {}: {}", finalSessionId, error.getMessage(), error))
|
.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) {
|
|
||||||
|
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();
|
||||||
|
|
||||||
return memoryStoreNotificationService.getSessionByTelefono(userPhoneNumber)
|
return memoryStoreNotificationService.getSessionByTelefono(userPhoneNumber)
|
||||||
.switchIfEmpty(Mono.defer(() -> {
|
.switchIfEmpty(Mono.defer(() -> {
|
||||||
String newSessionId = SessionIdGenerator.generateStandardSessionId();
|
String newSessionId = SessionIdGenerator.generateStandardSessionId();
|
||||||
@@ -247,28 +289,58 @@ public class ConversationManagerService {
|
|||||||
}))
|
}))
|
||||||
.flatMap(session -> {
|
.flatMap(session -> {
|
||||||
final String sessionId = session.sessionId();
|
final String sessionId = session.sessionId();
|
||||||
ConversationEntryDTO userEntry = ConversationEntryDTO.forUser(userMessageText);
|
String conversationHistory = conversationContextMapper.toTextWithLimits(session);
|
||||||
|
String notificationText = notificationContextMapper.toText(notification);
|
||||||
|
|
||||||
|
Map<String, Object> 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();
|
Instant now = Instant.now();
|
||||||
if (Duration.between(session.lastModified(), now).toMinutes() < SESSION_RESET_THRESHOLD_MINUTES) {
|
if (Duration.between(session.lastModified(), now).toMinutes() < SESSION_RESET_THRESHOLD_MINUTES) {
|
||||||
return memoryStoreNotificationService.saveEntry(userId, sessionId, userEntry, userPhoneNumber)
|
finalRequest = request.withParameters(notification.parametros());
|
||||||
.then(dialogflowServiceClient.detectIntent(sessionId, request)
|
|
||||||
.doOnSuccess(response -> {
|
|
||||||
ConversationEntryDTO agentEntry = ConversationEntryDTO.forAgent(response.queryResult());
|
|
||||||
memoryStoreNotificationService.saveEntry(userId, sessionId, agentEntry, userPhoneNumber).subscribe();
|
|
||||||
}));
|
|
||||||
} else {
|
} else {
|
||||||
String conversationHistory = conversationContextMapper.toTextWithLimits(session);
|
finalRequest = request.withParameter(CONV_HISTORY_PARAM, conversationHistory)
|
||||||
DetectIntentRequestDTO newRequest = request.withParameter(CONV_HISTORY_PARAM, conversationHistory);
|
.withParameters(notification.parametros());
|
||||||
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();
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
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)
|
||||||
@@ -286,6 +358,18 @@ public class ConversationManagerService {
|
|||||||
entry.type().name(), e.getMessage(), e));
|
entry.type().name(), e.getMessage(), e));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
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())
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
41
src/main/resources/prompts/notification_context_resolver.txt
Normal file
41
src/main/resources/prompts/notification_context_resolver.txt
Normal 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
|
||||||
@@ -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 {
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user