UPDATE 24-sept

This commit is contained in:
PAVEL PALMA
2025-09-24 17:20:48 -06:00
parent 26cbe71d2f
commit 535a1d8ca0
4 changed files with 113 additions and 43 deletions

58
pom.xml
View File

@@ -21,7 +21,7 @@
<spring-cloud-gcp.version>5.4.0</spring-cloud-gcp.version>
<spring-cloud.version>2023.0.0</spring-cloud.version>
<lettuce.version>6.4.0.RELEASE</lettuce.version>
<spring-framework.version>6.1.20</spring-framework.version>
<spring-framework.version>6.1.21</spring-framework.version>
</properties>
<dependencyManagement>
@@ -61,36 +61,88 @@
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
<version>2.5.0</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>spring-cloud-gcp-starter-data-firestore</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>spring-cloud-gcp-data-firestore</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>spring-cloud-gcp-starter-storage</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
@@ -172,6 +224,10 @@
<version>2.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>

View File

@@ -2,24 +2,28 @@
* 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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import org.springframework.util.FileCopyUtils;
import jakarta.annotation.PostConstruct;
import java.io.IOException;
import java.io.Reader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.io.InputStream;
/**
* Service to classify message entries (user text input from DetectIntent) into predefined categories
* like "CONVERSATION" or "NOTIFICATION" using Gemini model.
* Classifies a user's text input into a predefined category using a Gemini
* model.
* It analyzes the user's query in the context of a conversation history and any
* relevant notifications to determine if the message is part of the ongoing
* dialogue
* or an interruption. The classification is used to route the request to the
* appropriate handler (e.g., a standard conversational flow or a specific
* notification processor).
*/
@Service
public class MessageEntryFilter {
@@ -50,23 +54,25 @@ public class MessageEntryFilter {
private String promptTemplate;
public MessageEntryFilter(GeminiClientService geminiService) {
this.geminiService = Objects.requireNonNull(geminiService, "GeminiClientService cannot be null for MessageEntryFilter.");
this.geminiService = Objects.requireNonNull(geminiService,
"GeminiClientService cannot be null for MessageEntryFilter.");
}
@PostConstruct
public void loadPromptTemplate() {
try {
ClassPathResource resource = new ClassPathResource(promptFilePath);
try (Reader reader = new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8)) {
this.promptTemplate = FileCopyUtils.copyToString(reader);
try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(promptFilePath)) {
if (inputStream == null) {
throw new IOException("Resource not found: " + promptFilePath);
}
logger.info("Successfully loaded prompt template from '{}'.", promptFilePath);
byte[] fileBytes = inputStream.readAllBytes();
this.promptTemplate = new String(fileBytes, StandardCharsets.UTF_8);
logger.info("Successfully loaded prompt template from '" + promptFilePath + "'.");
} catch (IOException e) {
logger.error("Failed to load prompt template from '{}'. Please ensure the file exists.", promptFilePath, e);
logger.error("Failed to load prompt template from '" + promptFilePath
+ "'. Please ensure the file exists. Error: " + e.getMessage());
throw new IllegalStateException("Could not load prompt template.", e);
}
}
public String classifyMessage(String queryInputText, String notificationsJson, String conversationJson) {
if (queryInputText == null || queryInputText.isBlank()) {
logger.warn("Query input text for classification is null or blank. Returning {}.", CATEGORY_UNKNOWN);

View File

@@ -8,16 +8,24 @@ import com.example.service.notification.MemoryStoreNotificationService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import org.springframework.util.FileCopyUtils;
import jakarta.annotation.PostConstruct;
import java.io.IOException;
import java.io.Reader;
import java.io.InputStreamReader;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
/**
* Resolves the conversational context of a user query by leveraging a large
* language model (LLM). This service evaluates a user's question in the context
* of a specific notification and conversation history, then decides if the
* query
* can be answered by the LLM or if it should be handled by a standard
* Dialogflow agent.
* The class loads an LLM prompt from an external file and dynamically
* formats it with a user's query and other context to drive its decision-making
* process.
*/
@Service
public class NotificationContextResolver {
@@ -43,21 +51,24 @@ public class NotificationContextResolver {
private String promptTemplate;
public NotificationContextResolver(GeminiClientService geminiService, MemoryStoreNotificationService memoryStoreNotificationService) {
public NotificationContextResolver(GeminiClientService geminiService,
MemoryStoreNotificationService memoryStoreNotificationService) {
this.geminiService = Objects.requireNonNull(geminiService,
"GeminiClientService cannot be null for NotificationContextResolver.");
}
@PostConstruct
public void loadPromptTemplate() {
try {
ClassPathResource resource = new ClassPathResource(promptFilePath);
try (Reader reader = new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8)) {
this.promptTemplate = FileCopyUtils.copyToString(reader);
try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(promptFilePath)) {
if (inputStream == null) {
throw new IOException("Resource not found: " + promptFilePath);
}
logger.info("Successfully loaded prompt template from '{}'.", promptFilePath);
byte[] fileBytes = inputStream.readAllBytes();
this.promptTemplate = new String(fileBytes, StandardCharsets.UTF_8);
logger.info("Successfully loaded prompt template from '" + promptFilePath + "'.");
} catch (IOException e) {
logger.error("Failed to load prompt template from '{}'. Please ensure the file exists.", promptFilePath, e);
logger.error("Failed to load prompt template from '" + promptFilePath
+ "'. Please ensure the file exists. Error: " + e.getMessage());
throw new IllegalStateException("Could not load prompt template.", e);
}
}

View File

@@ -12,35 +12,32 @@ import com.google.cloud.firestore.DocumentReference;
import com.google.cloud.firestore.DocumentSnapshot;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import jakarta.annotation.PostConstruct;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
@Component
public class FirestoreDataImporter {
private static final Logger logger = LoggerFactory.getLogger(FirestoreDataImporter.class);
private static final String QUICK_REPLIES_COLLECTION_PATH_FORMAT = "artifacts/%s/quick-replies";
@Autowired
private FirestoreBaseRepository firestoreBaseRepository;
private final FirestoreBaseRepository firestoreBaseRepository;
private final ObjectMapper objectMapper;
@Autowired
private ObjectMapper objectMapper;
private final boolean isImporterEnabled;
@Autowired
private Environment env;
public FirestoreDataImporter(FirestoreBaseRepository firestoreBaseRepository, ObjectMapper objectMapper) {
this.firestoreBaseRepository = firestoreBaseRepository;
this.objectMapper = objectMapper;
this.isImporterEnabled = Boolean.parseBoolean(System.getProperty("firestore.data.importer.enabled"));
@PostConstruct
public void importDataOnStartup() {
if (Boolean.parseBoolean(env.getProperty("firestore.data.importer.enabled"))) {
}
public void runImport() {
if (isImporterEnabled) {
try {
importQuickReplies();
} catch (Exception e) {