This is an automated email from the ASF dual-hosted git repository. orpiske pushed a commit to branch camel-4.14.x in repository https://gitbox.apache.org/repos/asf/camel.git
commit eda29bc13c92daff2160ef71d397512e4463f43d Author: Otavio Rodolfo Piske <[email protected]> AuthorDate: Mon Sep 1 13:38:35 2025 +0200 CAMEL-22398: allow agents to define the specifics of the exchange body --- .../catalog/components/langchain4j-agent.json | 4 +- .../component/langchain4j/agent/api/Agent.java | 59 ++++++++++++++++++++++ .../langchain4j/agent/api/AgentFactory.java | 3 +- .../component/langchain4j/agent/api}/Headers.java | 2 +- .../langchain4j/agent/langchain4j-agent.json | 4 +- .../agent/LangChain4jAgentEndpoint.java | 1 + .../agent/LangChain4jAgentProducer.java | 33 ++---------- .../integration/LangChain4jAgentNaiveRagIT.java | 2 +- .../integration/LangChain4jAgentWithMemoryIT.java | 4 +- .../integration/LangChain4jSimpleAgentIT.java | 2 +- 10 files changed, 75 insertions(+), 39 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-agent.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-agent.json index 7edf39e80f9..a02042ed669 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-agent.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-agent.json @@ -32,8 +32,8 @@ "autowiredEnabled": { "index": 5, "kind": "property", "displayName": "Autowired Enabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching t [...] }, "headers": { - "CamelLangChain4jAgentSystemMessage": { "index": 0, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The system prompt.", "constantName": "org.apache.camel.component.langchain4j.agent.Headers#SYSTEM_MESSAGE" }, - "CamelLangChain4jAgentMemoryId": { "index": 1, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Object", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Memory ID.", "constantName": "org.apache.camel.component.langchain4j.agent.Headers#MEMORY_ID" } + "CamelLangChain4jAgentSystemMessage": { "index": 0, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The system prompt.", "constantName": "org.apache.camel.component.langchain4j.agent.api.Headers#SYSTEM_MESSAGE" }, + "CamelLangChain4jAgentMemoryId": { "index": 1, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Object", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Memory ID.", "constantName": "org.apache.camel.component.langchain4j.agent.api.Headers#MEMORY_ID" } }, "properties": { "agentId": { "index": 0, "kind": "path", "displayName": "Agent Id", "group": "producer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The Agent id" }, diff --git a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/Agent.java b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/Agent.java index fff853701a7..8df5577e954 100644 --- a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/Agent.java +++ b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/Agent.java @@ -17,6 +17,11 @@ package org.apache.camel.component.langchain4j.agent.api; import dev.langchain4j.service.tool.ToolProvider; +import org.apache.camel.Exchange; +import org.apache.camel.InvalidPayloadRuntimeException; + +import static org.apache.camel.component.langchain4j.agent.api.Headers.MEMORY_ID; +import static org.apache.camel.component.langchain4j.agent.api.Headers.SYSTEM_MESSAGE; /** * Core agent interface that abstracts different types of AI agents within the Apache Camel LangChain4j integration. @@ -44,6 +49,60 @@ import dev.langchain4j.service.tool.ToolProvider; */ public interface Agent { + /** + * Processes and normalizes the exchange message payload into an {@link AiAgentBody} instance. + * + * <p> + * This method serves as a payload adapter that ensures consistent input format for AI agent chat interactions. It + * handles different payload types and automatically extracts relevant headers to construct a properly formatted + * {@link AiAgentBody} object. + * </p> + * + * <p> + * The method performs the following transformations: + * </p> + * <ul> + * <li>If the payload is already an {@link AiAgentBody}, it returns it unchanged</li> + * <li>If the payload is a {@link String}, it creates a new {@link AiAgentBody} with: + * <ul> + * <li>The string as the user message</li> + * <li>The {@link Headers#SYSTEM_MESSAGE} header value as the system message (if present)</li> + * <li>The {@link Headers#MEMORY_ID} header value as the memory identifier (if present)</li> + * </ul> + * </li> + * <li>For any other payload type, it throws an {@link InvalidPayloadRuntimeException}</li> + * </ul> + * + * <p> + * This method is typically called automatically by the LangChain4j agent component before invoking the + * {@link #chat(AiAgentBody, ToolProvider)} method, ensuring that the agent always receives a properly structured + * request body regardless of how the original message was formatted. + * </p> + * + * @param messagePayload the message payload from the exchange body; must be either an + * {@link AiAgentBody} or a {@link String} + * @param exchange the Camel exchange containing headers and context information + * @return an {@link AiAgentBody} instance ready for agent processing; returns the + * original payload if it's already an {@link AiAgentBody}, or creates a new + * one from a string payload and relevant headers + * @throws InvalidPayloadRuntimeException if the payload is neither an {@link AiAgentBody} nor a {@link String} + * @throws Exception if any other error occurs during payload processing + */ + default AiAgentBody processBody(Object messagePayload, Exchange exchange) throws Exception { + if (messagePayload instanceof AiAgentBody) { + return (AiAgentBody) messagePayload; + } + + if (!(messagePayload instanceof String)) { + throw new InvalidPayloadRuntimeException(exchange, AiAgentBody.class); + } + + String systemMessage = exchange.getIn().getHeader(SYSTEM_MESSAGE, String.class); + Object memoryId = exchange.getIn().getHeader(MEMORY_ID); + + return new AiAgentBody((String) messagePayload, systemMessage, memoryId); + } + /** * Executes a chat interaction with the AI agent using the provided request body and tool provider. * diff --git a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentFactory.java b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentFactory.java index 61cb3e6cfa4..c14f3ce8272 100644 --- a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentFactory.java +++ b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentFactory.java @@ -64,7 +64,8 @@ public interface AgentFactory extends CamelContextAware { * configuration remains unchanged. The returned agent will be fully configured and ready to handle chat * interactions. * </p> - * @param exchange the exchange being processed which is triggering the creation of the agent + * + * @param exchange the exchange being processed which is triggering the creation of the agent * * @return a configured {@link Agent} instance ready for chat interactions * @throws Exception if unable to create the agent due to configuration issues, missing dependencies, or diff --git a/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/Headers.java b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/Headers.java similarity index 95% rename from components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/Headers.java rename to components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/Headers.java index dec5e0d5d0a..5e53a96dcd5 100644 --- a/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/Headers.java +++ b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/Headers.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.camel.component.langchain4j.agent; +package org.apache.camel.component.langchain4j.agent.api; import org.apache.camel.spi.Metadata; diff --git a/components/camel-ai/camel-langchain4j-agent/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/agent/langchain4j-agent.json b/components/camel-ai/camel-langchain4j-agent/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/agent/langchain4j-agent.json index 7edf39e80f9..a02042ed669 100644 --- a/components/camel-ai/camel-langchain4j-agent/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/agent/langchain4j-agent.json +++ b/components/camel-ai/camel-langchain4j-agent/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/agent/langchain4j-agent.json @@ -32,8 +32,8 @@ "autowiredEnabled": { "index": 5, "kind": "property", "displayName": "Autowired Enabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching t [...] }, "headers": { - "CamelLangChain4jAgentSystemMessage": { "index": 0, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The system prompt.", "constantName": "org.apache.camel.component.langchain4j.agent.Headers#SYSTEM_MESSAGE" }, - "CamelLangChain4jAgentMemoryId": { "index": 1, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Object", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Memory ID.", "constantName": "org.apache.camel.component.langchain4j.agent.Headers#MEMORY_ID" } + "CamelLangChain4jAgentSystemMessage": { "index": 0, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The system prompt.", "constantName": "org.apache.camel.component.langchain4j.agent.api.Headers#SYSTEM_MESSAGE" }, + "CamelLangChain4jAgentMemoryId": { "index": 1, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Object", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Memory ID.", "constantName": "org.apache.camel.component.langchain4j.agent.api.Headers#MEMORY_ID" } }, "properties": { "agentId": { "index": 0, "kind": "path", "displayName": "Agent Id", "group": "producer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The Agent id" }, diff --git a/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentEndpoint.java b/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentEndpoint.java index 9aa3d1c8fa5..65583b13b1d 100644 --- a/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentEndpoint.java +++ b/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentEndpoint.java @@ -21,6 +21,7 @@ import org.apache.camel.Component; import org.apache.camel.Consumer; import org.apache.camel.Processor; import org.apache.camel.Producer; +import org.apache.camel.component.langchain4j.agent.api.Headers; import org.apache.camel.spi.Metadata; import org.apache.camel.spi.UriEndpoint; import org.apache.camel.spi.UriParam; 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 2e7a920037b..09537d4d7bc 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 @@ -27,7 +27,6 @@ import dev.langchain4j.service.tool.ToolProvider; import dev.langchain4j.service.tool.ToolProviderRequest; import dev.langchain4j.service.tool.ToolProviderResult; import org.apache.camel.Exchange; -import org.apache.camel.InvalidPayloadRuntimeException; import org.apache.camel.component.langchain4j.agent.api.Agent; import org.apache.camel.component.langchain4j.agent.api.AgentFactory; import org.apache.camel.component.langchain4j.agent.api.AiAgentBody; @@ -38,9 +37,6 @@ import org.apache.camel.util.ObjectHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.apache.camel.component.langchain4j.agent.Headers.MEMORY_ID; -import static org.apache.camel.component.langchain4j.agent.Headers.SYSTEM_MESSAGE; - public class LangChain4jAgentProducer extends DefaultProducer { private static final Logger LOG = LoggerFactory.getLogger(LangChain4jAgentProducer.class); @@ -58,6 +54,9 @@ public class LangChain4jAgentProducer extends DefaultProducer { Object messagePayload = exchange.getIn().getBody(); ObjectHelper.notNull(messagePayload, "body"); + // tags for Camel Routes as Tools + String tags = endpoint.getConfiguration().getTags(); + Agent agent; if (agentFactory != null) { agent = agentFactory.createAgent(exchange); @@ -65,37 +64,13 @@ public class LangChain4jAgentProducer extends DefaultProducer { agent = endpoint.getConfiguration().getAgent(); } - AiAgentBody aiAgentBody = processBody(messagePayload, exchange); - - // tags for Camel Routes as Tools - String tags = endpoint.getConfiguration().getTags(); + AiAgentBody aiAgentBody = agent.processBody(messagePayload, exchange); ToolProvider toolProvider = createCamelToolProvider(tags, exchange); String response = agent.chat(aiAgentBody, toolProvider); exchange.getMessage().setBody(response); } - /** - * No matter if the user uses an AiAgentBody or headers, we manipulate an AiAgentBody - * - * @param messagePayload - * @param exchange - */ - private AiAgentBody processBody(Object messagePayload, Exchange exchange) { - if (messagePayload instanceof AiAgentBody) { - return (AiAgentBody) messagePayload; - } - - if (!(messagePayload instanceof String)) { - throw new InvalidPayloadRuntimeException(exchange, AiAgentBody.class); - } - - String systemMessage = exchange.getIn().getHeader(SYSTEM_MESSAGE, String.class); - Object memoryId = exchange.getIn().getHeader(MEMORY_ID); - - return new AiAgentBody((String) messagePayload, systemMessage, memoryId); - } - /** * We create our own Tool Provider * diff --git a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentNaiveRagIT.java b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentNaiveRagIT.java index 7f1116e6d05..d20a2423730 100644 --- a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentNaiveRagIT.java +++ b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentNaiveRagIT.java @@ -26,7 +26,7 @@ import org.apache.camel.component.mock.MockEndpoint; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIf; -import static org.apache.camel.component.langchain4j.agent.Headers.SYSTEM_MESSAGE; +import static org.apache.camel.component.langchain4j.agent.api.Headers.SYSTEM_MESSAGE; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentWithMemoryIT.java b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentWithMemoryIT.java index e65cd370fe3..4a7b7f82a01 100644 --- a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentWithMemoryIT.java +++ b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentWithMemoryIT.java @@ -33,8 +33,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIf; -import static org.apache.camel.component.langchain4j.agent.Headers.MEMORY_ID; -import static org.apache.camel.component.langchain4j.agent.Headers.SYSTEM_MESSAGE; +import static org.apache.camel.component.langchain4j.agent.api.Headers.MEMORY_ID; +import static org.apache.camel.component.langchain4j.agent.api.Headers.SYSTEM_MESSAGE; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jSimpleAgentIT.java b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jSimpleAgentIT.java index b80c9b8b384..471ca255e13 100644 --- a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jSimpleAgentIT.java +++ b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jSimpleAgentIT.java @@ -29,7 +29,7 @@ import org.apache.camel.test.junit5.CamelTestSupport; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIf; -import static org.apache.camel.component.langchain4j.agent.Headers.SYSTEM_MESSAGE; +import static org.apache.camel.component.langchain4j.agent.api.Headers.SYSTEM_MESSAGE; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue;
