Initial commit
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.mapper.conversation;
|
||||
|
||||
import com.example.dto.dialogflow.conversation.ConversationEntryDTO;
|
||||
import com.example.dto.dialogflow.conversation.ConversationMessageDTO;
|
||||
import com.example.dto.dialogflow.conversation.MessageType;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class ConversationEntryMapper {
|
||||
|
||||
public ConversationMessageDTO toConversationMessageDTO(ConversationEntryDTO entry) {
|
||||
MessageType type = switch (entry.entity()) {
|
||||
case USUARIO -> MessageType.USER;
|
||||
case AGENTE -> MessageType.AGENT;
|
||||
case SISTEMA -> MessageType.SYSTEM;
|
||||
case LLM -> MessageType.LLM;
|
||||
};
|
||||
|
||||
return new ConversationMessageDTO(
|
||||
type,
|
||||
entry.timestamp(),
|
||||
entry.text(),
|
||||
entry.parameters(),
|
||||
entry.canal()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.mapper.conversation;
|
||||
import com.google.cloud.Timestamp;
|
||||
|
||||
import com.example.dto.dialogflow.conversation.ConversationMessageDTO;
|
||||
import com.example.dto.dialogflow.conversation.MessageType;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
public class ConversationMessageMapper {
|
||||
|
||||
public Map<String, Object> toMap(ConversationMessageDTO message) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("entidad", message.type().name());
|
||||
map.put("tiempo", message.timestamp());
|
||||
map.put("mensaje", message.text());
|
||||
if (message.parameters() != null) {
|
||||
map.put("parametros", message.parameters());
|
||||
}
|
||||
if (message.canal() != null) {
|
||||
map.put("canal", message.canal());
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public ConversationMessageDTO fromMap(Map<String, Object> map) {
|
||||
Object timeObject = map.get("tiempo");
|
||||
Instant timestamp = null;
|
||||
if (timeObject instanceof Timestamp) {
|
||||
timestamp = ((Timestamp) timeObject).toDate().toInstant();
|
||||
} else if (timeObject instanceof Instant) {
|
||||
timestamp = (Instant) timeObject;
|
||||
}
|
||||
return new ConversationMessageDTO(
|
||||
MessageType.valueOf((String) map.get("entidad")),
|
||||
timestamp,
|
||||
(String) map.get("mensaje"),
|
||||
(Map<String, Object>) map.get("parametros"),
|
||||
(String) map.get("canal")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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.mapper.conversation;
|
||||
|
||||
import com.example.dto.dialogflow.base.DetectIntentRequestDTO;
|
||||
import com.example.dto.dialogflow.conversation.QueryInputDTO;
|
||||
import com.example.util.ProtobufUtil;
|
||||
import com.google.cloud.dialogflow.cx.v3.EventInput;
|
||||
import com.google.cloud.dialogflow.cx.v3.DetectIntentRequest;
|
||||
|
||||
import com.google.cloud.dialogflow.cx.v3.QueryInput;
|
||||
import com.google.cloud.dialogflow.cx.v3.QueryParameters;
|
||||
import com.google.cloud.dialogflow.cx.v3.TextInput;
|
||||
import com.google.protobuf.Struct;
|
||||
import com.google.protobuf.Value;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Spring component responsible for mapping a custom `DetectIntentRequestDTO`
|
||||
* into a Dialogflow CX `DetectIntentRequest.Builder`. It handles the conversion
|
||||
* of user text or event inputs and the serialization of custom session parameters,
|
||||
* ensuring the data is in the correct Protobuf format for API communication.
|
||||
*/
|
||||
@Component
|
||||
public class DialogflowRequestMapper {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DialogflowRequestMapper.class);
|
||||
|
||||
@org.springframework.beans.factory.annotation.Value("${dialogflow.default-language-code:es}")
|
||||
String defaultLanguageCode;
|
||||
|
||||
public DetectIntentRequest.Builder mapToDetectIntentRequestBuilder(DetectIntentRequestDTO requestDto) {
|
||||
Objects.requireNonNull(requestDto, "DetectIntentRequestDTO cannot be null for mapping.");
|
||||
|
||||
logger.debug(
|
||||
"Building partial Dialogflow CX DetectIntentRequest Protobuf Builder from DTO (only QueryInput and QueryParams).");
|
||||
QueryInput.Builder queryInputBuilder = QueryInput.newBuilder();
|
||||
QueryInputDTO queryInputDTO = requestDto.queryInput();
|
||||
|
||||
String languageCodeToSet = (queryInputDTO.languageCode() != null
|
||||
&& !queryInputDTO.languageCode().trim().isEmpty())
|
||||
? queryInputDTO.languageCode()
|
||||
: defaultLanguageCode;
|
||||
queryInputBuilder.setLanguageCode(languageCodeToSet);
|
||||
logger.debug("Setting languageCode for QueryInput to: {}", languageCodeToSet);
|
||||
|
||||
if (queryInputDTO.text() != null && queryInputDTO.text().text() != null
|
||||
&& !queryInputDTO.text().text().trim().isEmpty()) {
|
||||
queryInputBuilder.setText(TextInput.newBuilder()
|
||||
.setText(queryInputDTO.text().text())
|
||||
.build());
|
||||
logger.debug("Mapped text input for QueryInput: '{}'", queryInputDTO.text().text());
|
||||
|
||||
} else if (queryInputDTO.event() != null && queryInputDTO.event().event() != null
|
||||
&& !queryInputDTO.event().event().trim().isEmpty()) {
|
||||
queryInputBuilder.setEvent(EventInput.newBuilder()
|
||||
.setEvent(queryInputDTO.event().event())
|
||||
.build());
|
||||
logger.debug("Mapped event input for QueryInput: '{}'", queryInputDTO.event().event());
|
||||
|
||||
} else {
|
||||
logger.error("Dialogflow query input (either text or event) is required and must not be empty.");
|
||||
throw new IllegalArgumentException("Dialogflow query input (either text or event) is required.");
|
||||
}
|
||||
|
||||
QueryParameters.Builder queryParametersBuilder = QueryParameters.newBuilder();
|
||||
Struct.Builder paramsStructBuilder = Struct.newBuilder();
|
||||
|
||||
if (requestDto.queryParams() != null && requestDto.queryParams().parameters() != null) {
|
||||
for (Map.Entry<String, Object> entry : requestDto.queryParams().parameters().entrySet()) {
|
||||
Value protobufValue = ProtobufUtil.convertJavaObjectToProtobufValue(entry.getValue());
|
||||
paramsStructBuilder.putFields(entry.getKey(), protobufValue);
|
||||
logger.debug("Added session parameter from DTO queryParams: Key='{}', Value='{}'",
|
||||
entry.getKey(),entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
if (paramsStructBuilder.getFieldsCount() > 0) {
|
||||
queryParametersBuilder.setParameters(paramsStructBuilder.build());
|
||||
logger.debug("All custom session parameters added to Protobuf request builder.");
|
||||
} else {
|
||||
logger.debug("No custom session parameters to add to Protobuf request.");
|
||||
}
|
||||
|
||||
DetectIntentRequest.Builder detectIntentRequestBuilder = DetectIntentRequest.newBuilder()
|
||||
.setQueryInput(queryInputBuilder.build());
|
||||
|
||||
if (queryParametersBuilder.hasParameters()) {
|
||||
detectIntentRequestBuilder.setQueryParams(queryParametersBuilder.build());
|
||||
}
|
||||
|
||||
logger.debug("Finished building partial DetectIntentRequest Protobuf Builder.");
|
||||
return detectIntentRequestBuilder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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.mapper.conversation;
|
||||
|
||||
import com.google.cloud.dialogflow.cx.v3.QueryResult;
|
||||
import com.google.cloud.dialogflow.cx.v3.ResponseMessage;
|
||||
import com.google.cloud.dialogflow.cx.v3.DetectIntentResponse;
|
||||
import com.example.dto.dialogflow.base.DetectIntentResponseDTO;
|
||||
import com.example.dto.dialogflow.conversation.QueryResultDTO;
|
||||
import com.example.util.ProtobufUtil;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Spring component responsible for mapping a Dialogflow CX API response
|
||||
* (`DetectIntentResponse`) to a simplified custom DTO (`DetectIntentResponseDTO`).
|
||||
* It extracts and consolidates the fulfillment text, and converts Protobuf
|
||||
* session parameters into standard Java objects, providing a clean and
|
||||
* decoupled interface for consuming Dialogflow results.
|
||||
*/
|
||||
@Component
|
||||
public class DialogflowResponseMapper {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DialogflowResponseMapper.class);
|
||||
|
||||
public DetectIntentResponseDTO mapFromDialogflowResponse(DetectIntentResponse response, String sessionId) {
|
||||
|
||||
logger.info("Starting mapping of Dialogflow DetectIntentResponse for session: {}", sessionId);
|
||||
|
||||
String responseId = response.getResponseId();
|
||||
QueryResult dfQueryResult = response.getQueryResult();
|
||||
logger.debug("Extracted QueryResult object for session: {}", sessionId);
|
||||
|
||||
StringBuilder responseTextBuilder = new StringBuilder();
|
||||
if (dfQueryResult.getResponseMessagesList().isEmpty()) {
|
||||
logger.debug("No response messages found in QueryResult for session: {}", sessionId);
|
||||
}
|
||||
|
||||
for (ResponseMessage message : dfQueryResult.getResponseMessagesList()) {
|
||||
if (message.hasText()) {
|
||||
logger.debug("Processing text response message for session: {}", sessionId);
|
||||
for (String text : message.getText().getTextList()) {
|
||||
if (responseTextBuilder.length() > 0) {
|
||||
responseTextBuilder.append(" ");
|
||||
}
|
||||
responseTextBuilder.append(text);
|
||||
logger.debug("Appended text segment: '{}' to fulfillment text for session: {}", text, sessionId);
|
||||
}
|
||||
} else {
|
||||
logger.debug("Skipping non-text response message type: {} for session: {}", message.getMessageCase(), sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
String responseText = responseTextBuilder.toString().trim();
|
||||
|
||||
Map<String, Object> parameters = new LinkedHashMap<>(); // Inicializamos vacío para evitar NPEs después
|
||||
|
||||
if (dfQueryResult.hasParameters()) {
|
||||
// Usamos un forEach en lugar de Collectors.toMap para tener control total sobre nulos
|
||||
dfQueryResult.getParameters().getFieldsMap().forEach((key, value) -> {
|
||||
try {
|
||||
Object convertedValue = ProtobufUtil.convertProtobufValueToJavaObject(value);
|
||||
|
||||
// Si el valor convertido es nulo, decidimos qué hacer.
|
||||
// Lo mejor es poner un String vacío o ignorarlo para que no explote tu lógica.
|
||||
if (convertedValue != null) {
|
||||
parameters.put(key, convertedValue);
|
||||
} else {
|
||||
logger.warn("El parámetro '{}' devolvió un valor nulo al convertir. Se ignorará.", key);
|
||||
// Opcional: parameters.put(key, "");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error convirtiendo el parámetro '{}' de Protobuf a Java: {}", key, e.getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
logger.debug("Extracted parameters: {} for session: {}", parameters, sessionId);
|
||||
} else {
|
||||
logger.debug("No parameters found in QueryResult for session: {}. Using empty map.", sessionId);
|
||||
}
|
||||
|
||||
QueryResultDTO ourQueryResult = new QueryResultDTO(responseText, parameters);
|
||||
logger.debug("Internal QueryResult DTO created for session: {}. Details: {}", sessionId, ourQueryResult);
|
||||
|
||||
DetectIntentResponseDTO finalResponse = new DetectIntentResponseDTO(responseId, ourQueryResult);
|
||||
logger.info("Finished mapping DialogflowDetectIntentResponse for session: {}. Full response ID: {}", sessionId, responseId);
|
||||
return finalResponse;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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.mapper.conversation;
|
||||
|
||||
import com.example.dto.dialogflow.base.DetectIntentRequestDTO;
|
||||
import com.example.dto.dialogflow.conversation.ExternalConvRequestDTO;
|
||||
import com.example.dto.dialogflow.conversation.QueryInputDTO;
|
||||
import com.example.dto.dialogflow.conversation.QueryParamsDTO;
|
||||
import com.example.dto.dialogflow.conversation.TextInputDTO;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Spring component responsible for mapping a simplified, external API request
|
||||
* into a structured `DetectIntentRequestDTO` for Dialogflow. It processes
|
||||
* user messages and relevant context, such as phone numbers and channel information,
|
||||
* and populates the `QueryInputDTO` and `QueryParamsDTO` fields required for
|
||||
* a Dialogflow API call.
|
||||
*/
|
||||
@Component
|
||||
public class ExternalConvRequestMapper {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ExternalConvRequestMapper.class);
|
||||
private static final String DEFAULT_LANGUAGE_CODE = "es";
|
||||
|
||||
public DetectIntentRequestDTO mapExternalRequestToDetectIntentRequest(ExternalConvRequestDTO externalRequest) {
|
||||
Objects.requireNonNull(externalRequest, "ExternalRequestDTO cannot be null for mapping.");
|
||||
|
||||
if (externalRequest.message() == null || externalRequest.message().isBlank()) {
|
||||
throw new IllegalArgumentException("External request 'mensaje' (message) is required.");
|
||||
}
|
||||
TextInputDTO textInput = new TextInputDTO(externalRequest.message());
|
||||
QueryInputDTO queryInputDTO = new QueryInputDTO(textInput,null,DEFAULT_LANGUAGE_CODE);
|
||||
|
||||
// 2. Map ALL relevant external fields into QueryParamsDTO.parameters
|
||||
Map<String, Object> parameters = new HashMap<>();
|
||||
|
||||
String primaryPhoneNumber = null;
|
||||
if (externalRequest.user() != null && externalRequest.user().telefono() != null
|
||||
&& !externalRequest.user().telefono().isBlank()) {
|
||||
primaryPhoneNumber = externalRequest.user().telefono();
|
||||
parameters.put("telefono", primaryPhoneNumber);
|
||||
}
|
||||
|
||||
if (primaryPhoneNumber == null || primaryPhoneNumber.isBlank()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Phone number is required in the 'usuario' field for conversation management.");
|
||||
}
|
||||
|
||||
String resolvedUserId = null;
|
||||
// Derive from phone number if not provided by 'userId' parameter
|
||||
resolvedUserId = "user_by_phone_" + primaryPhoneNumber.replaceAll("[^0-9]", "");
|
||||
parameters.put("usuario_id", resolvedUserId); // Ensure derived ID is also in params
|
||||
logger.warn("User ID not provided in external request. Using derived ID from phone number: {}", resolvedUserId);
|
||||
|
||||
|
||||
if (externalRequest.channel() != null && !externalRequest.channel().isBlank()) {
|
||||
parameters.put("canal", externalRequest.channel());
|
||||
logger.debug("Mapped 'canal' from external request: {}", externalRequest.channel());
|
||||
}
|
||||
|
||||
if (externalRequest.user() != null && externalRequest.user().nickname() != null
|
||||
&& !externalRequest.user().nickname().isBlank()) {
|
||||
parameters.put("nickname", externalRequest.user().nickname());
|
||||
logger.debug("Mapped 'nickname' from external request: {}", externalRequest.user().nickname());
|
||||
}
|
||||
if (externalRequest.tipo() != null) {
|
||||
parameters.put("tipo", externalRequest.tipo());
|
||||
logger.debug("Mapped 'tipo' from external request: {}", externalRequest.tipo());
|
||||
}
|
||||
|
||||
QueryParamsDTO queryParamsDTO = new QueryParamsDTO(parameters);
|
||||
|
||||
// 3. Construct the final DetectIntentRequestDTO
|
||||
return new DetectIntentRequestDTO(queryInputDTO, queryParamsDTO);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.mapper.conversation;
|
||||
|
||||
import com.example.dto.dialogflow.conversation.ConversationSessionDTO;
|
||||
import com.google.cloud.Timestamp;
|
||||
import com.google.cloud.firestore.DocumentSnapshot;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
public class FirestoreConversationMapper {
|
||||
|
||||
public ConversationSessionDTO mapFirestoreDocumentToConversationSessionDTO(DocumentSnapshot document) {
|
||||
if (document == null || !document.exists()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Timestamp createdAtTimestamp = document.getTimestamp("fechaCreacion");
|
||||
Timestamp lastModifiedTimestamp = document.getTimestamp("ultimaActualizacion");
|
||||
|
||||
Instant createdAt = (createdAtTimestamp != null) ? createdAtTimestamp.toDate().toInstant() : null;
|
||||
Instant lastModified = (lastModifiedTimestamp != null) ? lastModifiedTimestamp.toDate().toInstant() : null;
|
||||
|
||||
return new ConversationSessionDTO(
|
||||
document.getString("sessionId"),
|
||||
document.getString("userId"),
|
||||
document.getString("telefono"),
|
||||
createdAt,
|
||||
lastModified,
|
||||
document.getString("ultimoMensaje"),
|
||||
document.getString("pantallaContexto")
|
||||
);
|
||||
}
|
||||
|
||||
public Map<String, Object> createSessionMap(ConversationSessionDTO session) {
|
||||
Map<String, Object> sessionMap = new HashMap<>();
|
||||
sessionMap.put("sessionId", session.sessionId());
|
||||
sessionMap.put("userId", session.userId());
|
||||
sessionMap.put("telefono", session.telefono());
|
||||
sessionMap.put("fechaCreacion", session.createdAt());
|
||||
sessionMap.put("ultimaActualizacion", session.lastModified());
|
||||
sessionMap.put("ultimoMensaje", session.lastMessage());
|
||||
sessionMap.put("pantallaContexto", session.pantallaContexto());
|
||||
return sessionMap;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user