Description
When Quarkus LangChain4j (QL4J) is on the classpath and a default ChatModel is configured via quarkus.langchain4j.* properties, QL4J's ChatMemoryProvider silently leaks into Camel's AgentWithoutMemory, making it stateful despite the user's explicit intent for stateless behavior.
Root Cause
QL4J replaces AiServices.builder() via SPI (AiServicesFactory) in JVM mode and via GraalVM @Substitute in native mode. When Camel calls AiServices.builder(AiAgentWithoutMemoryService.class):
-
QL4J's AiServicesProcessor.determinedImpliedRegisterAiService() discovers AiAgentWithoutMemoryService because it has @UserMessage and @SystemMessage annotations. The AGENTIC_PACKAGE_PREFIX exclusion (dev.langchain4j.agentic.*) does not match Camel's package (org.apache.camel.component.langchain4j.agent.api).
-
QL4J creates a synthetic QuarkusAiServiceContext CDI bean for this interface. Since no explicit chatMemoryProviderSupplier is set on the implied registration, it defaults to BEAN_CHAT_MEMORY_PROVIDER_SUPPLIER — meaning "inject ChatMemoryProvider from CDI."
-
ChatMemoryProcessor always creates a default @ApplicationScoped ChatMemoryProvider (MessageWindowChatMemory). This gets injected into the synthetic context.
-
QuarkusAiServiceContextFactory.create() returns this pre-configured context (with ChatMemoryProvider already set) to Camel's builder.
-
Camel's AgentWithoutMemory.createAiAgentService() only calls .chatModel(config.getChatModel()) — it never resets chatMemoryProvider. The QL4J-injected ChatMemoryProvider survives into the built proxy.
Impact
Since AiAgentWithoutMemoryService has no @MemoryId parameter, the memory ID falls back to either a request-scope hash (HTTP routes) or the literal string "default" (non-HTTP routes like timer, file, kafka). This causes:
- Non-HTTP routes: All
AgentWithoutMemory instances across the application share a single global chat memory. Messages from unrelated conversations contaminate each other.
- HTTP routes: Per-request isolation, but
InMemoryChatMemoryStore entries are never evicted (memory leak).
- All routes: Each call sends up to 9 prior unrelated messages to the LLM (default window size = 10), increasing token cost, latency, and producing incorrect responses.
No error or warning is logged. The agent silently behaves differently than its name and contract imply.
Steps to Reproduce
- Add
quarkus-langchain4j-ollama (or another QL4J model provider) as a dependency
- Configure a default model:
quarkus.langchain4j.ollama.base-url=http://localhost:11434
quarkus.langchain4j.ollama.chat-model.model-id=llama3.2
- Create an
AgentWithoutMemory with a Camel-managed ChatModel:
new AgentWithoutMemory(new AgentConfiguration().withChatModel(chatModel))
- Send multiple unrelated messages through the agent
- Observe that responses reference content from previous, unrelated messages
Expected Behavior
AgentWithoutMemory should be stateless — each call processed independently with no conversation history, regardless of whether QL4J is on the classpath.
Why Current Tests Don't Catch This
The langchain4j-agent-ql4j integration test uses @Identifier-qualified ChatModel beans and does not configure a default QL4J model via properties. Without a default ChatModel, the CDI context bean creation fails and QuarkusAiServiceContextFactory falls back to a clean empty context. The ChatMemoryProvider doesn't leak — but only by accident, not by design.
Additional Context
The langchain4j-agent-ql4j test README acknowledges related interference: RetrievalAugmentor had to be disabled (cq-test.retrieval.augmentor.disabled=true) because "Testing with quarkus-langchain4j does not work when the RetrievalAugmentor bean is present with scope ."
Description
When Quarkus LangChain4j (QL4J) is on the classpath and a default
ChatModelis configured viaquarkus.langchain4j.*properties, QL4J'sChatMemoryProvidersilently leaks into Camel'sAgentWithoutMemory, making it stateful despite the user's explicit intent for stateless behavior.Root Cause
QL4J replaces
AiServices.builder()via SPI (AiServicesFactory) in JVM mode and via GraalVM@Substitutein native mode. When Camel callsAiServices.builder(AiAgentWithoutMemoryService.class):QL4J's
AiServicesProcessor.determinedImpliedRegisterAiService()discoversAiAgentWithoutMemoryServicebecause it has@UserMessageand@SystemMessageannotations. TheAGENTIC_PACKAGE_PREFIXexclusion (dev.langchain4j.agentic.*) does not match Camel's package (org.apache.camel.component.langchain4j.agent.api).QL4J creates a synthetic
QuarkusAiServiceContextCDI bean for this interface. Since no explicitchatMemoryProviderSupplieris set on the implied registration, it defaults toBEAN_CHAT_MEMORY_PROVIDER_SUPPLIER— meaning "injectChatMemoryProviderfrom CDI."ChatMemoryProcessoralways creates a default@ApplicationScopedChatMemoryProvider(MessageWindowChatMemory). This gets injected into the synthetic context.QuarkusAiServiceContextFactory.create()returns this pre-configured context (withChatMemoryProvideralready set) to Camel's builder.Camel's
AgentWithoutMemory.createAiAgentService()only calls.chatModel(config.getChatModel())— it never resetschatMemoryProvider. The QL4J-injectedChatMemoryProvidersurvives into the built proxy.Impact
Since
AiAgentWithoutMemoryServicehas no@MemoryIdparameter, the memory ID falls back to either a request-scope hash (HTTP routes) or the literal string"default"(non-HTTP routes like timer, file, kafka). This causes:AgentWithoutMemoryinstances across the application share a single global chat memory. Messages from unrelated conversations contaminate each other.InMemoryChatMemoryStoreentries are never evicted (memory leak).No error or warning is logged. The agent silently behaves differently than its name and contract imply.
Steps to Reproduce
quarkus-langchain4j-ollama(or another QL4J model provider) as a dependencyAgentWithoutMemorywith a Camel-managedChatModel:Expected Behavior
AgentWithoutMemoryshould be stateless — each call processed independently with no conversation history, regardless of whether QL4J is on the classpath.Why Current Tests Don't Catch This
The
langchain4j-agent-ql4jintegration test uses@Identifier-qualifiedChatModelbeans and does not configure a default QL4J model via properties. Without a defaultChatModel, the CDI context bean creation fails andQuarkusAiServiceContextFactoryfalls back to a clean empty context. TheChatMemoryProviderdoesn't leak — but only by accident, not by design.Additional Context
The
langchain4j-agent-ql4jtest README acknowledges related interference:RetrievalAugmentorhad to be disabled (cq-test.retrieval.augmentor.disabled=true) because "Testing with quarkus-langchain4j does not work when the RetrievalAugmentor bean is present with scope ."