From af0303f65a166ce9e0f6e816ebfc58254805de8d Mon Sep 17 00:00:00 2001 From: Berjan Jonker Date: Thu, 17 Apr 2025 22:45:31 +0200 Subject: [PATCH] Make responseMessage in AzureOpenAiChatModel.buildGeneration null-safe Signed-off-by: Berjan Jonker --- .../ai/azure/openai/AzureOpenAiChatModel.java | 24 +++++++++++-------- .../azure/openai/AzureOpenAiChatModelIT.java | 23 +++++++++++++++++- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatModel.java b/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatModel.java index bed72e982..2b431371c 100644 --- a/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatModel.java +++ b/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatModel.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-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. @@ -112,6 +112,7 @@ import org.springframework.util.CollectionUtils; * @author Jihoon Kim * @author Ilayaperumal Gopinathan * @author Alexandros Pappas + * @author Berjan Jonker * @see ChatModel * @see com.azure.ai.openai.OpenAIClient * @since 1.0.0 @@ -462,16 +463,19 @@ public class AzureOpenAiChatModel implements ChatModel { var responseMessage = Optional.ofNullable(choice.getMessage()).orElse(choice.getDelta()); - List toolCalls = responseMessage.getToolCalls() == null ? List.of() - : responseMessage.getToolCalls().stream().map(toolCall -> { - final var tc1 = (ChatCompletionsFunctionToolCall) toolCall; - String id = tc1.getId(); - String name = tc1.getFunction().getName(); - String arguments = tc1.getFunction().getArguments(); - return new AssistantMessage.ToolCall(id, "function", name, arguments); - }).toList(); + List toolCalls = List.of(); + if (responseMessage != null && responseMessage.getToolCalls() != null) { + toolCalls = responseMessage.getToolCalls().stream().map(toolCall -> { + final var tc1 = (ChatCompletionsFunctionToolCall) toolCall; + String id = tc1.getId(); + String name = tc1.getFunction().getName(); + String arguments = tc1.getFunction().getArguments(); + return new AssistantMessage.ToolCall(id, "function", name, arguments); + }).toList(); + } - var assistantMessage = new AssistantMessage(responseMessage.getContent(), metadata, toolCalls); + var content = responseMessage == null ? "" : responseMessage.getContent(); + var assistantMessage = new AssistantMessage(content, metadata, toolCalls); var generationMetadata = generateChoiceMetadata(choice); return new Generation(assistantMessage, generationMetadata); diff --git a/models/spring-ai-azure-openai/src/test/java/org/springframework/ai/azure/openai/AzureOpenAiChatModelIT.java b/models/spring-ai-azure-openai/src/test/java/org/springframework/ai/azure/openai/AzureOpenAiChatModelIT.java index e6e17c26f..83713cd4a 100644 --- a/models/spring-ai-azure-openai/src/test/java/org/springframework/ai/azure/openai/AzureOpenAiChatModelIT.java +++ b/models/spring-ai-azure-openai/src/test/java/org/springframework/ai/azure/openai/AzureOpenAiChatModelIT.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * Copyright 2023-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. @@ -22,6 +22,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import com.azure.ai.openai.OpenAIClientBuilder; @@ -106,6 +107,26 @@ class AzureOpenAiChatModelIT { assertThat(response.getResult().getOutput().getText()).containsAnyOf("Blackbeard"); } + @Test + void testStreaming() { + String prompt = """ + Provide a list of planets in our solar system + """; + + final var counter = new AtomicInteger(); + String content = this.chatModel.stream(prompt) + .doOnEach(listSignal -> counter.getAndIncrement()) + .collectList() + .block() + .stream() + .collect(Collectors.joining()); + logger.info("Response: {}", content); + + assertThat(counter.get()).isGreaterThan(8).as("More than 8 chuncks because there are 8 planets"); + + assertThat(content).contains("Earth", "Mars", "Jupiter"); + } + @Test void listOutputConverter() { DefaultConversionService conversionService = new DefaultConversionService();