Add Azure OpenAI support

This commit is contained in:
Mark Pollack
2023-08-05 17:15:30 -04:00
parent e042a8e604
commit a105548a27
13 changed files with 402 additions and 8 deletions

View File

@@ -14,8 +14,10 @@
<modules>
<module>spring-ai-core</module>
<module>spring-ai-openai</module>
<module>spring-ai-azure-openai</module>
<module>spring-ai-spring-boot-autoconfigure</module>
<module>spring-ai-spring-boot-starters/spring-ai-starter-openai</module>
<module>spring-ai-spring-boot-starters/spring-ai-starter-azure-openai</module>
<module>spring-ai-docs</module>
</modules>
@@ -63,6 +65,7 @@
<spring-boot.version>3.1.2</spring-boot.version>
<stringtemplate.version>4.0.2</stringtemplate.version>
<open-ai-client.version>0.12.0</open-ai-client.version>
<azure-open-ai-client.version>1.0.0-beta.3</azure-open-ai-client.version>
<!-- documentation dependencies -->
<asciidoctorj-pdf.version>1.6.2</asciidoctorj-pdf.version> <!-- FIXME build failure with version 2.3.9 -->

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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.experimental.ai</groupId>
<artifactId>spring-ai</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>spring-ai-azure-openai</artifactId>
<packaging>jar</packaging>
<name>Spring AI Azure OpenAI</name>
<description>OpenAI support</description>
<url>https://github.com/spring-projects-experimental/spring-ai</url>
<scm>
<url>https://github.com/spring-projects-experimental/spring-ai</url>
<connection>git://github.com/spring-projects-experimental/spring-ai.git</connection>
<developerConnection>git@github.com:spring-projects-experimental/spring-ai.git</developerConnection>
</scm>
<dependencies>
<!-- production dependencies -->
<dependency>
<groupId>org.springframework.experimental.ai</groupId>
<artifactId>spring-ai-core</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-ai-openai</artifactId>
<version>${azure-open-ai-client.version}</version>
</dependency>
<!-- Spring Framework -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<!-- test dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,94 @@
/*
* Copyright 2023 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.azure.openai.llm;
import java.util.List;
import com.azure.ai.openai.OpenAIClient;
import com.azure.ai.openai.models.ChatChoice;
import com.azure.ai.openai.models.ChatCompletions;
import com.azure.ai.openai.models.ChatCompletionsOptions;
import com.azure.ai.openai.models.ChatMessage;
import com.azure.ai.openai.models.ChatRole;
import com.azure.ai.openai.models.Choice;
import com.azure.ai.openai.models.Completions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.core.llm.LLMResult;
import org.springframework.ai.core.llm.LlmClient;
import org.springframework.ai.core.prompt.Prompt;
import org.springframework.util.Assert;
/**
* Implementation of {@link LlmClient} backed by an OpenAiService
*/
public class AzureOpenAiClient implements LlmClient {
private static final Logger logger = LoggerFactory.getLogger(AzureOpenAiClient.class);
private final OpenAIClient msoftOpenAiClient;
private Double temperature = 0.5;
private String model = "gpt-35-turbo";
public AzureOpenAiClient(OpenAIClient msoftOpenAiClient) {
Assert.notNull(msoftOpenAiClient, "OpenAiClient must not be null");
this.msoftOpenAiClient = msoftOpenAiClient;
}
@Override
public String generate(String text) {
ChatMessage chatMessage = new ChatMessage(ChatRole.USER, text);
ChatCompletionsOptions options = new ChatCompletionsOptions(List.of(chatMessage));
options.setTemperature(this.getTemperature());
options.setModel(this.getModel());
ChatCompletions chatCompletions = this.msoftOpenAiClient.getChatCompletions(this.getModel(), options);
StringBuilder sb = new StringBuilder();
for (ChatChoice choice : chatCompletions.getChoices()) {
if (choice.getMessage() != null && choice.getMessage().getContent() != null) {
sb.append(choice.getMessage().getContent());
}
}
return sb.toString();
}
@Override
public LLMResult generate(Prompt... prompts) {
throw new RuntimeException("Method LLMResult generate(Prompt... prompts) not implemented.");
}
public Double getTemperature() {
return temperature;
}
public void setTemperature(Double temperature) {
this.temperature = temperature;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
}

View File

@@ -25,6 +25,7 @@ import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
@SuppressWarnings("unchecked")
class PromptTests {
@Test

View File

@@ -39,8 +39,9 @@ public class OpenAiClient implements LlmClient {
private static final Logger logger = LoggerFactory.getLogger(OpenAiClient.class);
// TODO how to set default options for the entire client
// TODO expose request options into Prompt API via PromptOptions
private Double temperature = 0.3;
private Double temperature = 0.5;
private String model = "gpt-3.5-turbo";
@@ -51,6 +52,22 @@ public class OpenAiClient implements LlmClient {
this.openAiService = openAiService;
}
public Double getTemperature() {
return temperature;
}
public void setTemperature(Double temperature) {
this.temperature = temperature;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
@Override
public String generate(String text) {
ChatCompletionRequest chatCompletionRequest = getChatCompletionRequest(text);

View File

@@ -34,6 +34,13 @@
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.experimental.ai</groupId>
<artifactId>spring-ai-azure-openai</artifactId>
<version>${project.parent.version}</version>
<optional>true</optional>
</dependency>
<!-- test dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2023 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.azure.openai;
import com.azure.ai.openai.OpenAIClient;
import com.azure.ai.openai.OpenAIClientBuilder;
import com.azure.core.credential.AzureKeyCredential;
import org.springframework.ai.azure.openai.llm.AzureOpenAiClient;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.util.StringUtils;
@AutoConfiguration
@ConditionalOnClass(OpenAIClientBuilder.class)
@EnableConfigurationProperties(AzureOpenAiProperties.class)
public class AzureOpenAiAutoConfiguration {
private final AzureOpenAiProperties azureOpenAiProperties;
public AzureOpenAiAutoConfiguration(AzureOpenAiProperties azureOpenAiProperties) {
this.azureOpenAiProperties = azureOpenAiProperties;
}
@Bean
@ConditionalOnMissingBean
public OpenAIClient msoftSdkOpenAiClient(AzureOpenAiProperties azureOpenAiProperties) {
if (!StringUtils.hasText(azureOpenAiProperties.getApiKey())) {
throw new IllegalArgumentException("You must provide an API key with the property name "
+ AzureOpenAiProperties.CONFIG_PREFIX + ".api-key");
}
OpenAIClient msoftSdkOpenAiClient = new OpenAIClientBuilder().endpoint(this.azureOpenAiProperties.getEndpoint())
.credential(new AzureKeyCredential(this.azureOpenAiProperties.getApiKey()))
.buildClient();
return msoftSdkOpenAiClient;
}
@Bean
public AzureOpenAiClient azureOpenAiClient(OpenAIClient msoftSdkOpenAiClient) {
AzureOpenAiClient azureOpenAiClient = new AzureOpenAiClient(msoftSdkOpenAiClient);
azureOpenAiClient.setTemperature(this.azureOpenAiProperties.getTemperature());
azureOpenAiClient.setModel(this.azureOpenAiProperties.getModel());
return azureOpenAiClient;
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2023 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.azure.openai;
import java.net.URI;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(AzureOpenAiProperties.CONFIG_PREFIX)
public class AzureOpenAiProperties {
// TODO look into Spring Cloud Azure project for credentials as well as
// e.g. com.azure.core.credential.AzureKeyCredential
public static final String CONFIG_PREFIX = "spring.ai.azure.openai";
private String apiKey;
private String endpoint;
private Double temperature = 0.5;
private String model = "gpt-35-turbo";
public String getEndpoint() {
return endpoint;
}
/**
* Sets the service endpoint that will be connected to by clients.
* @param endpoint The URL of the service endpoint
*/
public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}
public Double getTemperature() {
return temperature;
}
public void setTemperature(Double temperature) {
this.temperature = temperature;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public void setApiKey(String apiKey) {
this.apiKey = apiKey;
}
public String getApiKey() {
return apiKey;
}
}

View File

@@ -25,6 +25,8 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean;
import org.springframework.util.StringUtils;
import static org.springframework.ai.autoconfigure.openai.OpenAiProperties.CONFIG_PREFIX;
@AutoConfiguration
@ConditionalOnClass(OpenAiService.class)
@EnableConfigurationProperties(OpenAiProperties.class)
@@ -40,10 +42,13 @@ public class OpenAiAutoConfiguration {
public OpenAiClient openAiClient(OpenAiProperties openAiProperties) {
if (!StringUtils.hasText(openAiProperties.getApiKey())) {
throw new IllegalArgumentException(
"You must provide an API key with the property name spring.ai.openai.api-key");
"You must provide an API key with the property name " + CONFIG_PREFIX + ".api-key");
}
OpenAiService openAiService = new OpenAiService(openAiProperties.getApiKey());
return new OpenAiClient(openAiService);
OpenAiService theoOpenAiService = new OpenAiService(openAiProperties.getApiKey());
OpenAiClient openAiClient = new OpenAiClient(theoOpenAiService);
openAiClient.setTemperature(openAiProperties.getTemperature());
openAiClient.setModel(openAiProperties.getModel());
return openAiClient;
}
}

View File

@@ -18,11 +18,19 @@ package org.springframework.ai.autoconfigure.openai;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "spring.ai.openai")
import static org.springframework.ai.autoconfigure.openai.OpenAiProperties.CONFIG_PREFIX;
@ConfigurationProperties(CONFIG_PREFIX)
public class OpenAiProperties {
public static final String CONFIG_PREFIX = "spring.ai.openai";
private String apiKey;
private Double temperature = 0.5;
private String model = "gpt-3.5-turbo";
public String getApiKey() {
return apiKey;
}
@@ -31,4 +39,20 @@ public class OpenAiProperties {
this.apiKey = apiKey;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public Double getTemperature() {
return temperature;
}
public void setTemperature(Double temperature) {
this.temperature = temperature;
}
}

View File

@@ -1 +1,2 @@
org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration
org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration
org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiAutoConfiguration

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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.experimental.ai</groupId>
<artifactId>spring-ai</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>spring-ai-azure-openai-spring-boot-starter</artifactId>
<packaging>jar</packaging>
<name>Spring AI Starter - Azure OpenAI</name>
<description>Spring AI Azure OpenAI Auto Configuration</description>
<url>https://github.com/spring-projects-experimental/spring-ai</url>
<scm>
<url>https://github.com/spring-projects-experimental/spring-ai</url>
<connection>git://github.com/spring-projects-experimental/spring-ai.git</connection>
<developerConnection>git@github.com:spring-projects-experimental/spring-ai.git</developerConnection>
</scm>
<dependencies>
<!-- production dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.experimental.ai</groupId>
<artifactId>spring-ai-spring-boot-autoconfigure</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.experimental.ai</groupId>
<artifactId>spring-ai-azure-openai</artifactId>
<version>${project.parent.version}</version>
</dependency>
<!-- test dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -8,8 +8,8 @@
</parent>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<packaging>jar</packaging>
<name>Spring AI Starters</name>
<description>Spring AI Auto Configuration</description>
<name>Spring AI Starter - OpenAI</name>
<description>Spring AI Open AI Auto Configuration</description>
<url>https://github.com/spring-projects-experimental/spring-ai</url>
<scm>