UPDATE 12-sept
This commit is contained in:
50
pom.xml
50
pom.xml
@@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-parent</artifactId>
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
<version>3.3.0</version>
|
<version>3.3.11</version>
|
||||||
<relativePath/>
|
<relativePath/>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
<spring-cloud-gcp.version>5.4.0</spring-cloud-gcp.version>
|
<spring-cloud-gcp.version>5.4.0</spring-cloud-gcp.version>
|
||||||
<spring-cloud.version>2023.0.0</spring-cloud.version>
|
<spring-cloud.version>2023.0.0</spring-cloud.version>
|
||||||
<lettuce.version>6.4.0.RELEASE</lettuce.version>
|
<lettuce.version>6.4.0.RELEASE</lettuce.version>
|
||||||
|
<spring-framework.version>6.1.20</spring-framework.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
@@ -46,6 +47,13 @@
|
|||||||
<type>pom</type>
|
<type>pom</type>
|
||||||
<scope>import</scope>
|
<scope>import</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.projectreactor</groupId>
|
||||||
|
<artifactId>reactor-bom</artifactId>
|
||||||
|
<version>2024.0.8</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
@@ -91,7 +99,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.genai</groupId>
|
<groupId>com.google.genai</groupId>
|
||||||
<artifactId>google-genai</artifactId>
|
<artifactId>google-genai</artifactId>
|
||||||
<version>1.13.0</version>
|
<version>1.14.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.protobuf</groupId>
|
<groupId>com.google.protobuf</groupId>
|
||||||
@@ -126,13 +134,49 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.netty</groupId>
|
<groupId>io.netty</groupId>
|
||||||
<artifactId>netty-codec-http2</artifactId>
|
<artifactId>netty-codec-http2</artifactId>
|
||||||
<version>4.1.124.Final</version>
|
<version>4.1.125.Final</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.netty</groupId>
|
||||||
|
<artifactId>netty-handler</artifactId>
|
||||||
|
<version>4.1.125.Final</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.netty</groupId>
|
||||||
|
<artifactId>netty-common</artifactId>
|
||||||
|
<version>4.1.125.Final</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.netty</groupId>
|
||||||
|
<artifactId>netty-codec-http</artifactId>
|
||||||
|
<version>4.1.125.Final</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.netty</groupId>
|
||||||
|
<artifactId>netty-codec</artifactId>
|
||||||
|
<version>4.1.125.Final</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.protobuf</groupId>
|
<groupId>com.google.protobuf</groupId>
|
||||||
<artifactId>protobuf-java</artifactId>
|
<artifactId>protobuf-java</artifactId>
|
||||||
<version>3.25.5</version>
|
<version>3.25.5</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.minidev</groupId>
|
||||||
|
<artifactId>json-smart</artifactId>
|
||||||
|
<version>2.5.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.xmlunit</groupId>
|
||||||
|
<artifactId>xmlunit-core</artifactId>
|
||||||
|
<version>2.10.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-lang3</artifactId>
|
||||||
|
<version>3.18.0</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.controller;
|
|
||||||
|
|
||||||
import com.example.dto.gemini.ConversationSummaryRequest;
|
|
||||||
import com.example.dto.gemini.ConversationSummaryResponse;
|
|
||||||
import com.example.service.summary.ConversationSummaryService;
|
|
||||||
|
|
||||||
import jakarta.validation.Valid;
|
|
||||||
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/v1/summary")
|
|
||||||
public class ConversationSummaryController {
|
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ConversationSummaryController.class);
|
|
||||||
private final ConversationSummaryService conversationSummaryService;
|
|
||||||
|
|
||||||
public ConversationSummaryController(ConversationSummaryService conversationSummaryService) {
|
|
||||||
this.conversationSummaryService = conversationSummaryService;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@PostMapping("/conversation")
|
|
||||||
public ResponseEntity<ConversationSummaryResponse> summarizeConversation(
|
|
||||||
@Valid @RequestBody ConversationSummaryRequest request) {
|
|
||||||
|
|
||||||
logger.info("Received request to summarize conversation for session ID: {}",
|
|
||||||
request.sessionId());
|
|
||||||
|
|
||||||
ConversationSummaryResponse response = conversationSummaryService.summarizeConversation(request);
|
|
||||||
|
|
||||||
if (response.summaryText() != null &&
|
|
||||||
(response.summaryText().contains("Error:") ||
|
|
||||||
response.summaryText().contains("Failed:") ||
|
|
||||||
response.summaryText().contains("not found") ||
|
|
||||||
response.summaryText().contains("No conversation provided"))) {
|
|
||||||
logger.error("Summarization failed for session ID {}: {}", request.sessionId(), response.summaryText());
|
|
||||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
|
|
||||||
} else {
|
|
||||||
logger.info("Successfully processed summarization request for session ID: {}", request.sessionId());
|
|
||||||
return ResponseEntity.ok(response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.gemini;
|
|
||||||
|
|
||||||
import com.example.dto.dialogflow.conversation.ConversationEntryEntity;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import com.google.cloud.Timestamp;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
|
||||||
public record ConversationEntrySummaryDTO(
|
|
||||||
@JsonProperty("text") String text,
|
|
||||||
@JsonProperty("timestamp") Timestamp timestamp,
|
|
||||||
Optional<ConversationEntryEntity> type,
|
|
||||||
@JsonProperty("intentDisplayName") String intentDisplayName,
|
|
||||||
@JsonProperty("parameters") Map<String, Object> parameters,
|
|
||||||
@JsonProperty("webhookStatus") String webhookStatus,
|
|
||||||
@JsonProperty("canal") String canal
|
|
||||||
) {
|
|
||||||
@JsonCreator
|
|
||||||
public ConversationEntrySummaryDTO(
|
|
||||||
@JsonProperty("text") String text,
|
|
||||||
@JsonProperty("timestamp") Timestamp timestamp,
|
|
||||||
@JsonProperty("type") String typeString,
|
|
||||||
@JsonProperty("intentDisplayName") String intentDisplayName,
|
|
||||||
@JsonProperty("parameters") Map<String, Object> parameters,
|
|
||||||
@JsonProperty("webhookStatus") String webhookStatus,
|
|
||||||
@JsonProperty("canal") String canal
|
|
||||||
) {
|
|
||||||
this(
|
|
||||||
text,
|
|
||||||
timestamp,
|
|
||||||
Optional.ofNullable(typeString).map(t -> {
|
|
||||||
try {
|
|
||||||
return ConversationEntryEntity.valueOf(t);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
System.err.println("Warning: Invalid ConversationEntryType string during deserialization: " + t);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
intentDisplayName,
|
|
||||||
parameters,
|
|
||||||
webhookStatus,
|
|
||||||
canal
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.gemini;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import com.google.cloud.Timestamp;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
|
||||||
public record ConversationSessionSummaryDTO(
|
|
||||||
@JsonProperty("sessionId") String sessionId,
|
|
||||||
@JsonProperty("userId") String userId,
|
|
||||||
@JsonProperty("startTime") Timestamp startTime,
|
|
||||||
@JsonProperty("lastUpdated") Timestamp lastUpdated,
|
|
||||||
@JsonProperty("entries") List<ConversationEntrySummaryDTO> entries
|
|
||||||
) {
|
|
||||||
@JsonCreator
|
|
||||||
public ConversationSessionSummaryDTO(
|
|
||||||
@JsonProperty("sessionId") String sessionId,
|
|
||||||
@JsonProperty("userId") String userId,
|
|
||||||
@JsonProperty("startTime") Timestamp startTime,
|
|
||||||
@JsonProperty("lastUpdated") Timestamp lastUpdated,
|
|
||||||
@JsonProperty("entries") List<ConversationEntrySummaryDTO> entries
|
|
||||||
) {
|
|
||||||
this.sessionId = sessionId;
|
|
||||||
this.userId = userId;
|
|
||||||
this.startTime = startTime;
|
|
||||||
this.lastUpdated = lastUpdated;
|
|
||||||
this.entries = entries != null ? entries : Collections.emptyList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.gemini;
|
|
||||||
|
|
||||||
import jakarta.validation.constraints.DecimalMax;
|
|
||||||
import jakarta.validation.constraints.DecimalMin;
|
|
||||||
import jakarta.validation.constraints.NotBlank;
|
|
||||||
|
|
||||||
public record ConversationSummaryRequest(
|
|
||||||
@NotBlank(message = "Session ID is required.")
|
|
||||||
String sessionId,
|
|
||||||
@NotBlank(message = "Prompt for summarization is required.")
|
|
||||||
String prompt,
|
|
||||||
@DecimalMin(value = "0.0", message = "Temperature must be between 0.0 and 1.0.")
|
|
||||||
@DecimalMax(value = "0.1", message = "Temperature must be between 0.0 and 1.0.")
|
|
||||||
Float temperature,
|
|
||||||
@DecimalMin(value = "0.1", message = "Max Output Tokens must be at least 1.")
|
|
||||||
Integer maxOutputTokens,
|
|
||||||
@NotBlank(message = "model is required.")
|
|
||||||
String modelName,
|
|
||||||
@NotBlank(message = "topP is required.")
|
|
||||||
@DecimalMin(value = "0.0", message = "topP must be between 0.0 and 1.0.")
|
|
||||||
Float top_P
|
|
||||||
|
|
||||||
|
|
||||||
) {}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.gemini;
|
|
||||||
|
|
||||||
import jakarta.validation.constraints.NotBlank;
|
|
||||||
|
|
||||||
public record ConversationSummaryResponse(
|
|
||||||
@NotBlank
|
|
||||||
String summaryText
|
|
||||||
) {}
|
|
||||||
@@ -48,12 +48,11 @@ public class ExternalConvRequestMapper {
|
|||||||
&& !externalRequest.user().telefono().isBlank()) {
|
&& !externalRequest.user().telefono().isBlank()) {
|
||||||
primaryPhoneNumber = externalRequest.user().telefono();
|
primaryPhoneNumber = externalRequest.user().telefono();
|
||||||
parameters.put("telefono", primaryPhoneNumber);
|
parameters.put("telefono", primaryPhoneNumber);
|
||||||
logger.debug("Mapped 'telefono' from external request: {}", primaryPhoneNumber);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (primaryPhoneNumber == null || primaryPhoneNumber.isBlank()) {
|
if (primaryPhoneNumber == null || primaryPhoneNumber.isBlank()) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"Phone number (telefono) is required in the 'usuario' field for conversation management.");
|
"Phone number is required in the 'usuario' field for conversation management.");
|
||||||
}
|
}
|
||||||
|
|
||||||
String resolvedUserId = null;
|
String resolvedUserId = null;
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ public class ExternalNotRequestMapper {
|
|||||||
Objects.requireNonNull(request, "NotificationRequestDTO cannot be null for mapping.");
|
Objects.requireNonNull(request, "NotificationRequestDTO cannot be null for mapping.");
|
||||||
|
|
||||||
if (request.phoneNumber() == null || request.phoneNumber().isEmpty()) {
|
if (request.phoneNumber() == null || request.phoneNumber().isEmpty()) {
|
||||||
throw new IllegalArgumentException("List of 'telefonos' (phone numbers) is required and cannot be empty in NotificationRequestDTO.");
|
throw new IllegalArgumentException("Phone numbers is required and cannot be empty in NotificationRequestDTO.");
|
||||||
}
|
}
|
||||||
String phoneNumber = request.phoneNumber();
|
String phoneNumber = request.phoneNumber();
|
||||||
|
|
||||||
|
|||||||
@@ -104,7 +104,6 @@ public class FirestoreConversationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Mono<ConversationSessionDTO> getSessionByTelefono(String userPhoneNumber) {
|
public Mono<ConversationSessionDTO> getSessionByTelefono(String userPhoneNumber) {
|
||||||
logger.info("Attempting to retrieve conversation session for phone number {}.", userPhoneNumber);
|
|
||||||
return firestoreBaseRepository.getDocumentsByField(getConversationCollectionPath(), "userPhoneNumber", userPhoneNumber)
|
return firestoreBaseRepository.getDocumentsByField(getConversationCollectionPath(), "userPhoneNumber", userPhoneNumber)
|
||||||
.map(documentSnapshot -> {
|
.map(documentSnapshot -> {
|
||||||
if (documentSnapshot != null && documentSnapshot.exists()) {
|
if (documentSnapshot != null && documentSnapshot.exists()) {
|
||||||
@@ -112,7 +111,6 @@ public class FirestoreConversationService {
|
|||||||
logger.info("Successfully retrieved and mapped conversation session for session {}.", sessionDTO.sessionId());
|
logger.info("Successfully retrieved and mapped conversation session for session {}.", sessionDTO.sessionId());
|
||||||
return sessionDTO;
|
return sessionDTO;
|
||||||
}
|
}
|
||||||
logger.info("Conversation session not found for phone number {}.", userPhoneNumber);
|
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,20 +88,18 @@ public class MemoryStoreConversationService {
|
|||||||
return Mono.empty();
|
return Mono.empty();
|
||||||
}
|
}
|
||||||
String phoneToSessionKey = PHONE_TO_SESSION_KEY_PREFIX + telefono;
|
String phoneToSessionKey = PHONE_TO_SESSION_KEY_PREFIX + telefono;
|
||||||
logger.debug("Attempting to retrieve session ID for phone number {} from Memorystore.", telefono);
|
|
||||||
return stringRedisTemplate.opsForValue().get(phoneToSessionKey)
|
return stringRedisTemplate.opsForValue().get(phoneToSessionKey)
|
||||||
.flatMap(sessionId -> {
|
.flatMap(sessionId -> {
|
||||||
logger.debug("Found session ID {} for phone number {}. Retrieving session data.", sessionId, telefono);
|
|
||||||
return redisTemplate.opsForValue().get(SESSION_KEY_PREFIX + sessionId);
|
return redisTemplate.opsForValue().get(SESSION_KEY_PREFIX + sessionId);
|
||||||
})
|
})
|
||||||
.doOnSuccess(session -> {
|
.doOnSuccess(session -> {
|
||||||
if (session != null) {
|
if (session != null) {
|
||||||
logger.info("Successfully retrieved session {} by phone number {}.", session.sessionId(), telefono);
|
logger.info("Successfully retrieved session by phone number");
|
||||||
} else {
|
} else {
|
||||||
logger.info("No session found in Redis for phone number {}.", telefono);
|
logger.info("No session found in Redis for phone number.");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.doOnError(e -> logger.error("Error retrieving session by phone number {}: {}", telefono, e.getMessage(), e));
|
.doOnError(e -> logger.error("Error retrieving session by phone number: {}", e));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Mono<Void> updateSession(ConversationSessionDTO session) {
|
public Mono<Void> updateSession(ConversationSessionDTO session) {
|
||||||
|
|||||||
@@ -95,12 +95,11 @@ public class FirestoreNotificationService {
|
|||||||
}
|
}
|
||||||
} catch (ExecutionException e) {
|
} catch (ExecutionException e) {
|
||||||
logger.error(
|
logger.error(
|
||||||
"Error saving notification to Firestore for phone {}: {}",
|
"Error saving notification to Firestore for phone: {}",
|
||||||
phoneNumber,
|
|
||||||
e.getMessage(),
|
e.getMessage(),
|
||||||
e);
|
e);
|
||||||
throw new FirestorePersistenceException(
|
throw new FirestorePersistenceException(
|
||||||
"Failed to save notification to Firestore for phone " + phoneNumber, e);
|
"Failed to save notification to Firestore for phone ", e);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
logger.error(
|
logger.error(
|
||||||
@@ -109,7 +108,7 @@ public class FirestoreNotificationService {
|
|||||||
e.getMessage(),
|
e.getMessage(),
|
||||||
e);
|
e);
|
||||||
throw new FirestorePersistenceException(
|
throw new FirestorePersistenceException(
|
||||||
"Saving notification was interrupted for phone " + phoneNumber, e);
|
"Saving notification was interrupted for phone ", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -111,12 +111,12 @@ public class MemoryStoreNotificationService {
|
|||||||
return stringRedisTemplate.opsForValue().get(key)
|
return stringRedisTemplate.opsForValue().get(key)
|
||||||
.doOnSuccess(sessionId -> {
|
.doOnSuccess(sessionId -> {
|
||||||
if (sessionId != null) {
|
if (sessionId != null) {
|
||||||
logger.info("Session ID {} found for phone {}.", sessionId, phone);
|
logger.info("Session ID {} found for phone.", sessionId);
|
||||||
} else {
|
} else {
|
||||||
logger.debug("Session ID not found for phone {}.", phone);
|
logger.debug("Session ID not found for phone.");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.doOnError(e -> logger.error("Error retrieving session ID for phone {} from MemoryStore: {}", phone,
|
.doOnError(e -> logger.error("Error retrieving session ID for phone from MemoryStore: {}",
|
||||||
e.getMessage(), e));
|
e.getMessage(), e));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,19 +144,18 @@ public class MemoryStoreNotificationService {
|
|||||||
return Mono.empty();
|
return Mono.empty();
|
||||||
}
|
}
|
||||||
String phoneToSessionKey = PHONE_TO_CONVERSATION_SESSION_KEY_PREFIX + telefono;
|
String phoneToSessionKey = PHONE_TO_CONVERSATION_SESSION_KEY_PREFIX + telefono;
|
||||||
logger.debug("Attempting to retrieve session ID for phone number {} from Redis.", telefono);
|
|
||||||
return stringRedisTemplate.opsForValue().get(phoneToSessionKey)
|
return stringRedisTemplate.opsForValue().get(phoneToSessionKey)
|
||||||
.flatMap(sessionId -> {
|
.flatMap(sessionId -> {
|
||||||
logger.debug("Found session ID {} for phone number {}. Retrieving session data.", sessionId, telefono);
|
logger.debug("Found session ID {} for phone number. Retrieving session data.", sessionId);
|
||||||
return conversationRedisTemplate.opsForValue().get(CONVERSATION_SESSION_KEY_PREFIX + sessionId);
|
return conversationRedisTemplate.opsForValue().get(CONVERSATION_SESSION_KEY_PREFIX + sessionId);
|
||||||
})
|
})
|
||||||
.doOnSuccess(session -> {
|
.doOnSuccess(session -> {
|
||||||
if (session != null) {
|
if (session != null) {
|
||||||
logger.info("Successfully retrieved session {} by phone number {}.", session.sessionId(), telefono);
|
logger.info("Successfully retrieved session {} by phone number.", session.sessionId());
|
||||||
} else {
|
} else {
|
||||||
logger.info("No session found in Redis for phone number {}.", telefono);
|
logger.info("No session found in Redis for phone number.");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.doOnError(e -> logger.error("Error retrieving session by phone number {}: {}", telefono, e.getMessage(), e));
|
.doOnError(e -> logger.error("Error retrieving session by phone number: {}",e.getMessage(), e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,11 +93,9 @@ public class NotificationManagerService {
|
|||||||
firestoreNotificationService.saveOrAppendNotificationEntry(newNotificationEntry)
|
firestoreNotificationService.saveOrAppendNotificationEntry(newNotificationEntry)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
ignored -> logger.debug(
|
ignored -> logger.debug(
|
||||||
"Background: Notification entry persistence initiated for phone {} in Firestore.",
|
"Background: Notification entry persistence initiated for phone in Firestore."),
|
||||||
telefono),
|
|
||||||
e -> logger.error(
|
e -> logger.error(
|
||||||
"Background: Error during notification entry persistence for phone {} in Firestore: {}",
|
"Background: Error during notification entry persistence for phone in Firestore: {}", e.getMessage(), e));
|
||||||
telefono, e.getMessage(), e));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2. Resolve or create a conversation session
|
// 2. Resolve or create a conversation session
|
||||||
@@ -117,8 +115,7 @@ public class NotificationManagerService {
|
|||||||
})
|
})
|
||||||
.switchIfEmpty(Mono.defer(() -> {
|
.switchIfEmpty(Mono.defer(() -> {
|
||||||
String newSessionId = SessionIdGenerator.generateStandardSessionId();
|
String newSessionId = SessionIdGenerator.generateStandardSessionId();
|
||||||
logger.info("No existing conversation session found for phone number {}. Creating new session: {}",
|
logger.info("No existing conversation session found for phone number. Creating new session: {}", newSessionId);
|
||||||
telefono, newSessionId);
|
|
||||||
String userId = "user_by_phone_" + telefono;
|
String userId = "user_by_phone_" + telefono;
|
||||||
Map<String, Object> prefixedParameters = new HashMap<>();
|
Map<String, Object> prefixedParameters = new HashMap<>();
|
||||||
if (obfuscatedRequest.hiddenParameters() != null) {
|
if (obfuscatedRequest.hiddenParameters() != null) {
|
||||||
@@ -143,7 +140,7 @@ public class NotificationManagerService {
|
|||||||
return dialogflowClientService.detectIntent(sessionId, detectIntentRequest);
|
return dialogflowClientService.detectIntent(sessionId, detectIntentRequest);
|
||||||
})
|
})
|
||||||
.doOnSuccess(response -> logger
|
.doOnSuccess(response -> logger
|
||||||
.info("Finished processing notification. Dialogflow response received for phone {}.", telefono))
|
.info("Finished processing notification. Dialogflow response received for phone"))
|
||||||
.doOnError(e -> logger.error("Overall error in NotificationManagerService: {}", e.getMessage(), e));
|
.doOnError(e -> logger.error("Overall error in NotificationManagerService: {}", e.getMessage(), e));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,7 +68,6 @@ public class MemoryStoreQRService {
|
|||||||
return Mono.empty();
|
return Mono.empty();
|
||||||
}
|
}
|
||||||
String phoneToSessionKey = PHONE_TO_SESSION_KEY_PREFIX + telefono;
|
String phoneToSessionKey = PHONE_TO_SESSION_KEY_PREFIX + telefono;
|
||||||
logger.debug("Attempting to retrieve quick reply session ID for phone number {} from Redis.", telefono);
|
|
||||||
return stringRedisTemplate.opsForValue().get(phoneToSessionKey)
|
return stringRedisTemplate.opsForValue().get(phoneToSessionKey)
|
||||||
.flatMap(sessionId -> {
|
.flatMap(sessionId -> {
|
||||||
logger.debug("Found quick reply session ID {} for phone number {}. Retrieving session data.",
|
logger.debug("Found quick reply session ID {} for phone number {}. Retrieving session data.",
|
||||||
@@ -77,13 +76,12 @@ public class MemoryStoreQRService {
|
|||||||
})
|
})
|
||||||
.doOnSuccess(session -> {
|
.doOnSuccess(session -> {
|
||||||
if (session != null) {
|
if (session != null) {
|
||||||
logger.info("Successfully retrieved quick reply session {} by phone number {}.",
|
logger.info("Successfully retrieved quick reply session {} by phone number",
|
||||||
session.sessionId(), telefono);
|
session.sessionId());
|
||||||
} else {
|
} else {
|
||||||
logger.info("No quick reply session found in Redis for phone number {}.", telefono);
|
logger.info("No quick reply session found in Redis for phone number");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.doOnError(e -> logger.error("Error retrieving quick reply session by phone number {}: {}", telefono,
|
.doOnError(e -> logger.error("Error retrieving quick reply session by phone numbe: {}",e.getMessage(), e));
|
||||||
e.getMessage(), e));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,7 +84,7 @@ public class QuickRepliesManagerService {
|
|||||||
|
|
||||||
return memoryStoreConversationService.getSessionByTelefono(userPhoneNumber)
|
return memoryStoreConversationService.getSessionByTelefono(userPhoneNumber)
|
||||||
.switchIfEmpty(Mono.error(
|
.switchIfEmpty(Mono.error(
|
||||||
new IllegalStateException("No quick reply session found for phone number: " + userPhoneNumber)))
|
new IllegalStateException("No quick reply session found for phone number" )))
|
||||||
.flatMap(session -> {
|
.flatMap(session -> {
|
||||||
String userId = session.userId();
|
String userId = session.userId();
|
||||||
String sessionId = session.sessionId();
|
String sessionId = session.sessionId();
|
||||||
@@ -96,7 +96,8 @@ public class QuickRepliesManagerService {
|
|||||||
.map(i -> entries.size() - 1 - i)
|
.map(i -> entries.size() - 1 - i)
|
||||||
.filter(i -> {
|
.filter(i -> {
|
||||||
ConversationEntryDTO entry = entries.get(i);
|
ConversationEntryDTO entry = entries.get(i);
|
||||||
return entry.entity() == ConversationEntryEntity.SISTEMA && entry.type() == ConversationEntryType.INICIO;
|
return entry.entity() == ConversationEntryEntity.SISTEMA
|
||||||
|
&& entry.type() == ConversationEntryType.INICIO;
|
||||||
})
|
})
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElse(-1);
|
.orElse(-1);
|
||||||
@@ -136,7 +137,8 @@ public class QuickRepliesManagerService {
|
|||||||
// Matched question, return the answer
|
// Matched question, return the answer
|
||||||
String respuesta = matchedPreguntas.get(0).respuesta();
|
String respuesta = matchedPreguntas.get(0).respuesta();
|
||||||
QueryResultDTO queryResult = new QueryResultDTO(respuesta, null);
|
QueryResultDTO queryResult = new QueryResultDTO(respuesta, null);
|
||||||
DetectIntentResponseDTO response = new DetectIntentResponseDTO(sessionId, queryResult, null);
|
DetectIntentResponseDTO response = new DetectIntentResponseDTO(sessionId,
|
||||||
|
queryResult, null);
|
||||||
|
|
||||||
return memoryStoreConversationService
|
return memoryStoreConversationService
|
||||||
.updateSession(session.withPantallaContexto(null))
|
.updateSession(session.withPantallaContexto(null))
|
||||||
|
|||||||
@@ -1,132 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.summary;
|
|
||||||
|
|
||||||
import com.example.dto.gemini.ConversationSummaryRequest;
|
|
||||||
import com.example.dto.gemini.ConversationSummaryResponse;
|
|
||||||
import com.example.dto.gemini.ConversationSessionSummaryDTO;
|
|
||||||
import com.example.dto.gemini.ConversationEntrySummaryDTO;
|
|
||||||
import com.example.repository.FirestoreBaseRepository;
|
|
||||||
import com.example.service.base.GeminiClientService;
|
|
||||||
import com.google.cloud.firestore.DocumentReference;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class ConversationSummaryService {
|
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ConversationSummaryService.class);
|
|
||||||
private final GeminiClientService geminiService;
|
|
||||||
private final FirestoreBaseRepository firestoreBaseRepository;
|
|
||||||
|
|
||||||
private static final String CONVERSATION_COLLECTION_PATH_FORMAT = "artifacts/%s/conversations";
|
|
||||||
|
|
||||||
private static final String DEFAULT_GEMINI_MODEL_NAME = "gemini-2.0-flash-001";
|
|
||||||
private static final Float DEFAULT_TEMPERATURE = 0.7f;
|
|
||||||
private static final Integer DEFAULT_MAX_OUTPUT_TOKENS = 800;
|
|
||||||
private static final Float DEFAULT_tOPP = 0.1f;
|
|
||||||
|
|
||||||
|
|
||||||
public ConversationSummaryService(GeminiClientService geminiService, FirestoreBaseRepository firestoreBaseRepository) {
|
|
||||||
this.geminiService = geminiService;
|
|
||||||
this.firestoreBaseRepository = firestoreBaseRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConversationSummaryResponse summarizeConversation(ConversationSummaryRequest request) {
|
|
||||||
if (request == null) {
|
|
||||||
logger.warn("Summarization request is null.");
|
|
||||||
return new ConversationSummaryResponse("Request cannot be null.");
|
|
||||||
}
|
|
||||||
if (request.sessionId() == null || request.sessionId().isBlank()) {
|
|
||||||
logger.warn("Session ID is missing in the summarization request.");
|
|
||||||
return new ConversationSummaryResponse("Session ID is required.");
|
|
||||||
}
|
|
||||||
if (request.prompt() == null || request.prompt().isBlank()) {
|
|
||||||
logger.warn("Prompt for summarization is missing in the request.");
|
|
||||||
return new ConversationSummaryResponse("Prompt for summarization is required.");
|
|
||||||
}
|
|
||||||
|
|
||||||
String sessionId = request.sessionId();
|
|
||||||
String summarizationPromptInstruction = request.prompt();
|
|
||||||
|
|
||||||
String actualModelName = (request.modelName() != null && !request.modelName().isBlank())
|
|
||||||
? request.modelName() : DEFAULT_GEMINI_MODEL_NAME;
|
|
||||||
Float actualTemperature = (request.temperature() != null)
|
|
||||||
? request.temperature() : DEFAULT_TEMPERATURE;
|
|
||||||
Integer actualMaxOutputTokens = (request.maxOutputTokens() != null)
|
|
||||||
? request.maxOutputTokens() : DEFAULT_MAX_OUTPUT_TOKENS;
|
|
||||||
Float actualTopP = (request.top_P() != null)
|
|
||||||
? request.top_P() : DEFAULT_tOPP;
|
|
||||||
|
|
||||||
String collectionPath = String.format(CONVERSATION_COLLECTION_PATH_FORMAT, firestoreBaseRepository.getAppId());
|
|
||||||
String documentId = sessionId;
|
|
||||||
logger.info("Fetching conversation from Firestore: Collection='{}', Document='{}'", collectionPath, documentId);
|
|
||||||
|
|
||||||
ConversationSessionSummaryDTO sessionSummary;
|
|
||||||
try {
|
|
||||||
DocumentReference docRef = firestoreBaseRepository.getDocumentReference(collectionPath, documentId);
|
|
||||||
sessionSummary = firestoreBaseRepository.getDocument(docRef, ConversationSessionSummaryDTO.class);
|
|
||||||
|
|
||||||
logger.debug("Retrieved ConversationSessionSummaryDTO after Firestore fetch: sessionId={}, entries size={}",
|
|
||||||
sessionSummary != null ? sessionSummary.sessionId() : "null",
|
|
||||||
sessionSummary != null && sessionSummary.entries() != null ? sessionSummary.entries().size() : "N/A (entries list is null)");
|
|
||||||
|
|
||||||
if (sessionSummary == null) {
|
|
||||||
logger.warn("Firestore document not found or could not be mapped: {}/{}", collectionPath, documentId);
|
|
||||||
return new ConversationSummaryResponse("Conversation document not found for session ID: " + sessionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<ConversationEntrySummaryDTO> entries = sessionSummary.entries();
|
|
||||||
if (entries == null || entries.isEmpty()) {
|
|
||||||
logger.warn("No conversation entries found in document {}/{} for session ID: {}",
|
|
||||||
collectionPath, documentId, sessionId);
|
|
||||||
return new ConversationSummaryResponse("No conversation messages found in the document for session ID: " + sessionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> conversationMessages = entries.stream()
|
|
||||||
.map(entry -> {
|
|
||||||
String type = entry.type().map(t -> t.name()).orElse("UNKNOWN_TYPE");
|
|
||||||
String timestampString = entry.timestamp() != null ? entry.timestamp().toDate().toInstant().toString() : "UNKNOWN_TIMESTAMP";
|
|
||||||
return String.format("[%s - %s] %s", type, timestampString, entry.text());
|
|
||||||
})
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
String formattedConversation = String.join("\n", conversationMessages);
|
|
||||||
String fullPromptForGemini = summarizationPromptInstruction + "\n\n" + formattedConversation;
|
|
||||||
|
|
||||||
logger.info("Sending summarization request to Gemini with custom prompt (first 200 chars): \n{}",
|
|
||||||
fullPromptForGemini.substring(0, Math.min(fullPromptForGemini.length(), 200)) + "...");
|
|
||||||
|
|
||||||
String summaryText = geminiService.generateContent(
|
|
||||||
fullPromptForGemini,
|
|
||||||
actualTemperature,
|
|
||||||
actualMaxOutputTokens,
|
|
||||||
actualModelName,
|
|
||||||
actualTopP
|
|
||||||
);
|
|
||||||
|
|
||||||
if (summaryText == null || summaryText.trim().isEmpty()) {
|
|
||||||
logger.warn("Gemini returned an empty or null summary for the conversation.");
|
|
||||||
return new ConversationSummaryResponse("Could not generate a summary. The model returned no text.");
|
|
||||||
}
|
|
||||||
logger.info("Successfully generated summary for session ID: {}", sessionId);
|
|
||||||
return new ConversationSummaryResponse(summaryText);
|
|
||||||
|
|
||||||
} catch (InterruptedException | ExecutionException e) {
|
|
||||||
logger.error("Error accessing Firestore for session ID {}: {}", sessionId, e.getMessage(), e);
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
return new ConversationSummaryResponse("Error accessing conversation data: " + e.getMessage());
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("An unexpected error occurred during summarization for session ID {}: {}", sessionId, e.getMessage(), e);
|
|
||||||
return new ConversationSummaryResponse("An unexpected error occurred during summarization: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user