Add RAG client
This commit is contained in:
58
src/main/java/com/example/config/IntentDetectionConfig.java
Normal file
58
src/main/java/com/example/config/IntentDetectionConfig.java
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.config;
|
||||
|
||||
import com.example.service.base.IntentDetectionService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
|
||||
/**
|
||||
* Configuration class for selecting the intent detection implementation.
|
||||
* Allows switching between Dialogflow and RAG based on configuration property.
|
||||
*
|
||||
* Usage:
|
||||
* - Set intent.detection.client=dialogflow to use Dialogflow CX
|
||||
* - Set intent.detection.client=rag to use RAG server
|
||||
*/
|
||||
@Configuration
|
||||
public class IntentDetectionConfig {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(IntentDetectionConfig.class);
|
||||
|
||||
@Value("${intent.detection.client:dialogflow}")
|
||||
private String clientType;
|
||||
|
||||
/**
|
||||
* Creates the primary IntentDetectionService bean based on configuration.
|
||||
* This bean will be injected into ConversationManagerService and NotificationManagerService.
|
||||
*
|
||||
* @param dialogflowService The Dialogflow implementation
|
||||
* @param ragService The RAG implementation
|
||||
* @return The selected IntentDetectionService implementation
|
||||
*/
|
||||
@Bean
|
||||
@Primary
|
||||
public IntentDetectionService intentDetectionService(
|
||||
@Qualifier("dialogflowClientService") IntentDetectionService dialogflowService,
|
||||
@Qualifier("ragClientService") IntentDetectionService ragService) {
|
||||
|
||||
if ("rag".equalsIgnoreCase(clientType)) {
|
||||
logger.info("✓ Intent detection configured to use RAG client");
|
||||
return ragService;
|
||||
} else if ("dialogflow".equalsIgnoreCase(clientType)) {
|
||||
logger.info("✓ Intent detection configured to use Dialogflow CX client");
|
||||
return dialogflowService;
|
||||
} else {
|
||||
logger.warn("Unknown intent.detection.client value: '{}'. Defaulting to Dialogflow.", clientType);
|
||||
return dialogflowService;
|
||||
}
|
||||
}
|
||||
}
|
||||
33
src/main/java/com/example/dto/rag/RagQueryRequest.java
Normal file
33
src/main/java/com/example/dto/rag/RagQueryRequest.java
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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.dto.rag;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Internal DTO representing a request to the RAG server.
|
||||
* This is used only within the RAG client adapter and is not exposed to other services.
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public record RagQueryRequest(
|
||||
@JsonProperty("phone_number") String phoneNumber,
|
||||
@JsonProperty("text") String text,
|
||||
@JsonProperty("type") String type,
|
||||
@JsonProperty("notification") NotificationContext notification,
|
||||
@JsonProperty("language_code") String languageCode
|
||||
) {
|
||||
/**
|
||||
* Nested record for notification context
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public record NotificationContext(
|
||||
@JsonProperty("text") String text,
|
||||
@JsonProperty("parameters") Map<String, Object> parameters
|
||||
) {}
|
||||
}
|
||||
23
src/main/java/com/example/dto/rag/RagQueryResponse.java
Normal file
23
src/main/java/com/example/dto/rag/RagQueryResponse.java
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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.dto.rag;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Internal DTO representing a response from the RAG server.
|
||||
* This is used only within the RAG client adapter and is not exposed to other services.
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public record RagQueryResponse(
|
||||
@JsonProperty("response_id") String responseId,
|
||||
@JsonProperty("response_text") String responseText,
|
||||
@JsonProperty("parameters") Map<String, Object> parameters,
|
||||
@JsonProperty("confidence") Double confidence
|
||||
) {}
|
||||
21
src/main/java/com/example/exception/RagClientException.java
Normal file
21
src/main/java/com/example/exception/RagClientException.java
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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.exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when the RAG client encounters an error communicating with the RAG server.
|
||||
* This mirrors the structure of DialogflowClientException for consistency.
|
||||
*/
|
||||
public class RagClientException extends RuntimeException {
|
||||
|
||||
public RagClientException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public RagClientException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
154
src/main/java/com/example/mapper/rag/RagRequestMapper.java
Normal file
154
src/main/java/com/example/mapper/rag/RagRequestMapper.java
Normal file
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* 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.rag;
|
||||
|
||||
import com.example.dto.dialogflow.base.DetectIntentRequestDTO;
|
||||
import com.example.dto.dialogflow.conversation.QueryInputDTO;
|
||||
import com.example.dto.rag.RagQueryRequest;
|
||||
import com.example.dto.rag.RagQueryRequest.NotificationContext;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Mapper component responsible for converting DetectIntentRequestDTO to RAG API format.
|
||||
* This adapter preserves the existing DTO structure while translating to the simpler RAG API.
|
||||
*/
|
||||
@Component
|
||||
public class RagRequestMapper {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(RagRequestMapper.class);
|
||||
private static final String NOTIFICATION_PREFIX = "notification_po_";
|
||||
private static final String NOTIFICATION_TEXT_PARAM = "notification_text";
|
||||
|
||||
/**
|
||||
* Maps a DetectIntentRequestDTO to a RagQueryRequest.
|
||||
* Extracts the phone number, text/event, and notification data from the existing structure.
|
||||
*
|
||||
* @param requestDto The existing DetectIntentRequestDTO
|
||||
* @param sessionId The session ID (not used by RAG but kept for logging)
|
||||
* @return A RagQueryRequest ready to send to the RAG server
|
||||
*/
|
||||
public RagQueryRequest mapToRagRequest(DetectIntentRequestDTO requestDto, String sessionId) {
|
||||
Objects.requireNonNull(requestDto, "DetectIntentRequestDTO cannot be null");
|
||||
|
||||
logger.debug("Mapping DetectIntentRequestDTO to RagQueryRequest for session: {}", sessionId);
|
||||
|
||||
// Extract phone number from parameters
|
||||
Map<String, Object> parameters = requestDto.queryParams() != null
|
||||
? requestDto.queryParams().parameters()
|
||||
: Map.of();
|
||||
|
||||
String phoneNumber = extractPhoneNumber(parameters);
|
||||
if (phoneNumber == null || phoneNumber.isBlank()) {
|
||||
logger.error("Phone number is required but not found in request parameters");
|
||||
throw new IllegalArgumentException("Phone number is required in request parameters");
|
||||
}
|
||||
|
||||
// Extract text or event from QueryInputDTO
|
||||
QueryInputDTO queryInput = requestDto.queryInput();
|
||||
String text = extractText(queryInput);
|
||||
String languageCode = queryInput.languageCode();
|
||||
|
||||
// Determine request type and notification context
|
||||
String type = determineRequestType(queryInput, parameters);
|
||||
NotificationContext notificationContext = extractNotificationContext(parameters);
|
||||
|
||||
RagQueryRequest ragRequest = new RagQueryRequest(
|
||||
phoneNumber,
|
||||
text,
|
||||
type,
|
||||
notificationContext,
|
||||
languageCode
|
||||
);
|
||||
|
||||
logger.debug("Mapped RAG request: type={}, phoneNumber={}, hasNotification={}",
|
||||
type, phoneNumber, notificationContext != null);
|
||||
|
||||
return ragRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the phone number from request parameters.
|
||||
*/
|
||||
private String extractPhoneNumber(Map<String, Object> parameters) {
|
||||
Object telefono = parameters.get("telefono");
|
||||
if (telefono instanceof String) {
|
||||
return (String) telefono;
|
||||
}
|
||||
logger.warn("Phone number (telefono) not found or not a string in parameters");
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts text from QueryInputDTO (either text input or event).
|
||||
* For events, we use the event name as the text.
|
||||
*/
|
||||
private String extractText(QueryInputDTO queryInput) {
|
||||
if (queryInput.text() != null && queryInput.text().text() != null
|
||||
&& !queryInput.text().text().trim().isEmpty()) {
|
||||
return queryInput.text().text();
|
||||
} else if (queryInput.event() != null && queryInput.event().event() != null
|
||||
&& !queryInput.event().event().trim().isEmpty()) {
|
||||
// For events (like "LLM_RESPONSE_PROCESSED"), use the event name
|
||||
return queryInput.event().event();
|
||||
} else {
|
||||
logger.error("Query input must contain either text or event");
|
||||
throw new IllegalArgumentException("Query input must contain either text or event");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if this is a conversation or notification request.
|
||||
* If notification parameters are present, it's a notification request.
|
||||
*/
|
||||
private String determineRequestType(QueryInputDTO queryInput, Map<String, Object> parameters) {
|
||||
// Check if there are notification-prefixed parameters
|
||||
boolean hasNotificationParams = parameters.keySet().stream()
|
||||
.anyMatch(key -> key.startsWith(NOTIFICATION_PREFIX));
|
||||
|
||||
// Check if there's a notification_text parameter
|
||||
boolean hasNotificationText = parameters.containsKey(NOTIFICATION_TEXT_PARAM);
|
||||
|
||||
// Check if the input is an event (notifications use events)
|
||||
boolean isEvent = queryInput.event() != null && queryInput.event().event() != null;
|
||||
|
||||
if (hasNotificationParams || hasNotificationText ||
|
||||
(isEvent && "notificacion".equals(queryInput.event().event()))) {
|
||||
return "notification";
|
||||
}
|
||||
|
||||
return "conversation";
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts notification context from parameters.
|
||||
* Looks for notification_text and notification_po_* parameters.
|
||||
*/
|
||||
private NotificationContext extractNotificationContext(Map<String, Object> parameters) {
|
||||
String notificationText = (String) parameters.get(NOTIFICATION_TEXT_PARAM);
|
||||
|
||||
// Extract all notification_po_* parameters and remove the prefix
|
||||
Map<String, Object> notificationParams = new HashMap<>();
|
||||
parameters.forEach((key, value) -> {
|
||||
if (key.startsWith(NOTIFICATION_PREFIX)) {
|
||||
String cleanKey = key.substring(NOTIFICATION_PREFIX.length());
|
||||
notificationParams.put(cleanKey, value);
|
||||
}
|
||||
});
|
||||
|
||||
// Only create NotificationContext if we have notification data
|
||||
if (notificationText != null || !notificationParams.isEmpty()) {
|
||||
return new NotificationContext(notificationText, notificationParams);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
73
src/main/java/com/example/mapper/rag/RagResponseMapper.java
Normal file
73
src/main/java/com/example/mapper/rag/RagResponseMapper.java
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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.rag;
|
||||
|
||||
import com.example.dto.dialogflow.base.DetectIntentResponseDTO;
|
||||
import com.example.dto.dialogflow.conversation.QueryResultDTO;
|
||||
import com.example.dto.rag.RagQueryResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Mapper component responsible for converting RAG API responses to DetectIntentResponseDTO.
|
||||
* This adapter ensures the response structure matches what the rest of the application expects.
|
||||
*/
|
||||
@Component
|
||||
public class RagResponseMapper {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(RagResponseMapper.class);
|
||||
|
||||
/**
|
||||
* Maps a RagQueryResponse to a DetectIntentResponseDTO.
|
||||
* Preserves the existing response structure expected by the rest of the application.
|
||||
*
|
||||
* @param ragResponse The response from the RAG server
|
||||
* @param sessionId The session ID (for logging purposes)
|
||||
* @return A DetectIntentResponseDTO matching the expected structure
|
||||
*/
|
||||
public DetectIntentResponseDTO mapFromRagResponse(RagQueryResponse ragResponse, String sessionId) {
|
||||
logger.info("Mapping RAG response to DetectIntentResponseDTO for session: {}", sessionId);
|
||||
|
||||
// Use RAG's response_id if available, otherwise generate one
|
||||
String responseId = ragResponse.responseId() != null && !ragResponse.responseId().isBlank()
|
||||
? ragResponse.responseId()
|
||||
: "rag-" + UUID.randomUUID().toString();
|
||||
|
||||
// Extract response text
|
||||
String responseText = ragResponse.responseText() != null
|
||||
? ragResponse.responseText()
|
||||
: "";
|
||||
|
||||
if (responseText.isBlank()) {
|
||||
logger.warn("RAG returned empty response text for session: {}", sessionId);
|
||||
}
|
||||
|
||||
// Extract parameters (can be null or empty)
|
||||
Map<String, Object> parameters = ragResponse.parameters() != null
|
||||
? ragResponse.parameters()
|
||||
: Collections.emptyMap();
|
||||
|
||||
// Log confidence if available
|
||||
if (ragResponse.confidence() != null) {
|
||||
logger.debug("RAG response confidence: {} for session: {}", ragResponse.confidence(), sessionId);
|
||||
}
|
||||
|
||||
// Create QueryResultDTO with response text and parameters
|
||||
QueryResultDTO queryResult = new QueryResultDTO(responseText, parameters);
|
||||
|
||||
// Create DetectIntentResponseDTO (quickReplies is null for now)
|
||||
DetectIntentResponseDTO response = new DetectIntentResponseDTO(responseId, queryResult, null);
|
||||
|
||||
logger.info("Successfully mapped RAG response for session: {}. Response ID: {}", sessionId, responseId);
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ 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.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Mono;
|
||||
import javax.annotation.PreDestroy;
|
||||
@@ -32,7 +33,8 @@ import reactor.util.retry.Retry;
|
||||
* all within a reactive programming context.
|
||||
*/
|
||||
@Service
|
||||
public class DialogflowClientService {
|
||||
@Qualifier("dialogflowClientService")
|
||||
public class DialogflowClientService implements IntentDetectionService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DialogflowClientService.class);
|
||||
|
||||
@@ -81,6 +83,7 @@ public class DialogflowClientService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<DetectIntentResponseDTO> detectIntent(
|
||||
String sessionId,
|
||||
DetectIntentRequestDTO request) {
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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.dto.dialogflow.base.DetectIntentRequestDTO;
|
||||
import com.example.dto.dialogflow.base.DetectIntentResponseDTO;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* Common interface for intent detection services.
|
||||
* This abstraction allows switching between different intent detection implementations
|
||||
* (e.g., Dialogflow, RAG) without changing dependent services.
|
||||
*/
|
||||
public interface IntentDetectionService {
|
||||
|
||||
/**
|
||||
* Detects user intent and generates a response.
|
||||
*
|
||||
* @param sessionId The session identifier for this conversation
|
||||
* @param request The request containing user input and context parameters
|
||||
* @return A Mono of DetectIntentResponseDTO with the generated response
|
||||
*/
|
||||
Mono<DetectIntentResponseDTO> detectIntent(String sessionId, DetectIntentRequestDTO request);
|
||||
}
|
||||
183
src/main/java/com/example/service/base/RagClientService.java
Normal file
183
src/main/java/com/example/service/base/RagClientService.java
Normal file
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* 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.dto.dialogflow.base.DetectIntentRequestDTO;
|
||||
import com.example.dto.dialogflow.base.DetectIntentResponseDTO;
|
||||
import com.example.dto.rag.RagQueryRequest;
|
||||
import com.example.dto.rag.RagQueryResponse;
|
||||
import com.example.exception.RagClientException;
|
||||
import com.example.mapper.rag.RagRequestMapper;
|
||||
import com.example.mapper.rag.RagResponseMapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.reactive.function.client.WebClientRequestException;
|
||||
import org.springframework.web.reactive.function.client.WebClientResponseException;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.retry.Retry;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* Service for interacting with the RAG server to detect user intent and generate responses.
|
||||
* This service mirrors the structure of DialogflowClientService but calls a RAG API instead.
|
||||
* It maintains the same method signatures and reactive patterns for seamless integration.
|
||||
*/
|
||||
@Service
|
||||
@Qualifier("ragClientService")
|
||||
public class RagClientService implements IntentDetectionService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(RagClientService.class);
|
||||
|
||||
private final WebClient webClient;
|
||||
private final RagRequestMapper ragRequestMapper;
|
||||
private final RagResponseMapper ragResponseMapper;
|
||||
private final int maxRetries;
|
||||
private final Duration retryBackoff;
|
||||
private final Duration timeout;
|
||||
|
||||
public RagClientService(
|
||||
@Value("${rag.server.url}") String ragServerUrl,
|
||||
@Value("${rag.server.timeout:30s}") Duration timeout,
|
||||
@Value("${rag.server.retry.max-attempts:3}") int maxRetries,
|
||||
@Value("${rag.server.retry.backoff:1s}") Duration retryBackoff,
|
||||
@Value("${rag.server.api-key:}") String apiKey,
|
||||
RagRequestMapper ragRequestMapper,
|
||||
RagResponseMapper ragResponseMapper) {
|
||||
|
||||
this.ragRequestMapper = ragRequestMapper;
|
||||
this.ragResponseMapper = ragResponseMapper;
|
||||
this.maxRetries = maxRetries;
|
||||
this.retryBackoff = retryBackoff;
|
||||
this.timeout = timeout;
|
||||
|
||||
// Build WebClient with base URL and optional API key
|
||||
WebClient.Builder builder = WebClient.builder()
|
||||
.baseUrl(ragServerUrl)
|
||||
.defaultHeader("Content-Type", "application/json");
|
||||
|
||||
// Add API key header if provided
|
||||
if (apiKey != null && !apiKey.isBlank()) {
|
||||
builder.defaultHeader("X-API-Key", apiKey);
|
||||
logger.info("RAG Client initialized with API key authentication");
|
||||
}
|
||||
|
||||
this.webClient = builder.build();
|
||||
|
||||
logger.info("RAG Client initialized successfully with endpoint: {}", ragServerUrl);
|
||||
logger.info("RAG Client configuration - timeout: {}, max retries: {}, backoff: {}",
|
||||
timeout, maxRetries, retryBackoff);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects user intent by calling the RAG server.
|
||||
* This method signature matches DialogflowClientService.detectIntent() for compatibility.
|
||||
*
|
||||
* @param sessionId The session identifier (used for logging, not sent to RAG)
|
||||
* @param request The DetectIntentRequestDTO containing user input and parameters
|
||||
* @return A Mono of DetectIntentResponseDTO with the RAG-generated response
|
||||
*/
|
||||
@Override
|
||||
public Mono<DetectIntentResponseDTO> detectIntent(
|
||||
String sessionId,
|
||||
DetectIntentRequestDTO request) {
|
||||
|
||||
Objects.requireNonNull(sessionId, "Session ID cannot be null.");
|
||||
Objects.requireNonNull(request, "Request DTO cannot be null.");
|
||||
|
||||
logger.info("Initiating RAG query for session: {}", sessionId);
|
||||
|
||||
// Map DetectIntentRequestDTO to RAG format
|
||||
RagQueryRequest ragRequest;
|
||||
try {
|
||||
ragRequest = ragRequestMapper.mapToRagRequest(request, sessionId);
|
||||
logger.debug("Successfully mapped request to RAG format for session: {}", sessionId);
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.error("Failed to map DTO to RAG request for session {}: {}", sessionId, e.getMessage());
|
||||
return Mono.error(new IllegalArgumentException("Invalid RAG request input: " + e.getMessage()));
|
||||
}
|
||||
|
||||
// Call RAG API
|
||||
return Mono.defer(() ->
|
||||
webClient.post()
|
||||
.uri("/api/v1/query")
|
||||
.header("X-Session-Id", sessionId) // Optional: for RAG server logging
|
||||
.bodyValue(ragRequest)
|
||||
.retrieve()
|
||||
.onStatus(
|
||||
HttpStatusCode::is4xxClientError,
|
||||
response -> response.bodyToMono(String.class)
|
||||
.flatMap(body -> {
|
||||
logger.error("RAG client error for session {}: status={}, body={}",
|
||||
sessionId, response.statusCode(), body);
|
||||
return Mono.error(new RagClientException(
|
||||
"Invalid RAG request: " + response.statusCode() + " - " + body));
|
||||
})
|
||||
)
|
||||
.bodyToMono(RagQueryResponse.class)
|
||||
.timeout(timeout) // Timeout per attempt
|
||||
)
|
||||
.retryWhen(Retry.backoff(maxRetries, retryBackoff)
|
||||
.filter(throwable -> {
|
||||
// Retry on server errors and timeouts
|
||||
if (throwable instanceof WebClientResponseException wce) {
|
||||
int statusCode = wce.getStatusCode().value();
|
||||
boolean isRetryable = statusCode == 500 || statusCode == 503 || statusCode == 504;
|
||||
if (isRetryable) {
|
||||
logger.warn("Retrying RAG call for session {} due to status code: {}",
|
||||
sessionId, statusCode);
|
||||
}
|
||||
return isRetryable;
|
||||
}
|
||||
if (throwable instanceof TimeoutException) {
|
||||
logger.warn("Retrying RAG call for session {} due to timeout", sessionId);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.doBeforeRetry(retrySignal ->
|
||||
logger.debug("Retry attempt #{} for session {}: {}",
|
||||
retrySignal.totalRetries() + 1, sessionId, retrySignal.failure().getMessage()))
|
||||
.onRetryExhaustedThrow((retrySpec, retrySignal) -> {
|
||||
logger.error("RAG retries exhausted for session {}", sessionId);
|
||||
return retrySignal.failure();
|
||||
})
|
||||
)
|
||||
.onErrorMap(WebClientResponseException.class, e -> {
|
||||
int statusCode = e.getStatusCode().value();
|
||||
logger.error("RAG server error for session {}: status={}, body={}",
|
||||
sessionId, statusCode, e.getResponseBodyAsString());
|
||||
return new RagClientException(
|
||||
"RAG server error: " + statusCode + " - " + e.getResponseBodyAsString(), e);
|
||||
})
|
||||
.onErrorMap(WebClientRequestException.class, e -> {
|
||||
logger.error("RAG connection error for session {}: {}", sessionId, e.getMessage());
|
||||
return new RagClientException("RAG connection failed: " + e.getMessage(), e);
|
||||
})
|
||||
.onErrorMap(TimeoutException.class, e -> {
|
||||
logger.error("RAG timeout for session {}: {}", sessionId, e.getMessage());
|
||||
return new RagClientException("RAG request timeout after " + timeout.getSeconds() + "s", e);
|
||||
})
|
||||
.onErrorMap(RagClientException.class, e -> e) // Pass through RagClientException
|
||||
.onErrorMap(throwable -> !(throwable instanceof RagClientException), throwable -> {
|
||||
logger.error("Unexpected error during RAG call for session {}: {}", sessionId, throwable.getMessage(), throwable);
|
||||
return new RagClientException("Unexpected RAG error: " + throwable.getMessage(), throwable);
|
||||
})
|
||||
.map(ragResponse -> ragResponseMapper.mapFromRagResponse(ragResponse, sessionId))
|
||||
.doOnSuccess(response ->
|
||||
logger.info("RAG query successful for session: {}, response ID: {}",
|
||||
sessionId, response.responseId()))
|
||||
.doOnError(error ->
|
||||
logger.error("RAG query failed for session {}: {}", sessionId, error.getMessage()));
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import com.example.mapper.conversation.ConversationEntryMapper;
|
||||
import com.example.mapper.conversation.ExternalConvRequestMapper;
|
||||
import com.example.mapper.messagefilter.ConversationContextMapper;
|
||||
import com.example.mapper.messagefilter.NotificationContextMapper;
|
||||
import com.example.service.base.DialogflowClientService;
|
||||
import com.example.service.base.IntentDetectionService;
|
||||
import com.example.service.base.MessageEntryFilter;
|
||||
import com.example.service.base.NotificationContextResolver;
|
||||
import com.example.service.notification.MemoryStoreNotificationService;
|
||||
@@ -67,7 +67,7 @@ public class ConversationManagerService {
|
||||
private static final String CONV_HISTORY_PARAM = "conversation_history";
|
||||
private static final String HISTORY_PARAM = "historial";
|
||||
private final ExternalConvRequestMapper externalRequestToDialogflowMapper;
|
||||
private final DialogflowClientService dialogflowServiceClient;
|
||||
private final IntentDetectionService intentDetectionService;
|
||||
private final FirestoreConversationService firestoreConversationService;
|
||||
private final MemoryStoreConversationService memoryStoreConversationService;
|
||||
private final QuickRepliesManagerService quickRepliesManagerService;
|
||||
@@ -83,7 +83,7 @@ public class ConversationManagerService {
|
||||
private final ConversationEntryMapper conversationEntryMapper;
|
||||
|
||||
public ConversationManagerService(
|
||||
DialogflowClientService dialogflowServiceClient,
|
||||
IntentDetectionService intentDetectionService,
|
||||
FirestoreConversationService firestoreConversationService,
|
||||
MemoryStoreConversationService memoryStoreConversationService,
|
||||
ExternalConvRequestMapper externalRequestToDialogflowMapper,
|
||||
@@ -97,7 +97,7 @@ public class ConversationManagerService {
|
||||
LlmResponseTunerService llmResponseTunerService,
|
||||
ConversationEntryMapper conversationEntryMapper,
|
||||
@Value("${google.cloud.dlp.dlpTemplateCompleteFlow}") String dlpTemplateCompleteFlow) {
|
||||
this.dialogflowServiceClient = dialogflowServiceClient;
|
||||
this.intentDetectionService = intentDetectionService;
|
||||
this.firestoreConversationService = firestoreConversationService;
|
||||
this.memoryStoreConversationService = memoryStoreConversationService;
|
||||
this.externalRequestToDialogflowMapper = externalRequestToDialogflowMapper;
|
||||
@@ -305,7 +305,7 @@ public class ConversationManagerService {
|
||||
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(() -> intentDetectionService.detectIntent(finalSessionId, request)
|
||||
.flatMap(response -> {
|
||||
logger.debug(
|
||||
"RTest eceived Dialogflow CX response for session {}. Initiating agent response persistence.",
|
||||
@@ -366,7 +366,7 @@ public class ConversationManagerService {
|
||||
request.queryParams())
|
||||
.withParameter("llm_reponse_uuid", uuid);
|
||||
|
||||
return dialogflowServiceClient.detectIntent(sessionId, newRequest)
|
||||
return intentDetectionService.detectIntent(sessionId, newRequest)
|
||||
.flatMap(response -> {
|
||||
ConversationEntryDTO agentEntry = ConversationEntryDTO
|
||||
.forAgent(response.queryResult());
|
||||
@@ -387,7 +387,7 @@ public class ConversationManagerService {
|
||||
.withParameters(notification.parametros());
|
||||
}
|
||||
return persistConversationTurn(session, conversationEntryMapper.toConversationMessageDTO(userEntry))
|
||||
.then(dialogflowServiceClient.detectIntent(sessionId, finalRequest)
|
||||
.then(intentDetectionService.detectIntent(sessionId, finalRequest)
|
||||
.flatMap(response -> {
|
||||
ConversationEntryDTO agentEntry = ConversationEntryDTO
|
||||
.forAgent(response.queryResult());
|
||||
|
||||
@@ -14,7 +14,7 @@ import com.example.dto.dialogflow.conversation.ConversationSessionDTO;
|
||||
import com.example.dto.dialogflow.notification.NotificationDTO;
|
||||
import com.example.mapper.conversation.ConversationEntryMapper;
|
||||
import com.example.mapper.notification.ExternalNotRequestMapper;
|
||||
import com.example.service.base.DialogflowClientService;
|
||||
import com.example.service.base.IntentDetectionService;
|
||||
import com.example.service.conversation.DataLossPrevention;
|
||||
import com.example.service.conversation.FirestoreConversationService;
|
||||
import com.example.service.conversation.MemoryStoreConversationService;
|
||||
@@ -36,7 +36,7 @@ public class NotificationManagerService {
|
||||
private static final String eventName = "notificacion";
|
||||
private static final String PREFIX_PO_PARAM = "notification_po_";
|
||||
|
||||
private final DialogflowClientService dialogflowClientService;
|
||||
private final IntentDetectionService intentDetectionService;
|
||||
private final FirestoreNotificationService firestoreNotificationService;
|
||||
private final MemoryStoreNotificationService memoryStoreNotificationService;
|
||||
private final ExternalNotRequestMapper externalNotRequestMapper;
|
||||
@@ -50,7 +50,7 @@ public class NotificationManagerService {
|
||||
private String defaultLanguageCode;
|
||||
|
||||
public NotificationManagerService(
|
||||
DialogflowClientService dialogflowClientService,
|
||||
IntentDetectionService intentDetectionService,
|
||||
FirestoreNotificationService firestoreNotificationService,
|
||||
MemoryStoreNotificationService memoryStoreNotificationService,
|
||||
MemoryStoreConversationService memoryStoreConversationService,
|
||||
@@ -60,8 +60,8 @@ public class NotificationManagerService {
|
||||
DataLossPrevention dataLossPrevention,
|
||||
ConversationEntryMapper conversationEntryMapper,
|
||||
@Value("${google.cloud.dlp.dlpTemplateCompleteFlow}") String dlpTemplateCompleteFlow) {
|
||||
|
||||
this.dialogflowClientService = dialogflowClientService;
|
||||
|
||||
this.intentDetectionService = intentDetectionService;
|
||||
this.firestoreNotificationService = firestoreNotificationService;
|
||||
this.memoryStoreNotificationService = memoryStoreNotificationService;
|
||||
this.externalNotRequestMapper = externalNotRequestMapper;
|
||||
@@ -147,7 +147,7 @@ public class NotificationManagerService {
|
||||
|
||||
DetectIntentRequestDTO detectIntentRequest = externalNotRequestMapper.map(obfuscatedRequest);
|
||||
|
||||
return dialogflowClientService.detectIntent(sessionId, detectIntentRequest);
|
||||
return intentDetectionService.detectIntent(sessionId, detectIntentRequest);
|
||||
})
|
||||
.doOnSuccess(response -> logger
|
||||
.info("Finished processing notification. Dialogflow response received for phone {}.", telefono))
|
||||
|
||||
@@ -38,12 +38,27 @@ spring.data.redis.port=${REDIS_PORT}
|
||||
# spring.data.redis.ssl.key-store=classpath:keystore.p12
|
||||
# spring.data.redis.ssl.key-store-password=${REDIS_KEY_PWD}
|
||||
# =========================================================
|
||||
# Google Conversational Agents Configuration
|
||||
# Intent Detection Client Selection
|
||||
# =========================================================
|
||||
# Options: 'dialogflow' or 'rag'
|
||||
# Set to 'dialogflow' to use Dialogflow CX (default)
|
||||
# Set to 'rag' to use RAG server
|
||||
intent.detection.client=${INTENT_DETECTION_CLIENT:dialogflow}
|
||||
# =========================================================
|
||||
# Google Conversational Agents Configuration (Dialogflow)
|
||||
# =========================================================
|
||||
dialogflow.cx.project-id=${DIALOGFLOW_CX_PROJECT_ID}
|
||||
dialogflow.cx.location=${DIALOGFLOW_CX_LOCATION}
|
||||
dialogflow.cx.agent-id=${DIALOGFLOW_CX_AGENT_ID}
|
||||
dialogflow.default-language-code=${DIALOGFLOW_DEFAULT_LANGUAGE_CODE}
|
||||
dialogflow.default-language-code=${DIALOGFLOW_DEFAULT_LANGUAGE_CODE:es}
|
||||
# =========================================================
|
||||
# RAG Server Configuration
|
||||
# =========================================================
|
||||
rag.server.url=${RAG_SERVER_URL:http://localhost:8080}
|
||||
rag.server.timeout=${RAG_SERVER_TIMEOUT:30s}
|
||||
rag.server.retry.max-attempts=${RAG_SERVER_RETRY_MAX_ATTEMPTS:3}
|
||||
rag.server.retry.backoff=${RAG_SERVER_RETRY_BACKOFF:1s}
|
||||
rag.server.api-key=${RAG_SERVER_API_KEY:}
|
||||
# =========================================================
|
||||
# Google Generative AI (Gemini) Configuration
|
||||
# =========================================================
|
||||
|
||||
@@ -38,12 +38,27 @@ spring.data.redis.port=${REDIS_PORT}
|
||||
# spring.data.redis.ssl.key-store=classpath:keystore.p12
|
||||
# spring.data.redis.ssl.key-store-password=${REDIS_KEY_PWD}
|
||||
# =========================================================
|
||||
# Google Conversational Agents Configuration
|
||||
# Intent Detection Client Selection
|
||||
# =========================================================
|
||||
# Options: 'dialogflow' or 'rag'
|
||||
# Set to 'dialogflow' to use Dialogflow CX (default)
|
||||
# Set to 'rag' to use RAG server
|
||||
intent.detection.client=${INTENT_DETECTION_CLIENT:dialogflow}
|
||||
# =========================================================
|
||||
# Google Conversational Agents Configuration (Dialogflow)
|
||||
# =========================================================
|
||||
dialogflow.cx.project-id=${DIALOGFLOW_CX_PROJECT_ID}
|
||||
dialogflow.cx.location=${DIALOGFLOW_CX_LOCATION}
|
||||
dialogflow.cx.agent-id=${DIALOGFLOW_CX_AGENT_ID}
|
||||
dialogflow.default-language-code=${DIALOGFLOW_DEFAULT_LANGUAGE_CODE}
|
||||
dialogflow.default-language-code=${DIALOGFLOW_DEFAULT_LANGUAGE_CODE:es}
|
||||
# =========================================================
|
||||
# RAG Server Configuration
|
||||
# =========================================================
|
||||
rag.server.url=${RAG_SERVER_URL:http://localhost:8080}
|
||||
rag.server.timeout=${RAG_SERVER_TIMEOUT:30s}
|
||||
rag.server.retry.max-attempts=${RAG_SERVER_RETRY_MAX_ATTEMPTS:3}
|
||||
rag.server.retry.backoff=${RAG_SERVER_RETRY_BACKOFF:1s}
|
||||
rag.server.api-key=${RAG_SERVER_API_KEY:}
|
||||
# =========================================================
|
||||
# Google Generative AI (Gemini) Configuration
|
||||
# =========================================================
|
||||
|
||||
@@ -38,12 +38,27 @@ spring.data.redis.port=${REDIS_PORT}
|
||||
# spring.data.redis.ssl.key-store=classpath:keystore.p12
|
||||
# spring.data.redis.ssl.key-store-password=${REDIS_KEY_PWD}
|
||||
# =========================================================
|
||||
# Google Conversational Agents Configuration
|
||||
# Intent Detection Client Selection
|
||||
# =========================================================
|
||||
# Options: 'dialogflow' or 'rag'
|
||||
# Set to 'dialogflow' to use Dialogflow CX (default)
|
||||
# Set to 'rag' to use RAG server
|
||||
intent.detection.client=${INTENT_DETECTION_CLIENT:dialogflow}
|
||||
# =========================================================
|
||||
# Google Conversational Agents Configuration (Dialogflow)
|
||||
# =========================================================
|
||||
dialogflow.cx.project-id=${DIALOGFLOW_CX_PROJECT_ID}
|
||||
dialogflow.cx.location=${DIALOGFLOW_CX_LOCATION}
|
||||
dialogflow.cx.agent-id=${DIALOGFLOW_CX_AGENT_ID}
|
||||
dialogflow.default-language-code=${DIALOGFLOW_DEFAULT_LANGUAGE_CODE}
|
||||
dialogflow.default-language-code=${DIALOGFLOW_DEFAULT_LANGUAGE_CODE:es}
|
||||
# =========================================================
|
||||
# RAG Server Configuration
|
||||
# =========================================================
|
||||
rag.server.url=${RAG_SERVER_URL:http://localhost:8080}
|
||||
rag.server.timeout=${RAG_SERVER_TIMEOUT:30s}
|
||||
rag.server.retry.max-attempts=${RAG_SERVER_RETRY_MAX_ATTEMPTS:3}
|
||||
rag.server.retry.backoff=${RAG_SERVER_RETRY_BACKOFF:1s}
|
||||
rag.server.api-key=${RAG_SERVER_API_KEY:}
|
||||
# =========================================================
|
||||
# Google Generative AI (Gemini) Configuration
|
||||
# =========================================================
|
||||
|
||||
Reference in New Issue
Block a user