Skip to content

Commit

Permalink
Modularise Spring AI Spring Boot autoconfigurations
Browse files Browse the repository at this point in the history
   - Split spring-ai-spring-boot-autoconfigure into modules

     - This PR addresses the restructuring of the following spring boot autoconfigurations:

       - spring-ai retry -> common
       - spring-ai chat client/model/memory -> chat
       - spring-ai chat/embedding/image observation -> observation
       - spring-ai chat/embedding models -> models

     - Update the Spring AI BOM and boot starters with the new autoconfigure modules

Signed-off-by: Ilayaperumal Gopinathan <[email protected]>
  • Loading branch information
ilayaperumalg committed Feb 21, 2025
1 parent 413ab96 commit b73b93a
Show file tree
Hide file tree
Showing 295 changed files with 23,040 additions and 51 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<artifactId>spring-ai-chat-client-spring-boot-autoconfigure</artifactId>
<packaging>jar</packaging>
<name>Spring AI Chat Client Auto Configuration</name>
<description>Spring AI Chat Client Auto Configuration</description>
<url>https://github.com/spring-projects/spring-ai</url>

<scm>
<url>https://github.com/spring-projects/spring-ai</url>
<connection>git://github.com/spring-projects/spring-ai.git</connection>
<developerConnection>[email protected]:spring-projects/spring-ai.git</developerConnection>
</scm>


<dependencies>

<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-core</artifactId>
<version>${parent.version}</version>
</dependency>

<!-- Boot dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

<!-- Test dependencies -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-test</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.ai.autoconfigure.chat.client;

import io.micrometer.observation.ObservationRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.ChatClientCustomizer;
import org.springframework.ai.chat.client.observation.ChatClientInputContentObservationFilter;
import org.springframework.ai.chat.client.observation.ChatClientObservationConvention;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;

/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link ChatClient}.
* <p>
* This will produce a {@link ChatClient.Builder ChatClient.Builder} bean with the
* {@code prototype} scope, meaning each injection point will receive a newly cloned
* instance of the builder.
*
* @author Christian Tzolov
* @author Mark Pollack
* @author Josh Long
* @author Arjen Poutsma
* @author Thomas Vitale
* @since 1.0.0
*/
@AutoConfiguration
@ConditionalOnClass(ChatClient.class)
@EnableConfigurationProperties(ChatClientBuilderProperties.class)
@ConditionalOnProperty(prefix = ChatClientBuilderProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
matchIfMissing = true)
public class ChatClientAutoConfiguration {

private static final Logger logger = LoggerFactory.getLogger(ChatClientAutoConfiguration.class);

@Bean
@ConditionalOnMissingBean
ChatClientBuilderConfigurer chatClientBuilderConfigurer(ObjectProvider<ChatClientCustomizer> customizerProvider) {
ChatClientBuilderConfigurer configurer = new ChatClientBuilderConfigurer();
configurer.setChatClientCustomizers(customizerProvider.orderedStream().toList());
return configurer;
}

@Bean
@Scope("prototype")
@ConditionalOnMissingBean
ChatClient.Builder chatClientBuilder(ChatClientBuilderConfigurer chatClientBuilderConfigurer, ChatModel chatModel,
ObjectProvider<ObservationRegistry> observationRegistry,
ObjectProvider<ChatClientObservationConvention> observationConvention) {

ChatClient.Builder builder = ChatClient.builder(chatModel,
observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP),
observationConvention.getIfUnique(() -> null));
return chatClientBuilderConfigurer.configure(builder);
}

@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = ChatClientBuilderProperties.CONFIG_PREFIX + ".observations", name = "include-input",
havingValue = "true")
ChatClientInputContentObservationFilter chatClientInputContentObservationFilter() {
logger.warn(
"You have enabled the inclusion of the input content in the observations, with the risk of exposing sensitive or private information. Please, be careful!");
return new ChatClientInputContentObservationFilter();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.ai.autoconfigure.chat.client;

import java.util.List;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.ChatClientCustomizer;

/**
* Builder for configuring a {@link ChatClient.Builder}.
*
* @author Christian Tzolov
* @author Mark Pollack
* @author Josh Long
* @author Arjen Poutsma
* @since 1.0.0 M1
*/
public class ChatClientBuilderConfigurer {

private List<ChatClientCustomizer> customizers;

void setChatClientCustomizers(List<ChatClientCustomizer> customizers) {
this.customizers = customizers;
}

/**
* Configure the specified {@link ChatClient.Builder}. The builder can be further
* tuned and default settings can be overridden.
* @param builder the {@link ChatClient.Builder} instance to configure
* @return the configured builder
*/
public ChatClient.Builder configure(ChatClient.Builder builder) {
applyCustomizers(builder);
return builder;
}

private void applyCustomizers(ChatClient.Builder builder) {
if (this.customizers != null) {
for (ChatClientCustomizer customizer : this.customizers) {
customizer.customize(builder);
}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.ai.autoconfigure.chat.client;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
* Configuration properties for the chat client builder.
*
* @author Christian Tzolov
* @author Mark Pollack
* @author Josh Long
* @author Arjen Poutsma
* @since 1.0.0
*/
@ConfigurationProperties(ChatClientBuilderProperties.CONFIG_PREFIX)
public class ChatClientBuilderProperties {

public static final String CONFIG_PREFIX = "spring.ai.chat.client";

/**
* Enable chat client builder.
*/
private boolean enabled = true;

private Observations observations = new Observations();

public Observations getObservations() {
return this.observations;
}

public boolean isEnabled() {
return this.enabled;
}

public void setEnabled(boolean enabled) {
this.enabled = enabled;
}

public static class Observations {

/**
* Whether to include the input content in the observations.
*/
private boolean includeInput = false;

public boolean isIncludeInput() {
return this.includeInput;
}

public void setIncludeInput(boolean includeCompletion) {
this.includeInput = includeCompletion;
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#
# Copyright 2025-2025 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
org.springframework.ai.autoconfigure.chat.client.ChatClientAutoConfiguration
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.ai.autoconfigure.chat.client;

import org.junit.jupiter.api.Test;

import org.springframework.ai.chat.client.observation.ChatClientInputContentObservationFilter;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Unit tests for {@link ChatClientAutoConfiguration} observability support.
*
* @author Christian Tzolov
*/
class ChatClientObservationAutoConfigurationTests {

private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(ChatClientAutoConfiguration.class));

@Test
void inputContentFilterDefault() {
this.contextRunner
.run(context -> assertThat(context).doesNotHaveBean(ChatClientInputContentObservationFilter.class));
}

@Test
void inputContentFilterEnabled() {
this.contextRunner.withPropertyValues("spring.ai.chat.client.observations.include-input=true")
.run(context -> assertThat(context).hasSingleBean(ChatClientInputContentObservationFilter.class));
}

}
Loading

0 comments on commit b73b93a

Please sign in to comment.