From 3648f2c8a989a5143e88157f6bae766d73822880 Mon Sep 17 00:00:00 2001 From: ZYinNJU <1754350460@qq.com> Date: Wed, 18 Sep 2024 16:53:34 +0800 Subject: [PATCH] AiService support @Profile (#41) Closes https://github.com/langchain4j/langchain4j/issues/1430 Support `@AiService` with `@Profile`. --- langchain4j-spring-boot-starter/pom.xml | 13 ------- .../spring/AiServiceScannerProcessor.java | 38 ++++++++++++++++--- .../service/spring/AiServicesAutoConfig.java | 5 ++- .../withProfiles/AiServiceWithProfiles.java | 11 ++++++ .../AiServiceWithProfilesApplication.java | 20 ++++++++++ .../withProfiles/AiServiceWithProfilesIT.java | 37 ++++++++++++++++++ 6 files changed, 105 insertions(+), 19 deletions(-) create mode 100644 langchain4j-spring-boot-starter/src/test/java/dev/langchain4j/service/spring/mode/automatic/withProfiles/AiServiceWithProfiles.java create mode 100644 langchain4j-spring-boot-starter/src/test/java/dev/langchain4j/service/spring/mode/automatic/withProfiles/AiServiceWithProfilesApplication.java create mode 100644 langchain4j-spring-boot-starter/src/test/java/dev/langchain4j/service/spring/mode/automatic/withProfiles/AiServiceWithProfilesIT.java diff --git a/langchain4j-spring-boot-starter/pom.xml b/langchain4j-spring-boot-starter/pom.xml index 2482b1d0..8d036570 100644 --- a/langchain4j-spring-boot-starter/pom.xml +++ b/langchain4j-spring-boot-starter/pom.xml @@ -69,19 +69,6 @@ test - - org.tinylog - tinylog-impl - 2.6.2 - test - - - org.tinylog - slf4j-tinylog - 2.6.2 - test - - diff --git a/langchain4j-spring-boot-starter/src/main/java/dev/langchain4j/service/spring/AiServiceScannerProcessor.java b/langchain4j-spring-boot-starter/src/main/java/dev/langchain4j/service/spring/AiServiceScannerProcessor.java index 12e75bf6..cfbf177a 100644 --- a/langchain4j-spring-boot-starter/src/main/java/dev/langchain4j/service/spring/AiServiceScannerProcessor.java +++ b/langchain4j-spring-boot-starter/src/main/java/dev/langchain4j/service/spring/AiServiceScannerProcessor.java @@ -6,18 +6,20 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.boot.autoconfigure.AutoConfigurationPackages; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Profile; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; import org.springframework.util.ClassUtils; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; +import java.util.*; @Component -public class AiServiceScannerProcessor implements BeanDefinitionRegistryPostProcessor { +public class AiServiceScannerProcessor implements BeanDefinitionRegistryPostProcessor, EnvironmentAware { + + private Environment environment; @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { @@ -26,6 +28,8 @@ public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) t for (String basePackage : basePackages) { classPathAiServiceScanner.scan(basePackage); } + + filterBeanDefinitions(registry); } private Set getBasePackages(ConfigurableListableBeanFactory beanFactory) { @@ -63,4 +67,28 @@ private Set getBasePackages(ConfigurableListableBeanFactory beanFactory) return basePackages; } + + private void filterBeanDefinitions(BeanDefinitionRegistry registry) { + Arrays.stream(registry.getBeanDefinitionNames()) + .filter(beanName -> { + try { + Class beanClass = Class.forName(registry.getBeanDefinition(beanName).getBeanClassName()); + if (beanClass.isAnnotationPresent(AiService.class) && beanClass.isAnnotationPresent(Profile.class)) { + Profile profileAnnotation = beanClass.getAnnotation(Profile.class); + String[] profiles = profileAnnotation.value(); + + return !environment.matchesProfiles(profiles); + } else { + return false; + } + } catch (ClassNotFoundException e) { + return false; + } + }).forEach(registry::removeBeanDefinition); + } + + @Override + public void setEnvironment(Environment environment) { + this.environment = environment; + } } diff --git a/langchain4j-spring-boot-starter/src/main/java/dev/langchain4j/service/spring/AiServicesAutoConfig.java b/langchain4j-spring-boot-starter/src/main/java/dev/langchain4j/service/spring/AiServicesAutoConfig.java index 4c3f5a6e..575769e8 100644 --- a/langchain4j-spring-boot-starter/src/main/java/dev/langchain4j/service/spring/AiServicesAutoConfig.java +++ b/langchain4j-spring-boot-starter/src/main/java/dev/langchain4j/service/spring/AiServicesAutoConfig.java @@ -17,7 +17,10 @@ import org.springframework.context.annotation.Bean; import java.lang.reflect.Method; -import java.util.*; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; import static dev.langchain4j.exception.IllegalConfigurationException.illegalConfiguration; import static dev.langchain4j.internal.Exceptions.illegalArgument; diff --git a/langchain4j-spring-boot-starter/src/test/java/dev/langchain4j/service/spring/mode/automatic/withProfiles/AiServiceWithProfiles.java b/langchain4j-spring-boot-starter/src/test/java/dev/langchain4j/service/spring/mode/automatic/withProfiles/AiServiceWithProfiles.java new file mode 100644 index 00000000..c5d573a8 --- /dev/null +++ b/langchain4j-spring-boot-starter/src/test/java/dev/langchain4j/service/spring/mode/automatic/withProfiles/AiServiceWithProfiles.java @@ -0,0 +1,11 @@ +package dev.langchain4j.service.spring.mode.automatic.withProfiles; + +import dev.langchain4j.service.spring.AiService; +import org.springframework.context.annotation.Profile; + +@AiService +@Profile("!test") +public interface AiServiceWithProfiles { + + String chat(String userMessage); +} diff --git a/langchain4j-spring-boot-starter/src/test/java/dev/langchain4j/service/spring/mode/automatic/withProfiles/AiServiceWithProfilesApplication.java b/langchain4j-spring-boot-starter/src/test/java/dev/langchain4j/service/spring/mode/automatic/withProfiles/AiServiceWithProfilesApplication.java new file mode 100644 index 00000000..5a6e133f --- /dev/null +++ b/langchain4j-spring-boot-starter/src/test/java/dev/langchain4j/service/spring/mode/automatic/withProfiles/AiServiceWithProfilesApplication.java @@ -0,0 +1,20 @@ +package dev.langchain4j.service.spring.mode.automatic.withProfiles; + +import dev.langchain4j.model.chat.ChatLanguageModel; +import dev.langchain4j.model.openai.OpenAiChatModel; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +@SpringBootApplication +public class AiServiceWithProfilesApplication { + + @Bean + ChatLanguageModel chatLanguageModel() { + return OpenAiChatModel.withApiKey(System.getenv("OPENAI_API_KEY")); + } + + public static void main(String[] args) { + SpringApplication.run(AiServiceWithProfilesApplication.class, args); + } +} diff --git a/langchain4j-spring-boot-starter/src/test/java/dev/langchain4j/service/spring/mode/automatic/withProfiles/AiServiceWithProfilesIT.java b/langchain4j-spring-boot-starter/src/test/java/dev/langchain4j/service/spring/mode/automatic/withProfiles/AiServiceWithProfilesIT.java new file mode 100644 index 00000000..5f65c4a7 --- /dev/null +++ b/langchain4j-spring-boot-starter/src/test/java/dev/langchain4j/service/spring/mode/automatic/withProfiles/AiServiceWithProfilesIT.java @@ -0,0 +1,37 @@ +package dev.langchain4j.service.spring.mode.automatic.withProfiles; + +import dev.langchain4j.service.spring.AiServicesAutoConfig; +import org.junit.jupiter.api.Test; +import org.springframework.beans.BeansException; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class AiServiceWithProfilesIT { + + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(AiServicesAutoConfig.class)); + + @Test + void should_not_create_ai_service() { + contextRunner + .withPropertyValues( + "spring.profiles.active=test" + ) + .withUserConfiguration(AiServiceWithProfilesApplication.class) + .run(context -> assertThatThrownBy(() -> context.getBean(AiServiceWithProfiles.class)) + .isInstanceOf(BeansException.class)); + } + + @Test + void should_create_ai_service() { + contextRunner + .withPropertyValues( + "spring.profiles.active=dev" + ) + .withUserConfiguration(AiServiceWithProfilesApplication.class) + .run(context -> assertThat(context.getBean(AiServiceWithProfiles.class)).isNotNull()); + } +}