This is an automated email from the ASF dual-hosted git repository. orpiske pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
commit 52973b5c5d7a0dbdd8c1ee04ea57343c36aaff57 Author: Otavio Rodolfo Piske <[email protected]> AuthorDate: Wed Aug 6 14:35:16 2025 +0200 CAMEL-22326: rework the agent component to support any type of agent - Adjust some of the tests so it can be possible to run tests with Google --- .../catalog/components/langchain4j-agent.json | 22 +-- .../camel-ai/camel-langchain4j-agent/pom.xml | 7 + .../agent/LangChain4jAgentComponentConfigurer.java | 35 +--- .../LangChain4jAgentConfigurationConfigurer.java | 33 +--- .../agent/LangChain4jAgentEndpointConfigurer.java | 35 +--- .../agent/LangChain4jAgentEndpointUriFactory.java | 8 +- .../langchain4j/agent/langchain4j-agent.json | 22 +-- .../src/main/docs/langchain4j-agent-component.adoc | 180 ++++++++++++++------- .../agent/LangChain4jAgentConfiguration.java | 91 ++--------- .../agent/LangChain4jAgentProducer.java | 95 +---------- .../langchain4j/agent/{ => api}/Agent.java | 9 +- .../langchain4j/agent/api/AgentConfiguration.java | 125 ++++++++++++++ .../agent/{ => api}/AgentWithMemory.java | 58 +++---- .../agent/{ => api}/AgentWithoutMemory.java | 49 ++---- .../LangChain4jAgentWithMemoryServiceTest.java | 20 ++- .../agent/integration/AbstractRAGIT.java | 21 +-- .../LangChain4jAgentGuardrailsIntegrationIT.java | 74 +++++---- .../integration/LangChain4jAgentNaiveRagIT.java | 24 ++- .../integration/LangChain4jAgentServiceIT.java | 26 +-- .../integration/LangChain4jAgentWithMemoryIT.java | 46 +++--- .../LangChain4jAgentWithMemoryServiceIT.java | 64 +++++--- .../integration/LangChain4jAgentWithToolsIT.java | 52 +++--- .../integration/LangChain4jSimpleAgentIT.java | 44 +++-- .../langchain4j/agent/integration/ModelHelper.java | 111 +++++++++++++ .../LangChain4jAgentEndpointBuilderFactory.java | 133 ++------------- 25 files changed, 670 insertions(+), 714 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 122ec55a592..aff58a2245a 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 @@ -24,15 +24,11 @@ "remote": true }, "componentProperties": { - "configuration": { "index": 0, "kind": "property", "displayName": "Configuration", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "deprecated": false, "autowired": false, "secret": false, "description": "The configuration" }, - "inputGuardrails": { "index": 1, "kind": "property", "displayName": "Input Guardrails", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "configurationField": "configuration", "description": "Comma-separated list of input guardrail class names to validate user input before se [...] + "agent": { "index": 0, "kind": "property", "displayName": "Agent", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "org.apache.camel.component.langchain4j.agent.Agent", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "configurationField": "configuration", "description": "The agent to use for the component" }, + "configuration": { "index": 1, "kind": "property", "displayName": "Configuration", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "deprecated": false, "autowired": false, "secret": false, "description": "The configuration" }, "lazyStartProducer": { "index": 2, "kind": "property", "displayName": "Lazy Start Producer", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail [...] - "outputGuardrails": { "index": 3, "kind": "property", "displayName": "Output Guardrails", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "configurationField": "configuration", "description": "Comma-separated list of output guardrail class names to validate LLM responses" }, - "tags": { "index": 4, "kind": "property", "displayName": "Tags", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "configurationField": "configuration", "description": "Tags for discovering and calling Camel route tools" }, - "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 [...] - "chatMemoryProvider": { "index": 6, "kind": "property", "displayName": "Chat Memory Provider", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "dev.langchain4j.memory.chat.ChatMemoryProvider", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "configurationField": "configuration", "description": "Chat Memory Provi [...] - "chatModel": { "index": 7, "kind": "property", "displayName": "Chat Model", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "dev.langchain4j.model.chat.ChatModel", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "configurationField": "configuration", "description": "Chat Model of type dev.langchain4j.model.chat. [...] - "retrievalAugmentor": { "index": 8, "kind": "property", "displayName": "Retrieval Augmentor", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "dev.langchain4j.rag.RetrievalAugmentor", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "configurationField": "configuration", "description": "Retrieval Augmentor for ad [...] + "tags": { "index": 3, "kind": "property", "displayName": "Tags", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "configurationField": "configuration", "description": "Tags for discovering and calling Camel route tools" }, + "autowiredEnabled": { "index": 4, "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.LangChain4jAgent$Headers#SYSTEM_MESSAGE" }, @@ -40,12 +36,8 @@ }, "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" }, - "inputGuardrails": { "index": 1, "kind": "parameter", "displayName": "Input Guardrails", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "configurationField": "configuration", "description": "Comma-separated list of input guardrail class names to validate user input before s [...] - "outputGuardrails": { "index": 2, "kind": "parameter", "displayName": "Output Guardrails", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "configurationField": "configuration", "description": "Comma-separated list of output guardrail class names to validate LLM responses" }, - "tags": { "index": 3, "kind": "parameter", "displayName": "Tags", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "configurationField": "configuration", "description": "Tags for discovering and calling Camel route tools" }, - "lazyStartProducer": { "index": 4, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a produc [...] - "chatMemoryProvider": { "index": 5, "kind": "parameter", "displayName": "Chat Memory Provider", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "dev.langchain4j.memory.chat.ChatMemoryProvider", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "configurationField": "configuration", "description": "Chat Memory Prov [...] - "chatModel": { "index": 6, "kind": "parameter", "displayName": "Chat Model", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "dev.langchain4j.model.chat.ChatModel", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "configurationField": "configuration", "description": "Chat Model of type dev.langchain4j.model.chat [...] - "retrievalAugmentor": { "index": 7, "kind": "parameter", "displayName": "Retrieval Augmentor", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "dev.langchain4j.rag.RetrievalAugmentor", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "configurationField": "configuration", "description": "Retrieval Augmentor for a [...] + "agent": { "index": 1, "kind": "parameter", "displayName": "Agent", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "org.apache.camel.component.langchain4j.agent.Agent", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "configurationField": "configuration", "description": "The agent to use for the component" }, + "tags": { "index": 2, "kind": "parameter", "displayName": "Tags", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "configurationField": "configuration", "description": "Tags for discovering and calling Camel route tools" }, + "lazyStartProducer": { "index": 3, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a produc [...] } } diff --git a/components/camel-ai/camel-langchain4j-agent/pom.xml b/components/camel-ai/camel-langchain4j-agent/pom.xml index 4a45240f472..e9b4ccccd89 100644 --- a/components/camel-ai/camel-langchain4j-agent/pom.xml +++ b/components/camel-ai/camel-langchain4j-agent/pom.xml @@ -67,6 +67,13 @@ <version>${langchain4j-version}</version> <scope>test</scope> </dependency> + <dependency> + <groupId>dev.langchain4j</groupId> + <artifactId>langchain4j-google-ai-gemini</artifactId> + <version>${langchain4j-version}</version> + <scope>test</scope> + </dependency> + <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-embeddings-bge-small-en-v15-q</artifactId> diff --git a/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentComponentConfigurer.java b/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentComponentConfigurer.java index 645093469c4..339dc8578bf 100644 --- a/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentComponentConfigurer.java +++ b/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentComponentConfigurer.java @@ -30,21 +30,12 @@ public class LangChain4jAgentComponentConfigurer extends PropertyConfigurerSuppo public boolean configure(CamelContext camelContext, Object obj, String name, Object value, boolean ignoreCase) { LangChain4jAgentComponent target = (LangChain4jAgentComponent) obj; switch (ignoreCase ? name.toLowerCase() : name) { + case "agent": getOrCreateConfiguration(target).setAgent(property(camelContext, org.apache.camel.component.langchain4j.agent.api.Agent.class, value)); return true; case "autowiredenabled": case "autowiredEnabled": target.setAutowiredEnabled(property(camelContext, boolean.class, value)); return true; - case "chatmemoryprovider": - case "chatMemoryProvider": getOrCreateConfiguration(target).setChatMemoryProvider(property(camelContext, dev.langchain4j.memory.chat.ChatMemoryProvider.class, value)); return true; - case "chatmodel": - case "chatModel": getOrCreateConfiguration(target).setChatModel(property(camelContext, dev.langchain4j.model.chat.ChatModel.class, value)); return true; case "configuration": target.setConfiguration(property(camelContext, org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration.class, value)); return true; - case "inputguardrails": - case "inputGuardrails": getOrCreateConfiguration(target).setInputGuardrails(property(camelContext, java.lang.String.class, value)); return true; case "lazystartproducer": case "lazyStartProducer": target.setLazyStartProducer(property(camelContext, boolean.class, value)); return true; - case "outputguardrails": - case "outputGuardrails": getOrCreateConfiguration(target).setOutputGuardrails(property(camelContext, java.lang.String.class, value)); return true; - case "retrievalaugmentor": - case "retrievalAugmentor": getOrCreateConfiguration(target).setRetrievalAugmentor(property(camelContext, dev.langchain4j.rag.RetrievalAugmentor.class, value)); return true; case "tags": getOrCreateConfiguration(target).setTags(property(camelContext, java.lang.String.class, value)); return true; default: return false; } @@ -52,27 +43,18 @@ public class LangChain4jAgentComponentConfigurer extends PropertyConfigurerSuppo @Override public String[] getAutowiredNames() { - return new String[]{"chatMemoryProvider", "chatModel", "retrievalAugmentor"}; + return new String[]{"agent"}; } @Override public Class<?> getOptionType(String name, boolean ignoreCase) { switch (ignoreCase ? name.toLowerCase() : name) { + case "agent": return org.apache.camel.component.langchain4j.agent.api.Agent.class; case "autowiredenabled": case "autowiredEnabled": return boolean.class; - case "chatmemoryprovider": - case "chatMemoryProvider": return dev.langchain4j.memory.chat.ChatMemoryProvider.class; - case "chatmodel": - case "chatModel": return dev.langchain4j.model.chat.ChatModel.class; case "configuration": return org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration.class; - case "inputguardrails": - case "inputGuardrails": return java.lang.String.class; case "lazystartproducer": case "lazyStartProducer": return boolean.class; - case "outputguardrails": - case "outputGuardrails": return java.lang.String.class; - case "retrievalaugmentor": - case "retrievalAugmentor": return dev.langchain4j.rag.RetrievalAugmentor.class; case "tags": return java.lang.String.class; default: return null; } @@ -82,21 +64,12 @@ public class LangChain4jAgentComponentConfigurer extends PropertyConfigurerSuppo public Object getOptionValue(Object obj, String name, boolean ignoreCase) { LangChain4jAgentComponent target = (LangChain4jAgentComponent) obj; switch (ignoreCase ? name.toLowerCase() : name) { + case "agent": return getOrCreateConfiguration(target).getAgent(); case "autowiredenabled": case "autowiredEnabled": return target.isAutowiredEnabled(); - case "chatmemoryprovider": - case "chatMemoryProvider": return getOrCreateConfiguration(target).getChatMemoryProvider(); - case "chatmodel": - case "chatModel": return getOrCreateConfiguration(target).getChatModel(); case "configuration": return target.getConfiguration(); - case "inputguardrails": - case "inputGuardrails": return getOrCreateConfiguration(target).getInputGuardrails(); case "lazystartproducer": case "lazyStartProducer": return target.isLazyStartProducer(); - case "outputguardrails": - case "outputGuardrails": return getOrCreateConfiguration(target).getOutputGuardrails(); - case "retrievalaugmentor": - case "retrievalAugmentor": return getOrCreateConfiguration(target).getRetrievalAugmentor(); case "tags": return getOrCreateConfiguration(target).getTags(); default: return null; } diff --git a/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentConfigurationConfigurer.java b/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentConfigurationConfigurer.java index d7a3456841e..e250e084443 100644 --- a/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentConfigurationConfigurer.java +++ b/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentConfigurationConfigurer.java @@ -23,16 +23,7 @@ public class LangChain4jAgentConfigurationConfigurer extends org.apache.camel.su public boolean configure(CamelContext camelContext, Object obj, String name, Object value, boolean ignoreCase) { org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration target = (org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration) obj; switch (ignoreCase ? name.toLowerCase() : name) { - case "chatmemoryprovider": - case "chatMemoryProvider": target.setChatMemoryProvider(property(camelContext, dev.langchain4j.memory.chat.ChatMemoryProvider.class, value)); return true; - case "chatmodel": - case "chatModel": target.setChatModel(property(camelContext, dev.langchain4j.model.chat.ChatModel.class, value)); return true; - case "inputguardrails": - case "inputGuardrails": target.setInputGuardrails(property(camelContext, java.lang.String.class, value)); return true; - case "outputguardrails": - case "outputGuardrails": target.setOutputGuardrails(property(camelContext, java.lang.String.class, value)); return true; - case "retrievalaugmentor": - case "retrievalAugmentor": target.setRetrievalAugmentor(property(camelContext, dev.langchain4j.rag.RetrievalAugmentor.class, value)); return true; + case "agent": target.setAgent(property(camelContext, org.apache.camel.component.langchain4j.agent.api.Agent.class, value)); return true; case "tags": target.setTags(property(camelContext, java.lang.String.class, value)); return true; default: return false; } @@ -41,16 +32,7 @@ public class LangChain4jAgentConfigurationConfigurer extends org.apache.camel.su @Override public Class<?> getOptionType(String name, boolean ignoreCase) { switch (ignoreCase ? name.toLowerCase() : name) { - case "chatmemoryprovider": - case "chatMemoryProvider": return dev.langchain4j.memory.chat.ChatMemoryProvider.class; - case "chatmodel": - case "chatModel": return dev.langchain4j.model.chat.ChatModel.class; - case "inputguardrails": - case "inputGuardrails": return java.lang.String.class; - case "outputguardrails": - case "outputGuardrails": return java.lang.String.class; - case "retrievalaugmentor": - case "retrievalAugmentor": return dev.langchain4j.rag.RetrievalAugmentor.class; + case "agent": return org.apache.camel.component.langchain4j.agent.api.Agent.class; case "tags": return java.lang.String.class; default: return null; } @@ -60,16 +42,7 @@ public class LangChain4jAgentConfigurationConfigurer extends org.apache.camel.su public Object getOptionValue(Object obj, String name, boolean ignoreCase) { org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration target = (org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration) obj; switch (ignoreCase ? name.toLowerCase() : name) { - case "chatmemoryprovider": - case "chatMemoryProvider": return target.getChatMemoryProvider(); - case "chatmodel": - case "chatModel": return target.getChatModel(); - case "inputguardrails": - case "inputGuardrails": return target.getInputGuardrails(); - case "outputguardrails": - case "outputGuardrails": return target.getOutputGuardrails(); - case "retrievalaugmentor": - case "retrievalAugmentor": return target.getRetrievalAugmentor(); + case "agent": return target.getAgent(); case "tags": return target.getTags(); default: return null; } diff --git a/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentEndpointConfigurer.java b/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentEndpointConfigurer.java index e33ad5c6c11..e3b50101ee9 100644 --- a/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentEndpointConfigurer.java +++ b/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentEndpointConfigurer.java @@ -23,18 +23,9 @@ public class LangChain4jAgentEndpointConfigurer extends PropertyConfigurerSuppor public boolean configure(CamelContext camelContext, Object obj, String name, Object value, boolean ignoreCase) { LangChain4jAgentEndpoint target = (LangChain4jAgentEndpoint) obj; switch (ignoreCase ? name.toLowerCase() : name) { - case "chatmemoryprovider": - case "chatMemoryProvider": target.getConfiguration().setChatMemoryProvider(property(camelContext, dev.langchain4j.memory.chat.ChatMemoryProvider.class, value)); return true; - case "chatmodel": - case "chatModel": target.getConfiguration().setChatModel(property(camelContext, dev.langchain4j.model.chat.ChatModel.class, value)); return true; - case "inputguardrails": - case "inputGuardrails": target.getConfiguration().setInputGuardrails(property(camelContext, java.lang.String.class, value)); return true; + case "agent": target.getConfiguration().setAgent(property(camelContext, org.apache.camel.component.langchain4j.agent.api.Agent.class, value)); return true; case "lazystartproducer": case "lazyStartProducer": target.setLazyStartProducer(property(camelContext, boolean.class, value)); return true; - case "outputguardrails": - case "outputGuardrails": target.getConfiguration().setOutputGuardrails(property(camelContext, java.lang.String.class, value)); return true; - case "retrievalaugmentor": - case "retrievalAugmentor": target.getConfiguration().setRetrievalAugmentor(property(camelContext, dev.langchain4j.rag.RetrievalAugmentor.class, value)); return true; case "tags": target.getConfiguration().setTags(property(camelContext, java.lang.String.class, value)); return true; default: return false; } @@ -42,24 +33,15 @@ public class LangChain4jAgentEndpointConfigurer extends PropertyConfigurerSuppor @Override public String[] getAutowiredNames() { - return new String[]{"chatMemoryProvider", "chatModel", "retrievalAugmentor"}; + return new String[]{"agent"}; } @Override public Class<?> getOptionType(String name, boolean ignoreCase) { switch (ignoreCase ? name.toLowerCase() : name) { - case "chatmemoryprovider": - case "chatMemoryProvider": return dev.langchain4j.memory.chat.ChatMemoryProvider.class; - case "chatmodel": - case "chatModel": return dev.langchain4j.model.chat.ChatModel.class; - case "inputguardrails": - case "inputGuardrails": return java.lang.String.class; + case "agent": return org.apache.camel.component.langchain4j.agent.api.Agent.class; case "lazystartproducer": case "lazyStartProducer": return boolean.class; - case "outputguardrails": - case "outputGuardrails": return java.lang.String.class; - case "retrievalaugmentor": - case "retrievalAugmentor": return dev.langchain4j.rag.RetrievalAugmentor.class; case "tags": return java.lang.String.class; default: return null; } @@ -69,18 +51,9 @@ public class LangChain4jAgentEndpointConfigurer extends PropertyConfigurerSuppor public Object getOptionValue(Object obj, String name, boolean ignoreCase) { LangChain4jAgentEndpoint target = (LangChain4jAgentEndpoint) obj; switch (ignoreCase ? name.toLowerCase() : name) { - case "chatmemoryprovider": - case "chatMemoryProvider": return target.getConfiguration().getChatMemoryProvider(); - case "chatmodel": - case "chatModel": return target.getConfiguration().getChatModel(); - case "inputguardrails": - case "inputGuardrails": return target.getConfiguration().getInputGuardrails(); + case "agent": return target.getConfiguration().getAgent(); case "lazystartproducer": case "lazyStartProducer": return target.isLazyStartProducer(); - case "outputguardrails": - case "outputGuardrails": return target.getConfiguration().getOutputGuardrails(); - case "retrievalaugmentor": - case "retrievalAugmentor": return target.getConfiguration().getRetrievalAugmentor(); case "tags": return target.getConfiguration().getTags(); default: return null; } diff --git a/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentEndpointUriFactory.java b/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentEndpointUriFactory.java index 1255201c815..984f16790e3 100644 --- a/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentEndpointUriFactory.java +++ b/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentEndpointUriFactory.java @@ -23,14 +23,10 @@ public class LangChain4jAgentEndpointUriFactory extends org.apache.camel.support private static final Set<String> SECRET_PROPERTY_NAMES; private static final Set<String> MULTI_VALUE_PREFIXES; static { - Set<String> props = new HashSet<>(8); + Set<String> props = new HashSet<>(4); + props.add("agent"); props.add("agentId"); - props.add("chatMemoryProvider"); - props.add("chatModel"); - props.add("inputGuardrails"); props.add("lazyStartProducer"); - props.add("outputGuardrails"); - props.add("retrievalAugmentor"); props.add("tags"); PROPERTY_NAMES = Collections.unmodifiableSet(props); SECRET_PROPERTY_NAMES = Collections.emptySet(); 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 122ec55a592..528c6f36f70 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 @@ -24,15 +24,11 @@ "remote": true }, "componentProperties": { - "configuration": { "index": 0, "kind": "property", "displayName": "Configuration", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "deprecated": false, "autowired": false, "secret": false, "description": "The configuration" }, - "inputGuardrails": { "index": 1, "kind": "property", "displayName": "Input Guardrails", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "configurationField": "configuration", "description": "Comma-separated list of input guardrail class names to validate user input before se [...] + "agent": { "index": 0, "kind": "property", "displayName": "Agent", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "org.apache.camel.component.langchain4j.agent.api.Agent", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "configurationField": "configuration", "description": "The agent to use for the component" }, + "configuration": { "index": 1, "kind": "property", "displayName": "Configuration", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "deprecated": false, "autowired": false, "secret": false, "description": "The configuration" }, "lazyStartProducer": { "index": 2, "kind": "property", "displayName": "Lazy Start Producer", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail [...] - "outputGuardrails": { "index": 3, "kind": "property", "displayName": "Output Guardrails", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "configurationField": "configuration", "description": "Comma-separated list of output guardrail class names to validate LLM responses" }, - "tags": { "index": 4, "kind": "property", "displayName": "Tags", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "configurationField": "configuration", "description": "Tags for discovering and calling Camel route tools" }, - "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 [...] - "chatMemoryProvider": { "index": 6, "kind": "property", "displayName": "Chat Memory Provider", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "dev.langchain4j.memory.chat.ChatMemoryProvider", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "configurationField": "configuration", "description": "Chat Memory Provi [...] - "chatModel": { "index": 7, "kind": "property", "displayName": "Chat Model", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "dev.langchain4j.model.chat.ChatModel", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "configurationField": "configuration", "description": "Chat Model of type dev.langchain4j.model.chat. [...] - "retrievalAugmentor": { "index": 8, "kind": "property", "displayName": "Retrieval Augmentor", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "dev.langchain4j.rag.RetrievalAugmentor", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "configurationField": "configuration", "description": "Retrieval Augmentor for ad [...] + "tags": { "index": 3, "kind": "property", "displayName": "Tags", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "configurationField": "configuration", "description": "Tags for discovering and calling Camel route tools" }, + "autowiredEnabled": { "index": 4, "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.LangChain4jAgent$Headers#SYSTEM_MESSAGE" }, @@ -40,12 +36,8 @@ }, "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" }, - "inputGuardrails": { "index": 1, "kind": "parameter", "displayName": "Input Guardrails", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "configurationField": "configuration", "description": "Comma-separated list of input guardrail class names to validate user input before s [...] - "outputGuardrails": { "index": 2, "kind": "parameter", "displayName": "Output Guardrails", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "configurationField": "configuration", "description": "Comma-separated list of output guardrail class names to validate LLM responses" }, - "tags": { "index": 3, "kind": "parameter", "displayName": "Tags", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "configurationField": "configuration", "description": "Tags for discovering and calling Camel route tools" }, - "lazyStartProducer": { "index": 4, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a produc [...] - "chatMemoryProvider": { "index": 5, "kind": "parameter", "displayName": "Chat Memory Provider", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "dev.langchain4j.memory.chat.ChatMemoryProvider", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "configurationField": "configuration", "description": "Chat Memory Prov [...] - "chatModel": { "index": 6, "kind": "parameter", "displayName": "Chat Model", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "dev.langchain4j.model.chat.ChatModel", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "configurationField": "configuration", "description": "Chat Model of type dev.langchain4j.model.chat [...] - "retrievalAugmentor": { "index": 7, "kind": "parameter", "displayName": "Retrieval Augmentor", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "dev.langchain4j.rag.RetrievalAugmentor", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "configurationField": "configuration", "description": "Retrieval Augmentor for a [...] + "agent": { "index": 1, "kind": "parameter", "displayName": "Agent", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "org.apache.camel.component.langchain4j.agent.api.Agent", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "configurationField": "configuration", "description": "The agent to use for the component" }, + "tags": { "index": 2, "kind": "parameter", "displayName": "Tags", "group": "producer", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration", "configurationField": "configuration", "description": "Tags for discovering and calling Camel route tools" }, + "lazyStartProducer": { "index": 3, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a produc [...] } } diff --git a/components/camel-ai/camel-langchain4j-agent/src/main/docs/langchain4j-agent-component.adoc b/components/camel-ai/camel-langchain4j-agent/src/main/docs/langchain4j-agent-component.adoc index 3410b878040..58438c12f7e 100644 --- a/components/camel-ai/camel-langchain4j-agent/src/main/docs/langchain4j-agent-component.adoc +++ b/components/camel-ai/camel-langchain4j-agent/src/main/docs/langchain4j-agent-component.adoc @@ -21,10 +21,21 @@ The LangChain4j Agent component provides comprehensive AI agent capabilities by The LangChain4j Agent component offers the following key features: +* **Agent-Based Architecture**: Flexible agent creation using the `Agent` API interface * **Tool Integration**: Seamless integration with Camel routes via the `langchain4j-tools` component * **Conversation Memory**: Persistent chat memory for maintaining conversation context * **RAG Support**: Integration with retrieval systems for naive and advanced RAG * **Guardrails**: Input and output validation and transformation +* **Configuration Flexibility**: Centralized agent configuration using `AgentConfiguration` + +== Component Options + +The component has been simplified to use only two main options: + +* **agent**: Reference to an `Agent` implementation registered in the Camel registry +* **tags**: Tags for discovering and calling Camel route tools (optional) + +All other configuration (chat models, memory, RAG, guardrails) is now handled through the `AgentConfiguration` when creating agents. == URI format @@ -58,66 +69,108 @@ include::spring-boot:partial$starter.adoc[] == Usage -=== Using a specific Chat Model +=== Creating Custom Agents -The Camel LangChain4j chat component provides an abstraction for interacting with various types of Large Language Models (LLMs) supported by https://github.com/langchain4j/langchain4j[LangChain4j]. +Starting from Camel 4.14, the LangChain4j Agent component uses an agent-based architecture where agents are created by implementing the `org.apache.camel.component.langchain4j.agent.api.Agent` interface. The component provides two built-in agent implementations: -==== Integrating with specific LLM +* `AgentWithMemory` - For chat interactions with conversation history +* `AgentWithoutMemory` - For stateless chat interactions -To integrate with a specific LLM, users should follow the steps described below, which explain -how to integrate with OpenAI. +==== Agent Configuration -Add the dependency for LangChain4j OpenAI support: +Agents are configured using the `AgentConfiguration` class which provides a fluent API for setting up: -.Example -[source,xml] +* Chat Model +* Chat Memory Provider (for memory-enabled agents) +* Retrieval Augmentor (for RAG functionality) +* Input and Output Guardrails + +==== Creating an Agent without Memory + +[source, java] ---- -<dependency> - <groupId>dev.langchain4j</groupId> - <artifactId>langchain4j-open-ai</artifactId> - <version>x.x.x</version> -</dependency> +// Create and configure the chat model +ChatModel chatModel = OpenAiChatModel.builder() + .apiKey(openApiKey) + .modelName(GPT_3_5_TURBO) + .temperature(0.3) + .timeout(ofSeconds(3000)) + .build(); + +// Create agent configuration +AgentConfiguration configuration = new AgentConfiguration() + .withChatModel(chatModel) + .withInputGuardrailClasses(List.of()) + .withOutputGuardrailClasses(List.of()); + +// Create the agent +Agent simpleAgent = new AgentWithoutMemory(configuration); + +// Register the agent in the Camel context +context.getRegistry().bind("simpleAgent", simpleAgent); ---- -Initialize the OpenAI Chat Model, and add it to the Camel Registry: +Use the agent in your Camel route: [source, java] ---- -ChatModel model = OpenAiChatModel.builder() - .apiKey(openApiKey) - .modelName(GPT_3_5_TURBO) - .temperature(0.3) - .timeout(ofSeconds(3000)) - .build(); -context.getRegistry().bind("chatModel", model); +from("direct:chat") + .to("langchain4j-agent:test?agent=#simpleAgent") ---- -Use the model in the Camel LangChain4j Agent Producer +==== Creating an Agent with Memory [source, java] ---- - from("direct:chat") - .to("langchain4j-agent:test?chatModel=#chatModel") +// Create chat model (same as above) +ChatModel chatModel = OpenAiChatModel.builder()... + +// Create memory provider +ChatMemoryProvider memoryProvider = memoryId -> MessageWindowChatMemory.builder() + .id(memoryId) + .maxMessages(10) + .chatMemoryStore(persistentStore) // Your persistent store implementation + .build(); + +// Create agent configuration with memory +AgentConfiguration configuration = new AgentConfiguration() + .withChatModel(chatModel) + .withChatMemoryProvider(memoryProvider) + .withInputGuardrailClasses(List.of()) + .withOutputGuardrailClasses(List.of()); +// Create the agent +Agent memoryAgent = new AgentWithMemory(configuration); + +// Register the agent +context.getRegistry().bind("memoryAgent", memoryAgent); ---- [NOTE] ==== -To switch to another Large Language Model and its corresponding dependency, replace the `langchain4j-open-ai` dependency with the appropriate dependency for the desired model. Update the initialization parameters accordingly in the code snippet provided above. +Add the `camel-langchain4j-agent-api` dependency to access the Agent API classes in your application: + +[source,xml] +---- +<dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-langchain4j-agent-api</artifactId> + <version>x.x.x</version> +</dependency> +---- ==== === Basic Chat with only a userMessage -For simple chat interactions, you can use the producer only by setting the chatModel. +For simple chat interactions, you can use an agent without memory. [source,java] ---- from("direct:chat") - .to("langchain4j-agent:simple?chatModel=#chatModel") - + .to("langchain4j-agent:simple?agent=#simpleAgent") ---- -The body can either contain the prompt as a String, or you can create an object of type *org.apache.camel.component.langchain4j.agent.AiAgentBody* containing the userMessage. +The body can either contain the prompt as a String, or you can create an object of type *org.apache.camel.component.langchain4j.agent.api.AiAgentBody* containing the userMessage. .Usage example with a body as String: [source, java] @@ -138,16 +191,15 @@ String response = template.requestBody("direct:chat", body, String.class); === Basic Chat with user and system messages -For simple chat interactions, you can use the producer only by setting the chatModel. +For chat interactions with system prompts, you can use an agent without memory. [source,java] ---- from("direct:chat") - .to("langchain4j-agent:simple?chatModel=#chatModel") - + .to("langchain4j-agent:simple?agent=#simpleAgent") ---- -The body can either contain the user prompt as a String and specifying the *CamelLangChain4jAgentSystemMessage* header for the system prompt, or you can create an object of type *org.apache.camel.component.langchain4j.agent.AiAgentBody* containing both *userMessage* and *systemMessage*. +The body can either contain the user prompt as a String and specifying the *CamelLangChain4jAgentSystemMessage* header for the system prompt, or you can create an object of type *org.apache.camel.component.langchain4j.agent.api.AiAgentBody* containing both *userMessage* and *systemMessage*. .Usage example with a body as String: [source, java] @@ -174,7 +226,7 @@ String response = template.requestBody("direct:chat", body, String.class); === Chat with Tools -Integrate with Camel routes as tools. This is powerful, because under the hood the LangChain4j Agent component reuses under the hood LangChain4j AIService to integrate with any Camel Routes defined using the Camel LangChain4j Tools component. +Integrate with Camel routes as tools. The LangChain4j Agent component integrates with Camel Routes defined using the Camel LangChain4j Tools component via the `tags` parameter. [source,java] ---- @@ -185,10 +237,9 @@ from("langchain4j-tools:userDb?tags=users&description=Query user database¶me from("langchain4j-tools:weather?tags=weather&description=Get weather information¶meter.city=string") .setBody(constant("{\"weather\": \"sunny\", \"temperature\": \"22°C\"}")); -// Agent with tools +// Agent with tools (using the created agent) from("direct:chat") - .to("langchain4j-agent:tools?chatModel=#chatModel&tags=users,weather") - + .to("langchain4j-agent:tools?agent=#simpleAgent&tags=users,weather"); ---- .Usage example : @@ -208,17 +259,26 @@ There's no need to add Camel LangChain4j Tools component as a dependency when us === RAG Integration -For the current version of the component, RAG is an advanced feature that requires adding a Retrieval Augmentor of type *dev.langchain4j.rag.RetrievalAugmentor*. This class enables to use either naive or advanced RAG. -For more information and examples you can check the https://docs.langchain4j.dev/tutorials/rag#retrieval-augmentor[LangChain4j RAG documentation page] - +RAG (Retrieval-Augmented Generation) is supported by configuring a `RetrievalAugmentor` in the `AgentConfiguration`. Create an agent with RAG capabilities: [source,java] ---- +// Create the retrieval augmentor (shown below) +RetrievalAugmentor retrievalAugmentor = createRetrievalAugmentor(); +// Create agent configuration with RAG +AgentConfiguration configuration = new AgentConfiguration() + .withChatModel(chatModel) + .withRetrievalAugmentor(retrievalAugmentor) + .withInputGuardrailClasses(List.of()) + .withOutputGuardrailClasses(List.of()); -from("direct:chat") - .to("langchain4j-agent:rag?chatModel=#chatModel&retrievalAugmentor=#retrievalAugmentor") +Agent ragAgent = new AgentWithoutMemory(configuration); +context.getRegistry().bind("ragAgent", ragAgent); +// Use the RAG agent +from("direct:chat") + .to("langchain4j-agent:rag?agent=#ragAgent") ---- .Usage example with Retrieval Augmentor serving as naive RAG : @@ -245,30 +305,23 @@ String response = template.requestBody("direct:chat", body, String.class); ---- === Chat with Memory -For the current version of the component, Memory is an advanced feature that requires adding a Chat Memory Provider of type dev.langchain4j.memory.chat.ChatMemoryProvider. This class enables using a dedicated LangChain4j ChatMemoryProvider. -For more information and examples you can check the https://docs.langchain4j.dev/tutorials/chat-memory[LangChain4j Chat Memory documentation page]. + +Memory functionality is supported by using `AgentWithMemory` and configuring a `ChatMemoryProvider` in the `AgentConfiguration`. The memory works for multiple users/sessions. [NOTE] ==== The component requires using a Chat Memory Provider that uses a https://docs.langchain4j.dev/tutorials/chat-memory#persistence[persistent memory store]. ==== -The memory works for multiple users/sessions. For each context window, the users needs to set the memory ID. -2 ways to set the memory ID: +The memory works for multiple users/sessions. For each context window, the users needs to set the memory ID: - By setting the Header CamelLangChain4jAgentMemoryId. This supposes that user is using a body as String. - By setting the AiAgentBody.memoryId field. This supposes that that user is using a body as AiAgentBody. -[NOTE] -==== -If there's no need to use different sessions, it is recommended to use the same memory ID. -==== - -.Example of Route +.Example of Route with Memory Agent [source,java] ---- from("direct:chat") - .to("langchain4j-agent:memory?chatModel=#chatModel&chatMemoryProvider=#chatMemoryProvider") - + .to("langchain4j-agent:memory?agent=#memoryAgent") ---- .Example of usage with AiAgentBody @@ -291,18 +344,23 @@ String response = template.requestBody("direct:chat", request, String.class); === Input and Output Guardrails -There's a possibility to use these advanced features to add Input and Output Guardrails to the AiAgent Route. -For this, create classes defining InputGuardrails and OutputGuardrails as defined in the https://docs.langchain4j.dev/tutorials/guardrails[LangChain4j Guardrails documentation] page. -The list of input guardrails classes should be defined as inputGuardrails option as a list of classes separated by comma. -The list of output guardrails classes should be defined as outputGuardrails option as a list of classes separated by comma. +Guardrails are configured in the `AgentConfiguration` using the fluent API methods. Create classes defining InputGuardrails and OutputGuardrails as defined in the https://docs.langchain4j.dev/tutorials/guardrails[LangChain4j Guardrails documentation] page. [source,java] ---- +// Create agent configuration with guardrails +AgentConfiguration configuration = new AgentConfiguration() + .withChatModel(chatModel) + .withInputGuardrailClassesList("com.example.MyInputGuardrail") + .withOutputGuardrailClassesList("com.example.MyOutputGuardrail1,com.example.MyOutputGuardrail2"); + +Agent safeAgent = new AgentWithoutMemory(configuration); +context.getRegistry().bind("safeAgent", safeAgent); + +// Use the agent with guardrails from("direct:agent-with-guardrails") - .to("langchain4j-agent:safe?chatModel=#chatModel" + - "&inputGuardrails=com.example.MyInputGuardrail" + - "&outputGuardrails=com.example.MyOutputGuardrail1,com.example.MyOutputGuardrail2") + .to("langchain4j-agent:safe?agent=#safeAgent") ---- [NOTE] diff --git a/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentConfiguration.java b/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentConfiguration.java index dfdd0557c9b..3b64a04dbd6 100644 --- a/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentConfiguration.java +++ b/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentConfiguration.java @@ -16,10 +16,8 @@ */ package org.apache.camel.component.langchain4j.agent; -import dev.langchain4j.memory.chat.ChatMemoryProvider; -import dev.langchain4j.model.chat.ChatModel; -import dev.langchain4j.rag.RetrievalAugmentor; import org.apache.camel.RuntimeCamelException; +import org.apache.camel.component.langchain4j.agent.api.Agent; import org.apache.camel.spi.Configurer; import org.apache.camel.spi.Metadata; import org.apache.camel.spi.UriParam; @@ -29,43 +27,16 @@ import org.apache.camel.spi.UriParams; @UriParams public class LangChain4jAgentConfiguration implements Cloneable { - @UriParam(label = "advanced") + @UriParam(description = "The agent to use for the component") @Metadata(autowired = true) - private ChatModel chatModel; + private Agent agent; @UriParam(description = "Tags for discovering and calling Camel route tools") private String tags; - @UriParam(label = "advanced") - @Metadata(autowired = true) - ChatMemoryProvider chatMemoryProvider; - - @UriParam(label = "advanced") - @Metadata(autowired = true) - RetrievalAugmentor retrievalAugmentor; - - @UriParam(description = "Comma-separated list of input guardrail class names to validate user input before sending to LLM") - private String inputGuardrails; - - @UriParam(description = "Comma-separated list of output guardrail class names to validate LLM responses") - private String outputGuardrails; - public LangChain4jAgentConfiguration() { } - /** - * Chat Model of type dev.langchain4j.model.chat.ChatModel - * - * @return - */ - public ChatModel getChatModel() { - return chatModel; - } - - public void setChatModel(ChatModel chatModel) { - this.chatModel = chatModel; - } - /** * Tags for discovering and calling Camel route tools * @@ -88,59 +59,15 @@ public class LangChain4jAgentConfiguration implements Cloneable { } /** - * Chat Memory Provider of type dev.langchain4j.memory.ChatMemoryProvider. Note for this to be successful, you need - * to use a reliable ChatMemoryStore. This provider supposes that a user has multiple sessions, if need only a - * single session, use a default memoryId - * - * @return - */ - public ChatMemoryProvider getChatMemoryProvider() { - return chatMemoryProvider; - } - - public void setChatMemoryProvider(ChatMemoryProvider chatMemoryProvider) { - this.chatMemoryProvider = chatMemoryProvider; - } - - /** - * Retrieval Augmentor for advanced RAG of type dev.langchain4j.rag.RetrievalAugmentor. This allows using RAG on - * both Naive and Advanced RAG - * - * @return the retrieval augmentor - */ - public RetrievalAugmentor getRetrievalAugmentor() { - return retrievalAugmentor; - } - - public void setRetrievalAugmentor(RetrievalAugmentor retrievalAugmentor) { - this.retrievalAugmentor = retrievalAugmentor; - } - - /** - * Input guardrails class names for validating user input before sending to LLM. Provide comma-separated fully - * qualified class names that implement InputGuardrail interface. - * - * @return comma-separated input guardrail class names - */ - public String getInputGuardrails() { - return inputGuardrails; - } - - public void setInputGuardrails(String inputGuardrails) { - this.inputGuardrails = inputGuardrails; - } - - /** - * Output guardrails class names for validating LLM responses. Provide comma-separated fully qualified class names - * that implement OutputGuardrail interface. + * The agent providing the service * - * @return comma-separated output guardrail class names + * @return the instance of the agent providing the service */ - public String getOutputGuardrails() { - return outputGuardrails; + public Agent getAgent() { + return agent; } - public void setOutputGuardrails(String outputGuardrails) { - this.outputGuardrails = outputGuardrails; + public void setAgent(Agent agent) { + this.agent = agent; } } 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 9c3e0c756c2..d68f2e526d9 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 @@ -22,15 +22,13 @@ import java.util.stream.Collectors; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import dev.langchain4j.agent.tool.ToolSpecification; -import dev.langchain4j.memory.chat.ChatMemoryProvider; -import dev.langchain4j.model.chat.ChatModel; -import dev.langchain4j.rag.RetrievalAugmentor; import dev.langchain4j.service.tool.ToolExecutor; 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.tools.spec.CamelToolExecutorCache; import org.apache.camel.component.langchain4j.tools.spec.CamelToolSpecification; import org.apache.camel.support.DefaultProducer; @@ -45,7 +43,6 @@ public class LangChain4jAgentProducer extends DefaultProducer { private static final Logger LOG = LoggerFactory.getLogger(LangChain4jAgentProducer.class); private final LangChain4jAgentEndpoint endpoint; - private ChatModel chatModel; private final ObjectMapper objectMapper = new ObjectMapper(); public LangChain4jAgentProducer(LangChain4jAgentEndpoint endpoint) { @@ -58,30 +55,15 @@ public class LangChain4jAgentProducer extends DefaultProducer { Object messagePayload = exchange.getIn().getBody(); ObjectHelper.notNull(messagePayload, "body"); - AiAgentBody aiAgentBody = processBody(messagePayload, exchange); - - // get chatMemory if specified - ChatMemoryProvider chatMemoryProvider = endpoint.getConfiguration().getChatMemoryProvider(); - if (chatMemoryProvider != null) { - ObjectHelper.notNull(aiAgentBody.getMemoryId(), "memoryId"); - } + Agent agent = endpoint.getConfiguration().getAgent(); - // RetrievalAugmentor for naive or advanced RAG - RetrievalAugmentor retrievalAugmentor = endpoint.getConfiguration().getRetrievalAugmentor(); + AiAgentBody aiAgentBody = processBody(messagePayload, exchange); // tags for Camel Routes as Tools String tags = endpoint.getConfiguration().getTags(); - // Input Guardrails - List<Class<?>> inputGuardrailClasses = parseGuardrailClasses(endpoint.getConfiguration().getInputGuardrails()); - - // Output Guardrails - List<Class<?>> outputGuardrailClasses = parseGuardrailClasses(endpoint.getConfiguration().getOutputGuardrails()); - - // Create appropriate agent implementation based on memory requirements - Agent agent = createAgent(chatMemoryProvider, tags, retrievalAugmentor, inputGuardrailClasses, outputGuardrailClasses, - exchange); - String response = agent.chat(aiAgentBody); + ToolProvider toolProvider = createCamelToolProvider(tags, exchange); + String response = agent.chat(aiAgentBody, toolProvider); exchange.getMessage().setBody(response); } @@ -103,36 +85,7 @@ public class LangChain4jAgentProducer extends DefaultProducer { String systemMessage = exchange.getIn().getHeader(SYSTEM_MESSAGE, String.class); Object memoryId = exchange.getIn().getHeader(MEMORY_ID); - AiAgentBody aiAgentBody = new AiAgentBody((String) messagePayload, systemMessage, memoryId); - return aiAgentBody; - - } - - /** - * Creates the appropriate Agent implementation based on whether memory is required. - * - * @param chatMemoryProvider the memory provider, null if no memory is needed - * @param tags the tags for tool discovery - * @param retrievalAugmentor the RAG augmentor - * @param inputGuardrailClasses input guardrail classes - * @param outputGuardrailClasses output guardrail classes - * @param exchange the Camel exchange - * @return Agent implementation (with or without memory) - */ - private Agent createAgent( - ChatMemoryProvider chatMemoryProvider, String tags, RetrievalAugmentor retrievalAugmentor, - List<Class<?>> inputGuardrailClasses, List<Class<?>> outputGuardrailClasses, Exchange exchange) { - ToolProvider toolProvider = createCamelToolProvider(tags, exchange); - - if (chatMemoryProvider != null) { - return new AgentWithMemory( - chatModel, chatMemoryProvider, tags, retrievalAugmentor, - inputGuardrailClasses, outputGuardrailClasses, exchange, toolProvider); - } else { - return new AgentWithoutMemory( - chatModel, tags, retrievalAugmentor, - inputGuardrailClasses, outputGuardrailClasses, exchange, toolProvider); - } + return new AiAgentBody((String) messagePayload, systemMessage, memoryId); } /** @@ -238,44 +191,8 @@ public class LangChain4jAgentProducer extends DefaultProducer { return toolsByName; } - /** - * Parse comma-separated guardrail class names into a list of loaded classes. - * - * @param guardrailClassNames comma-separated class names, can be null or empty - * @return list of loaded classes, empty list if input is null or empty - */ - private List<Class<?>> parseGuardrailClasses(String guardrailClassNames) { - if (guardrailClassNames == null || guardrailClassNames.trim().isEmpty()) { - return Collections.emptyList(); - } - - return Arrays.stream(guardrailClassNames.split(",")) - .map(String::trim) - .filter(name -> !name.isEmpty()) - .map(this::loadGuardrailClass) - .filter(clazz -> clazz != null) - .collect(java.util.stream.Collectors.toList()); - } - - /** - * Load a guardrail class by name. - * - * @param className the fully qualified class name - * @return the loaded class, or null if loading failed - */ - private Class<?> loadGuardrailClass(String className) { - try { - return Class.forName(className); - } catch (ClassNotFoundException e) { - LOG.warn("Failed to load guardrail class: {}", className, e); - return null; - } - } - @Override protected void doStart() throws Exception { super.doStart(); - this.chatModel = this.endpoint.getConfiguration().getChatModel(); - ObjectHelper.notNull(chatModel, "chatModel"); } } diff --git a/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/Agent.java b/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/api/Agent.java similarity index 83% rename from components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/Agent.java rename to components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/api/Agent.java index 06897209f30..2756358c362 100644 --- a/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/Agent.java +++ b/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/api/Agent.java @@ -14,7 +14,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.camel.component.langchain4j.agent; +package org.apache.camel.component.langchain4j.agent.api; + +import dev.langchain4j.service.tool.ToolProvider; +import org.apache.camel.component.langchain4j.agent.AiAgentBody; /** * Agent interface that abstracts the different types of AI agents (with or without memory). This interface provides a @@ -22,7 +25,7 @@ package org.apache.camel.component.langchain4j.agent; * * This is an internal interface used only within the LangChain4j agent component. */ -interface Agent { +public interface Agent { /** * Executes a chat interaction with the AI agent using the provided body. @@ -30,6 +33,6 @@ interface Agent { * @param aiAgentBody the body containing user message, system message, and memory ID * @return the AI agent's response */ - String chat(AiAgentBody aiAgentBody); + String chat(AiAgentBody aiAgentBody, ToolProvider toolProvider); } diff --git a/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentConfiguration.java b/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentConfiguration.java new file mode 100644 index 00000000000..c7223e3fd04 --- /dev/null +++ b/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentConfiguration.java @@ -0,0 +1,125 @@ +/* + * 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.api; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import dev.langchain4j.memory.chat.ChatMemoryProvider; +import dev.langchain4j.model.chat.ChatModel; +import dev.langchain4j.rag.RetrievalAugmentor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AgentConfiguration { + private static final Logger LOG = LoggerFactory.getLogger(AgentConfiguration.class); + + private ChatModel chatModel; + private ChatMemoryProvider chatMemoryProvider; + private RetrievalAugmentor retrievalAugmentor; + private List<Class<?>> inputGuardrailClasses; + private List<Class<?>> outputGuardrailClasses; + + public ChatModel getChatModel() { + return chatModel; + } + + public AgentConfiguration withChatModel(ChatModel chatModel) { + this.chatModel = chatModel; + return this; + } + + public ChatMemoryProvider getChatMemoryProvider() { + return chatMemoryProvider; + } + + public AgentConfiguration withChatMemoryProvider(ChatMemoryProvider chatMemoryProvider) { + this.chatMemoryProvider = chatMemoryProvider; + return this; + } + + public RetrievalAugmentor getRetrievalAugmentor() { + return retrievalAugmentor; + } + + public AgentConfiguration withRetrievalAugmentor(RetrievalAugmentor retrievalAugmentor) { + this.retrievalAugmentor = retrievalAugmentor; + return this; + } + + public List<Class<?>> getInputGuardrailClasses() { + return inputGuardrailClasses; + } + + public AgentConfiguration withInputGuardrailClassesList(String inputGuardrailClasses) { + return withInputGuardrailClasses(parseGuardrailClasses(inputGuardrailClasses)); + } + + public AgentConfiguration withInputGuardrailClasses(List<Class<?>> inputGuardrailClasses) { + this.inputGuardrailClasses = inputGuardrailClasses; + return this; + } + + public List<Class<?>> getOutputGuardrailClasses() { + return outputGuardrailClasses; + } + + public AgentConfiguration withOutputGuardrailClassesList(String outputGuardrailClasses) { + return withOutputGuardrailClasses(parseGuardrailClasses(outputGuardrailClasses)); + } + + public AgentConfiguration withOutputGuardrailClasses(List<Class<?>> outputGuardrailClasses) { + this.outputGuardrailClasses = outputGuardrailClasses; + return this; + } + + /** + * Parse comma-separated guardrail class names into a list of loaded classes. + * + * @param guardrailClassNames comma-separated class names, can be null or empty + * @return list of loaded classes, empty list if input is null or empty + */ + public static List<Class<?>> parseGuardrailClasses(String guardrailClassNames) { + if (guardrailClassNames == null || guardrailClassNames.trim().isEmpty()) { + return Collections.emptyList(); + } + + return Arrays.stream(guardrailClassNames.split(",")) + .map(String::trim) + .filter(name -> !name.isEmpty()) + .map(AgentConfiguration::loadGuardrailClass) + .filter(clazz -> clazz != null) + .collect(java.util.stream.Collectors.toList()); + } + + /** + * Load a guardrail class by name. + * + * @param className the fully qualified class name + * @return the loaded class, or null if loading failed + */ + protected static Class<?> loadGuardrailClass(String className) { + try { + return Class.forName(className); + } catch (ClassNotFoundException e) { + LOG.warn("Failed to load guardrail class: {}", className, e); + return null; + } + } +} diff --git a/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/AgentWithMemory.java b/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentWithMemory.java similarity index 50% rename from components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/AgentWithMemory.java rename to components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentWithMemory.java index 25985d86c6a..bf592b984b7 100644 --- a/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/AgentWithMemory.java +++ b/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentWithMemory.java @@ -14,16 +14,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.camel.component.langchain4j.agent; +package org.apache.camel.component.langchain4j.agent.api; import java.util.List; -import dev.langchain4j.memory.chat.ChatMemoryProvider; -import dev.langchain4j.model.chat.ChatModel; -import dev.langchain4j.rag.RetrievalAugmentor; import dev.langchain4j.service.AiServices; import dev.langchain4j.service.tool.ToolProvider; -import org.apache.camel.Exchange; +import org.apache.camel.component.langchain4j.agent.AiAgentBody; +import org.apache.camel.component.langchain4j.agent.AiAgentWithMemoryService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Implementation of Agent for AI agents with memory support. This agent handles chat interactions while maintaining @@ -31,33 +31,18 @@ import org.apache.camel.Exchange; * * This is an internal class used only within the LangChain4j agent component. */ -class AgentWithMemory implements Agent { +public class AgentWithMemory implements Agent { + private static final Logger LOG = LoggerFactory.getLogger(AgentWithMemory.class); - private final ChatModel chatModel; - private final ChatMemoryProvider chatMemoryProvider; - private final String tags; - private final RetrievalAugmentor retrievalAugmentor; - private final List<Class<?>> inputGuardrailClasses; - private final List<Class<?>> outputGuardrailClasses; - private final Exchange exchange; - private final ToolProvider toolProvider; + private final AgentConfiguration configuration; - public AgentWithMemory(ChatModel chatModel, ChatMemoryProvider chatMemoryProvider, String tags, - RetrievalAugmentor retrievalAugmentor, List<Class<?>> inputGuardrailClasses, - List<Class<?>> outputGuardrailClasses, Exchange exchange, ToolProvider toolProvider) { - this.chatModel = chatModel; - this.chatMemoryProvider = chatMemoryProvider; - this.tags = tags; - this.retrievalAugmentor = retrievalAugmentor; - this.inputGuardrailClasses = inputGuardrailClasses; - this.outputGuardrailClasses = outputGuardrailClasses; - this.exchange = exchange; - this.toolProvider = toolProvider; + public AgentWithMemory(AgentConfiguration configuration) { + this.configuration = configuration; } @Override - public String chat(AiAgentBody aiAgentBody) { - AiAgentWithMemoryService agentService = createAiAgentService(); + public String chat(AiAgentBody aiAgentBody, ToolProvider toolProvider) { + AiAgentWithMemoryService agentService = createAiAgentService(toolProvider); return aiAgentBody.getSystemMessage() != null ? agentService.chat(aiAgentBody.getMemoryId(), aiAgentBody.getUserMessage(), aiAgentBody.getSystemMessage()) @@ -67,10 +52,10 @@ class AgentWithMemory implements Agent { /** * Create AI service with a single universal tool that handles multiple Camel routes and Memory Provider */ - private AiAgentWithMemoryService createAiAgentService() { + private AiAgentWithMemoryService createAiAgentService(ToolProvider toolProvider) { var builder = AiServices.builder(AiAgentWithMemoryService.class) - .chatModel(chatModel) - .chatMemoryProvider(chatMemoryProvider); + .chatModel(configuration.getChatModel()) + .chatMemoryProvider(configuration.getChatMemoryProvider()); // Apache Camel Tool Provider if (toolProvider != null) { @@ -78,20 +63,21 @@ class AgentWithMemory implements Agent { } // RAG - if (retrievalAugmentor != null) { - builder.retrievalAugmentor(retrievalAugmentor); + if (configuration.getRetrievalAugmentor() != null) { + builder.retrievalAugmentor(configuration.getRetrievalAugmentor()); } // Input Guardrails - if (inputGuardrailClasses != null && !inputGuardrailClasses.isEmpty()) { - builder.inputGuardrailClasses((List) inputGuardrailClasses); + if (configuration.getInputGuardrailClasses() != null && !configuration.getInputGuardrailClasses().isEmpty()) { + builder.inputGuardrailClasses((List) configuration.getInputGuardrailClasses()); } // Output Guardrails - if (outputGuardrailClasses != null && !outputGuardrailClasses.isEmpty()) { - builder.outputGuardrailClasses((List) outputGuardrailClasses); + if (configuration.getOutputGuardrailClasses() != null && !configuration.getOutputGuardrailClasses().isEmpty()) { + builder.outputGuardrailClasses((List) configuration.getOutputGuardrailClasses()); } return builder.build(); } + } diff --git a/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/AgentWithoutMemory.java b/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentWithoutMemory.java similarity index 52% rename from components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/AgentWithoutMemory.java rename to components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentWithoutMemory.java index de9d0412600..c8700e31a38 100644 --- a/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/AgentWithoutMemory.java +++ b/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentWithoutMemory.java @@ -14,15 +14,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.camel.component.langchain4j.agent; +package org.apache.camel.component.langchain4j.agent.api; import java.util.List; -import dev.langchain4j.model.chat.ChatModel; -import dev.langchain4j.rag.RetrievalAugmentor; import dev.langchain4j.service.AiServices; import dev.langchain4j.service.tool.ToolProvider; -import org.apache.camel.Exchange; +import org.apache.camel.component.langchain4j.agent.AiAgentBody; +import org.apache.camel.component.langchain4j.agent.AiAgentWithoutMemoryService; /** * Implementation of Agent for AI agents without memory support. This agent handles chat interactions without @@ -30,31 +29,17 @@ import org.apache.camel.Exchange; * * This is an internal class used only within the LangChain4j agent component. */ -class AgentWithoutMemory implements Agent { +public class AgentWithoutMemory implements Agent { - private final ChatModel chatModel; - private final String tags; - private final RetrievalAugmentor retrievalAugmentor; - private final List<Class<?>> inputGuardrailClasses; - private final List<Class<?>> outputGuardrailClasses; - private final Exchange exchange; - private final ToolProvider toolProvider; + private final AgentConfiguration configuration; - public AgentWithoutMemory(ChatModel chatModel, String tags, RetrievalAugmentor retrievalAugmentor, - List<Class<?>> inputGuardrailClasses, List<Class<?>> outputGuardrailClasses, - Exchange exchange, ToolProvider toolProvider) { - this.chatModel = chatModel; - this.tags = tags; - this.retrievalAugmentor = retrievalAugmentor; - this.inputGuardrailClasses = inputGuardrailClasses; - this.outputGuardrailClasses = outputGuardrailClasses; - this.exchange = exchange; - this.toolProvider = toolProvider; + public AgentWithoutMemory(AgentConfiguration configuration) { + this.configuration = configuration; } @Override - public String chat(AiAgentBody aiAgentBody) { - AiAgentWithoutMemoryService agentService = createAiAgentService(); + public String chat(AiAgentBody aiAgentBody, ToolProvider toolProvider) { + AiAgentWithoutMemoryService agentService = createAiAgentService(toolProvider); return aiAgentBody.getSystemMessage() != null ? agentService.chat(aiAgentBody.getUserMessage(), aiAgentBody.getSystemMessage()) @@ -64,9 +49,9 @@ class AgentWithoutMemory implements Agent { /** * Create AI service with a single universal tool that handles multiple Camel routes */ - private AiAgentWithoutMemoryService createAiAgentService() { + private AiAgentWithoutMemoryService createAiAgentService(ToolProvider toolProvider) { var builder = AiServices.builder(AiAgentWithoutMemoryService.class) - .chatModel(chatModel); + .chatModel(configuration.getChatModel()); // Apache Camel Tool Provider if (toolProvider != null) { @@ -74,18 +59,18 @@ class AgentWithoutMemory implements Agent { } // RAG - if (retrievalAugmentor != null) { - builder.retrievalAugmentor(retrievalAugmentor); + if (configuration.getRetrievalAugmentor() != null) { + builder.retrievalAugmentor(configuration.getRetrievalAugmentor()); } // Input Guardrails - if (inputGuardrailClasses != null && !inputGuardrailClasses.isEmpty()) { - builder.inputGuardrailClasses((List) inputGuardrailClasses); + if (configuration.getInputGuardrailClasses() != null && !configuration.getInputGuardrailClasses().isEmpty()) { + builder.inputGuardrailClasses((List) configuration.getInputGuardrailClasses()); } // Output Guardrails - if (outputGuardrailClasses != null && !outputGuardrailClasses.isEmpty()) { - builder.outputGuardrailClasses((List) outputGuardrailClasses); + if (configuration.getOutputGuardrailClasses() != null && !configuration.getOutputGuardrailClasses().isEmpty()) { + builder.outputGuardrailClasses((List) configuration.getOutputGuardrailClasses()); } return builder.build(); diff --git a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentWithMemoryServiceTest.java b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentWithMemoryServiceTest.java index aae1003a4d1..414834185d0 100644 --- a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentWithMemoryServiceTest.java +++ b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentWithMemoryServiceTest.java @@ -16,7 +16,12 @@ */ package org.apache.camel.component.langchain4j.agent; +import java.util.List; + import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.langchain4j.agent.api.Agent; +import org.apache.camel.component.langchain4j.agent.api.AgentConfiguration; +import org.apache.camel.component.langchain4j.agent.api.AgentWithMemory; import org.apache.camel.component.langchain4j.agent.pojos.PersistentChatMemoryStore; import org.apache.camel.component.langchain4j.agent.pojos.TestSuccessInputGuardrail; import org.apache.camel.component.mock.MockEndpoint; @@ -127,16 +132,21 @@ public class LangChain4jAgentWithMemoryServiceTest extends BaseLangChain4jAgent @Override protected RouteBuilder createRouteBuilder() { - this.context.getRegistry().bind("chatModel", chatModel); - this.context.getRegistry().bind("memoryProvider", chatMemoryProvider); + AgentConfiguration configuration = new AgentConfiguration() + .withChatModel(chatModel) + .withChatMemoryProvider(chatMemoryProvider) + .withInputGuardrailClassesList("org.apache.camel.component.langchain4j.agent.pojos.TestSuccessInputGuardrail") + .withOutputGuardrailClasses(List.of()); + + Agent agent = new AgentWithMemory(configuration); + + this.context.getRegistry().bind("agent", agent); return new RouteBuilder() { public void configure() { // Tools + Memory + Guardrails + RAG from("direct:complete-agent") - .to("langchain4j-agent:complete?chatModel=#chatModel&chatMemoryProvider=#memoryProvider&tags=users,weather" - + - "&inputGuardrails=org.apache.camel.component.langchain4j.agent.pojos.TestSuccessInputGuardrail") + .to("langchain4j-agent:complete?agent=#agent&tags=users,weather") .to("mock:agent-response"); // Tool routes for function calling diff --git a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/AbstractRAGIT.java b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/AbstractRAGIT.java index 4f08e3a69c0..84b48bc7d8a 100644 --- a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/AbstractRAGIT.java +++ b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/AbstractRAGIT.java @@ -23,7 +23,6 @@ import dev.langchain4j.data.document.splitter.DocumentSplitters; import dev.langchain4j.data.embedding.Embedding; import dev.langchain4j.data.segment.TextSegment; import dev.langchain4j.model.embedding.EmbeddingModel; -import dev.langchain4j.model.openai.OpenAiEmbeddingModel; import dev.langchain4j.rag.DefaultRetrievalAugmentor; import dev.langchain4j.rag.RetrievalAugmentor; import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever; @@ -31,23 +30,14 @@ import dev.langchain4j.store.embedding.EmbeddingStore; import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore; import org.apache.camel.component.langchain4j.agent.BaseLangChain4jAgent; -import static java.time.Duration.ofSeconds; - public abstract class AbstractRAGIT extends BaseLangChain4jAgent { - private String openAiApiKey; - @Override protected void setupResources() throws Exception { super.setupResources(); - openAiApiKey = System.getenv("OPENAI_API_KEY"); - if (openAiApiKey == null || openAiApiKey.trim().isEmpty()) { - throw new IllegalStateException("OPENAI_API_KEY system property is required for testing"); - } - // Setup components - chatModel = createChatModel(openAiApiKey, null); + chatModel = ModelHelper.loadFromEnv(); retrievalAugmentor = createRetrievalAugmentor(); } @@ -59,11 +49,7 @@ public abstract class AbstractRAGIT extends BaseLangChain4jAgent { List<TextSegment> segments = DocumentSplitters.recursive(300, 100).split(document); // Create embeddings - EmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder() - .apiKey(openAiApiKey) - .modelName("text-embedding-ada-002") - .timeout(ofSeconds(30)) - .build(); + EmbeddingModel embeddingModel = ModelHelper.createEmbeddingModel(); List<Embedding> embeddings = embeddingModel.embedAll(segments).content(); @@ -79,9 +65,10 @@ public abstract class AbstractRAGIT extends BaseLangChain4jAgent { .minScore(0.6) .build(); - // Create a RetreivalAugmentor that uses only a content retriever : naive rag scenario + // Create a RetrievalAugmentor that uses only a content retriever : naive rag scenario return DefaultRetrievalAugmentor.builder() .contentRetriever(contentRetriever) .build(); } + } diff --git a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentGuardrailsIntegrationIT.java b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentGuardrailsIntegrationIT.java index ad3f437d715..70298c33aa5 100644 --- a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentGuardrailsIntegrationIT.java +++ b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentGuardrailsIntegrationIT.java @@ -16,9 +16,13 @@ */ package org.apache.camel.component.langchain4j.agent.integration; +import java.util.List; + import dev.langchain4j.model.chat.ChatModel; -import dev.langchain4j.model.openai.OpenAiChatModel; import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.langchain4j.agent.api.Agent; +import org.apache.camel.component.langchain4j.agent.api.AgentConfiguration; +import org.apache.camel.component.langchain4j.agent.api.AgentWithoutMemory; import org.apache.camel.component.langchain4j.agent.pojos.TestFailingInputGuardrail; import org.apache.camel.component.langchain4j.agent.pojos.TestJsonOutputGuardrail; import org.apache.camel.component.langchain4j.agent.pojos.TestSuccessInputGuardrail; @@ -26,41 +30,22 @@ import org.apache.camel.component.mock.MockEndpoint; import org.apache.camel.test.junit5.CamelTestSupport; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfSystemProperty; +import org.junit.jupiter.api.condition.EnabledIf; -import static dev.langchain4j.model.openai.OpenAiChatModelName.GPT_4_O_MINI; -import static java.time.Duration.ofSeconds; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -@EnabledIfSystemProperty(named = "OPENAI_API_KEY", matches = ".*", disabledReason = "OpenAI API key required") +@EnabledIf("org.apache.camel.component.langchain4j.agent.integration.ModelHelper#environmentWithoutEmbeddings") public class LangChain4jAgentGuardrailsIntegrationIT extends CamelTestSupport { protected ChatModel chatModel; - private String openAiApiKey; @Override protected void setupResources() throws Exception { super.setupResources(); - - openAiApiKey = System.getenv("OPENAI_API_KEY"); - if (openAiApiKey == null || openAiApiKey.trim().isEmpty()) { - throw new IllegalStateException("OPENAI_API_KEY system property is required for testing"); - } - chatModel = createModel(); - } - - protected ChatModel createModel() { - return OpenAiChatModel.builder() - .apiKey(openAiApiKey) - .modelName(GPT_4_O_MINI) - .temperature(1.0) - .timeout(ofSeconds(60)) - .logRequests(true) - .logResponses(true) - .build(); + chatModel = ModelHelper.loadFromEnv(); } @BeforeEach @@ -215,24 +200,57 @@ public class LangChain4jAgentGuardrailsIntegrationIT extends CamelTestSupport { @Override protected RouteBuilder createRouteBuilder() { - this.context.getRegistry().bind("chatModel", chatModel); + // Create agent with input guardrails + AgentConfiguration inputGuardrailsConfig = new AgentConfiguration() + .withChatModel(chatModel) + .withInputGuardrailClassesList("org.apache.camel.component.langchain4j.agent.pojos.TestSuccessInputGuardrail") + .withOutputGuardrailClasses(List.of()); + Agent agentWithInputGuardrails = new AgentWithoutMemory(inputGuardrailsConfig); + + // Create agent with multiple input guardrails + AgentConfiguration multipleInputGuardrailsConfig = new AgentConfiguration() + .withChatModel(chatModel) + .withInputGuardrailClassesList( + "org.apache.camel.component.langchain4j.agent.pojos.TestSuccessInputGuardrail,org.apache.camel.component.langchain4j.agent.pojos.TestFailingInputGuardrail") + .withOutputGuardrailClasses(List.of()); + Agent agentWithMultipleInputGuardrails = new AgentWithoutMemory(multipleInputGuardrailsConfig); + + // Create agent with output guardrails + AgentConfiguration outputGuardrailsConfig = new AgentConfiguration() + .withChatModel(chatModel) + .withInputGuardrailClasses(List.of()) + .withOutputGuardrailClassesList("org.apache.camel.component.langchain4j.agent.pojos.TestJsonOutputGuardrail"); + Agent agentWithOutputGuardrails = new AgentWithoutMemory(outputGuardrailsConfig); + + // Create agent with mixed guardrails + AgentConfiguration mixedGuardrailsConfig = new AgentConfiguration() + .withChatModel(chatModel) + .withInputGuardrailClassesList("org.apache.camel.component.langchain4j.agent.pojos.TestSuccessInputGuardrail") + .withOutputGuardrailClassesList("org.apache.camel.component.langchain4j.agent.pojos.TestJsonOutputGuardrail"); + Agent agentWithMixedGuardrails = new AgentWithoutMemory(mixedGuardrailsConfig); + + // Register agents in the context + this.context.getRegistry().bind("agentWithInputGuardrails", agentWithInputGuardrails); + this.context.getRegistry().bind("agentWithMultipleInputGuardrails", agentWithMultipleInputGuardrails); + this.context.getRegistry().bind("agentWithOutputGuardrails", agentWithOutputGuardrails); + this.context.getRegistry().bind("agentWithMixedGuardrails", agentWithMixedGuardrails); return new RouteBuilder() { public void configure() { from("direct:agent-with-input-guardrails") - .to("langchain4j-agent:test-agent?chatModel=#chatModel&inputGuardrails=org.apache.camel.component.langchain4j.agent.pojos.TestSuccessInputGuardrail") + .to("langchain4j-agent:test-agent?agent=#agentWithInputGuardrails") .to("mock:agent-response"); from("direct:agent-with-multiple-input-guardrails") - .to("langchain4j-agent:test-agent?chatModel=#chatModel&inputGuardrails=org.apache.camel.component.langchain4j.agent.pojos.TestSuccessInputGuardrail,org.apache.camel.component.langchain4j.agent.pojos.TestFailingInputGuardrail") + .to("langchain4j-agent:test-agent?agent=#agentWithMultipleInputGuardrails") .to("mock:agent-response"); from("direct:agent-with-json-output-guardrail") - .to("langchain4j-agent:test-agent?chatModel=#chatModel&outputGuardrails=org.apache.camel.component.langchain4j.agent.pojos.TestJsonOutputGuardrail") + .to("langchain4j-agent:test-agent?agent=#agentWithOutputGuardrails") .to("mock:agent-response"); from("direct:agent-with-mixed-guardrails") - .to("langchain4j-agent:test-agent?chatModel=#chatModel&inputGuardrails=org.apache.camel.component.langchain4j.agent.pojos.TestSuccessInputGuardrail&outputGuardrails=org.apache.camel.component.langchain4j.agent.pojos.TestJsonOutputGuardrail") + .to("langchain4j-agent:test-agent?agent=#agentWithMixedGuardrails") .to("mock:agent-response"); } }; 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 eb039465843..74b4645b400 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 @@ -16,16 +16,21 @@ */ package org.apache.camel.component.langchain4j.agent.integration; +import java.util.List; + import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.langchain4j.agent.api.Agent; +import org.apache.camel.component.langchain4j.agent.api.AgentConfiguration; +import org.apache.camel.component.langchain4j.agent.api.AgentWithoutMemory; import org.apache.camel.component.mock.MockEndpoint; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfSystemProperty; +import org.junit.jupiter.api.condition.EnabledIf; import static org.apache.camel.component.langchain4j.agent.LangChain4jAgent.Headers.SYSTEM_MESSAGE; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; -@EnabledIfSystemProperty(named = "OPENAI_API_KEY", matches = ".*", disabledReason = "OpenAI API key required") +@EnabledIf("org.apache.camel.component.langchain4j.agent.integration.ModelHelper#isEmbeddingCapable") public class LangChain4jAgentNaiveRagIT extends AbstractRAGIT { private static final String SYSTEM_MESSAGE_CUSTOMER_SERVICE @@ -147,13 +152,22 @@ public class LangChain4jAgentNaiveRagIT extends AbstractRAGIT { @Override protected RouteBuilder createRouteBuilder() { - this.context.getRegistry().bind("chatModel", chatModel); - this.context.getRegistry().bind("retrievalAugmentor", retrievalAugmentor); + // Create agent configuration with RAG support + AgentConfiguration configuration = new AgentConfiguration() + .withChatModel(chatModel) + .withRetrievalAugmentor(retrievalAugmentor) + .withInputGuardrailClasses(List.of()) + .withOutputGuardrailClasses(List.of()); + + Agent agentWithRag = new AgentWithoutMemory(configuration); + + // Register agent in the context + this.context.getRegistry().bind("agentWithRag", agentWithRag); return new RouteBuilder() { public void configure() { from("direct:agent-with-rag") - .to("langchain4j-agent:test-rag-agent?chatModel=#chatModel&retrievalAugmentor=#retrievalAugmentor") + .to("langchain4j-agent:test-rag-agent?agent=#agentWithRag") .to("mock:rag-response"); } }; diff --git a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentServiceIT.java b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentServiceIT.java index d4149596a4a..ef7d4ffeb83 100644 --- a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentServiceIT.java +++ b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentServiceIT.java @@ -18,17 +18,20 @@ package org.apache.camel.component.langchain4j.agent.integration; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.langchain4j.agent.AiAgentBody; +import org.apache.camel.component.langchain4j.agent.api.Agent; +import org.apache.camel.component.langchain4j.agent.api.AgentConfiguration; +import org.apache.camel.component.langchain4j.agent.api.AgentWithoutMemory; import org.apache.camel.component.langchain4j.agent.pojos.TestJsonOutputGuardrail; import org.apache.camel.component.langchain4j.agent.pojos.TestSuccessInputGuardrail; import org.apache.camel.component.mock.MockEndpoint; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfSystemProperty; +import org.junit.jupiter.api.condition.EnabledIf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; -@EnabledIfSystemProperty(named = "OPENAI_API_KEY", matches = ".*", disabledReason = "OpenAI API key required") +@EnabledIf("org.apache.camel.component.langchain4j.agent.integration.ModelHelper#isEmbeddingCapable") public class LangChain4jAgentServiceIT extends AbstractRAGIT { private static final String USER_DATABASE = """ @@ -91,18 +94,23 @@ public class LangChain4jAgentServiceIT extends AbstractRAGIT { @Override protected RouteBuilder createRouteBuilder() { - this.context.getRegistry().bind("chatModel", chatModel); - this.context.getRegistry().bind("retrievalAugmentor", retrievalAugmentor); + // Create agent configuration with all features: RAG, Tools, and Guardrails + AgentConfiguration configuration = new AgentConfiguration() + .withChatModel(chatModel) + .withRetrievalAugmentor(retrievalAugmentor) + .withInputGuardrailClassesList("org.apache.camel.component.langchain4j.agent.pojos.TestSuccessInputGuardrail") + .withOutputGuardrailClassesList("org.apache.camel.component.langchain4j.agent.pojos.TestJsonOutputGuardrail"); + + Agent completeAgent = new AgentWithoutMemory(configuration); + + // Register agent in the context + this.context.getRegistry().bind("completeAgent", completeAgent); return new RouteBuilder() { public void configure() { from("direct:complete-agent-no-memory") - .to("langchain4j-agent:no-memory?chatModel=#chatModel&tags=users,weather" + - "&retrievalAugmentor=#retrievalAugmentor" + - "&inputGuardrails=org.apache.camel.component.langchain4j.agent.pojos.TestSuccessInputGuardrail" - + - "&outputGuardrails=org.apache.camel.component.langchain4j.agent.pojos.TestJsonOutputGuardrail") + .to("langchain4j-agent:no-memory?agent=#completeAgent&tags=users,weather") .to("mock:agent-response"); from("langchain4j-tools:userDb?tags=users&description=Query user database by user ID¶meter.userId=string") 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 2cf92ccc68f..09a0c80c404 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 @@ -16,28 +16,29 @@ */ package org.apache.camel.component.langchain4j.agent.integration; +import java.util.List; import java.util.Map; import dev.langchain4j.memory.chat.ChatMemoryProvider; import dev.langchain4j.memory.chat.MessageWindowChatMemory; import dev.langchain4j.model.chat.ChatModel; -import dev.langchain4j.model.openai.OpenAiChatModel; import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.langchain4j.agent.api.Agent; +import org.apache.camel.component.langchain4j.agent.api.AgentConfiguration; +import org.apache.camel.component.langchain4j.agent.api.AgentWithMemory; import org.apache.camel.component.langchain4j.agent.pojos.PersistentChatMemoryStore; import org.apache.camel.component.mock.MockEndpoint; import org.apache.camel.test.junit5.CamelTestSupport; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfSystemProperty; +import org.junit.jupiter.api.condition.EnabledIf; -import static dev.langchain4j.model.openai.OpenAiChatModelName.GPT_4_O_MINI; -import static java.time.Duration.ofSeconds; import static org.apache.camel.component.langchain4j.agent.LangChain4jAgent.Headers.MEMORY_ID; import static org.apache.camel.component.langchain4j.agent.LangChain4jAgent.Headers.SYSTEM_MESSAGE; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; -@EnabledIfSystemProperty(named = "OPENAI_API_KEY", matches = ".*", disabledReason = "OpenAI API key required") +@EnabledIf("org.apache.camel.component.langchain4j.agent.integration.ModelHelper#environmentWithoutEmbeddings") public class LangChain4jAgentWithMemoryIT extends CamelTestSupport { private static final int MEMORY_ID_SESSION_1 = 1; @@ -47,34 +48,17 @@ public class LangChain4jAgentWithMemoryIT extends CamelTestSupport { protected ChatModel chatModel; protected ChatMemoryProvider chatMemoryProvider; - private String openAiApiKey; private PersistentChatMemoryStore store; @Override protected void setupResources() throws Exception { super.setupResources(); - openAiApiKey = System.getenv("OPENAI_API_KEY"); - if (openAiApiKey == null || openAiApiKey.trim().isEmpty()) { - throw new IllegalStateException("OPENAI_API_KEY system property is required for testing"); - } - - chatModel = createModel(); + chatModel = ModelHelper.loadFromEnv(); store = new PersistentChatMemoryStore(); chatMemoryProvider = createMemoryProvider(); } - protected ChatModel createModel() { - return OpenAiChatModel.builder() - .apiKey(openAiApiKey) - .modelName(GPT_4_O_MINI) - .temperature(1.0) - .timeout(ofSeconds(60)) - .logRequests(true) - .logResponses(true) - .build(); - } - protected ChatMemoryProvider createMemoryProvider() { // Create a message window memory that keeps the last 10 messages ChatMemoryProvider chatMemoryProvider = memoryId -> MessageWindowChatMemory.builder() @@ -212,19 +196,27 @@ public class LangChain4jAgentWithMemoryIT extends CamelTestSupport { @Override protected RouteBuilder createRouteBuilder() { - this.context.getRegistry().bind("chatModel", chatModel); + // Create agent configuration with memory support + AgentConfiguration configuration = new AgentConfiguration() + .withChatModel(chatModel) + .withChatMemoryProvider(chatMemoryProvider) + .withInputGuardrailClasses(List.of()) + .withOutputGuardrailClasses(List.of()); + + Agent agentWithMemory = new AgentWithMemory(configuration); - this.context.getRegistry().bind("chatMemoryProvider", chatMemoryProvider); + // Register agent in the context + this.context.getRegistry().bind("agentWithMemory", agentWithMemory); return new RouteBuilder() { public void configure() { // Agent routes for memory testing from("direct:agent-with-memory") - .to("langchain4j-agent:test-memory-agent?chatModel=#chatModel&chatMemoryProvider=#chatMemoryProvider") + .to("langchain4j-agent:test-memory-agent?agent=#agentWithMemory") .to("mock:memory-response"); from("direct:agent-with-memory-system") - .to("langchain4j-agent:test-memory-agent?chatModel=#chatModel&chatMemoryProvider=#chatMemoryProvider") + .to("langchain4j-agent:test-memory-agent?agent=#agentWithMemory") .to("mock:memory-response"); } diff --git a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentWithMemoryServiceIT.java b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentWithMemoryServiceIT.java index 4749e4557c8..f1cf5e714cb 100644 --- a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentWithMemoryServiceIT.java +++ b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentWithMemoryServiceIT.java @@ -16,24 +16,31 @@ */ package org.apache.camel.component.langchain4j.agent.integration; +import java.util.List; + import dev.langchain4j.memory.chat.ChatMemoryProvider; import dev.langchain4j.memory.chat.MessageWindowChatMemory; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.langchain4j.agent.AiAgentBody; +import org.apache.camel.component.langchain4j.agent.api.Agent; +import org.apache.camel.component.langchain4j.agent.api.AgentConfiguration; +import org.apache.camel.component.langchain4j.agent.api.AgentWithMemory; import org.apache.camel.component.langchain4j.agent.pojos.PersistentChatMemoryStore; import org.apache.camel.component.langchain4j.agent.pojos.TestJsonOutputGuardrail; import org.apache.camel.component.langchain4j.agent.pojos.TestSuccessInputGuardrail; import org.apache.camel.component.mock.MockEndpoint; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfSystemProperty; +import org.junit.jupiter.api.condition.EnabledIf; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Class to test a mix match between all those different concepts : memory / tool / RAG / guardrails */ -@EnabledIfSystemProperty(named = "OPENAI_API_KEY", matches = ".*", disabledReason = "OpenAI API key required") +@EnabledIf("org.apache.camel.component.langchain4j.agent.integration.ModelHelper#isEmbeddingCapable") public class LangChain4jAgentWithMemoryServiceIT extends AbstractRAGIT { private static final int MEMORY_ID_SESSION = 42; @@ -180,36 +187,53 @@ public class LangChain4jAgentWithMemoryServiceIT extends AbstractRAGIT { @Override protected RouteBuilder createRouteBuilder() { - this.context.getRegistry().bind("chatModel", chatModel); - this.context.getRegistry().bind("retrievalAugmentor", retrievalAugmentor); - this.context.getRegistry().bind("memoryProvider", chatMemoryProvider); + // Create complete agent configuration with Tools + Memory + Input Guardrails + RAG + AgentConfiguration completeConfig = new AgentConfiguration() + .withChatModel(chatModel) + .withChatMemoryProvider(chatMemoryProvider) + .withRetrievalAugmentor(retrievalAugmentor) + .withInputGuardrailClassesList("org.apache.camel.component.langchain4j.agent.pojos.TestSuccessInputGuardrail") + .withOutputGuardrailClasses(List.of()); + Agent completeAgent = new AgentWithMemory(completeConfig); + + // Create complete agent with JSON output guardrails + AgentConfiguration completeJsonConfig = new AgentConfiguration() + .withChatModel(chatModel) + .withChatMemoryProvider(chatMemoryProvider) + .withRetrievalAugmentor(retrievalAugmentor) + .withInputGuardrailClassesList("org.apache.camel.component.langchain4j.agent.pojos.TestSuccessInputGuardrail") + .withOutputGuardrailClassesList("org.apache.camel.component.langchain4j.agent.pojos.TestJsonOutputGuardrail"); + Agent completeJsonAgent = new AgentWithMemory(completeJsonConfig); + + // Create RAG-only agent (no tools, just RAG + Memory + Input Guardrails) + AgentConfiguration ragOnlyConfig = new AgentConfiguration() + .withChatModel(chatModel) + .withChatMemoryProvider(chatMemoryProvider) + .withRetrievalAugmentor(retrievalAugmentor) + .withInputGuardrailClassesList("org.apache.camel.component.langchain4j.agent.pojos.TestSuccessInputGuardrail") + .withOutputGuardrailClasses(List.of()); + Agent ragOnlyAgent = new AgentWithMemory(ragOnlyConfig); + + // Register agents in the context + this.context.getRegistry().bind("completeAgent", completeAgent); + this.context.getRegistry().bind("completeJsonAgent", completeJsonAgent); + this.context.getRegistry().bind("ragOnlyAgent", ragOnlyAgent); return new RouteBuilder() { public void configure() { // Tools + Memory + Guardrails + RAG from("direct:complete-agent") - .to("langchain4j-agent:complete?chatModel=#chatModel&chatMemoryProvider=#memoryProvider&tags=users,weather" - + - "&retrievalAugmentor=#retrievalAugmentor" + - "&inputGuardrails=org.apache.camel.component.langchain4j.agent.pojos.TestSuccessInputGuardrail") + .to("langchain4j-agent:complete?agent=#completeAgent&tags=users,weather") .to("mock:agent-response"); // Tools + Memory + JSON output Guardrails + RAG from("direct:complete-agent-json") - .to("langchain4j-agent:complete-json?chatModel=#chatModel&tags=users" + - "&chatMemoryProvider=#memoryProvider" + - "&retrievalAugmentor=#retrievalAugmentor" + - "&inputGuardrails=org.apache.camel.component.langchain4j.agent.pojos.TestSuccessInputGuardrail" - + - "&outputGuardrails=org.apache.camel.component.langchain4j.agent.pojos.TestJsonOutputGuardrail") + .to("langchain4j-agent:complete-json?agent=#completeJsonAgent&tags=users") .to("mock:agent-response"); // RAG only without tools from("direct:rag-only-agent") - .to("langchain4j-agent:rag-only?chatModel=#chatModel" + - "&chatMemoryProvider=#memoryProvider" + - "&retrievalAugmentor=#retrievalAugmentor" + - "&inputGuardrails=org.apache.camel.component.langchain4j.agent.pojos.TestSuccessInputGuardrail") + .to("langchain4j-agent:rag-only?agent=#ragOnlyAgent") .to("mock:agent-response"); // Tool routes for function calling diff --git a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentWithToolsIT.java b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentWithToolsIT.java index 29a1d2e5a35..d0638bcd200 100644 --- a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentWithToolsIT.java +++ b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentWithToolsIT.java @@ -16,21 +16,23 @@ */ package org.apache.camel.component.langchain4j.agent.integration; +import java.util.List; + import dev.langchain4j.model.chat.ChatModel; -import dev.langchain4j.model.openai.OpenAiChatModel; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.langchain4j.agent.AiAgentBody; +import org.apache.camel.component.langchain4j.agent.api.Agent; +import org.apache.camel.component.langchain4j.agent.api.AgentConfiguration; +import org.apache.camel.component.langchain4j.agent.api.AgentWithoutMemory; import org.apache.camel.component.mock.MockEndpoint; import org.apache.camel.test.junit5.CamelTestSupport; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfSystemProperty; +import org.junit.jupiter.api.condition.EnabledIf; -import static dev.langchain4j.model.openai.OpenAiChatModelName.GPT_4_O_MINI; -import static java.time.Duration.ofSeconds; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; -@EnabledIfSystemProperty(named = "OPENAI_API_KEY", matches = ".*", disabledReason = "OpenAI API key required") +@EnabledIf("org.apache.camel.component.langchain4j.agent.integration.ModelHelper#environmentWithoutEmbeddings") public class LangChain4jAgentWithToolsIT extends CamelTestSupport { private static final String USER_DB_NAME = "John Doe"; @@ -45,22 +47,7 @@ public class LangChain4jAgentWithToolsIT extends CamelTestSupport { protected void setupResources() throws Exception { super.setupResources(); - openAiApiKey = System.getenv("OPENAI_API_KEY"); - if (openAiApiKey == null || openAiApiKey.trim().isEmpty()) { - throw new IllegalStateException("OPENAI_API_KEY system property is required for testing"); - } - chatModel = createModel(); - } - - protected ChatModel createModel() { - return OpenAiChatModel.builder() - .apiKey(openAiApiKey) - .modelName(GPT_4_O_MINI) - .temperature(1.0) - .timeout(ofSeconds(60)) - .logRequests(true) - .logResponses(true) - .build(); + chatModel = ModelHelper.loadFromEnv(); } @Test @@ -159,32 +146,41 @@ public class LangChain4jAgentWithToolsIT extends CamelTestSupport { @Override protected RouteBuilder createRouteBuilder() { - this.context.getRegistry().bind("chatModel", chatModel); + // Create agent configuration for tools testing (no memory, RAG, or guardrails) + AgentConfiguration configuration = new AgentConfiguration() + .withChatModel(chatModel) + .withInputGuardrailClasses(List.of()) + .withOutputGuardrailClasses(List.of()); + + Agent agentWithTools = new AgentWithoutMemory(configuration); + + // Register agent in the context + this.context.getRegistry().bind("agentWithTools", agentWithTools); return new RouteBuilder() { public void configure() { from("direct:agent-with-user-tools") - .to("langchain4j-agent:test-agent?chatModel=#chatModel&tags=users") + .to("langchain4j-agent:test-agent?agent=#agentWithTools&tags=users") .to("mock:agent-response"); from("direct:agent-with-weather-tools") - .to("langchain4j-agent:test-agent?chatModel=#chatModel&tags=weather") + .to("langchain4j-agent:test-agent?agent=#agentWithTools&tags=weather") .to("mock:agent-response"); from("direct:agent-with-multiple-tools") - .to("langchain4j-agent:test-agent?chatModel=#chatModel&tags=users,weather") + .to("langchain4j-agent:test-agent?agent=#agentWithTools&tags=users,weather") .to("mock:agent-response"); from("direct:agent-with-configured-tags") - .to("langchain4j-agent:test-agent?chatModel=#chatModel&tags=weather") + .to("langchain4j-agent:test-agent?agent=#agentWithTools&tags=weather") .to("mock:agent-response"); from("direct:agent-without-tools") - .to("langchain4j-agent:test-agent?chatModel=#chatModel") + .to("langchain4j-agent:test-agent?agent=#agentWithTools") .to("mock:agent-response"); from("direct:agent-check-no-tools") - .to("langchain4j-agent:test-agent?chatModel=#chatModel&tags=nonexistent") + .to("langchain4j-agent:test-agent?agent=#agentWithTools&tags=nonexistent") .to("mock:check-no-tools"); from("langchain4j-tools:userDb?tags=users&description=Query user database by user ID¶meter.userId=integer") 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 b18d0ae6ef3..f74e402bb18 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 @@ -16,23 +16,25 @@ */ package org.apache.camel.component.langchain4j.agent.integration; +import java.util.List; + import dev.langchain4j.model.chat.ChatModel; -import dev.langchain4j.model.openai.OpenAiChatModel; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.langchain4j.agent.AiAgentBody; +import org.apache.camel.component.langchain4j.agent.api.Agent; +import org.apache.camel.component.langchain4j.agent.api.AgentConfiguration; +import org.apache.camel.component.langchain4j.agent.api.AgentWithoutMemory; import org.apache.camel.component.mock.MockEndpoint; import org.apache.camel.test.junit5.CamelTestSupport; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfSystemProperty; +import org.junit.jupiter.api.condition.EnabledIf; -import static dev.langchain4j.model.openai.OpenAiChatModelName.GPT_4_O_MINI; -import static java.time.Duration.ofSeconds; import static org.apache.camel.component.langchain4j.agent.LangChain4jAgent.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; -@EnabledIfSystemProperty(named = "OPENAI_API_KEY", matches = ".*", disabledReason = "OpenAI API key required") +@EnabledIf("org.apache.camel.component.langchain4j.agent.integration.ModelHelper#environmentWithoutEmbeddings") public class LangChain4jSimpleAgentIT extends CamelTestSupport { // Test constants @@ -46,28 +48,11 @@ public class LangChain4jSimpleAgentIT extends CamelTestSupport { protected ChatModel chatModel; - private String openAiApiKey; - @Override protected void setupResources() throws Exception { super.setupResources(); - openAiApiKey = System.getenv("OPENAI_API_KEY"); - if (openAiApiKey == null || openAiApiKey.trim().isEmpty()) { - throw new IllegalStateException("OPENAI_API_KEY environment variable is required for testing"); - } - chatModel = createModel(); - } - - protected ChatModel createModel() { - return OpenAiChatModel.builder() - .apiKey(openAiApiKey) - .modelName(GPT_4_O_MINI) - .temperature(1.0) - .timeout(ofSeconds(60)) - .logRequests(true) - .logResponses(true) - .build(); + chatModel = ModelHelper.loadFromEnv(); } @Test @@ -118,12 +103,21 @@ public class LangChain4jSimpleAgentIT extends CamelTestSupport { @Override protected RouteBuilder createRouteBuilder() { - this.context.getRegistry().bind("chatModel", chatModel); + // Create simple agent configuration (no memory, tools, RAG, or guardrails) + AgentConfiguration configuration = new AgentConfiguration() + .withChatModel(chatModel) + .withInputGuardrailClasses(List.of()) + .withOutputGuardrailClasses(List.of()); + + Agent simpleAgent = new AgentWithoutMemory(configuration); + + // Register agent in the context + this.context.getRegistry().bind("simpleAgent", simpleAgent); return new RouteBuilder() { public void configure() { from("direct:send-simple-user-message") - .to("langchain4j-agent:test-agent?chatModel=#chatModel") + .to("langchain4j-agent:test-agent?agent=#simpleAgent") .to("mock:response"); } }; diff --git a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/ModelHelper.java b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/ModelHelper.java new file mode 100644 index 00000000000..875c740a034 --- /dev/null +++ b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/ModelHelper.java @@ -0,0 +1,111 @@ +/* + * 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.integration; + +import dev.langchain4j.model.chat.ChatModel; +import dev.langchain4j.model.embedding.EmbeddingModel; +import dev.langchain4j.model.googleai.GoogleAiGeminiChatModel; +import dev.langchain4j.model.openai.OpenAiChatModel; +import dev.langchain4j.model.openai.OpenAiChatModelName; +import dev.langchain4j.model.openai.OpenAiEmbeddingModel; + +import static java.time.Duration.ofSeconds; + +public class ModelHelper { + + public static final String API_KEY = "API_KEY"; + public static final String MODEL_PROVIDER = "MODEL_PROVIDER"; + + protected static ChatModel createGeminiModel(String apiKey) { + return GoogleAiGeminiChatModel.builder() + .apiKey(apiKey) + .modelName("gemini-2.5-flash") + .temperature(1.0) + .timeout(ofSeconds(60)) + .logRequestsAndResponses(true) + .build(); + } + + protected static ChatModel createOpenAiModel(String apiKey) { + return OpenAiChatModel.builder() + .apiKey(apiKey) + .modelName(OpenAiChatModelName.GPT_4_O_MINI) + .temperature(1.0) + .timeout(ofSeconds(60)) + .logRequests(true) + .logResponses(true) + .build(); + } + + protected static ChatModel createExternalChatModel(String name, String apiKey) { + return switch (name) { + case "gemini" -> createGeminiModel(apiKey); + case "openai" -> createOpenAiModel(apiKey); + default -> throw new IllegalArgumentException("Unknown chat model: " + name); + }; + } + + public static ChatModel loadFromEnv() { + var apiKey = System.getenv(API_KEY); + var modelProvider = System.getenv(MODEL_PROVIDER); + + if (apiKey == null || apiKey.trim().isEmpty()) { + throw new IllegalStateException("API_KEY system property is required for testing"); + } + if (modelProvider == null || modelProvider.trim().isEmpty()) { + throw new IllegalStateException("MODEL_PROVIDER system property is required for testing"); + } + + return ModelHelper.createExternalChatModel(modelProvider, apiKey); + } + + public static EmbeddingModel createEmbeddingModel() { + var apiKey = System.getenv(API_KEY); + + // Create embeddings + return OpenAiEmbeddingModel.builder() + .apiKey(apiKey) + .modelName("text-embedding-ada-002") + .timeout(ofSeconds(30)) + .build(); + } + + public static boolean environmentWithoutEmbeddings() { + var apiKey = System.getenv(API_KEY); + if (apiKey == null) { + return false; + } + + var modelProvider = System.getenv(MODEL_PROVIDER); + if (modelProvider == null) { + return false; + } + + return true; + } + + public static boolean isEmbeddingCapable() { + var modelProvider = System.getenv(MODEL_PROVIDER); + + return "openai".equals(modelProvider); + } + + public static String getApiKey() { + return System.getenv(API_KEY); + } +} diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jAgentEndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jAgentEndpointBuilderFactory.java index 3c3bef374af..5e86361ca3e 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jAgentEndpointBuilderFactory.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jAgentEndpointBuilderFactory.java @@ -17,14 +17,11 @@ */ package org.apache.camel.builder.endpoint.dsl; -import java.util.*; -import java.util.concurrent.*; -import java.util.function.*; -import java.util.stream.*; import javax.annotation.processing.Generated; -import org.apache.camel.builder.EndpointConsumerBuilder; + import org.apache.camel.builder.EndpointProducerBuilder; import org.apache.camel.builder.endpoint.AbstractEndpointBuilder; +import org.apache.camel.component.langchain4j.agent.api.Agent; /** * LangChain4j Agent component @@ -45,33 +42,33 @@ public interface LangChain4jAgentEndpointBuilderFactory { } /** - * Comma-separated list of input guardrail class names to validate user - * input before sending to LLM. + * The agent to use for the component. * - * The option is a: <code>java.lang.String</code> type. + * The option is a: + * <code>org.apache.camel.component.langchain4j.agent.api.Agent</code> type. * * Group: producer * - * @param inputGuardrails the value to set + * @param agent the value to set * @return the dsl builder */ - default LangChain4jAgentEndpointBuilder inputGuardrails(String inputGuardrails) { - doSetProperty("inputGuardrails", inputGuardrails); + default LangChain4jAgentEndpointBuilder agent(Agent agent) { + doSetProperty("agent", agent); return this; } /** - * Comma-separated list of output guardrail class names to validate LLM - * responses. + * The agent to use for the component. * - * The option is a: <code>java.lang.String</code> type. + * The option will be converted to a + * <code>org.apache.camel.component.langchain4j.agent.api.Agent</code> type. * * Group: producer * - * @param outputGuardrails the value to set + * @param agent the value to set * @return the dsl builder */ - default LangChain4jAgentEndpointBuilder outputGuardrails(String outputGuardrails) { - doSetProperty("outputGuardrails", outputGuardrails); + default LangChain4jAgentEndpointBuilder agent(String agent) { + doSetProperty("agent", agent); return this; } /** @@ -146,108 +143,6 @@ public interface LangChain4jAgentEndpointBuilderFactory { doSetProperty("lazyStartProducer", lazyStartProducer); return this; } - /** - * Chat Memory Provider of type - * dev.langchain4j.memory.ChatMemoryProvider. Note for this to be - * successful, you need to use a reliable ChatMemoryStore. This provider - * supposes that a user has multiple sessions, if need only a single - * session, use a default memoryId. - * - * The option is a: - * <code>dev.langchain4j.memory.chat.ChatMemoryProvider</code> type. - * - * Group: advanced - * - * @param chatMemoryProvider the value to set - * @return the dsl builder - */ - default AdvancedLangChain4jAgentEndpointBuilder chatMemoryProvider(dev.langchain4j.memory.chat.ChatMemoryProvider chatMemoryProvider) { - doSetProperty("chatMemoryProvider", chatMemoryProvider); - return this; - } - /** - * Chat Memory Provider of type - * dev.langchain4j.memory.ChatMemoryProvider. Note for this to be - * successful, you need to use a reliable ChatMemoryStore. This provider - * supposes that a user has multiple sessions, if need only a single - * session, use a default memoryId. - * - * The option will be converted to a - * <code>dev.langchain4j.memory.chat.ChatMemoryProvider</code> type. - * - * Group: advanced - * - * @param chatMemoryProvider the value to set - * @return the dsl builder - */ - default AdvancedLangChain4jAgentEndpointBuilder chatMemoryProvider(String chatMemoryProvider) { - doSetProperty("chatMemoryProvider", chatMemoryProvider); - return this; - } - /** - * Chat Model of type dev.langchain4j.model.chat.ChatModel. - * - * The option is a: <code>dev.langchain4j.model.chat.ChatModel</code> - * type. - * - * Group: advanced - * - * @param chatModel the value to set - * @return the dsl builder - */ - default AdvancedLangChain4jAgentEndpointBuilder chatModel(dev.langchain4j.model.chat.ChatModel chatModel) { - doSetProperty("chatModel", chatModel); - return this; - } - /** - * Chat Model of type dev.langchain4j.model.chat.ChatModel. - * - * The option will be converted to a - * <code>dev.langchain4j.model.chat.ChatModel</code> type. - * - * Group: advanced - * - * @param chatModel the value to set - * @return the dsl builder - */ - default AdvancedLangChain4jAgentEndpointBuilder chatModel(String chatModel) { - doSetProperty("chatModel", chatModel); - return this; - } - /** - * Retrieval Augmentor for advanced RAG of type - * dev.langchain4j.rag.RetrievalAugmentor. This allows using RAG on both - * Naive and Advanced RAG. - * - * The option is a: <code>dev.langchain4j.rag.RetrievalAugmentor</code> - * type. - * - * Group: advanced - * - * @param retrievalAugmentor the value to set - * @return the dsl builder - */ - default AdvancedLangChain4jAgentEndpointBuilder retrievalAugmentor(dev.langchain4j.rag.RetrievalAugmentor retrievalAugmentor) { - doSetProperty("retrievalAugmentor", retrievalAugmentor); - return this; - } - /** - * Retrieval Augmentor for advanced RAG of type - * dev.langchain4j.rag.RetrievalAugmentor. This allows using RAG on both - * Naive and Advanced RAG. - * - * The option will be converted to a - * <code>dev.langchain4j.rag.RetrievalAugmentor</code> type. - * - * Group: advanced - * - * @param retrievalAugmentor the value to set - * @return the dsl builder - */ - default AdvancedLangChain4jAgentEndpointBuilder retrievalAugmentor(String retrievalAugmentor) { - doSetProperty("retrievalAugmentor", retrievalAugmentor); - return this; - } } public interface LangChain4jAgentBuilders {
