jamesnetherton opened a new issue, #9172:
URL: https://github.com/apache/camel-quarkus/issues/9172
I don't think we have clearly defined how Camel LangChain4j agents should
interact with Quarkus LangChain4j CDI beans and implied AI service registration.
## Background
QL4J treats any interface in the index that carries `@UserMessage`,
`@SystemMessage` or `@Moderate` as if it had
`@RegisterAiService`. Camel's `AiAgentWithMemoryService` and
`AiAgentWithoutMemoryService` carry those annotations, so
without an exclusion both are picked up automatically. The only built-in
exception is interfaces under
`dev.langchain4j.agentic.*`.
Extensions can opt interfaces out by producing
`ExcludeFromImpliedAiServiceBuildItem`, available in QL4J 1.13.x or
later.
## What the exclusion changes
| | Not excluded | Excluded |
|---|---|---|
| Synthetic `QuarkusAiServiceContext` bean for the interface (`@Dependent`,
qualified by interface name) | Created | Not created |
| `@DefaultBean` CDI bean you can inject for the interface | Created | Not
created |
| Build-time requests for a default `ChatModel`, a `ChatMemoryProvider`, and
a `RetrievalAugmentor` / `ToolProvider` / `ToolSearchStrategy` if one exists |
Made | Not made |
| What `AiServices.builder(X.class)` starts from at runtime
(`QuarkusAiServiceContextFactory.create()`) | The CDI context, already filled
in | A new empty `QuarkusAiServiceContext` |
## What the exclusion doesn't change
- **Generated service code:** the class metadata and `$$QuarkusImpl` are
still generated. Those interfaces are found
through their annotations and by scanning `AiServices` call sites, and
that code ignores the exclusion.
`AiServices.builder(...).build()` keeps working.
- **Per-method features:** metrics, OpenTelemetry spans, tool metadata and
guardrail handling are part of the
generated per-method code, not the context bean.
- **Camel's own settings:** everything Camel sets through
`AgentConfiguration` is applied the same way.
## What the context is pre-filled with
When the interface isn't excluded, the context Camel's builder receives
already has these values from CDI:
| Field | Filled from CDI with | Replaced by Camel? |
|---|---|---|
| `chatModel` | Default configured model | Always
(`.chatModel(config.getChatModel())`) |
| `chatMemoryProvider` | QL4J's CDI `ChatMemoryProvider` (default
`MessageWindowChatMemory`) | `AgentWithMemory`: yes, if the config has one.
`AgentWithoutMemory`: never. LangChain4j ignores `chatMemoryProvider(null)`, so
it can't be cleared. |
| `retrievalAugmentor` | Unqualified `RetrievalAugmentor` bean, if one
exists | Only if RAG is configured |
| `toolProvider` | Unqualified `ToolProvider` bean, if one exists (e.g.
`CamelAiToolProvider`) | Only if the agent has Camel tools or MCP clients |
| `toolSearchStrategy` | Bean, if one exists | Never |
Whatever Camel doesn't replace stays in effect. Which fields end up
inherited therefore depends on what Camel happens
to set, not on a deliberate design.
## Current state
| Agent | Before the #8836 fix | After the #8836 fix |
|---|---|---|
| `AgentWithoutMemory` | Partly inherits, including the `ChatMemoryProvider`
| Isolated |
| `AgentWithMemory` | Partly inherits (`RetrievalAugmentor`, `ToolProvider`,
`ToolSearchStrategy`) | Partly inherits |
Neither agent fully inherits QL4J defaults:
- The chat model is always replaced by Camel.
- The memory provider is always replaced for inline and auto-provisioned
`AgentWithMemory` agents.
- `CamelAiToolProvider` blocks itself from both agent interfaces.
In practice the beans that reach a Camel agent are a user's own unqualified
`RetrievalAugmentor`, `ToolProvider` or
`ToolSearchStrategy`. Before the #8836 fix, the memory leak came on top of
that.
Existing code has mostly treated this inheritance as unwanted:
- **`CamelAiToolProvider.isCamelAgentRequest()`** skips both Camel agent
interfaces.
- **`integration-tests/langchain4j-agent-ql4j`** uses `@Identifier`
qualifiers "to prevent QL4J's automatic bean
discovery from interfering with explicit Camel Agent configuration".
- **An earlier version of that test module** disabled a default-scoped
`RetrievalAugmentor` bean because QL4J applied
it to Camel agents.
None of this was recorded as a design decision.
## Options
### 1. Isolated
Exclude both agent service interfaces from implied registration. Camel
agents use only what is configured on
`AgentConfiguration`.
- Same behaviour with or without QL4J, and the same as plain Camel.
- No beans are attached implicitly.
- Users who want app-wide CDI beans on an agent configure them through
`AgentConfiguration`.
- Behaviour changes for `AgentWithMemory`:
- CDI `RetrievalAugmentor` / `ToolProvider` / `ToolSearchStrategy` beans
are no longer applied.
- An `AgentWithMemory` created without a `ChatMemoryProvider` no longer
falls back to QL4J's default in-memory
`ChatMemoryProvider`.
- `@Inject AiAgentWithMemoryService` becomes an unsatisfied dependency
(unlikely to be used, it's an internal Camel
interface).
### 2. Inherit
Deliberately apply QL4J CDI defaults to Camel agents, the same way
`@RegisterAiService` services get them.
- More in line with Quarkus conventions.
- Needs defined, documented rules for how `AgentConfiguration` and CDI beans
combine.
- `AgentWithoutMemory` must still never inherit a `ChatMemoryProvider`
(#8836).
- `AiAgentWithoutMemoryService` would stay excluded, and the other fields
would need to be applied another way.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]