apache / apache/camel-quarkus

Quarkus LangChain4j ChatMemoryProvider silently leaks into AgentWithoutMemory when default model is configured

Closed
#8,836 4 comments 0 reactions 1 assignee Claimed by @JiriOndrusek View on GitHub
area/langchain4j-agent area/native bug native
Dominant language
Java
Stars
302
Forks
232
Avg merge
1d 20h
Merged PRs (30d)
114

Description

## 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)`:

1. 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`).

2. 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."

3. `ChatMemoryProcessor` always creates a default `@ApplicationScoped` `ChatMemoryProvider` (`MessageWindowChatMemory`). This gets injected into the synthetic context.

4. `QuarkusAiServiceContextFactory.create()` returns this pre-configured context (with `ChatMemoryProvider` already set) to Camel's builder.

5. 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

1. Add `quarkus-langchain4j-ollama` (or another QL4J model provider) as a dependency
2. Configure a default model:
```properties
quarkus.langchain4j.ollama.base-url=http://localhost:11434
quarkus.langchain4j.ollama.chat-model.model-id=llama3.2
```
3. Create an `AgentWithoutMemory` with a Camel-managed `ChatModel`:
```java
new AgentWithoutMemory(new AgentConfiguration().withChatModel(chatModel))
```
4. Send multiple unrelated messages through the agent
5. 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 ."

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.