Add support for QianFan AI models
- Add chat, embeddin and image models - Tests - Docs
This commit is contained in:
@@ -326,6 +326,13 @@
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-qianfan</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Typesense vector store -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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.qianfan;
|
||||
|
||||
import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration;
|
||||
import org.springframework.ai.model.function.FunctionCallbackContext;
|
||||
import org.springframework.ai.qianfan.QianFanChatModel;
|
||||
import org.springframework.ai.qianfan.QianFanEmbeddingModel;
|
||||
import org.springframework.ai.qianfan.QianFanImageModel;
|
||||
import org.springframework.ai.qianfan.api.QianFanApi;
|
||||
import org.springframework.ai.qianfan.api.QianFanImageApi;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
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.autoconfigure.web.client.RestClientAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.client.ResponseErrorHandler;
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
/**
|
||||
* @author Geng Rong
|
||||
*/
|
||||
@AutoConfiguration(after = { RestClientAutoConfiguration.class, SpringAiRetryAutoConfiguration.class })
|
||||
@ConditionalOnClass(QianFanApi.class)
|
||||
@EnableConfigurationProperties({ QianFanConnectionProperties.class, QianFanChatProperties.class,
|
||||
QianFanEmbeddingProperties.class, QianFanImageProperties.class })
|
||||
public class QianFanAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = QianFanChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
public QianFanChatModel qianFanChatModel(QianFanConnectionProperties commonProperties,
|
||||
QianFanChatProperties chatProperties, RestClient.Builder restClientBuilder, RetryTemplate retryTemplate,
|
||||
ResponseErrorHandler responseErrorHandler) {
|
||||
|
||||
var qianFanApi = qianFanApi(chatProperties.getBaseUrl(), commonProperties.getBaseUrl(),
|
||||
chatProperties.getApiKey(), commonProperties.getApiKey(), chatProperties.getSecretKey(),
|
||||
commonProperties.getSecretKey(), restClientBuilder, responseErrorHandler);
|
||||
|
||||
return new QianFanChatModel(qianFanApi, chatProperties.getOptions(), retryTemplate);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = QianFanEmbeddingProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
public QianFanEmbeddingModel qianFanEmbeddingModel(QianFanConnectionProperties commonProperties,
|
||||
QianFanEmbeddingProperties embeddingProperties, RestClient.Builder restClientBuilder,
|
||||
RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler) {
|
||||
|
||||
var qianFanApi = qianFanApi(embeddingProperties.getBaseUrl(), commonProperties.getBaseUrl(),
|
||||
embeddingProperties.getApiKey(), commonProperties.getApiKey(), embeddingProperties.getSecretKey(),
|
||||
commonProperties.getSecretKey(), restClientBuilder, responseErrorHandler);
|
||||
|
||||
return new QianFanEmbeddingModel(qianFanApi, embeddingProperties.getMetadataMode(),
|
||||
embeddingProperties.getOptions(), retryTemplate);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = QianFanImageProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
public QianFanImageModel qianFanImageModel(QianFanConnectionProperties commonProperties,
|
||||
QianFanImageProperties imageProperties, RestClient.Builder restClientBuilder, RetryTemplate retryTemplate,
|
||||
ResponseErrorHandler responseErrorHandler) {
|
||||
|
||||
String apiKey = StringUtils.hasText(imageProperties.getApiKey()) ? imageProperties.getApiKey()
|
||||
: commonProperties.getApiKey();
|
||||
|
||||
String secretKey = StringUtils.hasText(imageProperties.getSecretKey()) ? imageProperties.getSecretKey()
|
||||
: commonProperties.getSecretKey();
|
||||
|
||||
String baseUrl = StringUtils.hasText(imageProperties.getBaseUrl()) ? imageProperties.getBaseUrl()
|
||||
: commonProperties.getBaseUrl();
|
||||
|
||||
Assert.hasText(apiKey, "QianFan API key must be set. Use the property: spring.ai.qianfan.api-key");
|
||||
Assert.hasText(secretKey, "QianFan secret key must be set. Use the property: spring.ai.qianfan.secret-key");
|
||||
Assert.hasText(baseUrl, "QianFan base URL must be set. Use the property: spring.ai.qianfan.base-url");
|
||||
|
||||
var qianFanImageApi = new QianFanImageApi(baseUrl, apiKey, secretKey, restClientBuilder, responseErrorHandler);
|
||||
|
||||
return new QianFanImageModel(qianFanImageApi, imageProperties.getOptions(), retryTemplate);
|
||||
}
|
||||
|
||||
private QianFanApi qianFanApi(String baseUrl, String commonBaseUrl, String apiKey, String commonApiKey,
|
||||
String secretKey, String commonSecretKey, RestClient.Builder restClientBuilder,
|
||||
ResponseErrorHandler responseErrorHandler) {
|
||||
|
||||
String resolvedBaseUrl = StringUtils.hasText(baseUrl) ? baseUrl : commonBaseUrl;
|
||||
Assert.hasText(resolvedBaseUrl, "QianFan base URL must be set");
|
||||
|
||||
String resolvedApiKey = StringUtils.hasText(apiKey) ? apiKey : commonApiKey;
|
||||
Assert.hasText(resolvedApiKey, "QianFan API key must be set");
|
||||
|
||||
String resolvedSecretKey = StringUtils.hasText(secretKey) ? secretKey : commonSecretKey;
|
||||
Assert.hasText(resolvedSecretKey, "QianFan Secret key must be set");
|
||||
|
||||
return new QianFanApi(resolvedBaseUrl, resolvedApiKey, resolvedSecretKey, restClientBuilder,
|
||||
responseErrorHandler);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public FunctionCallbackContext springAiFunctionManager(ApplicationContext context) {
|
||||
FunctionCallbackContext manager = new FunctionCallbackContext();
|
||||
manager.setApplicationContext(context);
|
||||
return manager;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.qianfan;
|
||||
|
||||
import org.springframework.ai.qianfan.QianFanChatOptions;
|
||||
import org.springframework.ai.qianfan.api.QianFanApi;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
||||
|
||||
/**
|
||||
* @author Geng Rong
|
||||
*/
|
||||
@ConfigurationProperties(QianFanChatProperties.CONFIG_PREFIX)
|
||||
public class QianFanChatProperties extends QianFanParentProperties {
|
||||
|
||||
public static final String CONFIG_PREFIX = "spring.ai.qianfan.chat";
|
||||
|
||||
public static final String DEFAULT_CHAT_MODEL = QianFanApi.ChatModel.ERNIE_Speed_8K.value;
|
||||
|
||||
private static final Double DEFAULT_TEMPERATURE = 0.7;
|
||||
|
||||
/**
|
||||
* Enable QianFan chat client.
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
@NestedConfigurationProperty
|
||||
private QianFanChatOptions options = QianFanChatOptions.builder()
|
||||
.withModel(DEFAULT_CHAT_MODEL)
|
||||
.withTemperature(DEFAULT_TEMPERATURE.floatValue())
|
||||
.build();
|
||||
|
||||
public QianFanChatOptions getOptions() {
|
||||
return options;
|
||||
}
|
||||
|
||||
public void setOptions(QianFanChatOptions options) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.qianfan;
|
||||
|
||||
import org.springframework.ai.qianfan.api.ApiUtils;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
@ConfigurationProperties(QianFanConnectionProperties.CONFIG_PREFIX)
|
||||
public class QianFanConnectionProperties extends QianFanParentProperties {
|
||||
|
||||
public static final String CONFIG_PREFIX = "spring.ai.qianfan";
|
||||
|
||||
public static final String DEFAULT_BASE_URL = ApiUtils.DEFAULT_BASE_URL;
|
||||
|
||||
public QianFanConnectionProperties() {
|
||||
super.setBaseUrl(DEFAULT_BASE_URL);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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.qianfan;
|
||||
|
||||
import org.springframework.ai.document.MetadataMode;
|
||||
import org.springframework.ai.qianfan.QianFanEmbeddingOptions;
|
||||
import org.springframework.ai.qianfan.api.QianFanApi;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
||||
|
||||
/**
|
||||
* @author Geng Rong
|
||||
*/
|
||||
@ConfigurationProperties(QianFanEmbeddingProperties.CONFIG_PREFIX)
|
||||
public class QianFanEmbeddingProperties extends QianFanParentProperties {
|
||||
|
||||
public static final String CONFIG_PREFIX = "spring.ai.qianfan.embedding";
|
||||
|
||||
/**
|
||||
* Enable QianFan embedding client.
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
private MetadataMode metadataMode = MetadataMode.EMBED;
|
||||
|
||||
@NestedConfigurationProperty
|
||||
private QianFanEmbeddingOptions options = QianFanEmbeddingOptions.builder()
|
||||
.withModel(QianFanApi.DEFAULT_EMBEDDING_MODEL)
|
||||
.build();
|
||||
|
||||
public QianFanEmbeddingOptions getOptions() {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
public void setOptions(QianFanEmbeddingOptions options) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
public MetadataMode getMetadataMode() {
|
||||
return this.metadataMode;
|
||||
}
|
||||
|
||||
public void setMetadataMode(MetadataMode metadataMode) {
|
||||
this.metadataMode = metadataMode;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.qianfan;
|
||||
|
||||
import org.springframework.ai.qianfan.QianFanImageOptions;
|
||||
import org.springframework.ai.qianfan.api.QianFanImageApi;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
||||
|
||||
/**
|
||||
* QianFan Image autoconfiguration properties.
|
||||
*
|
||||
* @author Geng Rong
|
||||
*/
|
||||
@ConfigurationProperties(QianFanImageProperties.CONFIG_PREFIX)
|
||||
public class QianFanImageProperties extends QianFanParentProperties {
|
||||
|
||||
public static final String CONFIG_PREFIX = "spring.ai.qianfan.image";
|
||||
|
||||
public static final String DEFAULT_IMAGE_MODEL = QianFanImageApi.ImageModel.Stable_Diffusion_XL.getValue();
|
||||
|
||||
/**
|
||||
* Enable QianFan image model.
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* Options for QianFan Image API.
|
||||
*/
|
||||
@NestedConfigurationProperty
|
||||
private QianFanImageOptions options = QianFanImageOptions.builder().withModel(DEFAULT_IMAGE_MODEL).build();
|
||||
|
||||
public QianFanImageOptions getOptions() {
|
||||
return options;
|
||||
}
|
||||
|
||||
public void setOptions(QianFanImageOptions options) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.qianfan;
|
||||
|
||||
/**
|
||||
* @author Geng Rong
|
||||
*/
|
||||
class QianFanParentProperties {
|
||||
|
||||
private String apiKey;
|
||||
|
||||
private String secretKey;
|
||||
|
||||
private String baseUrl;
|
||||
|
||||
public String getApiKey() {
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
public void setApiKey(String apiKey) {
|
||||
this.apiKey = apiKey;
|
||||
}
|
||||
|
||||
public String getSecretKey() {
|
||||
return secretKey;
|
||||
}
|
||||
|
||||
public void setSecretKey(String secretKey) {
|
||||
this.secretKey = secretKey;
|
||||
}
|
||||
|
||||
public String getBaseUrl() {
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
public void setBaseUrl(String baseUrl) {
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -39,3 +39,4 @@ org.springframework.ai.autoconfigure.chat.client.ChatClientAutoConfiguration
|
||||
org.springframework.ai.autoconfigure.vectorstore.typesense.TypesenseVectorStoreAutoConfiguration
|
||||
org.springframework.ai.autoconfigure.vectorstore.opensearch.OpenSearchVectorStoreAutoConfiguration
|
||||
org.springframework.ai.autoconfigure.moonshot.MoonshotAutoConfiguration
|
||||
org.springframework.ai.autoconfigure.qianfan.QianFanAutoConfiguration
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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.qianfan;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariables;
|
||||
import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.embedding.EmbeddingResponse;
|
||||
import org.springframework.ai.image.ImagePrompt;
|
||||
import org.springframework.ai.image.ImageResponse;
|
||||
import org.springframework.ai.qianfan.QianFanChatModel;
|
||||
import org.springframework.ai.qianfan.QianFanEmbeddingModel;
|
||||
import org.springframework.ai.qianfan.QianFanImageModel;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Geng Rong
|
||||
*/
|
||||
@EnabledIfEnvironmentVariables(value = { @EnabledIfEnvironmentVariable(named = "QIANFAN_API_KEY", matches = ".+"),
|
||||
@EnabledIfEnvironmentVariable(named = "QIANFAN_SECRET_KEY", matches = ".+") })
|
||||
public class QianFanAutoConfigurationIT {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(QianFanAutoConfigurationIT.class);
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.qianfan.apiKey=" + System.getenv("QIANFAN_API_KEY"),
|
||||
"spring.ai.qianfan.secretKey=" + System.getenv("QIANFAN_SECRET_KEY"))
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, QianFanAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void generate() {
|
||||
contextRunner.run(context -> {
|
||||
QianFanChatModel client = context.getBean(QianFanChatModel.class);
|
||||
String response = client.call("Hello");
|
||||
assertThat(response).isNotEmpty();
|
||||
logger.info("Response: " + response);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateStreaming() {
|
||||
contextRunner.run(context -> {
|
||||
QianFanChatModel client = context.getBean(QianFanChatModel.class);
|
||||
Flux<ChatResponse> responseFlux = client.stream(new Prompt(new UserMessage("Hello")));
|
||||
String response = Objects.requireNonNull(responseFlux.collectList().block())
|
||||
.stream()
|
||||
.map(chatResponse -> chatResponse.getResults().get(0).getOutput().getContent())
|
||||
.collect(Collectors.joining());
|
||||
assertThat(response).isNotEmpty();
|
||||
logger.info("Response: " + response);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void embedding() {
|
||||
contextRunner.run(context -> {
|
||||
QianFanEmbeddingModel embeddingClient = context.getBean(QianFanEmbeddingModel.class);
|
||||
|
||||
EmbeddingResponse embeddingResponse = embeddingClient
|
||||
.embedForResponse(List.of("Hello World", "World is big and salvation is near"));
|
||||
assertThat(embeddingResponse.getResults()).hasSize(2);
|
||||
assertThat(embeddingResponse.getResults().get(0).getOutput()).isNotEmpty();
|
||||
assertThat(embeddingResponse.getResults().get(0).getIndex()).isEqualTo(0);
|
||||
assertThat(embeddingResponse.getResults().get(1).getOutput()).isNotEmpty();
|
||||
assertThat(embeddingResponse.getResults().get(1).getIndex()).isEqualTo(1);
|
||||
|
||||
assertThat(embeddingClient.dimensions()).isEqualTo(1024);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateImage() {
|
||||
contextRunner.withPropertyValues("spring.ai.qianfan.image.options.size=1024x1024").run(context -> {
|
||||
QianFanImageModel imageModel = context.getBean(QianFanImageModel.class);
|
||||
ImageResponse imageResponse = imageModel.call(new ImagePrompt("forest"));
|
||||
assertThat(imageResponse.getResults()).hasSize(1);
|
||||
assertThat(imageResponse.getResult().getOutput().getUrl()).isNull();
|
||||
assertThat(imageResponse.getResult().getOutput().getB64Json()).isNotEmpty();
|
||||
logger.info("Generated image: " + imageResponse.getResult().getOutput().getB64Json());
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,438 @@
|
||||
/*
|
||||
* 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.qianfan;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration;
|
||||
import org.springframework.ai.qianfan.QianFanChatModel;
|
||||
import org.springframework.ai.qianfan.QianFanEmbeddingModel;
|
||||
import org.springframework.ai.qianfan.QianFanImageModel;
|
||||
import org.springframework.ai.qianfan.api.QianFanApi;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit Tests for
|
||||
* {@link org.springframework.ai.autoconfigure.qianfan.QianFanConnectionProperties},
|
||||
* {@link org.springframework.ai.autoconfigure.qianfan.QianFanChatProperties} and
|
||||
* {@link org.springframework.ai.autoconfigure.qianfan.QianFanEmbeddingProperties}.
|
||||
*
|
||||
* @author Geng Rong
|
||||
*/
|
||||
public class QianFanPropertiesTests {
|
||||
|
||||
@Test
|
||||
public void chatProperties() {
|
||||
|
||||
new ApplicationContextRunner().withPropertyValues(
|
||||
// @formatter:off
|
||||
"spring.ai.qianfan.base-url=TEST_BASE_URL",
|
||||
"spring.ai.qianfan.api-key=abc123",
|
||||
"spring.ai.qianfan.secret-key=def123",
|
||||
"spring.ai.qianfan.chat.options.model=MODEL_XYZ",
|
||||
"spring.ai.qianfan.chat.options.temperature=0.55")
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, QianFanAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var chatProperties = context.getBean(QianFanChatProperties.class);
|
||||
var connectionProperties = context.getBean(QianFanConnectionProperties.class);
|
||||
|
||||
assertThat(connectionProperties.getApiKey()).isEqualTo("abc123");
|
||||
assertThat(connectionProperties.getSecretKey()).isEqualTo("def123");
|
||||
assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL");
|
||||
|
||||
assertThat(chatProperties.getApiKey()).isNull();
|
||||
assertThat(chatProperties.getBaseUrl()).isNull();
|
||||
|
||||
assertThat(chatProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ");
|
||||
assertThat(chatProperties.getOptions().getTemperature()).isEqualTo(0.55f);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chatOverrideConnectionProperties() {
|
||||
|
||||
new ApplicationContextRunner().withPropertyValues(
|
||||
// @formatter:off
|
||||
"spring.ai.qianfan.base-url=TEST_BASE_URL",
|
||||
"spring.ai.qianfan.api-key=abc123",
|
||||
"spring.ai.qianfan.secret-key=def123",
|
||||
"spring.ai.qianfan.chat.base-url=TEST_BASE_URL2",
|
||||
"spring.ai.qianfan.chat.api-key=456",
|
||||
"spring.ai.qianfan.chat.secret-key=def456",
|
||||
"spring.ai.qianfan.chat.options.model=MODEL_XYZ",
|
||||
"spring.ai.qianfan.chat.options.temperature=0.55")
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, QianFanAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var chatProperties = context.getBean(QianFanChatProperties.class);
|
||||
var connectionProperties = context.getBean(QianFanConnectionProperties.class);
|
||||
|
||||
assertThat(connectionProperties.getApiKey()).isEqualTo("abc123");
|
||||
assertThat(connectionProperties.getSecretKey()).isEqualTo("def123");
|
||||
assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL");
|
||||
|
||||
assertThat(chatProperties.getApiKey()).isEqualTo("456");
|
||||
assertThat(chatProperties.getSecretKey()).isEqualTo("def456");
|
||||
assertThat(chatProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL2");
|
||||
|
||||
assertThat(chatProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ");
|
||||
assertThat(chatProperties.getOptions().getTemperature()).isEqualTo(0.55f);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void embeddingProperties() {
|
||||
|
||||
new ApplicationContextRunner().withPropertyValues(
|
||||
// @formatter:off
|
||||
"spring.ai.qianfan.base-url=TEST_BASE_URL",
|
||||
"spring.ai.qianfan.api-key=abc123",
|
||||
"spring.ai.qianfan.secret-key=def123",
|
||||
"spring.ai.qianfan.embedding.options.model=MODEL_XYZ")
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, QianFanAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var embeddingProperties = context.getBean(QianFanEmbeddingProperties.class);
|
||||
var connectionProperties = context.getBean(QianFanConnectionProperties.class);
|
||||
|
||||
assertThat(connectionProperties.getApiKey()).isEqualTo("abc123");
|
||||
assertThat(connectionProperties.getSecretKey()).isEqualTo("def123");
|
||||
assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL");
|
||||
|
||||
assertThat(embeddingProperties.getApiKey()).isNull();
|
||||
assertThat(embeddingProperties.getBaseUrl()).isNull();
|
||||
|
||||
assertThat(embeddingProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void embeddingOverrideConnectionProperties() {
|
||||
|
||||
new ApplicationContextRunner().withPropertyValues(
|
||||
// @formatter:off
|
||||
"spring.ai.qianfan.base-url=TEST_BASE_URL",
|
||||
"spring.ai.qianfan.api-key=abc123",
|
||||
"spring.ai.qianfan.secret-key=def123",
|
||||
"spring.ai.qianfan.embedding.base-url=TEST_BASE_URL2",
|
||||
"spring.ai.qianfan.embedding.api-key=456",
|
||||
"spring.ai.qianfan.embedding.secret-key=def456",
|
||||
"spring.ai.qianfan.embedding.options.model=MODEL_XYZ")
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, QianFanAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var embeddingProperties = context.getBean(QianFanEmbeddingProperties.class);
|
||||
var connectionProperties = context.getBean(QianFanConnectionProperties.class);
|
||||
|
||||
assertThat(connectionProperties.getApiKey()).isEqualTo("abc123");
|
||||
assertThat(connectionProperties.getSecretKey()).isEqualTo("def123");
|
||||
assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL");
|
||||
|
||||
assertThat(embeddingProperties.getApiKey()).isEqualTo("456");
|
||||
assertThat(embeddingProperties.getSecretKey()).isEqualTo("def456");
|
||||
assertThat(embeddingProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL2");
|
||||
|
||||
assertThat(embeddingProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chatOptionsTest() {
|
||||
|
||||
new ApplicationContextRunner().withPropertyValues(
|
||||
// @formatter:off
|
||||
"spring.ai.qianfan.api-key=API_KEY",
|
||||
"spring.ai.qianfan.secret-key=SECRET_KEY",
|
||||
"spring.ai.qianfan.base-url=TEST_BASE_URL",
|
||||
|
||||
"spring.ai.qianfan.chat.options.model=MODEL_XYZ",
|
||||
"spring.ai.qianfan.chat.options.frequencyPenalty=-1.5",
|
||||
"spring.ai.qianfan.chat.options.logitBias.myTokenId=-5",
|
||||
"spring.ai.qianfan.chat.options.maxTokens=123",
|
||||
"spring.ai.qianfan.chat.options.presencePenalty=0",
|
||||
"spring.ai.qianfan.chat.options.responseFormat.type=json",
|
||||
"spring.ai.qianfan.chat.options.stop=boza,koza",
|
||||
"spring.ai.qianfan.chat.options.temperature=0.55",
|
||||
"spring.ai.qianfan.chat.options.topP=0.56"
|
||||
)
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, QianFanAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var chatProperties = context.getBean(QianFanChatProperties.class);
|
||||
var connectionProperties = context.getBean(QianFanConnectionProperties.class);
|
||||
var embeddingProperties = context.getBean(QianFanEmbeddingProperties.class);
|
||||
|
||||
assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL");
|
||||
assertThat(connectionProperties.getApiKey()).isEqualTo("API_KEY");
|
||||
assertThat(connectionProperties.getSecretKey()).isEqualTo("SECRET_KEY");
|
||||
|
||||
assertThat(embeddingProperties.getOptions().getModel()).isEqualTo("bge_large_zh");
|
||||
|
||||
assertThat(chatProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ");
|
||||
assertThat(chatProperties.getOptions().getFrequencyPenalty()).isEqualTo(-1.5f);
|
||||
assertThat(chatProperties.getOptions().getMaxTokens()).isEqualTo(123);
|
||||
assertThat(chatProperties.getOptions().getPresencePenalty()).isEqualTo(0);
|
||||
assertThat(chatProperties.getOptions().getResponseFormat())
|
||||
.isEqualTo(new QianFanApi.ChatCompletionRequest.ResponseFormat("json"));
|
||||
assertThat(chatProperties.getOptions().getStop()).contains("boza", "koza");
|
||||
assertThat(chatProperties.getOptions().getTemperature()).isEqualTo(0.55f);
|
||||
assertThat(chatProperties.getOptions().getTopP()).isEqualTo(0.56f);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void embeddingOptionsTest() {
|
||||
|
||||
new ApplicationContextRunner().withPropertyValues(
|
||||
// @formatter:off
|
||||
"spring.ai.qianfan.api-key=API_KEY",
|
||||
"spring.ai.qianfan.secret-key=SECRET_KEY",
|
||||
"spring.ai.qianfan.base-url=TEST_BASE_URL",
|
||||
|
||||
"spring.ai.qianfan.embedding.options.model=MODEL_XYZ",
|
||||
"spring.ai.qianfan.embedding.options.encodingFormat=MyEncodingFormat"
|
||||
)
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, QianFanAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var connectionProperties = context.getBean(QianFanConnectionProperties.class);
|
||||
var embeddingProperties = context.getBean(QianFanEmbeddingProperties.class);
|
||||
|
||||
assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL");
|
||||
assertThat(connectionProperties.getApiKey()).isEqualTo("API_KEY");
|
||||
assertThat(connectionProperties.getSecretKey()).isEqualTo("SECRET_KEY");
|
||||
|
||||
assertThat(embeddingProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void embeddingActivation() {
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.qianfan.api-key=API_KEY", "spring.ai.qianfan.secret-key=SECRET_KEY",
|
||||
"spring.ai.qianfan.base-url=TEST_BASE_URL", "spring.ai.qianfan.embedding.enabled=false")
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, QianFanAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(QianFanEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(QianFanEmbeddingModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.qianfan.api-key=API_KEY", "spring.ai.qianfan.secret-key=SECRET_KEY",
|
||||
"spring.ai.qianfan.base-url=TEST_BASE_URL")
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, QianFanAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(QianFanEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(QianFanEmbeddingModel.class)).isNotEmpty();
|
||||
});
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.qianfan.api-key=API_KEY", "spring.ai.qianfan.secret-key=SECRET_KEY",
|
||||
"spring.ai.qianfan.base-url=TEST_BASE_URL", "spring.ai.qianfan.embedding.enabled=true")
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, QianFanAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(QianFanEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(QianFanEmbeddingModel.class)).isNotEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void chatActivation() {
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.qianfan.api-key=API_KEY", "spring.ai.qianfan.secret-key=SECRET_KEY",
|
||||
"spring.ai.qianfan.base-url=TEST_BASE_URL", "spring.ai.qianfan.chat.enabled=false")
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, QianFanAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(QianFanChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(QianFanChatModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.qianfan.api-key=API_KEY", "spring.ai.qianfan.secret-key=SECRET_KEY",
|
||||
"spring.ai.qianfan.base-url=TEST_BASE_URL")
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, QianFanAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(QianFanChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(QianFanChatModel.class)).isNotEmpty();
|
||||
});
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.qianfan.api-key=API_KEY", "spring.ai.qianfan.secret-key=SECRET_KEY",
|
||||
"spring.ai.qianfan.base-url=TEST_BASE_URL", "spring.ai.qianfan.chat.enabled=true")
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, QianFanAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(QianFanChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(QianFanChatModel.class)).isNotEmpty();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void imageProperties() {
|
||||
new ApplicationContextRunner().withPropertyValues(
|
||||
// @formatter:off
|
||||
"spring.ai.qianfan.base-url=TEST_BASE_URL",
|
||||
"spring.ai.qianfan.api-key=abc123",
|
||||
"spring.ai.qianfan.secret-key=def123",
|
||||
"spring.ai.qianfan.image.options.model=MODEL_XYZ",
|
||||
"spring.ai.qianfan.image.options.n=3")
|
||||
// @formatter:on
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class,
|
||||
WebClientAutoConfiguration.class, QianFanAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var imageProperties = context.getBean(QianFanImageProperties.class);
|
||||
var connectionProperties = context.getBean(QianFanConnectionProperties.class);
|
||||
|
||||
assertThat(connectionProperties.getApiKey()).isEqualTo("abc123");
|
||||
assertThat(connectionProperties.getSecretKey()).isEqualTo("def123");
|
||||
assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL");
|
||||
|
||||
assertThat(imageProperties.getApiKey()).isNull();
|
||||
assertThat(imageProperties.getBaseUrl()).isNull();
|
||||
|
||||
assertThat(imageProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ");
|
||||
assertThat(imageProperties.getOptions().getN()).isEqualTo(3);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void imageOverrideConnectionProperties() {
|
||||
new ApplicationContextRunner().withPropertyValues(
|
||||
// @formatter:off
|
||||
"spring.ai.qianfan.base-url=TEST_BASE_URL",
|
||||
"spring.ai.qianfan.api-key=abc123",
|
||||
"spring.ai.qianfan.secret-key=def123",
|
||||
"spring.ai.qianfan.image.base-url=TEST_BASE_URL2",
|
||||
"spring.ai.qianfan.image.api-key=456",
|
||||
"spring.ai.qianfan.image.secret-key=def456",
|
||||
"spring.ai.qianfan.image.options.model=MODEL_XYZ",
|
||||
"spring.ai.qianfan.image.options.n=3")
|
||||
// @formatter:on
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class,
|
||||
WebClientAutoConfiguration.class, QianFanAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var imageProperties = context.getBean(QianFanImageProperties.class);
|
||||
var connectionProperties = context.getBean(QianFanConnectionProperties.class);
|
||||
|
||||
assertThat(connectionProperties.getApiKey()).isEqualTo("abc123");
|
||||
assertThat(connectionProperties.getSecretKey()).isEqualTo("def123");
|
||||
assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL");
|
||||
|
||||
assertThat(imageProperties.getApiKey()).isEqualTo("456");
|
||||
assertThat(imageProperties.getSecretKey()).isEqualTo("def456");
|
||||
assertThat(imageProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL2");
|
||||
|
||||
assertThat(imageProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ");
|
||||
assertThat(imageProperties.getOptions().getN()).isEqualTo(3);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void imageOptionsTest() {
|
||||
new ApplicationContextRunner().withPropertyValues(
|
||||
// @formatter:off
|
||||
"spring.ai.qianfan.api-key=API_KEY",
|
||||
"spring.ai.qianfan.secret-key=SECRET_KEY",
|
||||
"spring.ai.qianfan.base-url=TEST_BASE_URL",
|
||||
|
||||
"spring.ai.qianfan.image.options.n=3",
|
||||
"spring.ai.qianfan.image.options.model=MODEL_XYZ",
|
||||
"spring.ai.qianfan.image.options.size=1024x1024",
|
||||
"spring.ai.qianfan.image.options.width=1024",
|
||||
"spring.ai.qianfan.image.options.height=1024",
|
||||
"spring.ai.qianfan.image.options.style=vivid",
|
||||
"spring.ai.qianfan.image.options.user=userXYZ"
|
||||
)
|
||||
// @formatter:on
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class,
|
||||
WebClientAutoConfiguration.class, QianFanAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var imageProperties = context.getBean(QianFanImageProperties.class);
|
||||
var connectionProperties = context.getBean(QianFanConnectionProperties.class);
|
||||
|
||||
assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL");
|
||||
assertThat(connectionProperties.getApiKey()).isEqualTo("API_KEY");
|
||||
assertThat(connectionProperties.getSecretKey()).isEqualTo("SECRET_KEY");
|
||||
|
||||
assertThat(imageProperties.getOptions().getN()).isEqualTo(3);
|
||||
assertThat(imageProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ");
|
||||
assertThat(imageProperties.getOptions().getSize()).isEqualTo("1024x1024");
|
||||
assertThat(imageProperties.getOptions().getWidth()).isEqualTo(1024);
|
||||
assertThat(imageProperties.getOptions().getHeight()).isEqualTo(1024);
|
||||
assertThat(imageProperties.getOptions().getStyle()).isEqualTo("vivid");
|
||||
assertThat(imageProperties.getOptions().getUser()).isEqualTo("userXYZ");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void imageActivation() {
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.qianfan.api-key=API_KEY", "spring.ai.qianfan.secret-key=SECRET_KEY",
|
||||
"spring.ai.qianfan.base-url=TEST_BASE_URL", "spring.ai.qianfan.image.enabled=false")
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class,
|
||||
WebClientAutoConfiguration.class, QianFanAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(QianFanImageProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(QianFanImageModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.qianfan.api-key=API_KEY", "spring.ai.qianfan.secret-key=SECRET_KEY",
|
||||
"spring.ai.qianfan.base-url=TEST_BASE_URL")
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class,
|
||||
WebClientAutoConfiguration.class, QianFanAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(QianFanImageProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(QianFanImageModel.class)).isNotEmpty();
|
||||
});
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.qianfan.api-key=API_KEY", "spring.ai.qianfan.secret-key=SECRET_KEY",
|
||||
"spring.ai.qianfan.base-url=TEST_BASE_URL", "spring.ai.qianfan.image.enabled=true")
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class,
|
||||
WebClientAutoConfiguration.class, QianFanAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(QianFanImageProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(QianFanImageModel.class)).isNotEmpty();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -217,7 +217,7 @@ public class ZhiPuAiPropertiesTests {
|
||||
"spring.ai.zhipuai.chat.options.topP=0.56",
|
||||
|
||||
// "spring.ai.zhipuai.chat.options.toolChoice.functionName=toolChoiceFunctionName",
|
||||
"spring.ai.zhipuai.chat.options.toolChoice=" + ModelOptionsUtils.toJsonString(ZhiPuAiApi.ChatCompletionRequest.ToolChoiceBuilder.FUNCTION("toolChoiceFunctionName")),
|
||||
"spring.ai.zhipuai.chat.options.toolChoice=" + ModelOptionsUtils.toJsonString(ZhiPuAiApi.ChatCompletionRequest.ToolChoiceBuilder.function("toolChoiceFunctionName")),
|
||||
|
||||
"spring.ai.zhipuai.chat.options.tools[0].function.name=myFunction1",
|
||||
"spring.ai.zhipuai.chat.options.tools[0].function.description=function description",
|
||||
|
||||
Reference in New Issue
Block a user