From 39321e5301aabf0a3be548d30fec4feef8ef8f90 Mon Sep 17 00:00:00 2001 From: Andrea Cosentino Date: Thu, 3 Sep 2026 11:40:21 +0200 Subject: [PATCH 1/2] CAMEL-24614: camel-langchain4j-agent - fail fast when no agent can be resolved When an endpoint has no agent, agentConfiguration or agentFactory and no registry bean matches its agentId, lookupByNameAndType returns null and the producer later threw an opaque NullPointerException in process() at agent.chat(). Validate in doStart that an agent (or agentFactory) is available and throw a clear IllegalArgumentException naming the endpoint and how to configure it. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Ka4dAcJMpxahMfk3kmG5Ls Signed-off-by: Andrea Cosentino --- .../agent/LangChain4jAgentProducer.java | 10 ++++ .../LangChain4jAgentMissingAgentTest.java | 52 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentMissingAgentTest.java diff --git a/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentProducer.java b/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentProducer.java index 97aa2560e81bd..ec1461d1b6808 100644 --- a/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentProducer.java +++ b/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentProducer.java @@ -589,6 +589,16 @@ protected void doStart() throws Exception { LOG.debug("Materialized {} MCP clients from server definitions", materializedMcpClients.size()); } } + + // Fail fast with a clear message instead of a later NullPointerException in process() when nothing + // resolves to an agent (no agent/agentConfiguration/agentFactory and no matching registry bean). + if (agent == null && agentFactory == null) { + throw new IllegalArgumentException( + "No agent could be resolved for endpoint " + endpoint.getEndpointUri() + + ". Configure 'agent', 'agentConfiguration' or 'agentFactory', or bind a bean named '" + + endpoint.getAgentId() + "' of type " + Agent.class.getName() + + " in the registry."); + } } /** diff --git a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentMissingAgentTest.java b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentMissingAgentTest.java new file mode 100644 index 0000000000000..a20e57c29fc3f --- /dev/null +++ b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentMissingAgentTest.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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.apache.camel.component.langchain4j.agent; + +import org.apache.camel.Producer; +import org.apache.camel.impl.DefaultCamelContext; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class LangChain4jAgentMissingAgentTest { + + @Test + void missingAgentFailsFastWithAClearError() throws Exception { + try (DefaultCamelContext context = new DefaultCamelContext()) { + context.start(); + + // No agent / agentConfiguration / agentFactory, and no registry bean named "noSuchAgent". + LangChain4jAgentEndpoint endpoint + = context.getEndpoint("langchain4j-agent:noSuchAgent", LangChain4jAgentEndpoint.class); + Producer producer = endpoint.createProducer(); + + Exception ex = assertThrows(Exception.class, producer::start); + Throwable cause = ex; + boolean clearError = false; + while (cause != null) { + if (cause instanceof IllegalArgumentException && cause.getMessage() != null + && cause.getMessage().contains("No agent could be resolved")) { + clearError = true; + break; + } + cause = cause.getCause(); + } + assertTrue(clearError, "expected a clear 'No agent could be resolved' IllegalArgumentException, got: " + ex); + } + } +} From 361728b13a9f8a15ae067a8bf772928637834d06 Mon Sep 17 00:00:00 2001 From: Andrea Cosentino Date: Fri, 4 Sep 2026 09:14:07 +0200 Subject: [PATCH 2/2] CAMEL-24614: camel-langchain4j-agent - validate the agent at process time to support late endpoint configuration The producer resolved the agent once at doStart() and failed fast there when nothing resolved. That broke the supported pattern of configuring an agent on the endpoint after the route has started (e.g. tests and dynamic wiring set endpoint.getConfiguration().setAgent(...) post-start). Move the resolution and the clear error to process(): re-resolve from the endpoint configuration when no agent/agentFactory is set, and throw a descriptive IllegalArgumentException instead of an opaque NullPointerException only when nothing can be resolved. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_011y1gCrVvA3FowKoRM9EvmT Signed-off-by: Andrea Cosentino --- .../agent/LangChain4jAgentProducer.java | 22 ++++++++------- .../LangChain4jAgentMissingAgentTest.java | 28 ++++++++++++------- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentProducer.java b/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentProducer.java index ec1461d1b6808..5dbd4b6fecc14 100644 --- a/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentProducer.java +++ b/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentProducer.java @@ -164,6 +164,18 @@ public void process(Exchange exchange) throws Exception { String tags = endpoint.getConfiguration().getTags(); Agent agent = agentFactory != null ? agentFactory.createAgent(exchange) : this.agent; + if (agent == null && agentFactory == null) { + // Support an agent configured on the endpoint after the route started, and give a clear error + // instead of an opaque NullPointerException when nothing resolves to an agent. + agent = endpoint.getConfiguration().getAgent(); + if (agent == null) { + throw new IllegalArgumentException( + "No agent could be resolved for endpoint " + endpoint.getEndpointUri() + + ". Configure 'agent', 'agentConfiguration' or 'agentFactory', or bind a bean named '" + + endpoint.getAgentId() + "' of type " + Agent.class.getName() + + " in the registry."); + } + } AiAgentBody aiAgentBody = exchange.getMessage().getMandatoryBody(AiAgentBody.class); @@ -589,16 +601,6 @@ protected void doStart() throws Exception { LOG.debug("Materialized {} MCP clients from server definitions", materializedMcpClients.size()); } } - - // Fail fast with a clear message instead of a later NullPointerException in process() when nothing - // resolves to an agent (no agent/agentConfiguration/agentFactory and no matching registry bean). - if (agent == null && agentFactory == null) { - throw new IllegalArgumentException( - "No agent could be resolved for endpoint " + endpoint.getEndpointUri() - + ". Configure 'agent', 'agentConfiguration' or 'agentFactory', or bind a bean named '" - + endpoint.getAgentId() + "' of type " + Agent.class.getName() - + " in the registry."); - } } /** diff --git a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentMissingAgentTest.java b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentMissingAgentTest.java index a20e57c29fc3f..76c2549429d9b 100644 --- a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentMissingAgentTest.java +++ b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentMissingAgentTest.java @@ -16,27 +16,34 @@ */ package org.apache.camel.component.langchain4j.agent; -import org.apache.camel.Producer; +import org.apache.camel.Exchange; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.langchain4j.agent.api.AiAgentBody; import org.apache.camel.impl.DefaultCamelContext; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; class LangChain4jAgentMissingAgentTest { @Test - void missingAgentFailsFastWithAClearError() throws Exception { + void missingAgentReportsAClearError() throws Exception { try (DefaultCamelContext context = new DefaultCamelContext()) { + context.addRoutes(new RouteBuilder() { + @Override + public void configure() { + // No agent / agentConfiguration / agentFactory, and no registry bean named "noSuchAgent". + from("direct:x").to("langchain4j-agent:noSuchAgent"); + } + }); context.start(); - // No agent / agentConfiguration / agentFactory, and no registry bean named "noSuchAgent". - LangChain4jAgentEndpoint endpoint - = context.getEndpoint("langchain4j-agent:noSuchAgent", LangChain4jAgentEndpoint.class); - Producer producer = endpoint.createProducer(); + Exchange result = context.createProducerTemplate() + .request("direct:x", e -> e.getIn().setBody(new AiAgentBody<>("hello"))); - Exception ex = assertThrows(Exception.class, producer::start); - Throwable cause = ex; + Throwable cause = result.getException(); + assertNotNull(cause, "an error was expected"); boolean clearError = false; while (cause != null) { if (cause instanceof IllegalArgumentException && cause.getMessage() != null @@ -46,7 +53,8 @@ void missingAgentFailsFastWithAClearError() throws Exception { } cause = cause.getCause(); } - assertTrue(clearError, "expected a clear 'No agent could be resolved' IllegalArgumentException, got: " + ex); + assertTrue(clearError, "expected a clear 'No agent could be resolved' IllegalArgumentException, got: " + + result.getException()); } } }