From 574138a26a65be784c8f0bd08c389d96eab3b881 Mon Sep 17 00:00:00 2001 From: Mark Pollack Date: Mon, 27 May 2024 15:36:30 -0400 Subject: [PATCH] Remove chat.service package and related classes * This functionality is now provided using the advisors feature in ChatClient --- .../AnthropicChatResponseMetadata.java | 4 +- .../AzureOpenAiChatResponseMetadata.java | 4 +- .../metadata/OllamaChatResponseMetadata.java | 4 +- .../ai/openai/OpenAiChatOptions.java | 6 + .../ai/openai/api/OpenAiApi.java | 4 +- .../metadata/OpenAiChatResponseMetadata.java | 4 +- .../metadata/OpenAiImageResponseMetadata.java | 3 +- .../OpenAiAudioSpeechResponseMetadata.java | 4 +- ...nAiAudioTranscriptionResponseMetadata.java | 4 +- .../chat/OpenAiPaymentTransactionIT.java | 2 +- .../ChatMemoryLongTermSystemPromptIT.java | 133 --------- .../ChatMemoryShortTermMessageListIT.java | 109 -------- .../ChatMemoryShortTermSystemPromptIT.java | 110 -------- .../LongShortTermChatMemoryWithRagIT.java | 256 ------------------ ...OpenAiPromptTransformingChatServiceIT.java | 175 ------------ .../VertexAiChatResponseMetadata.java | 4 +- .../client/advisor/QuestionAnswerAdvisor.java | 30 ++ .../memory/ChatMemoryChatServiceListener.java | 65 ----- .../ai/chat/memory/ChatMemoryRetriever.java | 118 -------- .../LastMaxTokenSizeContentTransformer.java | 125 --------- .../memory/MessageChatMemoryAugmentor.java | 76 ------ .../SystemPromptChatMemoryAugmentor.java | 115 -------- ...torStoreChatMemoryChatServiceListener.java | 101 ------- .../VectorStoreChatMemoryRetriever.java | 91 ------- .../chat/metadata/ChatResponseMetadata.java | 9 +- .../AbstractPromptTransformer.java | 39 --- .../transformer/ChatServiceContext.java | 200 -------------- .../chat/prompt/transformer/PromptChange.java | 31 --- .../prompt/transformer/PromptTransformer.java | 40 --- .../transformer/QuestionContextAugmentor.java | 135 --------- .../transformer/TransformerContentType.java | 37 --- .../transformer/VectorStoreRetriever.java | 112 -------- .../ai/chat/service/ChatService.java | 39 --- .../ai/chat/service/ChatServiceListener.java | 36 --- .../ai/chat/service/ChatServiceResponse.java | 77 ------ .../PromptTransformingChatService.java | 143 ---------- .../ai/chat/service/StreamingChatService.java | 39 --- .../service/StreamingChatServiceResponse.java | 73 ----- ...treamingPromptTransformingChatService.java | 144 ---------- .../ai/evaluation/EvaluationRequest.java | 27 +- .../ai/evaluation/RelevancyEvaluator.java | 48 +--- .../ai/image/ImageResponseMetadata.java | 10 +- .../ai/model/ResponseMetadata.java | 4 +- .../ai/chat/memory/ChatMemoryTests.java | 147 ---------- .../ai/evaluation/BaseMemoryTest.java | 110 -------- 45 files changed, 110 insertions(+), 2937 deletions(-) delete mode 100644 models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/service/ChatMemoryLongTermSystemPromptIT.java delete mode 100644 models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/service/ChatMemoryShortTermMessageListIT.java delete mode 100644 models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/service/ChatMemoryShortTermSystemPromptIT.java delete mode 100644 models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/service/LongShortTermChatMemoryWithRagIT.java delete mode 100644 models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/service/OpenAiPromptTransformingChatServiceIT.java delete mode 100644 spring-ai-core/src/main/java/org/springframework/ai/chat/memory/ChatMemoryChatServiceListener.java delete mode 100644 spring-ai-core/src/main/java/org/springframework/ai/chat/memory/ChatMemoryRetriever.java delete mode 100644 spring-ai-core/src/main/java/org/springframework/ai/chat/memory/LastMaxTokenSizeContentTransformer.java delete mode 100644 spring-ai-core/src/main/java/org/springframework/ai/chat/memory/MessageChatMemoryAugmentor.java delete mode 100644 spring-ai-core/src/main/java/org/springframework/ai/chat/memory/SystemPromptChatMemoryAugmentor.java delete mode 100644 spring-ai-core/src/main/java/org/springframework/ai/chat/memory/VectorStoreChatMemoryChatServiceListener.java delete mode 100644 spring-ai-core/src/main/java/org/springframework/ai/chat/memory/VectorStoreChatMemoryRetriever.java delete mode 100644 spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/transformer/AbstractPromptTransformer.java delete mode 100644 spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/transformer/ChatServiceContext.java delete mode 100644 spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/transformer/PromptChange.java delete mode 100644 spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/transformer/PromptTransformer.java delete mode 100644 spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/transformer/QuestionContextAugmentor.java delete mode 100644 spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/transformer/TransformerContentType.java delete mode 100644 spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/transformer/VectorStoreRetriever.java delete mode 100644 spring-ai-core/src/main/java/org/springframework/ai/chat/service/ChatService.java delete mode 100644 spring-ai-core/src/main/java/org/springframework/ai/chat/service/ChatServiceListener.java delete mode 100644 spring-ai-core/src/main/java/org/springframework/ai/chat/service/ChatServiceResponse.java delete mode 100644 spring-ai-core/src/main/java/org/springframework/ai/chat/service/PromptTransformingChatService.java delete mode 100644 spring-ai-core/src/main/java/org/springframework/ai/chat/service/StreamingChatService.java delete mode 100644 spring-ai-core/src/main/java/org/springframework/ai/chat/service/StreamingChatServiceResponse.java delete mode 100644 spring-ai-core/src/main/java/org/springframework/ai/chat/service/StreamingPromptTransformingChatService.java delete mode 100644 spring-ai-core/src/test/java/org/springframework/ai/chat/memory/ChatMemoryTests.java delete mode 100644 spring-ai-test/src/main/java/org/springframework/ai/evaluation/BaseMemoryTest.java diff --git a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/metadata/AnthropicChatResponseMetadata.java b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/metadata/AnthropicChatResponseMetadata.java index 1d7a269a9..513c7dcd5 100644 --- a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/metadata/AnthropicChatResponseMetadata.java +++ b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/metadata/AnthropicChatResponseMetadata.java @@ -24,6 +24,8 @@ import org.springframework.ai.chat.metadata.Usage; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import java.util.HashMap; + /** * {@link ChatResponseMetadata} implementation for {@literal AnthropicApi}. * @@ -33,7 +35,7 @@ import org.springframework.util.Assert; * @see Usage * @since 1.0.0 */ -public class AnthropicChatResponseMetadata implements ChatResponseMetadata { +public class AnthropicChatResponseMetadata extends HashMap implements ChatResponseMetadata { protected static final String AI_METADATA_STRING = "{ @type: %1$s, id: %2$s, usage: %3$s, rateLimit: %4$s }"; diff --git a/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/metadata/AzureOpenAiChatResponseMetadata.java b/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/metadata/AzureOpenAiChatResponseMetadata.java index db9be1452..fdf8a327e 100644 --- a/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/metadata/AzureOpenAiChatResponseMetadata.java +++ b/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/metadata/AzureOpenAiChatResponseMetadata.java @@ -22,6 +22,8 @@ import org.springframework.ai.chat.metadata.PromptMetadata; import org.springframework.ai.chat.metadata.Usage; import org.springframework.util.Assert; +import java.util.HashMap; + /** * {@link ChatResponseMetadata} implementation for * {@literal Microsoft Azure OpenAI Service}. @@ -30,7 +32,7 @@ import org.springframework.util.Assert; * @see ChatResponseMetadata * @since 0.7.1 */ -public class AzureOpenAiChatResponseMetadata implements ChatResponseMetadata { +public class AzureOpenAiChatResponseMetadata extends HashMap implements ChatResponseMetadata { protected static final String AI_METADATA_STRING = "{ @type: %1$s, id: %2$s, usage: %3$s, rateLimit: %4$s }"; diff --git a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/metadata/OllamaChatResponseMetadata.java b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/metadata/OllamaChatResponseMetadata.java index 906cf1075..6f1d213fa 100644 --- a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/metadata/OllamaChatResponseMetadata.java +++ b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/metadata/OllamaChatResponseMetadata.java @@ -20,13 +20,15 @@ import org.springframework.ai.chat.metadata.Usage; import org.springframework.ai.ollama.api.OllamaApi; import org.springframework.util.Assert; +import java.util.HashMap; + /** * {@link ChatResponseMetadata} implementation for {@literal Ollama} * * @see ChatResponseMetadata * @author Fu Cheng */ -public class OllamaChatResponseMetadata implements ChatResponseMetadata { +public class OllamaChatResponseMetadata extends HashMap implements ChatResponseMetadata { protected static final String AI_METADATA_STRING = "{ @type: %1$s, usage: %2$s, rateLimit: %3$s }"; diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java index 09f86baac..d980c5c78 100644 --- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java +++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java @@ -29,6 +29,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.ai.model.function.FunctionCallback; import org.springframework.ai.model.function.FunctionCallingOptions; +import org.springframework.ai.openai.api.OpenAiApi; import org.springframework.ai.openai.api.OpenAiApi.ChatCompletionRequest.ResponseFormat; import org.springframework.ai.openai.api.OpenAiApi.FunctionTool; import org.springframework.boot.context.properties.NestedConfigurationProperty; @@ -178,6 +179,11 @@ public class OpenAiChatOptions implements FunctionCallingOptions, ChatOptions { return this; } + public Builder withModel(OpenAiApi.ChatModel openAiChatModel) { + this.options.model = openAiChatModel.getModelName(); + return this; + } + public Builder withFrequencyPenalty(Float frequencyPenalty) { this.options.frequencyPenalty = frequencyPenalty; return this; diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java index f3946b390..34713cb3f 100644 --- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java +++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java @@ -128,12 +128,12 @@ public class OpenAiApi { * Vision requests can now use JSON mode and function calling. * Currently points to gpt-4-turbo-2024-04-09. */ - GPT_4_0_TURBO("gpt-4-turbo"), + GPT_4_TURBO("gpt-4-turbo"), /** * GPT-4 Turbo with Vision model. Vision requests can now use JSON mode and function calling */ - GPT_4_0_TURBO_2204_04_09("gpt-4-turbo-2024-04-09"), + GPT_4_TURBO_2204_04_09("gpt-4-turbo-2024-04-09"), /** * (New) GPT-4 Turbo - latest GPT-4 model intended to reduce cases diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/metadata/OpenAiChatResponseMetadata.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/metadata/OpenAiChatResponseMetadata.java index 67f57438e..f926496c1 100644 --- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/metadata/OpenAiChatResponseMetadata.java +++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/metadata/OpenAiChatResponseMetadata.java @@ -24,6 +24,8 @@ import org.springframework.ai.openai.api.OpenAiApi; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import java.util.HashMap; + /** * {@link ChatResponseMetadata} implementation for {@literal OpenAI}. * @@ -33,7 +35,7 @@ import org.springframework.util.Assert; * @see Usage * @since 0.7.0 */ -public class OpenAiChatResponseMetadata implements ChatResponseMetadata { +public class OpenAiChatResponseMetadata extends HashMap implements ChatResponseMetadata { protected static final String AI_METADATA_STRING = "{ @type: %1$s, id: %2$s, usage: %3$s, rateLimit: %4$s }"; diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/metadata/OpenAiImageResponseMetadata.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/metadata/OpenAiImageResponseMetadata.java index fc2436497..ec9519c82 100644 --- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/metadata/OpenAiImageResponseMetadata.java +++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/metadata/OpenAiImageResponseMetadata.java @@ -19,9 +19,10 @@ import org.springframework.ai.image.ImageResponseMetadata; import org.springframework.ai.openai.api.OpenAiImageApi; import org.springframework.util.Assert; +import java.util.HashMap; import java.util.Objects; -public class OpenAiImageResponseMetadata implements ImageResponseMetadata { +public class OpenAiImageResponseMetadata extends HashMap implements ImageResponseMetadata { private final Long created; diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/metadata/audio/OpenAiAudioSpeechResponseMetadata.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/metadata/audio/OpenAiAudioSpeechResponseMetadata.java index 1c6260ffd..4f38f3c0d 100644 --- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/metadata/audio/OpenAiAudioSpeechResponseMetadata.java +++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/metadata/audio/OpenAiAudioSpeechResponseMetadata.java @@ -23,13 +23,15 @@ import org.springframework.ai.openai.api.OpenAiAudioApi; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import java.util.HashMap; + /** * Audio speech metadata implementation for {@literal OpenAI}. * * @author Ahmed Yousri * @see RateLimit */ -public class OpenAiAudioSpeechResponseMetadata implements ResponseMetadata { +public class OpenAiAudioSpeechResponseMetadata extends HashMap implements ResponseMetadata { protected static final String AI_METADATA_STRING = "{ @type: %1$s, requestsLimit: %2$s }"; diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/metadata/audio/OpenAiAudioTranscriptionResponseMetadata.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/metadata/audio/OpenAiAudioTranscriptionResponseMetadata.java index a58af71b6..5add8aa5b 100644 --- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/metadata/audio/OpenAiAudioTranscriptionResponseMetadata.java +++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/metadata/audio/OpenAiAudioTranscriptionResponseMetadata.java @@ -23,6 +23,8 @@ import org.springframework.ai.openai.metadata.OpenAiRateLimit; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import java.util.HashMap; + /** * Audio transcription metadata implementation for {@literal OpenAI}. * @@ -30,7 +32,7 @@ import org.springframework.util.Assert; * @since 0.8.1 * @see RateLimit */ -public class OpenAiAudioTranscriptionResponseMetadata implements ResponseMetadata { +public class OpenAiAudioTranscriptionResponseMetadata extends HashMap implements ResponseMetadata { protected static final String AI_METADATA_STRING = "{ @type: %1$s, rateLimit: %4$s }"; diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiPaymentTransactionIT.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiPaymentTransactionIT.java index c204f96e6..468524963 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiPaymentTransactionIT.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiPaymentTransactionIT.java @@ -198,7 +198,7 @@ public class OpenAiPaymentTransactionIT { public OpenAiChatModel openAiClient(OpenAiApi openAiApi, FunctionCallbackContext functionCallbackContext) { return new OpenAiChatModel(openAiApi, OpenAiChatOptions.builder() - .withModel(ChatModel.GPT_4_0_TURBO.getModelName()) + .withModel(ChatModel.GPT_4_TURBO.getModelName()) .withTemperature(0.1f) .build(), functionCallbackContext, RetryUtils.DEFAULT_RETRY_TEMPLATE); diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/service/ChatMemoryLongTermSystemPromptIT.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/service/ChatMemoryLongTermSystemPromptIT.java deleted file mode 100644 index 430802040..000000000 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/service/ChatMemoryLongTermSystemPromptIT.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2024 - 2024 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.openai.chat.service; - -import java.util.List; - -import io.qdrant.client.QdrantClient; -import io.qdrant.client.QdrantGrpcClient; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.springframework.ai.chat.service.ChatService; -import org.springframework.ai.chat.service.StreamingChatService; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; -import org.testcontainers.qdrant.QdrantContainer; - -import org.springframework.ai.chat.service.PromptTransformingChatService; -import org.springframework.ai.chat.service.StreamingPromptTransformingChatService; -import org.springframework.ai.chat.memory.VectorStoreChatMemoryChatServiceListener; -import org.springframework.ai.chat.memory.VectorStoreChatMemoryRetriever; -import org.springframework.ai.chat.memory.LastMaxTokenSizeContentTransformer; -import org.springframework.ai.chat.memory.SystemPromptChatMemoryAugmentor; -import org.springframework.ai.embedding.EmbeddingModel; -import org.springframework.ai.evaluation.BaseMemoryTest; -import org.springframework.ai.evaluation.RelevancyEvaluator; -import org.springframework.ai.openai.OpenAiChatModel; -import org.springframework.ai.openai.OpenAiEmbeddingModel; -import org.springframework.ai.openai.api.OpenAiApi; -import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator; -import org.springframework.ai.tokenizer.TokenCountEstimator; -import org.springframework.ai.vectorstore.VectorStore; -import org.springframework.ai.vectorstore.qdrant.QdrantVectorStore; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Bean; - -@Disabled("ChatService Memory implementation are deprecated. No need to test them.") -@Testcontainers -@SpringBootTest(classes = ChatMemoryLongTermSystemPromptIT.Config.class) -@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".+") -public class ChatMemoryLongTermSystemPromptIT extends BaseMemoryTest { - - private static final String COLLECTION_NAME = "test_collection"; - - private static final int QDRANT_GRPC_PORT = 6334; - - @Container - static QdrantContainer qdrantContainer = new QdrantContainer("qdrant/qdrant:v1.9.2"); - - @Autowired - public ChatMemoryLongTermSystemPromptIT(RelevancyEvaluator relevancyEvaluator, ChatService chatService, - StreamingChatService streamingChatService) { - super(relevancyEvaluator, chatService, streamingChatService); - } - - @SpringBootConfiguration - static class Config { - - @Bean - public OpenAiApi chatCompletionApi() { - return new OpenAiApi(System.getenv("OPENAI_API_KEY")); - } - - @Bean - public OpenAiChatModel openAiClient(OpenAiApi openAiApi) { - return new OpenAiChatModel(openAiApi); - } - - @Bean - public EmbeddingModel embeddingModel(OpenAiApi openAiApi) { - return new OpenAiEmbeddingModel(openAiApi); - } - - @Bean - public VectorStore qdrantVectorStore(EmbeddingModel embeddingModel) { - QdrantClient qdrantClient = new QdrantClient(QdrantGrpcClient - .newBuilder(qdrantContainer.getHost(), qdrantContainer.getMappedPort(QDRANT_GRPC_PORT), false) - .build()); - return new QdrantVectorStore(qdrantClient, COLLECTION_NAME, embeddingModel, true); - } - - @Bean - public TokenCountEstimator tokenCountEstimator() { - return new JTokkitTokenCountEstimator(); - } - - @Bean - public ChatService memoryChatService(OpenAiChatModel chatModel, VectorStore vectorStore, - TokenCountEstimator tokenCountEstimator) { - - return PromptTransformingChatService.builder(chatModel) - .withRetrievers(List.of(new VectorStoreChatMemoryRetriever(vectorStore, 10))) - .withContentPostProcessors(List.of(new LastMaxTokenSizeContentTransformer(tokenCountEstimator, 1000))) - .withAugmentors(List.of(new SystemPromptChatMemoryAugmentor())) - .withChatServiceListeners(List.of(new VectorStoreChatMemoryChatServiceListener(vectorStore))) - .build(); - } - - @Bean - public StreamingChatService memoryStreamingChatService(OpenAiChatModel streamingChatModel, - VectorStore vectorStore, TokenCountEstimator tokenCountEstimator) { - - return StreamingPromptTransformingChatService.builder(streamingChatModel) - .withRetrievers(List.of(new VectorStoreChatMemoryRetriever(vectorStore, 10))) - .withDocumentPostProcessors(List.of(new LastMaxTokenSizeContentTransformer(tokenCountEstimator, 1000))) - .withAugmentors(List.of(new SystemPromptChatMemoryAugmentor())) - .withChatServiceListeners(List.of(new VectorStoreChatMemoryChatServiceListener(vectorStore))) - .build(); - } - - @Bean - public RelevancyEvaluator relevancyEvaluator(OpenAiChatModel chatModel) { - return new RelevancyEvaluator(chatModel); - } - - } - -} diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/service/ChatMemoryShortTermMessageListIT.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/service/ChatMemoryShortTermMessageListIT.java deleted file mode 100644 index b31b8a940..000000000 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/service/ChatMemoryShortTermMessageListIT.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2024 - 2024 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.openai.chat.service; - -import java.util.List; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; - -import org.springframework.ai.chat.service.ChatService; -import org.springframework.ai.chat.service.PromptTransformingChatService; -import org.springframework.ai.chat.service.StreamingPromptTransformingChatService; -import org.springframework.ai.chat.service.StreamingChatService; -import org.springframework.ai.chat.memory.ChatMemory; -import org.springframework.ai.chat.memory.ChatMemoryChatServiceListener; -import org.springframework.ai.chat.memory.ChatMemoryRetriever; -import org.springframework.ai.chat.memory.InMemoryChatMemory; -import org.springframework.ai.chat.memory.LastMaxTokenSizeContentTransformer; -import org.springframework.ai.chat.memory.MessageChatMemoryAugmentor; -import org.springframework.ai.evaluation.BaseMemoryTest; -import org.springframework.ai.evaluation.RelevancyEvaluator; -import org.springframework.ai.openai.OpenAiChatModel; -import org.springframework.ai.openai.api.OpenAiApi; -import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator; -import org.springframework.ai.tokenizer.TokenCountEstimator; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Bean; - -@Disabled("ChatService Memory implementation are deprecated. No need to test them.") -@SpringBootTest(classes = ChatMemoryShortTermMessageListIT.Config.class) -@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".+") -public class ChatMemoryShortTermMessageListIT extends BaseMemoryTest { - - @Autowired - public ChatMemoryShortTermMessageListIT(RelevancyEvaluator relevancyEvaluator, ChatService chatService, - StreamingChatService streamingChatService) { - super(relevancyEvaluator, chatService, streamingChatService); - } - - @SpringBootConfiguration - static class Config { - - @Bean - public OpenAiApi chatCompletionApi() { - return new OpenAiApi(System.getenv("OPENAI_API_KEY")); - } - - @Bean - public OpenAiChatModel openAiClient(OpenAiApi openAiApi) { - return new OpenAiChatModel(openAiApi); - } - - @Bean - public ChatMemory chatHistory() { - return new InMemoryChatMemory(); - } - - @Bean - public TokenCountEstimator tokenCountEstimator() { - return new JTokkitTokenCountEstimator(); - } - - @Bean - public ChatService memoryChatService(OpenAiChatModel chatModel, ChatMemory chatHistory, - TokenCountEstimator tokenCountEstimator) { - - return PromptTransformingChatService.builder(chatModel) - .withRetrievers(List.of(new ChatMemoryRetriever(chatHistory))) - .withContentPostProcessors(List.of(new LastMaxTokenSizeContentTransformer(tokenCountEstimator, 1000))) - .withAugmentors(List.of(new MessageChatMemoryAugmentor())) - .withChatServiceListeners(List.of(new ChatMemoryChatServiceListener(chatHistory))) - .build(); - } - - @Bean - public StreamingChatService memoryStreamingChatService(OpenAiChatModel streamingChatModel, - ChatMemory chatHistory, TokenCountEstimator tokenCountEstimator) { - - return StreamingPromptTransformingChatService.builder(streamingChatModel) - .withRetrievers(List.of(new ChatMemoryRetriever(chatHistory))) - .withDocumentPostProcessors(List.of(new LastMaxTokenSizeContentTransformer(tokenCountEstimator, 1000))) - .withAugmentors(List.of(new MessageChatMemoryAugmentor())) - .withChatServiceListeners(List.of(new ChatMemoryChatServiceListener(chatHistory))) - .build(); - } - - @Bean - public RelevancyEvaluator relevancyEvaluator(OpenAiChatModel chatModel) { - return new RelevancyEvaluator(chatModel); - } - - } - -} diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/service/ChatMemoryShortTermSystemPromptIT.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/service/ChatMemoryShortTermSystemPromptIT.java deleted file mode 100644 index f3c854e5c..000000000 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/service/ChatMemoryShortTermSystemPromptIT.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2024 - 2024 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.openai.chat.service; - -import java.util.List; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; - -import org.springframework.ai.chat.service.ChatService; -import org.springframework.ai.chat.service.PromptTransformingChatService; -import org.springframework.ai.chat.service.StreamingPromptTransformingChatService; -import org.springframework.ai.chat.service.StreamingChatService; -import org.springframework.ai.chat.memory.ChatMemory; -import org.springframework.ai.chat.memory.ChatMemoryChatServiceListener; -import org.springframework.ai.chat.memory.ChatMemoryRetriever; -import org.springframework.ai.chat.memory.InMemoryChatMemory; -import org.springframework.ai.chat.memory.LastMaxTokenSizeContentTransformer; -import org.springframework.ai.chat.memory.SystemPromptChatMemoryAugmentor; -import org.springframework.ai.evaluation.BaseMemoryTest; -import org.springframework.ai.evaluation.RelevancyEvaluator; -import org.springframework.ai.openai.OpenAiChatModel; -import org.springframework.ai.openai.api.OpenAiApi; -import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator; -import org.springframework.ai.tokenizer.TokenCountEstimator; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Bean; - -@Disabled("ChatService Memory implementation are deprecated. No need to test them.") -@SpringBootTest(classes = ChatMemoryShortTermSystemPromptIT.Config.class) -@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".+") -public class ChatMemoryShortTermSystemPromptIT extends BaseMemoryTest { - - @Autowired - public ChatMemoryShortTermSystemPromptIT(RelevancyEvaluator relevancyEvaluator, ChatService chatService, - StreamingChatService streamingChatService) { - super(relevancyEvaluator, chatService, streamingChatService); - } - - @SpringBootConfiguration - static class Config { - - @Bean - public OpenAiApi chatCompletionApi() { - return new OpenAiApi(System.getenv("OPENAI_API_KEY")); - } - - @Bean - public OpenAiChatModel openAiClient(OpenAiApi openAiApi) { - return new OpenAiChatModel(openAiApi); - } - - @Bean - public ChatMemory chatHistory() { - return new InMemoryChatMemory(); - } - - @Bean - public TokenCountEstimator tokenCountEstimator() { - return new JTokkitTokenCountEstimator(); - } - - @Bean - public ChatService memoryChatService(OpenAiChatModel chatModel, ChatMemory chatHistory, - TokenCountEstimator tokenCountEstimator) { - - return PromptTransformingChatService.builder(chatModel) - .withRetrievers(List.of(new ChatMemoryRetriever(chatHistory))) - .withContentPostProcessors(List.of(new LastMaxTokenSizeContentTransformer(tokenCountEstimator, 1000))) - .withAugmentors(List.of(new SystemPromptChatMemoryAugmentor())) - .withChatServiceListeners(List.of(new ChatMemoryChatServiceListener(chatHistory))) - .build(); - } - - @Bean - public StreamingChatService memoryStreamingChatService(OpenAiChatModel streamingChatModel, - ChatMemory chatHistory, TokenCountEstimator tokenCountEstimator) { - - return StreamingPromptTransformingChatService.builder(streamingChatModel) - .withRetrievers(List.of(new ChatMemoryRetriever(chatHistory))) - .withDocumentPostProcessors(List.of(new LastMaxTokenSizeContentTransformer(tokenCountEstimator, 1000))) - .withAugmentors(List.of(new SystemPromptChatMemoryAugmentor())) - .withChatServiceListeners(List.of(new ChatMemoryChatServiceListener(chatHistory))) - .build(); - } - - @Bean - public RelevancyEvaluator relevancyEvaluator(OpenAiChatModel chatModel) { - return new RelevancyEvaluator(chatModel); - } - - } - -} diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/service/LongShortTermChatMemoryWithRagIT.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/service/LongShortTermChatMemoryWithRagIT.java deleted file mode 100644 index e929932c1..000000000 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/service/LongShortTermChatMemoryWithRagIT.java +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright 2024 - 2024 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.openai.chat.service; - -import java.util.List; -import java.util.Map; -import java.util.Set; - -import io.qdrant.client.QdrantClient; -import io.qdrant.client.QdrantGrpcClient; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.ai.chat.prompt.transformer.ChatServiceContext; -import org.springframework.ai.chat.service.ChatService; -import org.springframework.ai.chat.service.PromptTransformingChatService; -import org.springframework.ai.openai.OpenAiChatOptions; -import org.springframework.ai.openai.OpenAiChatModel; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; -import org.testcontainers.qdrant.QdrantContainer; - -import org.springframework.ai.chat.memory.ChatMemory; -import org.springframework.ai.chat.memory.ChatMemoryChatServiceListener; -import org.springframework.ai.chat.memory.ChatMemoryRetriever; -import org.springframework.ai.chat.memory.InMemoryChatMemory; -import org.springframework.ai.chat.memory.LastMaxTokenSizeContentTransformer; -import org.springframework.ai.chat.memory.SystemPromptChatMemoryAugmentor; -import org.springframework.ai.chat.memory.VectorStoreChatMemoryChatServiceListener; -import org.springframework.ai.chat.memory.VectorStoreChatMemoryRetriever; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.chat.prompt.transformer.QuestionContextAugmentor; -import org.springframework.ai.chat.prompt.transformer.TransformerContentType; -import org.springframework.ai.chat.prompt.transformer.VectorStoreRetriever; -import org.springframework.ai.document.Document; -import org.springframework.ai.document.DocumentTransformer; -import org.springframework.ai.embedding.EmbeddingModel; -import org.springframework.ai.evaluation.EvaluationResponse; -import org.springframework.ai.evaluation.RelevancyEvaluator; -import org.springframework.ai.openai.OpenAiEmbeddingModel; -import org.springframework.ai.openai.api.OpenAiApi; -import org.springframework.ai.reader.JsonReader; -import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator; -import org.springframework.ai.tokenizer.TokenCountEstimator; -import org.springframework.ai.transformer.splitter.TokenTextSplitter; -import org.springframework.ai.vectorstore.SearchRequest; -import org.springframework.ai.vectorstore.VectorStore; -import org.springframework.ai.vectorstore.qdrant.QdrantVectorStore; -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; -import org.springframework.core.io.Resource; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.springframework.ai.openai.api.OpenAiApi.ChatModel.GPT_4_TURBO_PREVIEW; - -@Disabled("ChatService Memory implementation are deprecated. No need to test them.") -@Testcontainers -@SpringBootTest(classes = LongShortTermChatMemoryWithRagIT.Config.class) -@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".+") -public class LongShortTermChatMemoryWithRagIT { - - protected final Logger logger = LoggerFactory.getLogger(getClass()); - - private static final String COLLECTION_NAME = "test_collection"; - - private static final int QDRANT_GRPC_PORT = 6334; - - @Container - static QdrantContainer qdrantContainer = new QdrantContainer("qdrant/qdrant:v1.9.2"); - - @Autowired - ChatService chatService; - - @Autowired - RelevancyEvaluator relevancyEvaluator; - - @Autowired - VectorStore vectorStore; - - @Value("classpath:/data/acme/bikes.json") - private Resource bikesResource; - - void loadData() { - - var metadataEnricher = new DocumentTransformer() { - - @Override - public List apply(List documents) { - documents.forEach(d -> { - Map metadata = d.getMetadata(); - metadata.put(TransformerContentType.EXTERNAL_KNOWLEDGE, "true"); - }); - - return documents; - } - - }; - - JsonReader jsonReader = new JsonReader(bikesResource, "name", "price", "shortDescription", "description"); - var textSplitter = new TokenTextSplitter(); - vectorStore.accept(metadataEnricher.apply(textSplitter.apply(jsonReader.get()))); - } - - // @Autowired - // StreamingChatService streamingChatService; - - @Test - void memoryChatService() { - - loadData(); - - var prompt = new Prompt(new UserMessage("My name is Christian and I like mountain bikes.")); - ChatServiceContext chatServiceContext = new ChatServiceContext(prompt); - - var chatServiceResponse1 = this.chatService.call(chatServiceContext); - - logger.info("Response1: " + chatServiceResponse1.getChatResponse().getResult().getOutput().getContent()); - - var chatServiceResponse2 = this.chatService.call(new ChatServiceContext( - new Prompt(new String("What is my name and what bike model would you suggest for me?")))); - logger.info("Response2: " + chatServiceResponse2.getChatResponse().getResult().getOutput().getContent()); - - // logger.info(chatServiceResponse2.getPromptContext().getContents().toString()); - assertThat(chatServiceResponse2.getChatResponse().getResult().getOutput().getContent()).contains("Christian"); - - EvaluationResponse evaluationResponse = this.relevancyEvaluator - .evaluate(chatServiceResponse2.toEvaluationRequest()); - - assertTrue(evaluationResponse.isPass(), "Response is not relevant to the question"); - - } - - @SpringBootConfiguration - static class Config { - - @Bean - public ChatMemory chatHistory() { - return new InMemoryChatMemory(); - } - - @Bean - public OpenAiApi chatCompletionApi() { - return new OpenAiApi(System.getenv("OPENAI_API_KEY")); - } - - @Bean - public OpenAiChatModel openAiClient(OpenAiApi openAiApi) { - return new OpenAiChatModel(openAiApi); - } - - @Bean - public OpenAiEmbeddingModel embeddingModel(OpenAiApi openAiApi) { - return new OpenAiEmbeddingModel(openAiApi); - } - - @Bean - public VectorStore qdrantVectorStore(EmbeddingModel embeddingModel) { - QdrantClient qdrantClient = new QdrantClient(QdrantGrpcClient - .newBuilder(qdrantContainer.getHost(), qdrantContainer.getMappedPort(QDRANT_GRPC_PORT), false) - .build()); - return new QdrantVectorStore(qdrantClient, COLLECTION_NAME, embeddingModel, true); - } - - @Bean - public TokenCountEstimator tokenCountEstimator() { - return new JTokkitTokenCountEstimator(); - } - - @Bean - public ChatService memoryChatService(OpenAiChatModel chatModel, VectorStore vectorStore, - TokenCountEstimator tokenCountEstimator, ChatMemory chatHistory) { - - return PromptTransformingChatService.builder(chatModel) - .withRetrievers(List.of(new VectorStoreRetriever(vectorStore, SearchRequest.defaults()), - ChatMemoryRetriever.builder() - .withChatHistory(chatHistory) - .withMetadata(Map.of(TransformerContentType.SHORT_TERM_MEMORY, "")) - .build(), - new VectorStoreChatMemoryRetriever(vectorStore, 10, - Map.of(TransformerContentType.LONG_TERM_MEMORY, "")))) - - .withContentPostProcessors(List.of( - new LastMaxTokenSizeContentTransformer(tokenCountEstimator, 1000, - Set.of(TransformerContentType.SHORT_TERM_MEMORY)), - new LastMaxTokenSizeContentTransformer(tokenCountEstimator, 1000, - Set.of(TransformerContentType.LONG_TERM_MEMORY)), - new LastMaxTokenSizeContentTransformer(tokenCountEstimator, 2000, - Set.of(TransformerContentType.EXTERNAL_KNOWLEDGE)))) - .withAugmentors(List.of(new QuestionContextAugmentor(), - new SystemPromptChatMemoryAugmentor( - """ - Use the long term conversation history from the LONG TERM HISTORY section to provide accurate answers. - - LONG TERM HISTORY: - {history} - """, - Set.of(TransformerContentType.LONG_TERM_MEMORY)), - new SystemPromptChatMemoryAugmentor(Set.of(TransformerContentType.SHORT_TERM_MEMORY)))) - - .withChatServiceListeners(List.of(new ChatMemoryChatServiceListener(chatHistory), - new VectorStoreChatMemoryChatServiceListener(vectorStore, - Map.of(TransformerContentType.LONG_TERM_MEMORY, "")))) - .build(); - } - - // @Bean - // public StreamingChatService memoryStreamingChatAgent(OpenAiChatModel - // streamingChatModel, - // VectorStore vectorStore, TokenCountEstimator tokenCountEstimator, ChatHistory - // chatHistory) { - - // return StreamingPromptTransformingChatService.builder(streamingChatModel) - // .withRetrievers(List.of(new ChatHistoryRetriever(chatHistory), new - // DocumentChatHistoryRetriever(vectorStore, 10))) - // .withDocumentPostProcessors(List.of(new - // LastMaxTokenSizeContentTransformer(tokenCountEstimator, 1000))) - // .withAugmentors(List.of(new TextChatHistoryAugmenter())) - // .withChatAgentListeners(List.of(new ChatHistoryAgentListener(chatHistory), new - // DocumentChatHistoryAgentListener(vectorStore))) - // .build(); - // } - - @Bean - public RelevancyEvaluator relevancyEvaluator(OpenAiChatModel chatModel) { - // Use GPT 4 as a better model for determining relevancy. gpt 3.5 makes basic - // mistakes - OpenAiChatOptions openAiChatOptions = OpenAiChatOptions.builder() - .withModel(GPT_4_TURBO_PREVIEW.getValue()) - .build(); - return new RelevancyEvaluator(chatModel, openAiChatOptions); - } - - } - -} diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/service/OpenAiPromptTransformingChatServiceIT.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/service/OpenAiPromptTransformingChatServiceIT.java deleted file mode 100644 index 636aa06fa..000000000 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/service/OpenAiPromptTransformingChatServiceIT.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright 2024 - 2024 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.openai.chat.service; - -import io.qdrant.client.QdrantClient; -import io.qdrant.client.QdrantGrpcClient; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatModel; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.chat.prompt.transformer.ChatServiceContext; -import org.springframework.ai.chat.prompt.transformer.QuestionContextAugmentor; -import org.springframework.ai.chat.prompt.transformer.TransformerContentType; -import org.springframework.ai.chat.prompt.transformer.VectorStoreRetriever; -import org.springframework.ai.chat.service.ChatService; -import org.springframework.ai.chat.service.PromptTransformingChatService; -import org.springframework.ai.document.Document; -import org.springframework.ai.embedding.EmbeddingModel; -import org.springframework.ai.evaluation.EvaluationResponse; -import org.springframework.ai.evaluation.RelevancyEvaluator; -import org.springframework.ai.openai.OpenAiChatModel; -import org.springframework.ai.openai.OpenAiChatOptions; -import org.springframework.ai.openai.OpenAiEmbeddingModel; -import org.springframework.ai.openai.api.OpenAiApi; -import org.springframework.ai.reader.JsonReader; -import org.springframework.ai.transformer.splitter.TokenTextSplitter; -import org.springframework.ai.vectorstore.SearchRequest; -import org.springframework.ai.vectorstore.VectorStore; -import org.springframework.ai.vectorstore.qdrant.QdrantVectorStore; -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; -import org.springframework.core.io.Resource; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; -import org.testcontainers.qdrant.QdrantContainer; - -import java.util.List; -import java.util.function.Supplier; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.ai.openai.api.OpenAiApi.ChatModel.GPT_4_0_TURBO; - -@Testcontainers -@SpringBootTest(classes = OpenAiPromptTransformingChatServiceIT.Config.class) -@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".+") -public class OpenAiPromptTransformingChatServiceIT { - - private static final String COLLECTION_NAME = "test_collection"; - - private static final int QDRANT_GRPC_PORT = 6334; - - @Container - static QdrantContainer qdrantContainer = new QdrantContainer("qdrant/qdrant:v1.9.2"); - - private final ChatModel chatModel; - - private final VectorStore vectorStore; - - @Value("classpath:/data/acme/bikes.json") - private Resource bikesResource; - - private ChatService chatService; - - @Autowired - public OpenAiPromptTransformingChatServiceIT(ChatModel chatModel, ChatService chatService, - VectorStore vectorStore) { - this.chatModel = chatModel; - this.chatService = chatService; - this.vectorStore = vectorStore; - } - - @Test - void simpleChat() { - loadData(); - - String question = "What reliable road bike?"; - var prompt = new Prompt(new UserMessage(question)); - var chatServiceResponse = this.chatService.call(new ChatServiceContext(prompt)); - String answer = chatServiceResponse.getChatResponse().getResult().getOutput().getContent(); - assertThat(answer).containsAnyOf("Celerity", "Velocity") - .as("Answer does not include 'Celerity' or 'Velocity'. Answer = %s", answer); - - // Use GPT 4 Turbo as a better model for determining relevancy. - OpenAiChatOptions openAiChatOptions = OpenAiChatOptions.builder().withModel(GPT_4_0_TURBO.getValue()).build(); - var relevancyEvaluator = new RelevancyEvaluator(this.chatModel, openAiChatOptions); - - EvaluationResponse evaluationResponse = relevancyEvaluator.evaluate(chatServiceResponse.toEvaluationRequest()); - assertThat(evaluationResponse.isPass()) - .as("Response is not relevant to the question. Question = %s; Answer = %s", question, answer); - - } - - void loadData() { - JsonReader jsonReader = new JsonReader(bikesResource, "name", "price", "shortDescription", "description"); - var textSplitter = new TokenTextSplitter(); - List splitDocuments = textSplitter.split(jsonReader.read()); - - for (Document splitDocument : splitDocuments) { - splitDocument.getMetadata().put(TransformerContentType.EXTERNAL_KNOWLEDGE, "true"); - } - - vectorStore.write(splitDocuments); - } - - void loadData2() { - JsonReader jsonReader = null; - TokenTextSplitter tokenTextSplitter = null; - VectorStore vectorStore = null; - - List documents = jsonReader.read(); - List splitDocuments = tokenTextSplitter.split(documents); - vectorStore.write(splitDocuments); - - // Now in java.util.Function style. - - Supplier> docs = jsonReader::read; - - } - - @SpringBootConfiguration - static class Config { - - @Bean - public OpenAiApi chatCompletionApi() { - return new OpenAiApi(System.getenv("OPENAI_API_KEY")); - } - - @Bean - public ChatModel openAiClient(OpenAiApi openAiApi) { - return new OpenAiChatModel(openAiApi); - } - - @Bean - public EmbeddingModel embeddingModel(OpenAiApi openAiApi) { - return new OpenAiEmbeddingModel(openAiApi); - } - - @Bean - public VectorStore qdrantVectorStore(EmbeddingModel embeddingModel) { - QdrantClient qdrantClient = new QdrantClient(QdrantGrpcClient - .newBuilder(qdrantContainer.getHost(), qdrantContainer.getMappedPort(QDRANT_GRPC_PORT), false) - .build()); - return new QdrantVectorStore(qdrantClient, COLLECTION_NAME, embeddingModel, true); - } - - @Bean - public ChatService chatService(ChatModel chatModel, VectorStore vectorStore) { - return PromptTransformingChatService.builder(chatModel) - .withRetrievers(List.of(new VectorStoreRetriever(vectorStore, SearchRequest.defaults()))) - .withAugmentors(List.of(new QuestionContextAugmentor())) - .build(); - - } - - } - -} diff --git a/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/metadata/VertexAiChatResponseMetadata.java b/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/metadata/VertexAiChatResponseMetadata.java index bc62a5d2e..3ef20dcb4 100644 --- a/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/metadata/VertexAiChatResponseMetadata.java +++ b/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/metadata/VertexAiChatResponseMetadata.java @@ -18,11 +18,13 @@ package org.springframework.ai.vertexai.gemini.metadata; import org.springframework.ai.chat.metadata.ChatResponseMetadata; import org.springframework.ai.chat.metadata.Usage; +import java.util.HashMap; + /** * @author Christian Tzolov * @since 0.8.1 */ -public class VertexAiChatResponseMetadata implements ChatResponseMetadata { +public class VertexAiChatResponseMetadata extends HashMap implements ChatResponseMetadata { private final VertexAiUsage usage; diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/QuestionAnswerAdvisor.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/QuestionAnswerAdvisor.java index 5c5f8d73f..25df8dfca 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/QuestionAnswerAdvisor.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/QuestionAnswerAdvisor.java @@ -23,11 +23,13 @@ import java.util.stream.Collectors; import org.springframework.ai.chat.client.AdvisedRequest; import org.springframework.ai.chat.client.RequestResponseAdvisor; +import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.document.Document; import org.springframework.ai.model.Content; import org.springframework.ai.vectorstore.SearchRequest; import org.springframework.ai.vectorstore.VectorStore; import org.springframework.util.Assert; +import reactor.core.publisher.Flux; /** * Context for the question is retrieved from a Vector Store and added to the prompt's @@ -54,10 +56,22 @@ public class QuestionAnswerAdvisor implements RequestResponseAdvisor { private final SearchRequest searchRequest; + public static String RETRIEVED_DOCUMENTS = "qa_retrieved_documents"; + public QuestionAnswerAdvisor(VectorStore vectorStore, SearchRequest searchRequest) { this(vectorStore, searchRequest, DEFAULT_USER_TEXT_ADVISE); } + /** + * The QuestionAnswerAdvisor retrieves context information from a Vector Store and + * combines it with the user's text. + * @param vectorStore The vector store to use + * @param searchRequest The search request defined using the portable filter + * expression syntax + * @param userTextAdvise the user text to append to the existing user prompt. The text + * should contain a placeholder named "question_answer_context". + * + */ public QuestionAnswerAdvisor(VectorStore vectorStore, SearchRequest searchRequest, String userTextAdvise) { Assert.notNull(vectorStore, "The vectorStore must not be null!"); @@ -78,6 +92,8 @@ public class QuestionAnswerAdvisor implements RequestResponseAdvisor { // 2. Search for similar documents in the vector store. List documents = vectorStore.similaritySearch(searchRequest.withQuery(request.userText())); + context.put(RETRIEVED_DOCUMENTS, documents); + // 3. Create the context from the documents. String documentContext = documents.stream() .map(Content::getContent) @@ -95,4 +111,18 @@ public class QuestionAnswerAdvisor implements RequestResponseAdvisor { return advisedRequest; } + @Override + public ChatResponse adviseResponse(ChatResponse response, Map context) { + response.getMetadata().put(RETRIEVED_DOCUMENTS, context.get(RETRIEVED_DOCUMENTS)); + return response; + } + + @Override + public Flux adviseResponse(Flux fluxResponse, Map context) { + return fluxResponse.map(cr -> { + cr.getMetadata().put(RETRIEVED_DOCUMENTS, context.get(RETRIEVED_DOCUMENTS)); + return cr; + }); + } + } diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/memory/ChatMemoryChatServiceListener.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/memory/ChatMemoryChatServiceListener.java deleted file mode 100644 index 7e6a4a44f..000000000 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/memory/ChatMemoryChatServiceListener.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2024-2024 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.chat.memory; - -import java.util.List; - -import org.springframework.ai.chat.service.ChatServiceResponse; -import org.springframework.ai.chat.service.ChatServiceListener; -import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; -import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor; -import org.springframework.ai.chat.messages.Message; -import org.springframework.ai.chat.messages.MessageType; -import org.springframework.ai.chat.prompt.transformer.ChatServiceContext; -import org.springframework.ai.chat.prompt.transformer.TransformerContentType; - -/** - * @deprecated Use the {@link MessageChatMemoryAdvisor} or {@link PromptChatMemoryAdvisor} - * instead. - * @author Christian Tzolov - */ -@Deprecated -public class ChatMemoryChatServiceListener implements ChatServiceListener { - - private final ChatMemory chatHistory; - - public ChatMemoryChatServiceListener(ChatMemory chatHistory) { - this.chatHistory = chatHistory; - } - - @Override - public void onStart(ChatServiceContext chatServiceContext) { - var messagesToAdd = chatServiceContext.getPrompt() - .getInstructions() - .stream() - .filter(m -> !m.getMetadata().containsKey(TransformerContentType.MEMORY)) - .filter(m -> (m.getMessageType() == MessageType.ASSISTANT || m.getMessageType() == MessageType.USER)) - .toList(); - this.chatHistory.add(chatServiceContext.getConversationId(), messagesToAdd); - } - - @Override - public void onComplete(ChatServiceResponse chatServiceResponse) { - List assistantMessages = chatServiceResponse.getChatResponse() - .getResults() - .stream() - .map(g -> (Message) g.getOutput()) - .toList(); - this.chatHistory.add(chatServiceResponse.getPromptContext().getConversationId(), assistantMessages); - } - -} diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/memory/ChatMemoryRetriever.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/memory/ChatMemoryRetriever.java deleted file mode 100644 index 23908cbaa..000000000 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/memory/ChatMemoryRetriever.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2024-2024 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.chat.memory; - -import org.springframework.ai.chat.messages.Message; -import org.springframework.ai.chat.messages.MessageType; -import org.springframework.ai.chat.prompt.transformer.AbstractPromptTransformer; -import org.springframework.ai.chat.prompt.transformer.ChatServiceContext; -import org.springframework.ai.chat.prompt.transformer.TransformerContentType; -import org.springframework.ai.document.Document; -import org.springframework.ai.model.Content; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * @author Christian Tzolov - */ -public class ChatMemoryRetriever extends AbstractPromptTransformer { - - private final ChatMemory chatHistory; - - /** - * Additional metadata to be assigned to the retrieved history messages. - */ - private final Map metadata; - - private final int maxHistorySize; - - public ChatMemoryRetriever(ChatMemory chatHistory) { - this(chatHistory, 1000, Map.of(), "ChatMemoryRetriever"); - } - - public ChatMemoryRetriever(ChatMemory chatHistory, int maxHistorySize, Map metadata, String name) { - this.chatHistory = chatHistory; - this.metadata = metadata; - this.maxHistorySize = maxHistorySize; - this.setName(name); - } - - @Override - public ChatServiceContext transform(ChatServiceContext chatServiceContext) { - - List messageHistory = this.chatHistory.get(chatServiceContext.getConversationId(), maxHistorySize); - - List historyContent = (messageHistory != null) - ? messageHistory.stream().filter(m -> m.getMessageType() != MessageType.SYSTEM).map(m -> { - Content content = new Document(m.getContent(), new ArrayList<>(m.getMedia()), - new HashMap<>(m.getMetadata())); - content.getMetadata().putAll(this.metadata); - content.getMetadata().put(TransformerContentType.MEMORY, true); - return content; - }).toList() : List.of(); - - List updatedContents = new ArrayList<>( - chatServiceContext.getContents() != null ? chatServiceContext.getContents() : List.of()); - updatedContents.addAll(historyContent); - - return ChatServiceContext.from(chatServiceContext).withContents(updatedContents).build(); - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - - private ChatMemory chatHistory; - - private Map metadata = Map.of(); - - private int maxHistorySize = 1000; - - private String name = "ChatMemoryRetriever"; - - public Builder withChatHistory(ChatMemory chatHistory) { - this.chatHistory = chatHistory; - return this; - } - - public Builder withMetadata(Map metadata) { - this.metadata = metadata; - return this; - } - - public Builder withMaxHistorySize(int maxHistorySize) { - this.maxHistorySize = maxHistorySize; - return this; - } - - public Builder withName(String name) { - this.name = name; - return this; - } - - public ChatMemoryRetriever build() { - return new ChatMemoryRetriever(this.chatHistory, this.maxHistorySize, this.metadata, this.name); - } - - } - -} diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/memory/LastMaxTokenSizeContentTransformer.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/memory/LastMaxTokenSizeContentTransformer.java deleted file mode 100644 index 903f6e3ba..000000000 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/memory/LastMaxTokenSizeContentTransformer.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2024-2024 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.chat.memory; - -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -import org.springframework.ai.chat.client.advisor.LastMaxTokenSizeContentPurger; -import org.springframework.ai.chat.prompt.transformer.AbstractPromptTransformer; -import org.springframework.ai.chat.prompt.transformer.ChatServiceContext; -import org.springframework.ai.model.Content; -import org.springframework.ai.tokenizer.TokenCountEstimator; - -/** - * - * Returns a new list of content (e.g list of messages of list of documents) that is a - * subset of the input list of contents and complies with the max token size constraint. - * - * The token estimator is used to estimate the token count of the datum. - * - * @deprecated Use the {@link LastMaxTokenSizeContentPurger} instead. - * @author Christian Tzolov - */ -@Deprecated -public class LastMaxTokenSizeContentTransformer extends AbstractPromptTransformer { - - protected final TokenCountEstimator tokenCountEstimator; - - protected final int maxTokenSize; - - /** - * Only Content entries with the following metadata tags will be included in the - * history. - */ - private final Set filterTags; - - public LastMaxTokenSizeContentTransformer(TokenCountEstimator tokenCountEstimator, int maxTokenSize) { - this(tokenCountEstimator, maxTokenSize, Set.of()); - } - - public LastMaxTokenSizeContentTransformer(TokenCountEstimator tokenCountEstimator, int maxTokenSize, - Set filterTags) { - this.tokenCountEstimator = tokenCountEstimator; - this.maxTokenSize = maxTokenSize; - this.filterTags = filterTags; - } - - protected List doGetDatumToModify(ChatServiceContext chatServiceContext) { - return chatServiceContext.getContents() - .stream() - .filter(content -> this.filterTags.stream().allMatch(tag -> content.getMetadata().containsKey(tag))) - .toList(); - } - - protected List doGetDatumNotToModify(ChatServiceContext chatServiceContext) { - return chatServiceContext.getContents() - .stream() - .filter(content -> !this.filterTags.stream().allMatch(tag -> content.getMetadata().containsKey(tag))) - .toList(); - } - - protected int doEstimateTokenCount(Content datum) { - return this.tokenCountEstimator.estimate(datum); - } - - protected int doEstimateTokenCount(List datum) { - return datum.stream().mapToInt(this::doEstimateTokenCount).sum(); - } - - @Override - public ChatServiceContext transform(ChatServiceContext chatServiceContext) { - - List datum = this.doGetDatumToModify(chatServiceContext); - - int totalSize = this.doEstimateTokenCount(datum); - - if (totalSize <= this.maxTokenSize) { - return chatServiceContext; - } - - List purgedContent = this.purgeExcess(datum, totalSize); - - var updatedContent = new ArrayList<>(doGetDatumNotToModify(chatServiceContext)); - updatedContent.addAll(purgedContent); - - return ChatServiceContext.from(chatServiceContext).withContents(updatedContent).build(); - } - - protected List purgeExcess(List datum, int totalSize) { - - int index = 0; - List newList = new ArrayList<>(); - - while (index < datum.size() && totalSize > this.maxTokenSize) { - Content oldDatum = datum.get(index++); - int oldMessageTokenSize = this.doEstimateTokenCount(oldDatum); - totalSize = totalSize - oldMessageTokenSize; - } - - if (index >= datum.size()) { - return List.of(); - } - - // add the rest of the messages. - newList.addAll(datum.subList(index, datum.size())); - - return newList; - } - -} \ No newline at end of file diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/memory/MessageChatMemoryAugmentor.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/memory/MessageChatMemoryAugmentor.java deleted file mode 100644 index 4c69b7c89..000000000 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/memory/MessageChatMemoryAugmentor.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2024-2024 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.chat.memory; - -import java.util.ArrayList; -import java.util.List; - -import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; -import org.springframework.ai.chat.messages.AbstractMessage; -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.messages.UserMessage; -import org.springframework.ai.chat.prompt.ChatOptions; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.chat.prompt.transformer.AbstractPromptTransformer; -import org.springframework.ai.chat.prompt.transformer.ChatServiceContext; -import org.springframework.ai.chat.prompt.transformer.PromptChange; -import org.springframework.ai.chat.prompt.transformer.TransformerContentType; - -/** - * @deprecated Use the {@link MessageChatMemoryAdvisor} instead. - * @author Christian Tzolov - */ -@Deprecated -public class MessageChatMemoryAugmentor extends AbstractPromptTransformer { - - @Override - public ChatServiceContext transform(ChatServiceContext chatServiceContext) { - - var originalPrompt = chatServiceContext.getPrompt(); - - // Convert the retrieved contents into a list of messages. - List historyMessages = chatServiceContext.getContents() - .stream() - .filter(content -> content.getMetadata().containsKey(TransformerContentType.MEMORY)) - .map(content -> { - MessageType messageType = MessageType - .valueOf("" + content.getMetadata().get(AbstractMessage.MESSAGE_TYPE)); - Message message = null; - if (messageType == MessageType.ASSISTANT) { - message = new AssistantMessage(content.getContent(), content.getMetadata()); - } - else if (messageType == MessageType.USER) { - message = new UserMessage(content.getContent(), List.of(), content.getMetadata()); - } - return message; - }) - .filter(m -> m != null) - .toList(); - - var promptMessages = new ArrayList<>(historyMessages); - promptMessages.addAll(originalPrompt.getInstructions()); - - Prompt newPrompt = new Prompt(promptMessages, (ChatOptions) originalPrompt.getOptions()); - PromptChange promptChange = new PromptChange(originalPrompt, newPrompt, this.getName(), - "Added chat memory as individual messages in the prompt"); - - return ChatServiceContext.from(chatServiceContext).withPrompt(newPrompt).withPromptChange(promptChange).build(); - } - -} \ No newline at end of file diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/memory/SystemPromptChatMemoryAugmentor.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/memory/SystemPromptChatMemoryAugmentor.java deleted file mode 100644 index 3fc6ee0dd..000000000 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/memory/SystemPromptChatMemoryAugmentor.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2024-2024 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.chat.memory; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor; -import org.springframework.ai.chat.messages.AbstractMessage; -import org.springframework.ai.chat.messages.Message; -import org.springframework.ai.chat.messages.MessageType; -import org.springframework.ai.chat.messages.SystemMessage; -import org.springframework.ai.chat.prompt.ChatOptions; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.chat.prompt.transformer.AbstractPromptTransformer; -import org.springframework.ai.chat.prompt.transformer.ChatServiceContext; -import org.springframework.ai.chat.prompt.transformer.PromptChange; -import org.springframework.ai.chat.prompt.transformer.TransformerContentType; -import org.springframework.util.Assert; - -/** - * @deprecated Use the {@link PromptChatMemoryAdvisor} instead. - * @author Christian Tzolov - */ -@Deprecated -public class SystemPromptChatMemoryAugmentor extends AbstractPromptTransformer { - - public static final String DEFAULT_HISTORY_PROMPT = """ - Use the conversation history from the HISTORY section to provide accurate answers. - - HISTORY: - {history} - """; - - private final String historyPrompt; - - /** - * Only Content entries with the following metadata tags will be included in the - * history. - */ - private final Set filterTags; - - public SystemPromptChatMemoryAugmentor() { - this(DEFAULT_HISTORY_PROMPT, new HashSet<>()); - } - - public SystemPromptChatMemoryAugmentor(Set filterTags) { - this(DEFAULT_HISTORY_PROMPT, filterTags); - } - - public SystemPromptChatMemoryAugmentor(String historyPrompt, Set metadataFilterTags) { - Assert.hasText(historyPrompt, "The historyPrompt must not be empty!"); - Assert.notNull(metadataFilterTags, "The metadataFilterTags must not be null!"); - this.historyPrompt = historyPrompt; - this.filterTags = new HashSet<>(metadataFilterTags); - - // Always include the message history type tag. - this.filterTags.add(TransformerContentType.MEMORY); - } - - @Override - public ChatServiceContext transform(ChatServiceContext chatServiceContext) { - - var originalPrompt = chatServiceContext.getPrompt(); - - List systemMessages = (originalPrompt.getInstructions() != null) ? originalPrompt.getInstructions() - .stream() - .filter(m -> m.getMessageType() == MessageType.SYSTEM) - .toList() : List.of(); - - List nonSystemMessages = (originalPrompt.getInstructions() != null) ? originalPrompt.getInstructions() - .stream() - .filter(m -> m.getMessageType() != MessageType.SYSTEM) - .toList() : List.of(); - - SystemMessage originalSystemMessage = (!systemMessages.isEmpty()) ? (SystemMessage) systemMessages.get(0) - : new SystemMessage(""); - - String historyContext = chatServiceContext.getContents() - .stream() - .filter(content -> this.filterTags.stream().allMatch(tag -> content.getMetadata().containsKey(tag))) - .map(content -> content.getMetadata().get(AbstractMessage.MESSAGE_TYPE) + ": " + content.getContent()) - .collect(Collectors.joining(System.lineSeparator())); - - SystemMessage newSystemMessage = new SystemMessage(originalSystemMessage.getContent() + System.lineSeparator() - + this.historyPrompt.replace("{history}", historyContext)); - - List newPromptMessages = new ArrayList<>(); - newPromptMessages.add(newSystemMessage); - newPromptMessages.addAll(nonSystemMessages); - - Prompt newPrompt = new Prompt(newPromptMessages, (ChatOptions) originalPrompt.getOptions()); - PromptChange promptChange = new PromptChange(originalPrompt, newPrompt, this.getName(), - "Added chat memory into the system prompt"); - return ChatServiceContext.from(chatServiceContext).withPrompt(newPrompt).withPromptChange(promptChange).build(); - } - -} \ No newline at end of file diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/memory/VectorStoreChatMemoryChatServiceListener.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/memory/VectorStoreChatMemoryChatServiceListener.java deleted file mode 100644 index ee5d52eb3..000000000 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/memory/VectorStoreChatMemoryChatServiceListener.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2024-2024 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.chat.memory; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.springframework.ai.chat.service.ChatServiceListener; -import org.springframework.ai.chat.service.ChatServiceResponse; -import org.springframework.ai.chat.client.advisor.VectorStoreChatMemoryAdvisor; -import org.springframework.ai.chat.messages.Message; -import org.springframework.ai.chat.messages.MessageType; -import org.springframework.ai.chat.prompt.transformer.TransformerContentType; -import org.springframework.ai.chat.prompt.transformer.ChatServiceContext; -import org.springframework.ai.document.Document; -import org.springframework.ai.vectorstore.VectorStore; -import org.springframework.util.CollectionUtils; - -/** - * @deprecated Use the {@link VectorStoreChatMemoryAdvisor} instead. - * @author Christian Tzolov - */ -@Deprecated -public class VectorStoreChatMemoryChatServiceListener implements ChatServiceListener { - - private final VectorStore vectorStore; - - private final Map additionalMetadata; - - public VectorStoreChatMemoryChatServiceListener(VectorStore vectorStore) { - this(vectorStore, new HashMap<>()); - } - - public VectorStoreChatMemoryChatServiceListener(VectorStore vectorStore, Map additionalMetadata) { - this.vectorStore = vectorStore; - this.additionalMetadata = additionalMetadata; - } - - @Override - public void onStart(ChatServiceContext chatServiceContext) { - - if (!CollectionUtils.isEmpty(chatServiceContext.getPrompt().getInstructions())) { - List docs = toDocuments(chatServiceContext.getPrompt().getInstructions(), - chatServiceContext.getConversationId()); - - this.vectorStore.add(docs); - } - } - - @Override - public void onComplete(ChatServiceResponse chatServiceResponse) { - if (!CollectionUtils.isEmpty(chatServiceResponse.getChatResponse().getResults())) { - List assistantMessages = chatServiceResponse.getChatResponse() - .getResults() - .stream() - .map(g -> (org.springframework.ai.chat.messages.Message) g.getOutput()) - .toList(); - - List docs = toDocuments(assistantMessages, - chatServiceResponse.getPromptContext().getConversationId()); - - this.vectorStore.add(docs); - } - } - - private List toDocuments(List messages, String conversationId) { - - List docs = messages.stream() - .filter(m -> m.getMessageType() == MessageType.USER || m.getMessageType() == MessageType.ASSISTANT) - .map(message -> { - var metadata = new HashMap<>(message.getMetadata() != null ? message.getMetadata() : new HashMap<>()); - metadata.putAll(this.additionalMetadata); - metadata.put(TransformerContentType.CONVERSATION_ID, conversationId); - metadata.put("messageType", message.getMessageType().name()); - metadata.put(TransformerContentType.MEMORY, true); - metadata.put(TransformerContentType.LONG_TERM_MEMORY, true); - var doc = new Document(message.getContent(), metadata); - return doc; - }) - .toList(); - - return docs; - - } - -} diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/memory/VectorStoreChatMemoryRetriever.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/memory/VectorStoreChatMemoryRetriever.java deleted file mode 100644 index 3a4914190..000000000 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/memory/VectorStoreChatMemoryRetriever.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2024-2024 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.chat.memory; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import org.springframework.ai.chat.client.advisor.VectorStoreChatMemoryAdvisor; -import org.springframework.ai.chat.messages.MessageType; -import org.springframework.ai.chat.prompt.transformer.AbstractPromptTransformer; -import org.springframework.ai.chat.prompt.transformer.ChatServiceContext; -import org.springframework.ai.chat.prompt.transformer.TransformerContentType; -import org.springframework.ai.document.Document; -import org.springframework.ai.model.Content; -import org.springframework.ai.vectorstore.SearchRequest; -import org.springframework.ai.vectorstore.VectorStore; -import org.springframework.util.CollectionUtils; - -/** - * @deprecated Use the {@link VectorStoreChatMemoryAdvisor} instead. - * @author Christian Tzolov - */ -@Deprecated -public class VectorStoreChatMemoryRetriever extends AbstractPromptTransformer { - - private final VectorStore vectorStore; - - private final int topK; - - /** - * Additional metadata to be assigned to the retrieved history messages. - */ - private final Map additionalMetadata; - - public VectorStoreChatMemoryRetriever(VectorStore vectorStore, int topK) { - this(vectorStore, topK, Map.of()); - } - - public VectorStoreChatMemoryRetriever(VectorStore vectorStore, int topK, Map additionalMetadata) { - this.vectorStore = vectorStore; - this.topK = topK; - this.additionalMetadata = additionalMetadata; - } - - @Override - public ChatServiceContext transform(ChatServiceContext chatServiceContext) { - List updatedContents = new ArrayList<>( - chatServiceContext.getContents() != null ? chatServiceContext.getContents() : List.of()); - - String query = chatServiceContext.getPrompt() - .getInstructions() - .stream() - .filter(m -> m.getMessageType() == MessageType.USER) - .map(m -> m.getContent()) - .collect(Collectors.joining()); - - var searchRequest = SearchRequest.query(query) - .withTopK(this.topK) - .withFilterExpression( - TransformerContentType.CONVERSATION_ID + "=='" + chatServiceContext.getConversationId() + "'"); - - List documents = this.vectorStore.similaritySearch(searchRequest); - - if (!CollectionUtils.isEmpty(documents)) { - documents.forEach(d -> { - d.getMetadata().putAll(this.additionalMetadata); - d.getMetadata().put(TransformerContentType.MEMORY, true); - }); - updatedContents.addAll(documents); - } - - return ChatServiceContext.from(chatServiceContext).withContents(updatedContents).build(); - } - -} diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/metadata/ChatResponseMetadata.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/metadata/ChatResponseMetadata.java index e93db0633..fa62014b0 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/metadata/ChatResponseMetadata.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/chat/metadata/ChatResponseMetadata.java @@ -17,6 +17,8 @@ package org.springframework.ai.chat.metadata; import org.springframework.ai.model.ResponseMetadata; +import java.util.HashMap; + /** * Abstract Data Type (ADT) modeling common AI provider metadata returned in an AI * response. @@ -26,8 +28,11 @@ import org.springframework.ai.model.ResponseMetadata; */ public interface ChatResponseMetadata extends ResponseMetadata { - ChatResponseMetadata NULL = new ChatResponseMetadata() { - }; + static class DefaultChatResponseMetadata extends HashMap implements ChatResponseMetadata { + + } + + ChatResponseMetadata NULL = new DefaultChatResponseMetadata(); /** * Returns AI provider specific metadata on rate limits. diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/transformer/AbstractPromptTransformer.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/transformer/AbstractPromptTransformer.java deleted file mode 100644 index 0636c9543..000000000 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/transformer/AbstractPromptTransformer.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2024 - 2024 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.chat.prompt.transformer; - -/** - * AbstractPromptTransformer is an abstract class that provides a base implementation of - * the PromptTransformer interface. It includes a name field and corresponding accessor - * methods, as well as a default implementation for the transform method. - * - * @author Mark Pollack - * @author Christian Tzolov - * @since 1.0.0 M1 - */ -public abstract class AbstractPromptTransformer implements PromptTransformer { - - private String name = getClass().getSimpleName(); - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - -} diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/transformer/ChatServiceContext.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/transformer/ChatServiceContext.java deleted file mode 100644 index 2b064c55d..000000000 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/transformer/ChatServiceContext.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright 2024-2024 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.chat.prompt.transformer; - -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.chat.service.ChatService; -import org.springframework.ai.model.Content; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; - -/** - * Represents the execution context for the {@link ChatService}. This context is used to - * pass initial parameters to the service and facilitate data sharing between different - * components within the service. - * - *

- * The {@code ChatServiceContext} includes essential information such as the initial - * prompt and a conversation ID, which are crucial for the correct operation of the chat - * service. - *

- * - * @author Mark Pollack - * @author Christian Tzolov - * @since 1.0.0 M1 - */ -public class ChatServiceContext { - - private Prompt prompt; // The most up-to-date prompt to use - - private List contents; // The most up-to-date data to use - - private List promptChanges; // The changes make due to transformations - - private String conversationId; - - /** - * Contextual data that can be shared between processing steps in a ChatService - * implementation. - */ - private Map context = new ConcurrentHashMap<>(); - - public ChatServiceContext(Prompt prompt) { - this(prompt, "default", new ArrayList<>()); - } - - public ChatServiceContext(Prompt prompt, String conversationId) { - this(prompt, conversationId, new ArrayList<>()); - } - - public ChatServiceContext(Prompt prompt, String conversationId, List contents) { - this.prompt = prompt; - this.conversationId = conversationId; - this.promptChanges = new ArrayList<>(); - this.promptChanges.add(new PromptChange(null, prompt, "none", "initial prompt")); - this.contents = contents; - } - - public Prompt getPrompt() { - return this.prompt; - } - - public void updatePrompt(Prompt prompt, String transformerName, String description) { - this.promptChanges.add(new PromptChange(this.prompt, prompt, transformerName, description)); - this.prompt = prompt; // set the new prompt as current - } - - public void addData(Content datum) { - this.contents.add(datum); - } - - public List getContents() { - return this.contents; - } - - public void setContents(List contents) { - this.contents = contents; - } - - public List getPromptChanges() { - return this.promptChanges; - } - - public String getConversationId() { - return this.conversationId; - } - - public Map getContext() { - return this.context; - } - - public static Builder from(ChatServiceContext chatServiceContext) { - return ChatServiceContext.builder() - .withContents(new ArrayList<>( - chatServiceContext.getContents() != null ? chatServiceContext.getContents() : List.of())) - .withPrompt(chatServiceContext.getPrompt().copy()) // deep copy - .withMetadata( - new HashMap<>(chatServiceContext.getContext() != null ? chatServiceContext.getContext() : Map.of())) - .withPromptChanges(new ArrayList<>( - chatServiceContext.getPromptChanges() != null ? chatServiceContext.getPromptChanges() : List.of())) - .withConversationId(chatServiceContext.getConversationId()); - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - - private Prompt prompt; - - private List contents; - - private List promptChanges; - - private String conversationId; - - private Map context = new HashMap<>(); - - public Builder withPrompt(Prompt prompt) { - this.prompt = prompt; - return this; - } - - public Builder withContents(List contents) { - this.contents = new ArrayList<>(contents); - return this; - } - - public Builder withPromptChanges(List promptChanges) { - this.promptChanges = new ArrayList<>(promptChanges); - return this; - } - - public Builder withPromptChange(PromptChange promptChange) { - this.promptChanges.add(promptChange); - return this; - } - - public Builder withConversationId(String conversationId) { - this.conversationId = conversationId; - return this; - } - - public Builder withMetadata(Map context) { - this.context = new HashMap<>(context); - return this; - } - - public ChatServiceContext build() { - ChatServiceContext chatServiceContext = new ChatServiceContext(this.prompt, this.conversationId, - this.contents); - chatServiceContext.promptChanges = promptChanges; - chatServiceContext.context = context; - return chatServiceContext; - } - - } - - @Override - public String toString() { - return "ChatServiceContext{" + "prompt=" + prompt + ", contents=" + contents + ", promptHistory=" - + promptChanges + ", conversationId='" + conversationId + '\'' + ", metadata=" + context + '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (!(o instanceof ChatServiceContext that)) - return false; - return Objects.equals(prompt, that.prompt) && Objects.equals(contents, that.contents) - && Objects.equals(promptChanges, that.promptChanges) - && Objects.equals(conversationId, that.conversationId) && Objects.equals(context, that.context); - } - - @Override - public int hashCode() { - return Objects.hash(prompt, contents, promptChanges, conversationId, context); - } - -} diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/transformer/PromptChange.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/transformer/PromptChange.java deleted file mode 100644 index b8e8e1e70..000000000 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/transformer/PromptChange.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2024 - 2024 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.chat.prompt.transformer; - -import org.springframework.ai.chat.prompt.Prompt; - -/** - * The PromptChange class represents a change made to a Prompt object. It contains - * information about the original prompt, the revised prompt, the name of the transformer - * that made the change, and a description of the change. - * - * @author Mark Pollack - * @author Christian Tzolov - * @since 1.0.0 M1 - */ -public record PromptChange(Prompt original, Prompt revised, String transformerName, String description) { - -} \ No newline at end of file diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/transformer/PromptTransformer.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/transformer/PromptTransformer.java deleted file mode 100644 index 67c74c5a3..000000000 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/transformer/PromptTransformer.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2024-2024 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.chat.prompt.transformer; - -/** - * Responsible for transforming a Prompt. The ChatServiceContext contains the necessary - * data to make the transformation - * - * Implementations may retrieve data and modify the Prompt object in the - * ChatServiceContext as needed. - * - * @author Mark Pollack - * @author Christian Tzolov - * @since 1.0.0 M1 - */ -@FunctionalInterface -public interface PromptTransformer { - - /** - * Transforms the given ChatServiceContext. - * @param context the ChatServiceContext to transform - * @return the transformed ChatServiceContext - */ - ChatServiceContext transform(ChatServiceContext context); - -} diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/transformer/QuestionContextAugmentor.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/transformer/QuestionContextAugmentor.java deleted file mode 100644 index bdd4830df..000000000 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/transformer/QuestionContextAugmentor.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright 2024-2024 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.chat.prompt.transformer; - -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import org.springframework.ai.chat.messages.Message; -import org.springframework.ai.chat.messages.MessageType; -import org.springframework.ai.chat.prompt.ChatOptions; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.chat.prompt.PromptTemplate; -import org.springframework.ai.model.Content; - -/** - * Transforms the Prompt by taking to the current prompt in the Prompt Context and adding - * additional context to create a new prompt. The default user text contains the - * placeholder names "question" and "context". The "question" placeholder is filled using - * the value of the current UserMessage and the "context" placeholder is filled with - * Documents contained in the ChatServiceContext's Nodes. - * - * @author Mark Pollack - * @author Christian Tzolov - * @since 1.0.0 M1 - */ -public class QuestionContextAugmentor extends AbstractPromptTransformer { - - private static final String DEFAULT_USER_TEXT = """ - "Context information is below.\\n" - "---------------------\\n" - "{context}\\n" - "---------------------\\n" - "Given the context and provided history information and not prior knowledge, " - "reply to the user comment. If the answer is not in the context, inform " - "the user that you can't answer the question.\\n" - "User comment: {question}\\n" - "Answer: " - """; - - private String userText; - - public QuestionContextAugmentor() { - this(DEFAULT_USER_TEXT); - } - - public QuestionContextAugmentor(String userText) { - this.userText = userText; - this.setName("QuestionContextAugmentor"); - } - - public String getUserText() { - return userText; - } - - @Override - public ChatServiceContext transform(ChatServiceContext chatServiceContext) { - String context = doCreateContext(chatServiceContext.getContents()); - Map contextMap = doCreateContextMap(chatServiceContext.getPrompt(), context); - Prompt prompt = doCreatePrompt(chatServiceContext.getPrompt(), contextMap); - chatServiceContext.updatePrompt(prompt, this.getName(), "Updated prompt with Q/A user text"); - - // For now return the modified instance instead of a copy - return chatServiceContext; - } - - protected String doCreateContext(List data) { - return data.stream().map(Content::getContent).collect(Collectors.joining(System.lineSeparator())); - } - - private Map doCreateContextMap(Prompt prompt, String context) { - String originalUserMessage = prompt.getInstructions() - .stream() - .filter(m -> m.getMessageType() == MessageType.USER) - .map(m -> m.getContent()) - .collect(Collectors.joining(System.lineSeparator())); - - return Map.of("context", context, "question", originalUserMessage); - } - - protected Prompt doCreatePrompt(Prompt originalPrompt, Map contextMap) { - PromptTemplate promptTemplate = new PromptTemplate(getUserText()); - Message userMessageToAppend = promptTemplate.createMessage(contextMap); - List messageList = originalPrompt.getInstructions() - .stream() - .filter(m -> m.getMessageType() != MessageType.USER) - .collect(Collectors.toList()); - messageList.add(userMessageToAppend); - return new Prompt(messageList, (ChatOptions) originalPrompt.getOptions()); - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - - private String name; - - private String userText; - - public Builder withName(String name) { - this.name = name; - return this; - } - - public Builder withUserText(String userText) { - this.userText = userText; - return this; - } - - public QuestionContextAugmentor build() { - QuestionContextAugmentor instance = new QuestionContextAugmentor(); - instance.userText = this.userText != null ? this.userText : instance.userText; - instance.setName(this.name != null ? this.name : instance.getName()); - return instance; - } - - } - -} diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/transformer/TransformerContentType.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/transformer/TransformerContentType.java deleted file mode 100644 index b4839ecf9..000000000 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/transformer/TransformerContentType.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2024-2024 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.chat.prompt.transformer; - -/** - * This class provides constants for different content types used by transformers. - * - * @author Christian Tzolov - * @since 1.0.0 M1 - */ -public class TransformerContentType { - - public static final String MEMORY = "MEMORY_TYPE"; - - public static final String LONG_TERM_MEMORY = "LONG_TERM_MEMORY_TYPE"; - - public static final String SHORT_TERM_MEMORY = "SHORT_TERM_MEMORY_TYPE"; - - public static final String CONVERSATION_ID = "conversationId"; - - public static final String EXTERNAL_KNOWLEDGE = "externalKnowledge"; - -} diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/transformer/VectorStoreRetriever.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/transformer/VectorStoreRetriever.java deleted file mode 100644 index 30851f644..000000000 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/transformer/VectorStoreRetriever.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2024-2024 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.chat.prompt.transformer; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.ai.chat.messages.Message; -import org.springframework.ai.chat.messages.MessageType; -import org.springframework.ai.document.Document; -import org.springframework.ai.vectorstore.SearchRequest; -import org.springframework.ai.vectorstore.VectorStore; - -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -/** - * A transformer class that retrieves documents from a {@link VectorStore} - * - *

- * The {@code VectorStoreRetriever} leverages a {@link SearchRequest} to query the - * {@link VectorStore} and retrieve documents that are semantically similar to the user's - * input. These documents are then added to the {@link ChatServiceContext} for further - * processing. - *

- * - * @see VectorStore - * @see SearchRequest - * @see ChatServiceContext - * @author Mark Pollack - * @author Christian Tzolov - * @since 1.0.0 M1 - */ -public class VectorStoreRetriever extends AbstractPromptTransformer { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private final VectorStore vectorStore; - - private final SearchRequest searchRequest; - - public VectorStoreRetriever(VectorStore vectorStore, SearchRequest searchRequest) { - this(vectorStore, searchRequest, "VectorStoreRetriever"); - } - - public VectorStoreRetriever(VectorStore vectorStore, SearchRequest searchRequest, String name) { - this.vectorStore = vectorStore; - this.searchRequest = searchRequest; - this.setName(name); - } - - public VectorStore getVectorStore() { - return vectorStore; - } - - public SearchRequest getSearchRequest() { - return searchRequest; - } - - @Override - public ChatServiceContext transform(ChatServiceContext chatServiceContext) { - List instructions = chatServiceContext.getPrompt().getInstructions(); - String userMessage = instructions.stream() - .filter(m -> m.getMessageType() == MessageType.USER) - .map(m -> m.getContent()) - .collect(Collectors.joining(System.lineSeparator())); - - List documents = vectorStore.similaritySearch(searchRequest.withQuery(userMessage)); - - logger.info("Retrieved {} documents for user message {}", documents.size(), userMessage); - for (Document document : documents) { - var content = new Document(document.getContent(), document.getMetadata()); - // content.getMetadata().put(TransformerContentType.DOMAIN_DATA, true); - chatServiceContext.addData(content); - } - return chatServiceContext; - } - - @Override - public String toString() { - return "VectorStoreRetriever{" + "vectorStore=" + vectorStore + ", searchRequest=" + searchRequest + '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (!(o instanceof VectorStoreRetriever that)) - return false; - return Objects.equals(vectorStore, that.vectorStore) && Objects.equals(searchRequest, that.searchRequest); - } - - @Override - public int hashCode() { - return Objects.hash(vectorStore, searchRequest); - } - -} diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/service/ChatService.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/service/ChatService.java deleted file mode 100644 index 647ad9c3c..000000000 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/service/ChatService.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2024 - 2024 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.chat.service; - -import org.springframework.ai.chat.prompt.transformer.ChatServiceContext; - -/** - * A ChatService encapsulates the logic to implement AI use cases. - * - * @author Mark Pollack - * @since 1.0 M1 - */ -public interface ChatService { - - /** - * Call the service to execute AI actions - * @param chatServiceContext A data structure used by the ChatService to perform - * processing of the Prompt. It includes the initial Prompt and a conversation ID at - * the start of execution. - * @return the ChatServiceResponse that contains the ChatResponse and the latest - * ChatServiceContext - */ - ChatServiceResponse call(ChatServiceContext chatServiceContext); - -} diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/service/ChatServiceListener.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/service/ChatServiceListener.java deleted file mode 100644 index 8e8596cc0..000000000 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/service/ChatServiceListener.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2024 - 2024 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.chat.service; - -import org.springframework.ai.chat.prompt.transformer.ChatServiceContext; - -/** - * The ChatServiceListener is a callback interface that can be implemented by classes that - * want to be notified of the completion of a ChatService execution. - * - * @author Mark Pollack - * @author Christian Tzolov - */ -public interface ChatServiceListener { - - default void onStart(ChatServiceContext chatServiceContext) { - - } - - void onComplete(ChatServiceResponse chatServiceResponse); - -} diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/service/ChatServiceResponse.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/service/ChatServiceResponse.java deleted file mode 100644 index bb27e0374..000000000 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/service/ChatServiceResponse.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2024 - 2024 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.chat.service; - -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.prompt.transformer.ChatServiceContext; -import org.springframework.ai.evaluation.EvaluationRequest; - -import java.util.Objects; - -/** - * Encapsulates the response from the ChatService. Contains the most up-to-date - * ChatServiceContext and the final ChatResponse - * - * @author Mark Pollack - * @since 1.0 M1 - */ -public class ChatServiceResponse { - - private final ChatServiceContext chatServiceContext; - - private final ChatResponse chatResponse; - - public ChatServiceResponse(ChatServiceContext chatServiceContext, ChatResponse chatResponse) { - this.chatServiceContext = chatServiceContext; - this.chatResponse = chatResponse; - } - - public ChatServiceContext getPromptContext() { - return chatServiceContext; - } - - public ChatResponse getChatResponse() { - return chatResponse; - } - - public EvaluationRequest toEvaluationRequest() { - return new EvaluationRequest(getPromptContext().getPromptChanges().get(0).revised(), - getPromptContext().getContents(), getChatResponse()); - } - - @Override - public String toString() { - return "ChatServiceResponse{" + "chatServiceContext=" + chatServiceContext + ", chatResponse=" + chatResponse - + '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (!(o instanceof ChatServiceResponse that)) - return false; - return Objects.equals(chatServiceContext, that.chatServiceContext) - && Objects.equals(chatResponse, that.chatResponse); - } - - @Override - public int hashCode() { - return Objects.hash(chatServiceContext, chatResponse); - } - -} diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/service/PromptTransformingChatService.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/service/PromptTransformingChatService.java deleted file mode 100644 index d6e6c1072..000000000 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/service/PromptTransformingChatService.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright 2024 - 2024 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.chat.service; - -import org.springframework.ai.chat.model.ChatModel; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.prompt.transformer.ChatServiceContext; -import org.springframework.ai.chat.prompt.transformer.PromptTransformer; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -/** - * A PromptTransformingChatService implements the ChatService interface and performs - * transformation of the prompt using a series of PromptTransformers. It also provides a - * builder class for easier construction of the PromptTransformingChatService instance. - * - * @author Mark Pollack - * @author Christian Tzolov - * @since 1.0 M1 - */ -public class PromptTransformingChatService implements ChatService { - - private ChatModel chatModel; - - private List retrievers; - - private List documentPostProcessors; - - private List augmentors; - - private List chatServiceListeners; - - public PromptTransformingChatService(ChatModel chatModel, List retrievers, - List documentPostProcessors, List augmentors, - List chatServiceListeners) { - Objects.requireNonNull(chatModel, "chatModel must not be null"); - this.chatModel = chatModel; - this.retrievers = retrievers; - this.documentPostProcessors = documentPostProcessors; - this.augmentors = augmentors; - this.chatServiceListeners = chatServiceListeners; - } - - public static Builder builder(ChatModel chatModel) { - return new Builder().withChatModel(chatModel); - } - - @Override - public ChatServiceResponse call(ChatServiceContext chatServiceContext) { - - ChatServiceContext chatServiceContextOnStart = ChatServiceContext.from(chatServiceContext).build(); - - // Perform retrieval of documents and messages - for (PromptTransformer retriever : this.retrievers) { - chatServiceContext = retriever.transform(chatServiceContext); - } - - // Perform post processing of all retrieved documents and messages - for (PromptTransformer documentPostProcessor : this.documentPostProcessors) { - chatServiceContext = documentPostProcessor.transform(chatServiceContext); - } - - // Perform prompt augmentation - for (PromptTransformer augmentor : this.augmentors) { - chatServiceContext = augmentor.transform(chatServiceContext); - } - - // Invoke Listeners onStart - for (ChatServiceListener listener : this.chatServiceListeners) { - listener.onStart(chatServiceContextOnStart); - } - - // Perform generation - ChatResponse chatResponse = this.chatModel.call(chatServiceContext.getPrompt()); - - // Invoke Listeners onComplete - ChatServiceResponse chatServiceResponse = new ChatServiceResponse(chatServiceContext, chatResponse); - for (ChatServiceListener listener : this.chatServiceListeners) { - listener.onComplete(chatServiceResponse); - } - return chatServiceResponse; - } - - public static class Builder { - - private ChatModel chatModel; - - private List retrievers = new ArrayList<>(); - - private List documentPostProcessors = new ArrayList<>(); - - private List augmentors = new ArrayList<>(); - - private List chatServiceListeners = new ArrayList<>(); - - public Builder withChatModel(ChatModel chatModel) { - this.chatModel = chatModel; - return this; - } - - public Builder withRetrievers(List retrievers) { - this.retrievers = retrievers; - return this; - } - - public Builder withContentPostProcessors(List documentPostProcessors) { - this.documentPostProcessors = documentPostProcessors; - return this; - } - - public Builder withAugmentors(List augmentors) { - this.augmentors = augmentors; - return this; - } - - public Builder withChatServiceListeners(List chatServiceListeners) { - this.chatServiceListeners = chatServiceListeners; - return this; - } - - public PromptTransformingChatService build() { - return new PromptTransformingChatService(chatModel, retrievers, documentPostProcessors, augmentors, - chatServiceListeners); - } - - } - -} \ No newline at end of file diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/service/StreamingChatService.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/service/StreamingChatService.java deleted file mode 100644 index 7abb8e63e..000000000 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/service/StreamingChatService.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2024 - 2024 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.chat.service; - -import org.springframework.ai.chat.prompt.transformer.ChatServiceContext; - -/** - * A ChatService encapsulates the logic to implement AI use cases. - * - * @author Mark Pollack - * @author Christian Tzolov - * @since 1.0 M1 - */ -public interface StreamingChatService { - - /** - * Call the service to execute AI actions - * @param chatServiceContext A shared data structure used by the ChatService to - * perform processing of the Prompt. It includes the intial Prompt and a conversation - * ID at the start of execution. - * @return the StreamingChatServiceResponse that contains the ChatResponse and the - * latest ChatServiceContext - */ - StreamingChatServiceResponse stream(ChatServiceContext chatServiceContext); - -} diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/service/StreamingChatServiceResponse.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/service/StreamingChatServiceResponse.java deleted file mode 100644 index cad4a8c1d..000000000 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/service/StreamingChatServiceResponse.java +++ /dev/null @@ -1,73 +0,0 @@ -package org.springframework.ai.chat.service; - -import org.springframework.ai.chat.prompt.transformer.ChatServiceContext; -import reactor.core.publisher.Flux; - -import org.springframework.ai.chat.model.ChatResponse; - -/** - * Encapsulates the response from the ChatService. Contains the most up-to-date - * ChatServiceContext and the final ChatResponse - * - * @author Mark Pollack - * @since 1.0 M1 - */ -public class StreamingChatServiceResponse { - - private final ChatServiceContext chatServiceContext; - - private final Flux chatResponse; - - public StreamingChatServiceResponse(ChatServiceContext chatServiceContext, Flux chatResponse) { - this.chatServiceContext = chatServiceContext; - this.chatResponse = chatResponse; - } - - public ChatServiceContext getPromptContext() { - return chatServiceContext; - } - - public Flux getChatResponse() { - return chatResponse; - } - - @Override - public String toString() { - return "ChatServiceResponse{" + "chatServiceContext=" + chatServiceContext + ", chatResponse=" + chatResponse - + '}'; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((chatServiceContext == null) ? 0 : chatServiceContext.hashCode()); - result = prime * result + ((chatResponse == null) ? 0 : chatResponse.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - StreamingChatServiceResponse other = (StreamingChatServiceResponse) obj; - if (chatServiceContext == null) { - if (other.chatServiceContext != null) - return false; - } - else if (!chatServiceContext.equals(other.chatServiceContext)) - return false; - if (chatResponse == null) { - if (other.chatResponse != null) - return false; - } - else if (!chatResponse.equals(other.chatResponse)) - return false; - return true; - } - -} diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/service/StreamingPromptTransformingChatService.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/service/StreamingPromptTransformingChatService.java deleted file mode 100644 index 49381eb75..000000000 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/service/StreamingPromptTransformingChatService.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2024 - 2024 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.chat.service; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -import org.springframework.ai.chat.prompt.transformer.ChatServiceContext; -import reactor.core.publisher.Flux; - -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.model.MessageAggregator; -import org.springframework.ai.chat.model.StreamingChatModel; -import org.springframework.ai.chat.prompt.transformer.PromptTransformer; - -/** - * @author Mark Pollack - * @author Christian Tzolov - */ -public class StreamingPromptTransformingChatService implements StreamingChatService { - - private StreamingChatModel streamingChatModel; - - private List retrievers; - - private List documentPostProcessors; - - private List augmentors; - - private List chatServiceListeners; - - public StreamingPromptTransformingChatService(StreamingChatModel chatModel, List retrievers, - List documentPostProcessors, List augmentors, - List chatServiceListeners) { - Objects.requireNonNull(chatModel, "chatModel must not be null"); - this.streamingChatModel = chatModel; - this.retrievers = retrievers; - this.documentPostProcessors = documentPostProcessors; - this.augmentors = augmentors; - this.chatServiceListeners = chatServiceListeners; - } - - public static Builder builder(StreamingChatModel chatModel) { - return new Builder().withChatModel(chatModel); - } - - @Override - public StreamingChatServiceResponse stream(ChatServiceContext chatServiceContext) { - - ChatServiceContext chatServiceContextOnStart = ChatServiceContext.from(chatServiceContext).build(); - - // Perform retrieval of documents and messages - for (PromptTransformer retriever : this.retrievers) { - chatServiceContext = retriever.transform(chatServiceContext); - } - - // Perform post processing of all retrieved documents and messages - for (PromptTransformer documentPostProcessor : this.documentPostProcessors) { - chatServiceContext = documentPostProcessor.transform(chatServiceContext); - } - - // Perform prompt augmentation - for (PromptTransformer augmentor : this.augmentors) { - chatServiceContext = augmentor.transform(chatServiceContext); - } - - // Invoke Listeners onStart - for (ChatServiceListener listener : this.chatServiceListeners) { - listener.onStart(chatServiceContextOnStart); - } - - // Perform generation - final var promptContext2 = chatServiceContext; - - Flux fluxChatResponse = new MessageAggregator() - .aggregate(this.streamingChatModel.stream(chatServiceContext.getPrompt()), chatResponse -> { - for (ChatServiceListener listener : this.chatServiceListeners) { - listener.onComplete(new ChatServiceResponse(promptContext2, chatResponse)); - } - }); - - // Invoke Listeners onComplete - return new StreamingChatServiceResponse(chatServiceContext, fluxChatResponse); - } - - public static class Builder { - - private StreamingChatModel chatModel; - - private List retrievers = new ArrayList<>(); - - private List documentPostProcessors = new ArrayList<>(); - - private List augmentors = new ArrayList<>(); - - private List chatServiceListeners = new ArrayList<>(); - - public Builder withChatModel(StreamingChatModel chatModel) { - this.chatModel = chatModel; - return this; - } - - public Builder withRetrievers(List retrievers) { - this.retrievers = retrievers; - return this; - } - - public Builder withDocumentPostProcessors(List documentPostProcessors) { - this.documentPostProcessors = documentPostProcessors; - return this; - } - - public Builder withAugmentors(List augmentors) { - this.augmentors = augmentors; - return this; - } - - public Builder withChatServiceListeners(List chatServiceListeners) { - this.chatServiceListeners = chatServiceListeners; - return this; - } - - public StreamingPromptTransformingChatService build() { - return new StreamingPromptTransformingChatService(chatModel, retrievers, documentPostProcessors, augmentors, - chatServiceListeners); - } - - } - -} \ No newline at end of file diff --git a/spring-ai-core/src/main/java/org/springframework/ai/evaluation/EvaluationRequest.java b/spring-ai-core/src/main/java/org/springframework/ai/evaluation/EvaluationRequest.java index 58fa353df..1beb2a5df 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/evaluation/EvaluationRequest.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/evaluation/EvaluationRequest.java @@ -1,28 +1,35 @@ package org.springframework.ai.evaluation; import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.model.Content; import java.util.List; import java.util.Objects; +/** + * Represents an evaluation request, which includes the user's text, a list of content + * data, and a chat response. The evaluation request is used to evaluate the relevance or + * correctness of the chat response based on the context. + * + * @author Mark Pollack + * @since 1.0.0 M1 + */ public class EvaluationRequest { - private final Prompt prompt; + private final String userText; private final List dataList; private final ChatResponse chatResponse; - public EvaluationRequest(Prompt prompt, List dataList, ChatResponse chatResponse) { - this.prompt = prompt; + public EvaluationRequest(String userText, List dataList, ChatResponse chatResponse) { + this.userText = userText; this.dataList = dataList; this.chatResponse = chatResponse; } - public Prompt getPrompt() { - return prompt; + public String getUserText() { + return this.userText; } public List getDataList() { @@ -35,8 +42,8 @@ public class EvaluationRequest { @Override public String toString() { - return "EvaluationRequest{" + "prompt=" + prompt + ", dataList=" + dataList + ", chatResponse=" + chatResponse - + '}'; + return "EvaluationRequest{" + "userText='" + userText + '\'' + ", dataList=" + dataList + ", chatResponse=" + + chatResponse + '}'; } @Override @@ -45,13 +52,13 @@ public class EvaluationRequest { return true; if (!(o instanceof EvaluationRequest that)) return false; - return Objects.equals(prompt, that.prompt) && Objects.equals(dataList, that.dataList) + return Objects.equals(userText, that.userText) && Objects.equals(dataList, that.dataList) && Objects.equals(chatResponse, that.chatResponse); } @Override public int hashCode() { - return Objects.hash(prompt, dataList, chatResponse); + return Objects.hash(userText, dataList, chatResponse); } } diff --git a/spring-ai-core/src/main/java/org/springframework/ai/evaluation/RelevancyEvaluator.java b/spring-ai-core/src/main/java/org/springframework/ai/evaluation/RelevancyEvaluator.java index c730cce14..650e32bd2 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/evaluation/RelevancyEvaluator.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/evaluation/RelevancyEvaluator.java @@ -1,18 +1,10 @@ package org.springframework.ai.evaluation; -import org.springframework.ai.chat.model.ChatModel; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.messages.Message; -import org.springframework.ai.chat.messages.MessageType; -import org.springframework.ai.chat.prompt.ChatOptions; -import org.springframework.ai.chat.prompt.ChatOptionsBuilder; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.chat.prompt.PromptTemplate; +import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.model.Content; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.stream.Collectors; public class RelevancyEvaluator implements Evaluator { @@ -29,32 +21,27 @@ public class RelevancyEvaluator implements Evaluator { Answer: " """; - private final ChatOptions chatOptions; + private final ChatClient.Builder chatClientBuilder; - private ChatModel chatModel; - - public RelevancyEvaluator(ChatModel chatModel) { - this(chatModel, ChatOptionsBuilder.builder().build()); - } - - public RelevancyEvaluator(ChatModel chatModel, ChatOptions chatOptions) { - this.chatModel = chatModel; - this.chatOptions = chatOptions; + public RelevancyEvaluator(ChatClient.Builder chatClientBuilder) { + this.chatClientBuilder = chatClientBuilder; } @Override public EvaluationResponse evaluate(EvaluationRequest evaluationRequest) { - var query = doGetUserQuestion(evaluationRequest); + var response = doGetResponse(evaluationRequest); var context = doGetSupportingData(evaluationRequest); - var promptTemplate = new PromptTemplate(DEFAULT_EVALUATION_PROMPT_TEXT); - Message message = promptTemplate - .createMessage(Map.of("query", query, "response", response, "context", context)); + String evaluationResponse = this.chatClientBuilder.build() + .prompt() + .user(userSpec -> userSpec.text(DEFAULT_EVALUATION_PROMPT_TEXT) + .param("query", evaluationRequest.getUserText()) + .param("response", response) + .param("context", context)) + .call() + .content(); - ChatResponse chatResponse = this.chatModel.call(new Prompt(message, this.chatOptions)); - - var evaluationResponse = chatResponse.getResult().getOutput().getContent(); boolean passing = false; float score = 0; if (evaluationResponse.toLowerCase().contains("yes")) { @@ -79,13 +66,4 @@ public class RelevancyEvaluator implements Evaluator { return supportingData; } - protected String doGetUserQuestion(EvaluationRequest evaluationRequest) { - List instructions = evaluationRequest.getPrompt().getInstructions(); - String userMessage = instructions.stream() - .filter(m -> m.getMessageType() == MessageType.USER) - .map(m -> m.getContent()) - .collect(Collectors.joining(System.lineSeparator())); - return userMessage; - } - } diff --git a/spring-ai-core/src/main/java/org/springframework/ai/image/ImageResponseMetadata.java b/spring-ai-core/src/main/java/org/springframework/ai/image/ImageResponseMetadata.java index 92d22a466..ede6fed38 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/image/ImageResponseMetadata.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/image/ImageResponseMetadata.java @@ -15,12 +15,18 @@ */ package org.springframework.ai.image; +import org.springframework.ai.chat.metadata.ChatResponseMetadata; import org.springframework.ai.model.ResponseMetadata; +import java.util.HashMap; + public interface ImageResponseMetadata extends ResponseMetadata { - ImageResponseMetadata NULL = new ImageResponseMetadata() { - }; + static class DefaultImageResponseMetadata extends HashMap implements ImageResponseMetadata { + + } + + ImageResponseMetadata NULL = new DefaultImageResponseMetadata(); default Long getCreated() { return System.currentTimeMillis(); diff --git a/spring-ai-core/src/main/java/org/springframework/ai/model/ResponseMetadata.java b/spring-ai-core/src/main/java/org/springframework/ai/model/ResponseMetadata.java index 554c6768f..b1516ec3f 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/model/ResponseMetadata.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/model/ResponseMetadata.java @@ -15,6 +15,8 @@ */ package org.springframework.ai.model; +import java.util.Map; + /** * Interface representing metadata associated with an AI model's response. This interface * is designed to provide additional information about the generative response from an AI @@ -25,6 +27,6 @@ package org.springframework.ai.model; * @author Mark Pollack * @since 0.8.0 */ -public interface ResponseMetadata { +public interface ResponseMetadata extends Map { } diff --git a/spring-ai-core/src/test/java/org/springframework/ai/chat/memory/ChatMemoryTests.java b/spring-ai-core/src/test/java/org/springframework/ai/chat/memory/ChatMemoryTests.java deleted file mode 100644 index c9920acbb..000000000 --- a/spring-ai-core/src/test/java/org/springframework/ai/chat/memory/ChatMemoryTests.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright 2024-2024 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.chat.memory; - -import java.util.List; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -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.StreamingChatModel; -import org.springframework.ai.chat.service.ChatServiceResponse; -import org.springframework.ai.chat.service.PromptTransformingChatService; -import org.springframework.ai.chat.messages.Message; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.chat.prompt.transformer.ChatServiceContext; -import org.springframework.ai.model.Content; -import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.when; - -/** - * @author Christian Tzolov - */ -@ExtendWith(MockitoExtension.class) -public class ChatMemoryTests { - - @Mock - ChatModel chatModel; - - @Mock - StreamingChatModel streamingChatModel; - - @Captor - ArgumentCaptor promptCaptor; - - @Test - public void chatMemoryMessageListAugmentor() { - - ChatMemory chatHistory = new InMemoryChatMemory(); - - PromptTransformingChatService chatService = PromptTransformingChatService.builder(chatModel) - .withRetrievers(List.of(ChatMemoryRetriever.builder().withChatHistory(chatHistory).build())) - .withContentPostProcessors( - List.of(new LastMaxTokenSizeContentTransformer(new JTokkitTokenCountEstimator(), 10))) - .withAugmentors(List.of(new MessageChatMemoryAugmentor())) - .withChatServiceListeners(List.of(new ChatMemoryChatServiceListener(chatHistory))) - .build(); - - chatModelUserMessages(chatService, chatHistory); - } - - @Test - public void chatMemorySystemPromptAugmentor() { - - ChatMemory chatHistory = new InMemoryChatMemory(); - - PromptTransformingChatService chatService = PromptTransformingChatService.builder(chatModel) - .withRetrievers(List.of(new ChatMemoryRetriever(chatHistory))) - .withContentPostProcessors( - List.of(new LastMaxTokenSizeContentTransformer(new JTokkitTokenCountEstimator(), 10))) - .withAugmentors(List.of(new SystemPromptChatMemoryAugmentor())) - .withChatServiceListeners(List.of(new ChatMemoryChatServiceListener(chatHistory))) - .build(); - - chatModelUserMessages(chatService, chatHistory); - } - - public void chatModelUserMessages(PromptTransformingChatService chatService, ChatMemory chatHistory) { - - when(chatModel.call(promptCaptor.capture())) - .thenReturn(new ChatResponse(List.of(new Generation("assistant:1")))) - .thenReturn(new ChatResponse(List.of(new Generation("assistant:2")))) - .thenReturn(new ChatResponse(List.of(new Generation("assistant:3")))); - - var promptContext = ChatServiceContext.builder() - .withConversationId("test-session-id") - .withPrompt(new Prompt( - List.of(new UserMessage("user:1"), new UserMessage("user:2"), new UserMessage("user:3"), - new UserMessage("user:4"), new UserMessage("user:5")))) - .build(); - - ChatServiceResponse response1 = chatService.call(promptContext); - - assertThat(response1.getChatResponse().getResult().getOutput().getContent()).isEqualTo("assistant:1"); - - List contents = response1.getPromptContext().getContents(); - assertThat(contents).hasSize(0); - - List history = chatHistory.get("test-session-id", 1000); - assertThat(history).hasSize(6); - - ChatServiceResponse response2 = chatService.call(ChatServiceContext.builder() - .withConversationId("test-session-id") - .withPrompt(new Prompt( - List.of(new UserMessage("user:6"), new UserMessage("user:7"), new UserMessage("user:8")))) - .build()); - - assertThat(response2.getChatResponse().getResult().getOutput().getContent()).isEqualTo("assistant:2"); - - history = chatHistory.get("test-session-id", 1000); - assertThat(history).hasSize(10); - - contents = response2.getPromptContext().getContents(); - assertThat(contents).hasSize(3); - assertThat(contents.get(0).getContent()).isEqualTo("user:4"); - assertThat(contents.get(1).getContent()).isEqualTo("user:5"); - assertThat(contents.get(2).getContent()).isEqualTo("assistant:1"); - - ChatServiceResponse response3 = chatService.call(ChatServiceContext.builder() - .withConversationId("test-session-id") - .withPrompt(new Prompt(List.of(new UserMessage("user:9")))).build()); - assertThat(response3.getChatResponse().getResult().getOutput().getContent()).isEqualTo("assistant:3"); - - history = chatHistory.get("test-session-id", 1000); - assertThat(history).hasSize(12); - - contents = response3.getPromptContext().getContents(); - assertThat(contents).hasSize(3); - assertThat(contents.get(0).getContent()).isEqualTo("user:7"); - assertThat(contents.get(1).getContent()).isEqualTo("user:8"); - assertThat(contents.get(2).getContent()).isEqualTo("assistant:2"); - } - -} diff --git a/spring-ai-test/src/main/java/org/springframework/ai/evaluation/BaseMemoryTest.java b/spring-ai-test/src/main/java/org/springframework/ai/evaluation/BaseMemoryTest.java deleted file mode 100644 index a5d65866e..000000000 --- a/spring-ai-test/src/main/java/org/springframework/ai/evaluation/BaseMemoryTest.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2024-2024 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.evaluation; - -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.ai.chat.prompt.transformer.ChatServiceContext; -import org.springframework.ai.chat.service.ChatService; -import org.springframework.ai.chat.service.StreamingChatService; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.prompt.Prompt; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Christian Tzolov - */ -public class BaseMemoryTest { - - protected final Logger logger = LoggerFactory.getLogger(getClass()); - - protected RelevancyEvaluator relevancyEvaluator; - - protected ChatService chatService; - - protected StreamingChatService streamingChatService; - - public BaseMemoryTest(RelevancyEvaluator relevancyEvaluator, ChatService chatService, - StreamingChatService streamingChatModel) { - this.relevancyEvaluator = relevancyEvaluator; - this.chatService = chatService; - this.streamingChatService = streamingChatModel; - } - - @Test - void memoryChatService() { - - var prompt = new Prompt(new UserMessage("my name John Vincent Atanasoff")); - ChatServiceContext chatServiceContext = new ChatServiceContext(prompt); - - var chatServiceResponse1 = this.chatService.call(chatServiceContext); - - logger.info("Response1: " + chatServiceResponse1.getChatResponse().getResult().getOutput().getContent()); - // response varies too much. - // assertThat(chatServiceResponse1.getChatResponse().getResult().getOutput().getContent()).contains("John"); - - var chatServiceResponse2 = this.chatService - .call(new ChatServiceContext(new Prompt(new String("What is my name?")))); - logger.info("Response2: " + chatServiceResponse2.getChatResponse().getResult().getOutput().getContent()); - assertThat(chatServiceResponse2.getChatResponse().getResult().getOutput().getContent()) - .contains("John Vincent Atanasoff"); - - EvaluationResponse evaluationResponse = this.relevancyEvaluator - .evaluate(chatServiceResponse2.toEvaluationRequest()); - logger.info("" + evaluationResponse); - } - - @Test - void memoryStreamingChatService() { - - var prompt = new Prompt(new UserMessage("my name John Vincent Atanasoff")); - ChatServiceContext chatServiceContext = new ChatServiceContext(prompt); - - var fluxChatServiceResponse1 = this.streamingChatService.stream(chatServiceContext); - - String chatServiceResponse1 = fluxChatServiceResponse1.getChatResponse() - .collectList() - .block() - .stream() - .filter(response -> response.getResult() != null) - .map(response -> response.getResult().getOutput().getContent()) - .collect(Collectors.joining()); - - logger.info("Response1: " + chatServiceResponse1); - // response varies too much assertThat(chatServiceResponse1).contains("John"); - - var fluxChatServiceResponse2 = this.streamingChatService - .stream(new ChatServiceContext(new Prompt(new String("What is my name?")))); - - String chatServiceResponse2 = fluxChatServiceResponse2.getChatResponse() - .collectList() - .block() - .stream() - .filter(response -> response.getResult() != null) - .map(response -> response.getResult().getOutput().getContent()) - .collect(Collectors.joining()); - - logger.info("Response2: " + chatServiceResponse2); - assertThat(chatServiceResponse2).contains("John Vincent Atanasoff"); - } - -}