Refine OpenAI and Microsoft Azure AiClient implementations.

* Apply consistent treatment to the organization of the source code.
* Fix Logger statements missing message placeholder formatting.
* Simplify logic using Java 17 syntax where applicable.

Closes #101
This commit is contained in:
John Blum
2023-11-14 15:39:33 -08:00
committed by Mark Pollack
parent 0aa8eb651e
commit 409fa1521d
2 changed files with 142 additions and 102 deletions

View File

@@ -17,7 +17,11 @@
package org.springframework.ai.azure.openai.client;
import com.azure.ai.openai.OpenAIClient;
import com.azure.ai.openai.models.*;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.client.AiClient;
@@ -31,83 +35,102 @@ import java.util.ArrayList;
import java.util.List;
/**
* Implementation of {@link AiClient} backed by an OpenAiService
* {@link AiClient} implementation for {@literal Microsoft Azure AI} backed by
* {@link OpenAIClient}.
*
* @author Mark Pollack
* @author Ueibin Kim
* @author John Blum
* @see org.springframework.ai.client.AiClient
* @see com.azure.ai.openai.OpenAIClient
*/
public class AzureOpenAiClient implements AiClient {
private static final Logger logger = LoggerFactory.getLogger(AzureOpenAiClient.class);
private final OpenAIClient msoftOpenAiClient;
private Double temperature = 0.7;
private String model = "gpt-35-turbo";
public AzureOpenAiClient(OpenAIClient msoftOpenAiClient) {
Assert.notNull(msoftOpenAiClient, "com.azure.ai.openai.OpenAIClient must not be null");
this.msoftOpenAiClient = msoftOpenAiClient;
}
private final Logger logger = LoggerFactory.getLogger(getClass());
@Override
public String generate(String text) {
ChatMessage azureChatMessage = new ChatMessage(ChatRole.USER, text);
private final OpenAIClient msoftOpenAiClient;
ChatCompletionsOptions options = new ChatCompletionsOptions(List.of(azureChatMessage));
options.setTemperature(this.getTemperature());
options.setModel(this.getModel());
logger.trace("Azure Chat Message: ", azureChatMessage);
ChatCompletions chatCompletions = this.msoftOpenAiClient.getChatCompletions(this.getModel(), options);
logger.trace("Azure ChatCompletions: ", chatCompletions);
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 AiResponse generate(Prompt prompt) {
List<Message> messages = prompt.getMessages();
List<ChatMessage> azureMessages = new ArrayList<>();
for (Message message : messages) {
String messageType = message.getMessageTypeValue();
ChatRole chatRole = ChatRole.fromString(messageType);
azureMessages.add(new ChatMessage(chatRole, message.getContent()));
}
ChatCompletionsOptions options = new ChatCompletionsOptions(azureMessages);
options.setTemperature(this.getTemperature());
options.setModel(this.getModel());
logger.trace("Azure ChatCompletionsOptions: ", options);
ChatCompletions chatCompletions = this.msoftOpenAiClient.getChatCompletions(this.getModel(), options);
logger.trace("Azure ChatCompletions: ", chatCompletions);
List<Generation> generations = new ArrayList<>();
for (ChatChoice choice : chatCompletions.getChoices()) {
ChatMessage choiceMessage = choice.getMessage();
// TODO investigate mapping of additional metadata/runtime info to the
// general model.
Generation generation = new Generation(choiceMessage.getContent());
generations.add(generation);
}
return new AiResponse(generations);
}
public Double getTemperature() {
return temperature;
}
public void setTemperature(Double temperature) {
this.temperature = temperature;
public AzureOpenAiClient(OpenAIClient microsoftOpenAiClient) {
Assert.notNull(microsoftOpenAiClient, "com.azure.ai.openai.OpenAIClient must not be null");
this.msoftOpenAiClient = microsoftOpenAiClient;
}
public String getModel() {
return model;
return this.model;
}
public void setModel(String model) {
this.model = model;
}
public Double getTemperature() {
return this.temperature;
}
public void setTemperature(Double temperature) {
this.temperature = temperature;
}
@Override
public String generate(String text) {
ChatMessage azureChatMessage = new ChatMessage(ChatRole.USER, text);
ChatCompletionsOptions options = new ChatCompletionsOptions(List.of(azureChatMessage));
options.setTemperature(this.getTemperature());
options.setModel(this.getModel());
logger.trace("Azure Chat Message: {}", azureChatMessage);
ChatCompletions chatCompletions = this.msoftOpenAiClient.getChatCompletions(this.getModel(), options);
logger.trace("Azure ChatCompletions: {}", chatCompletions);
StringBuilder stringBuilder = new StringBuilder();
for (ChatChoice choice : chatCompletions.getChoices()) {
ChatMessage message = choice.getMessage();
if (message != null && message.getContent() != null) {
stringBuilder.append(message.getContent());
}
}
return stringBuilder.toString();
}
@Override
public AiResponse generate(Prompt prompt) {
List<Message> messages = prompt.getMessages();
List<ChatMessage> azureMessages = new ArrayList<>();
for (Message message : messages) {
String messageType = message.getMessageTypeValue();
ChatRole chatRole = ChatRole.fromString(messageType);
azureMessages.add(new ChatMessage(chatRole, message.getContent()));
}
ChatCompletionsOptions options = new ChatCompletionsOptions(azureMessages);
options.setTemperature(this.getTemperature());
options.setModel(this.getModel());
logger.trace("Azure ChatCompletionsOptions: {}", options);
ChatCompletions chatCompletions = this.msoftOpenAiClient.getChatCompletions(this.getModel(), options);
logger.trace("Azure ChatCompletions: {}", chatCompletions);
List<Generation> generations = new ArrayList<>();
for (ChatChoice choice : chatCompletions.getChoices()) {
ChatMessage choiceMessage = choice.getMessage();
// TODO investigate mapping of additional metadata/runtime info to the general
// model.
Generation generation = new Generation(choiceMessage.getContent());
generations.add(generation);
}
return new AiResponse(generations);
}
}

View File

@@ -35,18 +35,25 @@ import java.util.List;
import java.util.Map;
/**
* Implementation of {@link AiClient} backed by an {@link OpenAiService}.
* {@link AiClient} implementation for {@literal OpenAI} backed by {@link OpenAiService}.
*
* @author Mark Pollack
* @author Christian Tzolov
* @author Ueibin Kim
* @author John Blum
* @see org.springframework.ai.client.AiClient
* @see com.theokanning.openai.service.OpenAiService
*/
public class OpenAiClient implements AiClient {
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.7;
private String model = "gpt-3.5-turbo";
private final Logger logger = LoggerFactory.getLogger(getClass());
private final OpenAiService openAiService;
public OpenAiClient(OpenAiService openAiService) {
@@ -54,22 +61,22 @@ public class OpenAiClient implements AiClient {
this.openAiService = openAiService;
}
public Double getTemperature() {
return temperature;
}
public void setTemperature(Double temperature) {
this.temperature = temperature;
}
public String getModel() {
return model;
return this.model;
}
public void setModel(String model) {
this.model = model;
}
public Double getTemperature() {
return this.temperature;
}
public void setTemperature(Double temperature) {
this.temperature = temperature;
}
@Override
public String generate(String text) {
ChatCompletionRequest chatCompletionRequest = getChatCompletionRequest(text);
@@ -78,6 +85,7 @@ public class OpenAiClient implements AiClient {
@Override
public AiResponse generate(Prompt prompt) {
List<Message> messages = prompt.getMessages();
List<ChatMessage> theoMessages = messages.stream()
@@ -89,86 +97,95 @@ public class OpenAiClient implements AiClient {
.temperature(this.temperature)
.messages(theoMessages)
.build();
return getAiResponse(chatCompletionRequest);
}
private ChatCompletionRequest getChatCompletionRequest(String text) {
List<ChatMessage> chatMessages = List.of(new ChatMessage("user", text));
logger.trace("ChatMessages: ", chatMessages);
logger.trace("ChatMessages: {}", chatMessages);
ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
.model(this.model)
.temperature(this.temperature)
.messages(List.of(new ChatMessage("user", text)))
.build();
logger.trace("ChatCompletionRequest: ", chatCompletionRequest);
logger.trace("ChatCompletionRequest: {}", chatCompletionRequest);
return chatCompletionRequest;
}
private AiResponse getAiResponse(ChatCompletionRequest chatCompletionRequest) {
List<Generation> generations = new ArrayList<>();
logger.trace("ChatMessages: ", chatCompletionRequest.getMessages());
logger.trace("ChatMessages: {}", chatCompletionRequest.getMessages());
List<ChatCompletionChoice> chatCompletionChoices = this.openAiService
.createChatCompletion(chatCompletionRequest)
.getChoices();
logger.trace("ChatCompletionChoice: ", chatCompletionChoices);
logger.trace("ChatCompletionChoice: {}", chatCompletionChoices);
for (ChatCompletionChoice chatCompletionChoice : chatCompletionChoices) {
ChatMessage chatMessage = chatCompletionChoice.getMessage();
// TODO investigate mapping of additional metadata/runtime info to the
// general model.
// TODO investigate mapping of additional metadata/runtime info to the general
// model.
Generation generation = new Generation(chatMessage.getContent(), Map.of("role", chatMessage.getRole()));
generations.add(generation);
}
return new AiResponse(generations);
}
private String getResponse(ChatCompletionRequest chatCompletionRequest) {
StringBuilder builder = new StringBuilder();
this.openAiService.createChatCompletion(chatCompletionRequest).getChoices().forEach(choice -> {
builder.append(choice.getMessage().getContent());
});
this.openAiService.createChatCompletion(chatCompletionRequest)
.getChoices()
.forEach(choice -> builder.append(choice.getMessage().getContent()));
String response = builder.toString();
return response;
}
private List<ChatCompletionRequest> getChatCompletionRequest(Prompt prompt) {
List<ChatCompletionRequest> chatCompletionRequests = new ArrayList<>();
List<ChatMessage> chatMessages = convertToChatMessages(prompt.getMessages());
List<ChatCompletionRequest> chatCompletionRequests = new ArrayList<>();
ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
.model(this.model)
.temperature(this.temperature)
.messages(chatMessages)
.build();
chatCompletionRequests.add(chatCompletionRequest);
return chatCompletionRequests;
}
private List<ChatMessage> convertToChatMessages(List<Message> messages) {
List<ChatMessage> chatMessages = new ArrayList<>();
for (Message promptMessage : messages) {
switch (promptMessage.getMessageType()) {
case USER:
chatMessages.add(new ChatMessage(MessageType.USER.getValue(), promptMessage.getContent()));
break;
case ASSISTANT:
// TODO - valid?
chatMessages.add(new ChatMessage(MessageType.ASSISTANT.getValue(), promptMessage.getContent()));
break;
case SYSTEM:
chatMessages.add(new ChatMessage(MessageType.SYSTEM.getValue(), promptMessage.getContent()));
break;
case FUNCTION:
logger.error(
"Can not send a Spring AI Function MessageType to the ChatGPT API, use 'system', 'user' or 'ai' message types.");
break;
default:
logger.error("Unknown Spring AI Chat MessageType. Use 'system', 'human' or 'ai' message types.");
break;
MessageType promptMessageType = promptMessage.getMessageType();
switch (promptMessageType) {
case USER, ASSISTANT, SYSTEM -> chatMessages.add(newChatMessage(promptMessage));
case FUNCTION -> logger.error(
"Cannot send a Spring AI Function MessageType to the ChatGPT API; use 'system', 'user' or 'ai' message types.");
default ->
logger.error("Unknown Spring AI Chat MessageType; use 'system', 'human' or 'ai' message types.");
}
}
return chatMessages;
}
private ChatMessage newChatMessage(Message message) {
return new ChatMessage(message.getMessageType().getValue(), message.getContent());
}
}