From e142647d8dbd104b9e93da9d15fe0dfd2ab5bbc8 Mon Sep 17 00:00:00 2001 From: Dario Ghunney Ware Date: Wed, 5 Nov 2025 12:50:09 +0000 Subject: [PATCH] Updating version, removing deprecated classes --- .../common/model/ApplicationProperties.java | 1 + .../src/main/resources/application.properties | 1 + .../src/main/resources/settings.yml.template | 1 + app/proprietary/build.gradle | 2 +- .../config/ChatbotAiConfiguration.java | 52 +++++++++++++++++++ .../controller/ChatbotController.java | 2 + .../controller/ChatbotExceptionHandler.java | 2 + .../service/chatbot/ChatbotCacheService.java | 2 + .../chatbot/ChatbotConversationService.java | 2 + .../chatbot/ChatbotFeatureProperties.java | 20 +++++-- .../chatbot/ChatbotIngestionService.java | 2 + .../chatbot/ChatbotRetrievalService.java | 2 + .../service/chatbot/ChatbotService.java | 2 + build.gradle | 2 +- devGuide/DeveloperGuide.md | 5 ++ 15 files changed, 93 insertions(+), 5 deletions(-) create mode 100644 app/proprietary/src/main/java/stirling/software/proprietary/config/ChatbotAiConfiguration.java diff --git a/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java b/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java index bb82ac1c2..f5e815111 100644 --- a/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java +++ b/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java @@ -625,6 +625,7 @@ public class ApplicationProperties { private String primary = "gpt-5-nano"; private String fallback = "gpt-5-mini"; private String embedding = "text-embedding-3-small"; + private String apiKey; } @Data diff --git a/app/core/src/main/resources/application.properties b/app/core/src/main/resources/application.properties index 18e1f4f8a..082a382cd 100644 --- a/app/core/src/main/resources/application.properties +++ b/app/core/src/main/resources/application.properties @@ -55,6 +55,7 @@ posthog.api.key=phc_fiR65u5j6qmXTYL56MNrLZSWqLaDW74OrZH0Insd2xq posthog.host=https://eu.i.posthog.com spring.main.allow-bean-definition-overriding=true +spring.ai.openai.enabled=false # Set up a consistent temporary directory location java.io.tmpdir=${stirling.tempfiles.directory:${java.io.tmpdir}/stirling-pdf} diff --git a/app/core/src/main/resources/settings.yml.template b/app/core/src/main/resources/settings.yml.template index 235c3c7eb..d1da10ce6 100644 --- a/app/core/src/main/resources/settings.yml.template +++ b/app/core/src/main/resources/settings.yml.template @@ -102,6 +102,7 @@ premium: primary: gpt-5-nano # Default lightweight model fallback: gpt-5-mini # Escalation model for complex prompts embedding: text-embedding-3-small # Embedding model for vector store usage + apiKey: '' # Provide your OpenAI-compatible API key (or use SPRING_AI_OPENAI_API_KEY env var) rag: chunkSizeTokens: 512 # Token window used when chunking text chunkOverlapTokens: 128 # Overlap between successive chunks diff --git a/app/proprietary/build.gradle b/app/proprietary/build.gradle index c11152810..e747ae333 100644 --- a/app/proprietary/build.gradle +++ b/app/proprietary/build.gradle @@ -52,7 +52,7 @@ dependencies { api 'org.springframework.boot:spring-boot-starter-cache' api 'com.github.ben-manes.caffeine:caffeine' api 'io.swagger.core.v3:swagger-core-jakarta:2.2.38' - implementation 'org.springframework.ai:spring-ai-openai-spring-boot-starter:1.0.0-M6' + implementation 'org.springframework.ai:spring-ai-openai' implementation 'com.bucket4j:bucket4j_jdk17-core:8.15.0' // https://mvnrepository.com/artifact/com.bucket4j/bucket4j_jdk17 diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/config/ChatbotAiConfiguration.java b/app/proprietary/src/main/java/stirling/software/proprietary/config/ChatbotAiConfiguration.java new file mode 100644 index 000000000..02f4bc6fd --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/config/ChatbotAiConfiguration.java @@ -0,0 +1,52 @@ +package stirling.software.proprietary.config; + +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.openai.OpenAiChatModel; +import org.springframework.ai.openai.OpenAiChatOptions; +import org.springframework.ai.openai.OpenAiEmbeddingModel; +import org.springframework.ai.openai.OpenAiEmbeddingOptions; +import org.springframework.ai.openai.api.OpenAiApi; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.StringUtils; + +import stirling.software.proprietary.service.chatbot.ChatbotFeatureProperties; +import stirling.software.proprietary.service.chatbot.ChatbotFeatureProperties.ChatbotSettings; + +@Configuration +@ConditionalOnProperty(value = "premium.proFeatures.chatbot.enabled", havingValue = "true") +public class ChatbotAiConfiguration { + + @Bean + public OpenAiApi chatbotOpenAiApi(ChatbotFeatureProperties properties) { + ChatbotSettings settings = properties.current(); + String apiKey = settings.models().apiKey(); + if (!StringUtils.hasText(apiKey)) { + throw new IllegalStateException( + "premium.proFeatures.chatbot.models.apiKey must be set (or provide SPRING_AI_OPENAI_API_KEY)"); + } + return new OpenAiApi(apiKey); + } + + @Bean + public ChatModel chatbotChatModel( + OpenAiApi chatbotOpenAiApi, ChatbotFeatureProperties properties) { + OpenAiChatOptions options = + OpenAiChatOptions.builder() + .withModel(properties.current().models().primary()) + .build(); + return new OpenAiChatModel(chatbotOpenAiApi, options); + } + + @Bean + public EmbeddingModel chatbotEmbeddingModel( + OpenAiApi chatbotOpenAiApi, ChatbotFeatureProperties properties) { + OpenAiEmbeddingOptions options = + OpenAiEmbeddingOptions.builder() + .withModel(properties.current().models().embedding()) + .build(); + return new OpenAiEmbeddingModel(chatbotOpenAiApi, options); + } +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/controller/ChatbotController.java b/app/proprietary/src/main/java/stirling/software/proprietary/controller/ChatbotController.java index 77370a1ad..281523e8e 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/controller/ChatbotController.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/controller/ChatbotController.java @@ -3,6 +3,7 @@ package stirling.software.proprietary.controller; import java.util.ArrayList; import java.util.List; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; @@ -32,6 +33,7 @@ import stirling.software.proprietary.service.chatbot.exception.ChatbotException; @RequestMapping("/api/internal/chatbot") @RequiredArgsConstructor @Slf4j +@ConditionalOnProperty(value = "premium.proFeatures.chatbot.enabled", havingValue = "true") public class ChatbotController { private final ChatbotService chatbotService; diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/controller/ChatbotExceptionHandler.java b/app/proprietary/src/main/java/stirling/software/proprietary/controller/ChatbotExceptionHandler.java index b2e82ac4e..f3f172b50 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/controller/ChatbotExceptionHandler.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/controller/ChatbotExceptionHandler.java @@ -3,6 +3,7 @@ package stirling.software.proprietary.controller; import java.time.Instant; import java.util.Map; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -15,6 +16,7 @@ import stirling.software.proprietary.service.chatbot.exception.NoTextDetectedExc @RestControllerAdvice(assignableTypes = ChatbotController.class) @Slf4j +@ConditionalOnProperty(value = "premium.proFeatures.chatbot.enabled", havingValue = "true") public class ChatbotExceptionHandler { @ExceptionHandler(NoTextDetectedException.class) diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotCacheService.java b/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotCacheService.java index edb713d97..935e95b69 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotCacheService.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotCacheService.java @@ -9,6 +9,7 @@ import java.util.Optional; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import com.github.benmanes.caffeine.cache.Cache; @@ -25,6 +26,7 @@ import stirling.software.proprietary.model.chatbot.ChatbotTextChunk; import stirling.software.proprietary.service.chatbot.exception.ChatbotException; @Service +@ConditionalOnProperty(value = "premium.proFeatures.chatbot.enabled", havingValue = "true") @Slf4j public class ChatbotCacheService { diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotConversationService.java b/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotConversationService.java index 7e8a637ee..f77126f12 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotConversationService.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotConversationService.java @@ -16,6 +16,7 @@ import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.openai.OpenAiChatModel; import org.springframework.ai.openai.OpenAiChatOptions; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; @@ -36,6 +37,7 @@ import stirling.software.proprietary.service.chatbot.exception.ChatbotException; @Service @Slf4j @RequiredArgsConstructor +@ConditionalOnProperty(value = "premium.proFeatures.chatbot.enabled", havingValue = "true") public class ChatbotConversationService { private final ChatModel chatModel; diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotFeatureProperties.java b/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotFeatureProperties.java index 653254822..46c6b71bf 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotFeatureProperties.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotFeatureProperties.java @@ -2,7 +2,9 @@ package stirling.software.proprietary.service.chatbot; import java.util.Optional; +import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties.Premium; @@ -13,13 +15,23 @@ import stirling.software.common.model.ApplicationProperties.Premium.ProFeatures. public class ChatbotFeatureProperties { private final ApplicationProperties applicationProperties; + private final Environment environment; - public ChatbotFeatureProperties(ApplicationProperties applicationProperties) { + public ChatbotFeatureProperties( + ApplicationProperties applicationProperties, Environment environment) { this.applicationProperties = applicationProperties; + this.environment = environment; } public ChatbotSettings current() { Chatbot chatbot = resolveChatbot(); + String configuredKey = Optional.ofNullable(chatbot.getModels().getApiKey()).orElse(""); + String fallbackKey = environment.getProperty("spring.ai.openai.api-key", ""); + String apiKey = + StringUtils.hasText(configuredKey) + ? configuredKey + : (StringUtils.hasText(fallbackKey) ? fallbackKey : ""); + return new ChatbotSettings( chatbot.isEnabled(), chatbot.isAlphaWarning(), @@ -28,7 +40,8 @@ public class ChatbotFeatureProperties { new ChatbotSettings.ModelSettings( chatbot.getModels().getPrimary(), chatbot.getModels().getFallback(), - chatbot.getModels().getEmbedding()), + chatbot.getModels().getEmbedding(), + apiKey), new ChatbotSettings.RagSettings( chatbot.getRag().getChunkSizeTokens(), chatbot.getRag().getChunkOverlapTokens(), @@ -64,7 +77,8 @@ public class ChatbotFeatureProperties { OcrSettings ocr, AuditSettings audit) { - public record ModelSettings(String primary, String fallback, String embedding) {} + public record ModelSettings( + String primary, String fallback, String embedding, String apiKey) {} public record RagSettings(int chunkSizeTokens, int chunkOverlapTokens, int topK) {} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotIngestionService.java b/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotIngestionService.java index 5cda9f34c..bb79eaad1 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotIngestionService.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotIngestionService.java @@ -8,6 +8,7 @@ import java.util.UUID; import org.springframework.ai.embedding.EmbeddingModel; import org.springframework.ai.embedding.EmbeddingResponse; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; @@ -22,6 +23,7 @@ import stirling.software.proprietary.service.chatbot.exception.ChatbotException; import stirling.software.proprietary.service.chatbot.exception.NoTextDetectedException; @Service +@ConditionalOnProperty(value = "premium.proFeatures.chatbot.enabled", havingValue = "true") @Slf4j @RequiredArgsConstructor public class ChatbotIngestionService { diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotRetrievalService.java b/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotRetrievalService.java index c2a35620d..d82a0b4fb 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotRetrievalService.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotRetrievalService.java @@ -7,6 +7,7 @@ import java.util.Optional; import org.springframework.ai.embedding.EmbeddingModel; import org.springframework.ai.embedding.EmbeddingResponse; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; @@ -21,6 +22,7 @@ import stirling.software.proprietary.service.chatbot.exception.ChatbotException; @Service @RequiredArgsConstructor @Slf4j +@ConditionalOnProperty(value = "premium.proFeatures.chatbot.enabled", havingValue = "true") public class ChatbotRetrievalService { private final ChatbotCacheService cacheService; diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotService.java b/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotService.java index edbd90e6e..fa910fe56 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotService.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotService.java @@ -3,6 +3,7 @@ package stirling.software.proprietary.service.chatbot; import java.util.HashMap; import java.util.Map; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import lombok.RequiredArgsConstructor; @@ -16,6 +17,7 @@ import stirling.software.proprietary.service.AuditService; import stirling.software.proprietary.service.chatbot.exception.ChatbotException; @Service +@ConditionalOnProperty(value = "premium.proFeatures.chatbot.enabled", havingValue = "true") @Slf4j @RequiredArgsConstructor public class ChatbotService { diff --git a/build.gradle b/build.gradle index 034486798..fc439f39d 100644 --- a/build.gradle +++ b/build.gradle @@ -15,7 +15,7 @@ import com.github.jk1.license.render.* ext { springBootVersion = "3.5.6" - springAiVersion = "1.0.0-M6" + springAiVersion = "1.0.3" pdfboxVersion = "3.0.5" imageioVersion = "3.12.0" lombokVersion = "1.18.42" diff --git a/devGuide/DeveloperGuide.md b/devGuide/DeveloperGuide.md index fb8911eaf..6ecbfe343 100644 --- a/devGuide/DeveloperGuide.md +++ b/devGuide/DeveloperGuide.md @@ -587,3 +587,8 @@ In your Thymeleaf templates, use the `#{key}` syntax to reference the new transl ``` Remember, never hard-code text in your templates or Java code. Always use translation keys to ensure proper localization. + +### Chatbot Feature Configuration + +- The chatbot backend is disabled unless `premium.proFeatures.chatbot.enabled` is true in `configs/settings.yml`. +- Provide an OpenAI-compatible key through `premium.proFeatures.chatbot.models.apiKey` (preferred) or the `SPRING_AI_OPENAI_API_KEY` environment variable. Without a key the app still boots, but the chatbot beans are not created.