From 409fa1521db8ac77b40d66d70bb1985c830d638c Mon Sep 17 00:00:00 2001 From: John Blum Date: Tue, 14 Nov 2023 15:39:33 -0800 Subject: [PATCH] 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 --- .../openai/client/AzureOpenAiClient.java | 147 ++++++++++-------- .../ai/openai/client/OpenAiClient.java | 97 +++++++----- 2 files changed, 142 insertions(+), 102 deletions(-) diff --git a/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/client/AzureOpenAiClient.java b/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/client/AzureOpenAiClient.java index c97e03db4..6e4f75699 100644 --- a/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/client/AzureOpenAiClient.java +++ b/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/client/AzureOpenAiClient.java @@ -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 messages = prompt.getMessages(); - List 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 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 messages = prompt.getMessages(); + List 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 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); + } + } diff --git a/spring-ai-openai/src/main/java/org/springframework/ai/openai/client/OpenAiClient.java b/spring-ai-openai/src/main/java/org/springframework/ai/openai/client/OpenAiClient.java index d76923d5f..630685e12 100644 --- a/spring-ai-openai/src/main/java/org/springframework/ai/openai/client/OpenAiClient.java +++ b/spring-ai-openai/src/main/java/org/springframework/ai/openai/client/OpenAiClient.java @@ -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 messages = prompt.getMessages(); List 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 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 generations = new ArrayList<>(); - logger.trace("ChatMessages: ", chatCompletionRequest.getMessages()); + logger.trace("ChatMessages: {}", chatCompletionRequest.getMessages()); + List 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 getChatCompletionRequest(Prompt prompt) { - List chatCompletionRequests = new ArrayList<>(); List chatMessages = convertToChatMessages(prompt.getMessages()); + List chatCompletionRequests = new ArrayList<>(); + ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder() .model(this.model) .temperature(this.temperature) .messages(chatMessages) .build(); + chatCompletionRequests.add(chatCompletionRequest); return chatCompletionRequests; } private List convertToChatMessages(List messages) { + List 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()); + } + }