/* * 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.mapper.conversation.DialogflowRequestMapper; import com.example.mapper.conversation.DialogflowResponseMapper; import com.example.dto.dialogflow.base.DetectIntentRequestDTO; import com.example.dto.dialogflow.base.DetectIntentResponseDTO; import com.example.exception.DialogflowClientException; import com.google.api.gax.rpc.ApiException; import com.google.cloud.dialogflow.cx.v3.DetectIntentRequest; import com.google.cloud.dialogflow.cx.v3.QueryParameters; import com.google.cloud.dialogflow.cx.v3.SessionsClient; import com.google.cloud.dialogflow.cx.v3.SessionName; import com.google.cloud.dialogflow.cx.v3.SessionsSettings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; import javax.annotation.PreDestroy; import java.io.IOException; import java.util.Objects; /** * Service for interacting with the Dialogflow CX API to detect user DetectIntent. * It encapsulates the low-level API calls, handling request mapping from DTOs, * managing the `SessionsClient`, and translating API responses into DTOs, * all within a reactive programming context. */ @Service public class DialogflowClientService { private static final Logger logger = LoggerFactory.getLogger(DialogflowClientService.class); private final String dialogflowCxProjectId; private final String dialogflowCxLocation; private final String dialogflowCxAgentId; private final DialogflowRequestMapper dialogflowRequestMapper; private final DialogflowResponseMapper dialogflowResponseMapper; private SessionsClient sessionsClient; public DialogflowClientService( @org.springframework.beans.factory.annotation.Value("${dialogflow.cx.project-id}") String dialogflowCxProjectId, @org.springframework.beans.factory.annotation.Value("${dialogflow.cx.location}") String dialogflowCxLocation, @org.springframework.beans.factory.annotation.Value("${dialogflow.cx.agent-id}") String dialogflowCxAgentId, DialogflowRequestMapper dialogflowRequestMapper, DialogflowResponseMapper dialogflowResponseMapper) throws IOException { this.dialogflowCxProjectId = dialogflowCxProjectId; this.dialogflowCxLocation = dialogflowCxLocation; this.dialogflowCxAgentId = dialogflowCxAgentId; this.dialogflowRequestMapper = dialogflowRequestMapper; this.dialogflowResponseMapper = dialogflowResponseMapper; try { String regionalEndpoint = String.format("%s-dialogflow.googleapis.com:443", dialogflowCxLocation); SessionsSettings sessionsSettings = SessionsSettings.newBuilder() .setEndpoint(regionalEndpoint) .build(); this.sessionsClient = SessionsClient.create(sessionsSettings); logger.info("Dialogflow CX SessionsClient initialized successfully for endpoint: {}", regionalEndpoint); logger.info("Dialogflow CX SessionsClient initialized successfully for agent - Test Agent version: {}", dialogflowCxAgentId); } catch (IOException e) { logger.error("Failed to create Dialogflow CX SessionsClient: {}", e.getMessage(), e); throw e; } } @PreDestroy public void closeSessionsClient() { if (sessionsClient != null) { sessionsClient.close(); logger.info("Dialogflow CX SessionsClient closed."); } } public Mono detectIntent( String sessionId, DetectIntentRequestDTO request) { Objects.requireNonNull(sessionId, "Dialogflow session ID cannot be null."); Objects.requireNonNull(request, "Dialogflow request DTO cannot be null."); logger.info("Initiating detectIntent for session: {}", sessionId); DetectIntentRequest.Builder detectIntentRequestBuilder; try { detectIntentRequestBuilder = dialogflowRequestMapper.mapToDetectIntentRequestBuilder(request); logger.debug("Obtained partial DetectIntentRequest.Builder from mapper for session: {}", sessionId); } catch (IllegalArgumentException e) { logger.error(" Failed to map DTO to partial Protobuf request for session {}: {}", sessionId, e.getMessage()); return Mono.error(new IllegalArgumentException("Invalid Dialogflow request input: " + e.getMessage())); } SessionName sessionName = SessionName.newBuilder() .setProject(dialogflowCxProjectId) .setLocation(dialogflowCxLocation) .setAgent(dialogflowCxAgentId) .setSession(sessionId) .build(); detectIntentRequestBuilder.setSession(sessionName.toString()); logger.debug("Set session path {} on the request builder for session: {}", sessionName.toString(), sessionId); QueryParameters.Builder queryParamsBuilder; if (detectIntentRequestBuilder.hasQueryParams()) { queryParamsBuilder = detectIntentRequestBuilder.getQueryParams().toBuilder(); } else { queryParamsBuilder = QueryParameters.newBuilder(); } detectIntentRequestBuilder.setQueryParams(queryParamsBuilder.build()); // Build the final DetectIntentRequest Protobuf object DetectIntentRequest detectIntentRequest = detectIntentRequestBuilder.build(); logger.debug("DetectIntentRequest created for session {}: {}", sessionId, detectIntentRequest); return Mono.fromCallable(() -> { logger.debug("Calling Dialogflow CX detectIntent for session: {}", sessionId); return sessionsClient.detectIntent(detectIntentRequest); }) .onErrorMap(ApiException.class, e -> { logger.error("Dialogflow CX API error for session {}: status={}, message={}", sessionId, e.getStatusCode().getCode(), e.getMessage(), e); return new DialogflowClientException( "Dialogflow CX API error: " + e.getStatusCode().getCode() + " - " + e.getMessage(), e); }) .map(dfResponse -> this.dialogflowResponseMapper.mapFromDialogflowResponse(dfResponse, sessionId)); } }