This is an automated email from the ASF dual-hosted git repository. Croway pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
commit 7f943d3bbbf6925e198fc4cd8612ea16954111ee Author: Croway <[email protected]> AuthorDate: Mon May 11 11:59:43 2026 +0200 Adapt chat memory to Spring AI 1.1.6 mandatory conversationId Spring AI 1.1.6 removed default conversationId from memory advisors. Move memory advisor from default ChatClient advisors to per-request, activated only when CamelSpringAiChatConversationId header is set. --- .../src/main/docs/spring-ai-chat-component.adoc | 2 + .../springai/chat/SpringAiChatProducer.java | 50 +++++++++++----------- .../springai/chat/SpringAiChatMemoryIT.java | 4 +- 3 files changed, 29 insertions(+), 27 deletions(-) diff --git a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/main/docs/spring-ai-chat-component.adoc b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/main/docs/spring-ai-chat-component.adoc index 6b3054cc229e..80af04348f10 100644 --- a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/main/docs/spring-ai-chat-component.adoc +++ b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/main/docs/spring-ai-chat-component.adoc @@ -293,6 +293,8 @@ The component provides automatic conversation memory management via Spring AI's IMPORTANT: Do not configure both `chatMemory` and `chatMemoryVectorStore` on the same endpoint. If both are provided, `chatMemory` (MessageChatMemoryAdvisor) will take precedence. +NOTE: Chat memory is only activated when the `CamelSpringAiChatConversationId` header is set on the exchange. Without this header, the memory advisor is not applied, even if `chatMemory` or `chatMemoryVectorStore` is configured. This allows you to selectively enable memory on a per-request basis. + ==== Message-based Memory (ChatMemory) Configure a `ChatMemory` on the endpoint for automatic conversation tracking using traditional message window approach. This strategy keeps a configurable number of recent messages in memory. diff --git a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/main/java/org/apache/camel/component/springai/chat/SpringAiChatProducer.java b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/main/java/org/apache/camel/component/springai/chat/SpringAiChatProducer.java index 1503c594ef19..34b01bfedb9e 100644 --- a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/main/java/org/apache/camel/component/springai/chat/SpringAiChatProducer.java +++ b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/main/java/org/apache/camel/component/springai/chat/SpringAiChatProducer.java @@ -76,6 +76,7 @@ public class SpringAiChatProducer extends DefaultProducer { private ChatClient chatClient; private SpringAiChatMcpManager mcpManager; + private Advisor chatMemoryAdvisor; public SpringAiChatProducer(SpringAiChatEndpoint endpoint) { super(endpoint); @@ -120,6 +121,23 @@ public class SpringAiChatProducer extends DefaultProducer { this.chatClient = builder.build(); } + // Build chat memory advisor (added per-request only when conversationId is set) + ChatMemory chatMemory = getEndpoint().getConfiguration().getChatMemory(); + VectorStore chatMemoryVectorStore = getEndpoint().getConfiguration().getChatMemoryVectorStore(); + if (chatMemory != null && chatMemoryVectorStore != null) { + LOG.warn("Both chatMemory and chatMemoryVectorStore are configured. Using MessageChatMemoryAdvisor (chatMemory). " + + "Configure only one memory type."); + } + if (chatMemory != null) { + this.chatMemoryAdvisor = MessageChatMemoryAdvisor.builder(chatMemory).build(); + LOG.debug("MessageChatMemoryAdvisor available (activated per-request via conversationId header)"); + } else if (chatMemoryVectorStore != null) { + this.chatMemoryAdvisor = VectorStoreChatMemoryAdvisor.builder(chatMemoryVectorStore) + .defaultTopK(getEndpoint().getConfiguration().getTopK()) + .build(); + LOG.debug("VectorStoreChatMemoryAdvisor available with topK={}", getEndpoint().getConfiguration().getTopK()); + } + // Initialize MCP clients if configured Map<String, Object> mcpConfig = getEndpoint().getConfiguration().getMcpServer(); if (mcpConfig != null && !mcpConfig.isEmpty()) { @@ -613,10 +631,12 @@ public class SpringAiChatProducer extends DefaultProducer { LOG.debug("Added tool context with {} entries", toolContext.size()); } - // Apply conversation ID for chat memory if provided + // Add chat memory advisor per-request only when conversationId header is set String conversationId = exchange.getIn().getHeader(SpringAiChatConstants.CONVERSATION_ID, String.class); - if (conversationId != null) { - request.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId)); + if (conversationId != null && chatMemoryAdvisor != null) { + final String convId = conversationId; + request.advisors(chatMemoryAdvisor); + request.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, convId)); } // Apply SafeGuard advisor overrides if provided via headers @@ -1179,28 +1199,8 @@ public class SpringAiChatProducer extends DefaultProducer { advisors.add(safeguardAdvisor); } - // Add ChatMemory advisor if configured - ChatMemory chatMemory = getEndpoint().getConfiguration().getChatMemory(); - VectorStore chatMemoryVectorStore = getEndpoint().getConfiguration().getChatMemoryVectorStore(); - - if (chatMemory != null && chatMemoryVectorStore != null) { - LOG.warn("Both chatMemory and chatMemoryVectorStore are configured. Using MessageChatMemoryAdvisor (chatMemory). " + - "Configure only one memory type."); - } - - if (chatMemory != null) { - advisors.add(MessageChatMemoryAdvisor.builder(chatMemory).build()); - LOG.debug("MessageChatMemoryAdvisor enabled"); - } else if (chatMemoryVectorStore != null) { - // Configure VectorStoreChatMemoryAdvisor with conversation isolation - // The conversationId parameter enables automatic filtering by conversation ID - advisors.add(VectorStoreChatMemoryAdvisor.builder(chatMemoryVectorStore) - .conversationId(ChatMemory.CONVERSATION_ID) - .defaultTopK(getEndpoint().getConfiguration().getTopK()) - .build()); - LOG.debug("VectorStoreChatMemoryAdvisor enabled with conversation isolation and topK={}", - getEndpoint().getConfiguration().getTopK()); - } + // Chat memory advisors are NOT added to defaults — they are added per-request + // only when a conversationId header is present (see applyRequestOptions) // Add QuestionAnswerAdvisor if VectorStore is configured VectorStore vectorStore = getEndpoint().getConfiguration().getVectorStore(); diff --git a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/test/java/org/apache/camel/component/springai/chat/SpringAiChatMemoryIT.java b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/test/java/org/apache/camel/component/springai/chat/SpringAiChatMemoryIT.java index 154a0d25b6da..c666df6a6379 100644 --- a/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/test/java/org/apache/camel/component/springai/chat/SpringAiChatMemoryIT.java +++ b/components/camel-spring-parent/camel-spring-ai/camel-spring-ai-chat/src/test/java/org/apache/camel/component/springai/chat/SpringAiChatMemoryIT.java @@ -165,7 +165,7 @@ public class SpringAiChatMemoryIT extends OllamaTestSupport { // First message var exchange1 = template().request("direct:chat-with-auto-memory", e -> { e.getIn().setBody("My favorite number is 42. Please remember this."); - e.getIn().setHeader("conversationId", conversationId); + e.getIn().setHeader(SpringAiChatConstants.CONVERSATION_ID, conversationId); }); String response1 = exchange1.getMessage().getBody(String.class); @@ -174,7 +174,7 @@ public class SpringAiChatMemoryIT extends OllamaTestSupport { // Second message - the advisor should remember the context var exchange2 = template().request("direct:chat-with-auto-memory", e -> { e.getIn().setBody("What is my favorite number? Answer with just the number."); - e.getIn().setHeader("conversationId", conversationId); + e.getIn().setHeader(SpringAiChatConstants.CONVERSATION_ID, conversationId); }); String response2 = exchange2.getMessage().getBody(String.class);
