/* * 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.service.notification.MemoryStoreNotificationService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import jakarta.annotation.PostConstruct; import java.io.IOException; 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 { private static final Logger logger = LoggerFactory.getLogger(NotificationContextResolver.class); private final GeminiClientService geminiService; @Value("${notificationcontext.geminimodel:gemini-2.0-flash-001}") private String geminiModelNameResolver; @Value("${notificationcontext.temperature:0.1f}") private Float resolverTemperature; @Value("${notificationcontext.maxOutputTokens:1024}") private Integer resolverMaxOutputTokens; @Value("${notificationcontext.topP:0.1f}") private Float resolverTopP; @Value("${notificationcontext.prompt:prompts/notification_context_resolver.txt}") private String promptFilePath; public static final String CATEGORY_DIALOGFLOW = "DIALOGFLOW"; private String promptTemplate; public NotificationContextResolver(GeminiClientService geminiService, MemoryStoreNotificationService memoryStoreNotificationService) { this.geminiService = Objects.requireNonNull(geminiService, "GeminiClientService cannot be null for NotificationContextResolver."); } @PostConstruct public void loadPromptTemplate() { try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(promptFilePath)) { if (inputStream == null) { throw new IOException("Resource not found: " + 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 '" + promptFilePath + "'. Please ensure the file exists. Error: " + e.getMessage()); throw new IllegalStateException("Could not load prompt template.", e); } } public String resolveContext(String queryInputText, String notificationsJson, String conversationJson, String metadata, String userId, String sessionId, String userPhoneNumber) { logger.debug("resolveContext -> queryInputText: {}, notificationsJson: {}, conversationJson: {}, metadata: {}", queryInputText, notificationsJson, conversationJson, metadata); if (queryInputText == null || queryInputText.isBlank()) { logger.warn("Query input text for context resolution is null or blank.", CATEGORY_DIALOGFLOW); return CATEGORY_DIALOGFLOW; } String notificationContent = (notificationsJson != null && !notificationsJson.isBlank()) ? notificationsJson : "No metadata in notification."; String conversationHistory = (conversationJson != null && !conversationJson.isBlank()) ? conversationJson : "No conversation history."; String contextPrompt = String.format( this.promptTemplate, conversationHistory, notificationContent, metadata, queryInputText); logger.debug("Sending context resolution request to Gemini for input (first 100 chars): '{}'...", queryInputText.substring(0, Math.min(queryInputText.length(), 100))); try { String geminiResponse = geminiService.generateContent( contextPrompt, resolverTemperature, resolverMaxOutputTokens, geminiModelNameResolver, resolverTopP); if (geminiResponse != null && !geminiResponse.isBlank()) { if (geminiResponse.trim().equalsIgnoreCase(CATEGORY_DIALOGFLOW)) { logger.debug("Resolved to {}. Input: '{}'", CATEGORY_DIALOGFLOW, queryInputText); return CATEGORY_DIALOGFLOW; } else { logger.debug("Resolved to a specific response. Input: '{}'", queryInputText); return geminiResponse; } } else { logger.warn("Gemini returned a null or blank response", queryInputText, CATEGORY_DIALOGFLOW); return CATEGORY_DIALOGFLOW; } } catch (Exception e) { logger.error("An error occurred during Gemini content generation for context resolution.", e); return CATEGORY_DIALOGFLOW; } } }