diff --git a/pom.xml b/pom.xml index dad285d..83982fd 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ 5.4.0 2023.0.0 6.4.0.RELEASE - 6.1.20 + 6.1.21 @@ -61,36 +61,88 @@ org.springframework.boot spring-boot-starter-webflux + + + org.springframework + spring-core + + + + + org.springframework + spring-web org.springdoc springdoc-openapi-starter-webflux-ui 2.5.0 + + + org.springframework + spring-core + + com.google.cloud spring-cloud-gcp-starter-data-firestore + + + org.springframework + spring-core + + com.google.cloud spring-cloud-gcp-data-firestore + + + org.springframework + spring-core + + com.google.cloud spring-cloud-gcp-starter-storage + + + org.springframework + spring-core + + org.springframework.boot spring-boot-starter-data-redis-reactive + + + org.springframework + spring-core + + org.springframework.boot spring-boot-starter-actuator + + + org.springframework + spring-core + + org.springframework.boot spring-boot-starter-test test + + + org.springframework + spring-core + + com.google.cloud @@ -172,6 +224,10 @@ 2.10.0 test + + org.springframework.boot + spring-boot-starter-validation + org.apache.commons commons-lang3 diff --git a/src/main/java/com/example/service/base/MessageEntryFilter.java b/src/main/java/com/example/service/base/MessageEntryFilter.java index 7d887b7..a021357 100644 --- a/src/main/java/com/example/service/base/MessageEntryFilter.java +++ b/src/main/java/com/example/service/base/MessageEntryFilter.java @@ -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 { @@ -46,27 +50,29 @@ public class MessageEntryFilter { public static final String CATEGORY_NOTIFICATION = "NOTIFICATION"; public static final String CATEGORY_UNKNOWN = "UNKNOWN"; public static final String CATEGORY_ERROR = "ERROR"; - + 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); diff --git a/src/main/java/com/example/service/base/NotificationContextResolver.java b/src/main/java/com/example/service/base/NotificationContextResolver.java index c6fa4d2..758ccd1 100644 --- a/src/main/java/com/example/service/base/NotificationContextResolver.java +++ b/src/main/java/com/example/service/base/NotificationContextResolver.java @@ -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); } } diff --git a/src/main/java/com/example/util/FirestoreDataImporter.java b/src/main/java/com/example/util/FirestoreDataImporter.java index 285cd65..6c437dd 100644 --- a/src/main/java/com/example/util/FirestoreDataImporter.java +++ b/src/main/java/com/example/util/FirestoreDataImporter.java @@ -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) { @@ -91,4 +88,4 @@ public class FirestoreDataImporter { } } } -} +} \ No newline at end of file