diff --git a/models/spring-ai-azure-openai/src/test/java/org/springframework/ai/azure/openai/AzureOpenAiChatClientTest.java b/models/spring-ai-azure-openai/src/test/java/org/springframework/ai/azure/openai/AzureOpenAiChatClientIT.java similarity index 67% rename from models/spring-ai-azure-openai/src/test/java/org/springframework/ai/azure/openai/AzureOpenAiChatClientTest.java rename to models/spring-ai-azure-openai/src/test/java/org/springframework/ai/azure/openai/AzureOpenAiChatClientIT.java index eec6e9526..4b931b799 100644 --- a/models/spring-ai-azure-openai/src/test/java/org/springframework/ai/azure/openai/AzureOpenAiChatClientTest.java +++ b/models/spring-ai-azure-openai/src/test/java/org/springframework/ai/azure/openai/AzureOpenAiChatClientIT.java @@ -20,13 +20,18 @@ import static com.azure.core.http.policy.HttpLogDetailLevel.BODY_AND_HEADERS; import static org.assertj.core.api.Assertions.assertThat; import java.util.Arrays; +import java.util.List; import java.util.stream.Collectors; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.converter.BeanOutputConverter; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; @@ -35,18 +40,77 @@ import com.azure.ai.openai.OpenAIClientBuilder; import com.azure.ai.openai.OpenAIServiceVersion; import com.azure.core.credential.AzureKeyCredential; import com.azure.core.http.policy.HttpLogOptions; +import org.springframework.core.io.Resource; +import reactor.core.publisher.Flux; /** * @author Soby Chacko */ -@SpringBootTest(classes = AzureOpenAiChatClientTest.TestConfiguration.class) +@SpringBootTest(classes = AzureOpenAiChatClientIT.TestConfiguration.class) @EnabledIfEnvironmentVariable(named = "AZURE_OPENAI_API_KEY", matches = ".+") @EnabledIfEnvironmentVariable(named = "AZURE_OPENAI_ENDPOINT", matches = ".+") -public class AzureOpenAiChatClientTest { +public class AzureOpenAiChatClientIT { @Autowired private ChatClient chatClient; + @Value("classpath:/prompts/system-message.st") + private Resource systemTextResource; + + record ActorsFilms(String actor, List movies) { + } + + @Test + void call() { + + // @formatter:off + ChatResponse response = chatClient.prompt() + .advisors(new SimpleLoggerAdvisor()) + .system(s -> s.text(systemTextResource) + .param("name", "Bob") + .param("voice", "pirate")) + .user("Tell me about 3 famous pirates from the Golden Age of Piracy and what they did") + .call() + .chatResponse(); + // @formatter:on + + assertThat(response.getResults()).hasSize(1); + assertThat(response.getResults().get(0).getOutput().getContent()).contains("Blackbeard"); + } + + @Test + void beanStreamOutputConverterRecords() { + + BeanOutputConverter outputConverter = new BeanOutputConverter<>(ActorsFilms.class); + + // @formatter:off + Flux chatResponse = chatClient + .prompt() + .advisors(new SimpleLoggerAdvisor()) + .user(u -> u + .text("Generate the filmography of 5 movies for Tom Hanks. " + System.lineSeparator() + + "{format}") + .param("format", outputConverter.getFormat())) + .stream() + .chatResponse(); + + List chatResponses = chatResponse.collectList() + .block() + .stream() + .toList(); + + String generationTextFromStream = chatResponses + .stream() + .map(cr -> cr.getResult().getOutput().getContent()) + .collect(Collectors.joining()); + // @formatter:on + + ActorsFilms actorsFilms = outputConverter.convert(generationTextFromStream); + + assertThat(actorsFilms.actor()).isEqualTo("Tom Hanks"); + assertThat(actorsFilms.movies()).hasSize(5); + } + @Test void streamingAndImperativeResponsesContainIdenticalRelevantResults() { String prompt = "Name all states in the USA and their capitals, add a space followed by a hyphen, then another space between the two. " diff --git a/models/spring-ai-azure-openai/src/test/resources/prompts/system-message.st b/models/spring-ai-azure-openai/src/test/resources/prompts/system-message.st new file mode 100644 index 000000000..dc2cf2dcd --- /dev/null +++ b/models/spring-ai-azure-openai/src/test/resources/prompts/system-message.st @@ -0,0 +1,4 @@ +"You are a helpful AI assistant. Your name is {name}. +You are an AI assistant that helps people find information. +Your name is {name} +You should reply to the user's request with your name and also in the style of a {voice}. \ No newline at end of file diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/client/OpenAiChatClientIT.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/client/OpenAiChatClientIT.java index 5dfaa0be5..8abaca4b9 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/client/OpenAiChatClientIT.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/client/OpenAiChatClientIT.java @@ -212,19 +212,25 @@ class OpenAiChatClientIT extends AbstractIT { BeanOutputConverter outputConverter = new BeanOutputConverter<>(ActorsFilms.class); // @formatter:off - Flux chatResponse = ChatClient.create(chatModel) + Flux chatResponse = ChatClient.create(chatModel) .prompt() + .options(OpenAiChatOptions.builder().withStreamUsage(true).build()) .advisors(new SimpleLoggerAdvisor()) .user(u -> u .text("Generate the filmography of 5 movies for Tom Hanks. " + System.lineSeparator() + "{format}") .param("format", outputConverter.getFormat())) .stream() - .content(); + .chatResponse(); - String generationTextFromStream = chatResponse.collectList() + List chatResponses = chatResponse.collectList() .block() .stream() + .toList(); + + String generationTextFromStream = chatResponses + .stream() + .map(cr -> cr.getResult().getOutput().getContent()) .collect(Collectors.joining()); // @formatter:on diff --git a/spring-ai-core/src/test/java/org/springframework/ai/chat/client/ChatClientAdvisorTests.java b/spring-ai-core/src/test/java/org/springframework/ai/chat/client/ChatClientAdvisorTests.java index 8a4ba658d..251dd184e 100644 --- a/spring-ai-core/src/test/java/org/springframework/ai/chat/client/ChatClientAdvisorTests.java +++ b/spring-ai-core/src/test/java/org/springframework/ai/chat/client/ChatClientAdvisorTests.java @@ -34,9 +34,12 @@ import org.springframework.ai.chat.memory.InMemoryChatMemory; import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.MessageType; +import org.springframework.ai.chat.metadata.ChatResponseMetadata; +import org.springframework.ai.chat.metadata.EmptyUsage; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.Generation; +import org.springframework.ai.chat.model.MessageAggregator; import org.springframework.ai.chat.prompt.Prompt; import reactor.core.publisher.Flux; @@ -60,9 +63,19 @@ public class ChatClientAdvisorTests { @Test public void promptChatMemory() { + var builder = ChatResponseMetadata.builder() + .withId("124") + .withUsage(new MessageAggregator.DefaultUsage(1, 2, 3)) + .withModel("gpt4o") + .withKeyValue("created", 0L) + .withKeyValue("system-fingerprint", "john doe"); + ChatResponseMetadata chatResponseMetadata = builder.build(); + when(chatModel.call(promptCaptor.capture())) - .thenReturn(new ChatResponse(List.of(new Generation(new AssistantMessage("Hello John"))))) - .thenReturn(new ChatResponse(List.of(new Generation(new AssistantMessage("Your name is John"))))); + .thenReturn( + new ChatResponse(List.of(new Generation(new AssistantMessage("Hello John"))), chatResponseMetadata)) + .thenReturn(new ChatResponse(List.of(new Generation(new AssistantMessage("Your name is John"))), + chatResponseMetadata)); ChatMemory chatMemory = new InMemoryChatMemory(); @@ -71,8 +84,9 @@ public class ChatClientAdvisorTests { .defaultAdvisors(new PromptChatMemoryAdvisor(chatMemory)) .build(); - var content = chatClient.prompt().user("my name is John").call().content(); + ChatResponse chatResponse = chatClient.prompt().user("my name is John").call().chatResponse(); + String content = chatResponse.getResult().getOutput().getContent(); assertThat(content).isEqualTo("Hello John"); Message systemMessage = promptCaptor.getValue().getInstructions().get(0);