From c7e212ea993eaabaf3c6870d7fe64566fa4963a9 Mon Sep 17 00:00:00 2001 From: sbliu Date: Fri, 12 Sep 2025 10:22:00 +0800 Subject: [PATCH 1/3] feat(QAPPINT-1639) using langchain4j 1.0.0 --- .../camel-ai/camel-langchain4j-tools/pom.xml | 2 +- .../LangChain4jToolsComponentConfigurer.java | 4 +- ...ngChain4jToolsConfigurationConfigurer.java | 4 +- .../LangChain4jToolsEndpointConfigurer.java | 4 +- .../langchain4j/tools/langchain4j-tools.json | 4 +- .../tools/LangChain4jToolsConfiguration.java | 10 +-- .../tools/LangChain4jToolsEndpoint.java | 44 ++++++++-- .../tools/LangChain4jToolsProducer.java | 80 ++++++++++++++----- .../tools/spec/NamedJsonSchemaProperty.java | 10 +-- .../langchain4j/tools/LangChain4jToolIT.java | 6 +- .../LangChain4jToolMultipleGroupsIT.java | 6 +- ...ngChain4jToolMultipleMatchingGroupsIT.java | 6 +- .../tools/LangChain4jToolNoToolsExistIT.java | 6 +- .../LangChain4jToolNoToolsToBeCalledIT.java | 6 +- parent/pom.xml | 2 +- 15 files changed, 135 insertions(+), 59 deletions(-) diff --git a/components/camel-ai/camel-langchain4j-tools/pom.xml b/components/camel-ai/camel-langchain4j-tools/pom.xml index 4c82a9c457d53..7e4d95970c71e 100644 --- a/components/camel-ai/camel-langchain4j-tools/pom.xml +++ b/components/camel-ai/camel-langchain4j-tools/pom.xml @@ -77,7 +77,7 @@ dev.langchain4j langchain4j-ollama - ${langchain4j-version} + 1.0.0-beta5 test diff --git a/components/camel-ai/camel-langchain4j-tools/src/generated/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsComponentConfigurer.java b/components/camel-ai/camel-langchain4j-tools/src/generated/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsComponentConfigurer.java index c2329a2fdd8df..61f1fe5b86ee7 100644 --- a/components/camel-ai/camel-langchain4j-tools/src/generated/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsComponentConfigurer.java +++ b/components/camel-ai/camel-langchain4j-tools/src/generated/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsComponentConfigurer.java @@ -35,7 +35,7 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj case "bridgeerrorhandler": case "bridgeErrorHandler": target.setBridgeErrorHandler(property(camelContext, boolean.class, value)); return true; case "chatmodel": - case "chatModel": getOrCreateConfiguration(target).setChatModel(property(camelContext, dev.langchain4j.model.chat.ChatLanguageModel.class, value)); return true; + 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.tools.LangChain4jToolsConfiguration.class, value)); return true; case "lazystartproducer": case "lazyStartProducer": target.setLazyStartProducer(property(camelContext, boolean.class, value)); return true; @@ -56,7 +56,7 @@ public Class getOptionType(String name, boolean ignoreCase) { case "bridgeerrorhandler": case "bridgeErrorHandler": return boolean.class; case "chatmodel": - case "chatModel": return dev.langchain4j.model.chat.ChatLanguageModel.class; + case "chatModel": return dev.langchain4j.model.chat.ChatModel.class; case "configuration": return org.apache.camel.component.langchain4j.tools.LangChain4jToolsConfiguration.class; case "lazystartproducer": case "lazyStartProducer": return boolean.class; diff --git a/components/camel-ai/camel-langchain4j-tools/src/generated/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsConfigurationConfigurer.java b/components/camel-ai/camel-langchain4j-tools/src/generated/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsConfigurationConfigurer.java index 3fe5fae0cf7e4..d8c0a9ed1c807 100644 --- a/components/camel-ai/camel-langchain4j-tools/src/generated/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsConfigurationConfigurer.java +++ b/components/camel-ai/camel-langchain4j-tools/src/generated/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsConfigurationConfigurer.java @@ -24,7 +24,7 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj org.apache.camel.component.langchain4j.tools.LangChain4jToolsConfiguration target = (org.apache.camel.component.langchain4j.tools.LangChain4jToolsConfiguration) obj; switch (ignoreCase ? name.toLowerCase() : name) { case "chatmodel": - case "chatModel": target.setChatModel(property(camelContext, dev.langchain4j.model.chat.ChatLanguageModel.class, value)); return true; + case "chatModel": target.setChatModel(property(camelContext, dev.langchain4j.model.chat.ChatModel.class, value)); return true; default: return false; } } @@ -33,7 +33,7 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj public Class getOptionType(String name, boolean ignoreCase) { switch (ignoreCase ? name.toLowerCase() : name) { case "chatmodel": - case "chatModel": return dev.langchain4j.model.chat.ChatLanguageModel.class; + case "chatModel": return dev.langchain4j.model.chat.ChatModel.class; default: return null; } } diff --git a/components/camel-ai/camel-langchain4j-tools/src/generated/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsEndpointConfigurer.java b/components/camel-ai/camel-langchain4j-tools/src/generated/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsEndpointConfigurer.java index cc6582f51f2bd..2927bf3f20e80 100644 --- a/components/camel-ai/camel-langchain4j-tools/src/generated/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsEndpointConfigurer.java +++ b/components/camel-ai/camel-langchain4j-tools/src/generated/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsEndpointConfigurer.java @@ -28,7 +28,7 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj case "cameltoolparameter": case "camelToolParameter": target.setCamelToolParameter(property(camelContext, org.apache.camel.component.langchain4j.tools.spec.CamelSimpleToolParameter.class, value)); return true; case "chatmodel": - case "chatModel": target.getConfiguration().setChatModel(property(camelContext, dev.langchain4j.model.chat.ChatLanguageModel.class, value)); return true; + case "chatModel": target.getConfiguration().setChatModel(property(camelContext, dev.langchain4j.model.chat.ChatModel.class, value)); return true; case "description": target.setDescription(property(camelContext, java.lang.String.class, value)); return true; case "exceptionhandler": case "exceptionHandler": target.setExceptionHandler(property(camelContext, org.apache.camel.spi.ExceptionHandler.class, value)); return true; @@ -55,7 +55,7 @@ public Class getOptionType(String name, boolean ignoreCase) { case "cameltoolparameter": case "camelToolParameter": return org.apache.camel.component.langchain4j.tools.spec.CamelSimpleToolParameter.class; case "chatmodel": - case "chatModel": return dev.langchain4j.model.chat.ChatLanguageModel.class; + case "chatModel": return dev.langchain4j.model.chat.ChatModel.class; case "description": return java.lang.String.class; case "exceptionhandler": case "exceptionHandler": return org.apache.camel.spi.ExceptionHandler.class; diff --git a/components/camel-ai/camel-langchain4j-tools/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/tools/langchain4j-tools.json b/components/camel-ai/camel-langchain4j-tools/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/tools/langchain4j-tools.json index e3fe60d8c10d8..fa49fc9d2b79c 100644 --- a/components/camel-ai/camel-langchain4j-tools/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/tools/langchain4j-tools.json +++ b/components/camel-ai/camel-langchain4j-tools/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/tools/langchain4j-tools.json @@ -27,7 +27,7 @@ "bridgeErrorHandler": { "index": 1, "kind": "property", "displayName": "Bridge Error Handler", "group": "consumer", "label": "consumer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Allows for bridging the consumer to the Camel routing Error Handler, which mean any exceptions (if possible) occurred while the Camel consumer is trying to pickup incoming messages, or the likes, will now be processed as a message and handled by the routing Error Handler. Important: This is only possible if the 3rd party component allows Camel to be alerted if an exception was thrown. Some components handle this internally only, and therefore bridgeErrorHandler is not possible. In other situations we may improve the Camel component to hook into the 3rd party component and make this possible for future releases. By default the consumer will use the org.apache.camel.spi.ExceptionHandler to deal with exceptions, that will be logged at WARN or ERROR level and ignored." }, "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 during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel's routing error handlers. Beware that when the first message is processed then creating and starting the producer may take a little time and prolong the total processing time of the processing." }, "autowiredEnabled": { "index": 3, "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 type, which then gets configured on the component. This can be used for automatic configuring JDBC data sources, JMS connection factories, AWS Clients, etc." }, - "chatModel": { "index": 4, "kind": "property", "displayName": "Chat Model", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "dev.langchain4j.model.chat.ChatLanguageModel", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.langchain4j.tools.LangChain4jToolsConfiguration", "configurationField": "configuration", "description": "Chat Language Model of type dev.langchain4j.model.chat.ChatLanguageModel" } + "chatModel": { "index": 4, "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.tools.LangChain4jToolsConfiguration", "configurationField": "configuration", "description": "Chat Language Model of type dev.langchain4j.model.chat.ChatModel" } }, "properties": { "toolId": { "index": 0, "kind": "path", "displayName": "Tool Id", "group": "common", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The tool name" }, @@ -39,6 +39,6 @@ "exceptionHandler": { "index": 6, "kind": "parameter", "displayName": "Exception Handler", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.", "deprecated": false, "autowired": false, "secret": false, "description": "To let the consumer use a custom ExceptionHandler. Notice if the option bridgeErrorHandler is enabled then this option is not in use. By default the consumer will deal with exceptions, that will be logged at WARN or ERROR level and ignored." }, "exchangePattern": { "index": 7, "kind": "parameter", "displayName": "Exchange Pattern", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "object", "javaType": "org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ], "deprecated": false, "autowired": false, "secret": false, "description": "Sets the exchange pattern when the consumer creates an exchange." }, "lazyStartProducer": { "index": 8, "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 producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel's routing error handlers. Beware that when the first message is processed then creating and starting the producer may take a little time and prolong the total processing time of the processing." }, - "chatModel": { "index": 9, "kind": "parameter", "displayName": "Chat Model", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "dev.langchain4j.model.chat.ChatLanguageModel", "deprecated": false, "deprecationNote": "", "autowired": true, "secret": false, "configurationClass": "org.apache.camel.component.langchain4j.tools.LangChain4jToolsConfiguration", "configurationField": "configuration", "description": "Chat Language Model of type dev.langchain4j.model.chat.ChatLanguageModel" } + "chatModel": { "index": 9, "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.tools.LangChain4jToolsConfiguration", "configurationField": "configuration", "description": "Chat Language Model of type dev.langchain4j.model.chat.ChatModel" } } } diff --git a/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsConfiguration.java b/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsConfiguration.java index 74f89f5087108..7f1d14fbcf0d6 100644 --- a/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsConfiguration.java +++ b/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsConfiguration.java @@ -16,7 +16,7 @@ */ package org.apache.camel.component.langchain4j.tools; -import dev.langchain4j.model.chat.ChatLanguageModel; +import dev.langchain4j.model.chat.ChatModel; import org.apache.camel.RuntimeCamelException; import org.apache.camel.spi.Configurer; import org.apache.camel.spi.Metadata; @@ -29,21 +29,21 @@ public class LangChain4jToolsConfiguration implements Cloneable { @UriParam(label = "advanced") @Metadata(autowired = true) - private ChatLanguageModel chatModel; + private ChatModel chatModel; public LangChain4jToolsConfiguration() { } /** - * Chat Language Model of type dev.langchain4j.model.chat.ChatLanguageModel + * Chat Language Model of type dev.langchain4j.model.chat.ChatModel * * @return */ - public ChatLanguageModel getChatModel() { + public ChatModel getChatModel() { return chatModel; } - public void setChatModel(ChatLanguageModel chatModel) { + public void setChatModel(ChatModel chatModel) { this.chatModel = chatModel; } diff --git a/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsEndpoint.java b/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsEndpoint.java index 6403d963a9281..2b5cd089fb5d7 100644 --- a/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsEndpoint.java +++ b/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsEndpoint.java @@ -16,10 +16,16 @@ */ package org.apache.camel.component.langchain4j.tools; +import java.util.List; import java.util.Map; -import dev.langchain4j.agent.tool.JsonSchemaProperty; import dev.langchain4j.agent.tool.ToolSpecification; +import dev.langchain4j.model.chat.request.json.JsonBooleanSchema; +import dev.langchain4j.model.chat.request.json.JsonIntegerSchema; +import dev.langchain4j.model.chat.request.json.JsonNumberSchema; +import dev.langchain4j.model.chat.request.json.JsonObjectSchema; +import dev.langchain4j.model.chat.request.json.JsonSchemaElement; +import dev.langchain4j.model.chat.request.json.JsonStringSchema; import org.apache.camel.Category; import org.apache.camel.Consumer; import org.apache.camel.Processor; @@ -87,15 +93,27 @@ public Consumer createConsumer(Processor processor) throws Exception { if (camelToolParameter != null) { toolSpecificationBuilder.description(camelToolParameter.getDescription()); - for (NamedJsonSchemaProperty namedJsonSchemaProperty : camelToolParameter.getProperties()) { - toolSpecificationBuilder.addParameter(namedJsonSchemaProperty.getName(), - namedJsonSchemaProperty.getProperties()); + JsonObjectSchema.Builder parametersBuilder = JsonObjectSchema.builder(); + + List properties = camelToolParameter.getProperties(); + + for (NamedJsonSchemaProperty namedProperty : properties) { + parametersBuilder.addProperty( + namedProperty.getName(), + namedProperty.getProperties()); } + + toolSpecificationBuilder.parameters(parametersBuilder.build()); } else if (description != null) { toolSpecificationBuilder.description(description); if (parameters != null) { - parameters.forEach((name, type) -> toolSpecificationBuilder.addParameter(name, JsonSchemaProperty.type(type))); + + JsonObjectSchema.Builder parametersBuilder = JsonObjectSchema.builder(); + + parameters.forEach((name, type) -> parametersBuilder.addProperty(name, createJsonSchema(type))); + + toolSpecificationBuilder.parameters(parametersBuilder.build()); } } else { // Consumer without toolParameter or description @@ -198,4 +216,20 @@ protected void doStop() throws Exception { CamelToolExecutorCache.getInstance().getTools().clear(); } + + /** + * Creates a JsonScheùaElement based on a String type + * + * @param type + * @return + */ + private JsonSchemaElement createJsonSchema(String type) { + return switch (type.toLowerCase()) { + case "string" -> JsonStringSchema.builder().build(); + case "integer" -> JsonIntegerSchema.builder().build(); + case "number" -> JsonNumberSchema.builder().build(); + case "boolean" -> JsonBooleanSchema.builder().build(); + default -> JsonStringSchema.builder().build(); // fallback for unkown types + }; + } } diff --git a/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsProducer.java b/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsProducer.java index 664696e42b1c2..b1e3d7e86c0b2 100644 --- a/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsProducer.java +++ b/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsProducer.java @@ -28,7 +28,10 @@ import dev.langchain4j.data.message.AiMessage; import dev.langchain4j.data.message.ChatMessage; import dev.langchain4j.data.message.ToolExecutionResultMessage; -import dev.langchain4j.model.chat.ChatLanguageModel; +import dev.langchain4j.model.chat.ChatModel; +import dev.langchain4j.model.chat.request.ChatRequest; +import dev.langchain4j.model.chat.response.ChatResponse; +import dev.langchain4j.model.output.FinishReason; import dev.langchain4j.model.output.Response; import org.apache.camel.Exchange; import org.apache.camel.InvalidPayloadException; @@ -41,7 +44,7 @@ public class LangChain4jToolsProducer extends DefaultProducer { private final LangChain4jToolsEndpoint endpoint; - private ChatLanguageModel chatLanguageModel; + private ChatModel chatModel; private final ObjectMapper objectMapper = new ObjectMapper(); @@ -66,8 +69,8 @@ private void processMultipleMessages(Exchange exchange) throws InvalidPayloadExc @Override protected void doStart() throws Exception { super.doStart(); - this.chatLanguageModel = this.endpoint.getConfiguration().getChatModel(); - ObjectHelper.notNull(chatLanguageModel, "chatLanguageModel"); + this.chatModel = this.endpoint.getConfiguration().getChatModel(); + ObjectHelper.notNull(chatModel, "chatLanguageModel"); } private void populateResponse(String response, Exchange exchange) { @@ -99,19 +102,45 @@ private String toolsChat(List chatMessages, Exchange exchange) { } // First talk to the model to get the tools to be called - final Response response = chatWithLLMForTools(chatMessages, toolPair, exchange); - if (response == null) { - return null; + int i = 0; + do { +// System.out.println("Starting iteration " + i); + final Response response = chatWithLLM(chatMessages, toolPair, exchange); + if (isDoneExecuting(response)) { + return extractAiResponse(response); + } + + // Only invoke the tools ... the response will be computed on the next loop + invokeTools(chatMessages, exchange, response, toolPair); +// System.out.println("Finished iteration " + i); + i++; + } while (true); + } + + private boolean isDoneExecuting(Response response) { + if (!response.content().hasToolExecutionRequests()) { + System.out.println("Finished executing tools because of there are no more execution requests"); + return true; + } + + if (response.finishReason() != null) { + System.out.println("Finished executing tools because of " + response.finishReason()); + + if (response.finishReason() == FinishReason.STOP) { + return true; + } } - // Then, talk again to call the tools and compute the final response - return chatWithLLMForToolCalling(chatMessages, exchange, response, toolPair); + return false; } - private String chatWithLLMForToolCalling( + private void invokeTools( List chatMessages, Exchange exchange, Response response, ToolPair toolPair) { - for (ToolExecutionRequest toolExecutionRequest : response.content().toolExecutionRequests()) { + int i = 0; + List toolExecutionRequests = response.content().toolExecutionRequests(); + for (ToolExecutionRequest toolExecutionRequest : toolExecutionRequests) { String toolName = toolExecutionRequest.name(); + System.out.println("Invoking tool " + i + " (" + toolName + ") of " + toolExecutionRequests.size()); final CamelToolSpecification camelToolSpecification = toolPair.callableTools().stream() .filter(c -> c.getToolSpecification().name().equals(toolName)).findFirst().get(); @@ -124,7 +153,9 @@ private String chatWithLLMForToolCalling( .forEachRemaining(name -> exchange.getMessage().setHeader(name, jsonNode.get(name))); // Execute the consumer route + camelToolSpecification.getConsumer().getProcessor().process(exchange); + i++; } catch (Exception e) { // How to handle this exception? exchange.setException(e); @@ -135,9 +166,6 @@ private String chatWithLLMForToolCalling( toolExecutionRequest.name(), exchange.getIn().getBody(String.class))); } - - final Response generate = this.chatLanguageModel.generate(chatMessages); - return extractAiResponse(generate); } /** @@ -148,12 +176,29 @@ private String chatWithLLMForToolCalling( * @param toolPair the toolPair containing the available tools to be called * @return the response provided by the model */ - private Response chatWithLLMForTools(List chatMessages, ToolPair toolPair, Exchange exchange) { - Response response = this.chatLanguageModel.generate(chatMessages, toolPair.toolSpecifications()); + private Response chatWithLLM(List chatMessages, ToolPair toolPair, Exchange exchange) { + + ChatRequest.Builder requestBuilder = ChatRequest.builder() + .messages(chatMessages); + + // Add tools if available + if (toolPair != null && toolPair.toolSpecifications() != null) { + requestBuilder.toolSpecifications(toolPair.toolSpecifications()); + } + + // build request + ChatRequest chatRequest = requestBuilder.build(); + + // generate response + ChatResponse chatResponse = this.chatModel.chat(chatRequest); + + // Convert ChatResponse to Response for compatibility + AiMessage aiMessage = chatResponse.aiMessage(); + Response response = Response.from(aiMessage); if (!response.content().hasToolExecutionRequests()) { exchange.getMessage().setHeader(LangChain4jTools.NO_TOOLS_CALLED_HEADER, Boolean.TRUE); - return null; + return response; } chatMessages.add(response.content()); @@ -209,5 +254,4 @@ private String extractAiResponse(Response response) { AiMessage message = response.content(); return message == null ? null : message.text(); } - } diff --git a/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/spec/NamedJsonSchemaProperty.java b/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/spec/NamedJsonSchemaProperty.java index c1b160d1d1760..16b1d6ec3ec5e 100644 --- a/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/spec/NamedJsonSchemaProperty.java +++ b/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/spec/NamedJsonSchemaProperty.java @@ -16,16 +16,14 @@ */ package org.apache.camel.component.langchain4j.tools.spec; -import java.util.List; - -import dev.langchain4j.agent.tool.JsonSchemaProperty; +import dev.langchain4j.model.chat.request.json.JsonSchemaElement; public class NamedJsonSchemaProperty { private final String name; - private final List properties; + private final JsonSchemaElement properties; - public NamedJsonSchemaProperty(String name, List properties) { + public NamedJsonSchemaProperty(String name, JsonSchemaElement properties) { this.name = name; this.properties = properties; } @@ -34,7 +32,7 @@ public String getName() { return name; } - public List getProperties() { + public JsonSchemaElement getProperties() { return properties; } } diff --git a/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolIT.java b/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolIT.java index e2187d5be094d..3a78162ea3d3f 100644 --- a/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolIT.java +++ b/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolIT.java @@ -22,7 +22,7 @@ import dev.langchain4j.data.message.ChatMessage; import dev.langchain4j.data.message.SystemMessage; import dev.langchain4j.data.message.UserMessage; -import dev.langchain4j.model.chat.ChatLanguageModel; +import dev.langchain4j.model.chat.ChatModel; import dev.langchain4j.model.openai.OpenAiChatModel; import org.apache.camel.CamelContext; import org.apache.camel.Exchange; @@ -42,7 +42,7 @@ public class LangChain4jToolIT extends CamelTestSupport { public static final String MODEL_NAME = "llama3.1:latest"; private final String nameFromDB = "pippo"; - private ChatLanguageModel chatLanguageModel; + private ChatModel chatLanguageModel; @RegisterExtension static OllamaService OLLAMA = OllamaServiceFactory.createServiceWithConfiguration(() -> MODEL_NAME); @@ -66,7 +66,7 @@ protected CamelContext createCamelContext() throws Exception { return context; } - protected ChatLanguageModel createModel() { + protected ChatModel createModel() { chatLanguageModel = OpenAiChatModel.builder() .apiKey("NO_API_KEY") .modelName(MODEL_NAME) diff --git a/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolMultipleGroupsIT.java b/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolMultipleGroupsIT.java index 783c98a3fc7bc..a6f18e47486d8 100644 --- a/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolMultipleGroupsIT.java +++ b/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolMultipleGroupsIT.java @@ -22,7 +22,7 @@ import dev.langchain4j.data.message.ChatMessage; import dev.langchain4j.data.message.SystemMessage; import dev.langchain4j.data.message.UserMessage; -import dev.langchain4j.model.chat.ChatLanguageModel; +import dev.langchain4j.model.chat.ChatModel; import dev.langchain4j.model.openai.OpenAiChatModel; import org.apache.camel.CamelContext; import org.apache.camel.Exchange; @@ -42,7 +42,7 @@ public class LangChain4jToolMultipleGroupsIT extends CamelTestSupport { public static final String MODEL_NAME = "llama3.1:latest"; private final String nameFromDB = "pippo"; - private ChatLanguageModel chatLanguageModel; + private ChatModel chatLanguageModel; @RegisterExtension static OllamaService OLLAMA = OllamaServiceFactory.createServiceWithConfiguration(() -> MODEL_NAME); @@ -66,7 +66,7 @@ protected CamelContext createCamelContext() throws Exception { return context; } - protected ChatLanguageModel createModel() { + protected ChatModel createModel() { chatLanguageModel = OpenAiChatModel.builder() .apiKey("NO_API_KEY") .modelName(MODEL_NAME) diff --git a/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolMultipleMatchingGroupsIT.java b/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolMultipleMatchingGroupsIT.java index cc6ab1c5397b4..380c5574a8f40 100644 --- a/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolMultipleMatchingGroupsIT.java +++ b/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolMultipleMatchingGroupsIT.java @@ -22,7 +22,7 @@ import dev.langchain4j.data.message.ChatMessage; import dev.langchain4j.data.message.SystemMessage; import dev.langchain4j.data.message.UserMessage; -import dev.langchain4j.model.chat.ChatLanguageModel; +import dev.langchain4j.model.chat.ChatModel; import dev.langchain4j.model.openai.OpenAiChatModel; import org.apache.camel.CamelContext; import org.apache.camel.Exchange; @@ -42,7 +42,7 @@ public class LangChain4jToolMultipleMatchingGroupsIT extends CamelTestSupport { public static final String MODEL_NAME = "llama3.1:latest"; private final String nameFromDB = "Paul McFly"; - private ChatLanguageModel chatLanguageModel; + private ChatModel chatLanguageModel; @RegisterExtension static OllamaService OLLAMA = OllamaServiceFactory.createServiceWithConfiguration(() -> MODEL_NAME); @@ -66,7 +66,7 @@ protected CamelContext createCamelContext() throws Exception { return context; } - protected ChatLanguageModel createModel() { + protected ChatModel createModel() { chatLanguageModel = OpenAiChatModel.builder() .apiKey("NO_API_KEY") .modelName(MODEL_NAME) diff --git a/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolNoToolsExistIT.java b/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolNoToolsExistIT.java index fa15cb817fdd3..34d9f16056045 100644 --- a/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolNoToolsExistIT.java +++ b/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolNoToolsExistIT.java @@ -22,7 +22,7 @@ import dev.langchain4j.data.message.ChatMessage; import dev.langchain4j.data.message.SystemMessage; import dev.langchain4j.data.message.UserMessage; -import dev.langchain4j.model.chat.ChatLanguageModel; +import dev.langchain4j.model.chat.ChatModel; import dev.langchain4j.model.openai.OpenAiChatModel; import org.apache.camel.CamelContext; import org.apache.camel.Exchange; @@ -41,7 +41,7 @@ public class LangChain4jToolNoToolsExistIT extends CamelTestSupport { public static final String MODEL_NAME = "llama3.1:latest"; - private ChatLanguageModel chatLanguageModel; + private ChatModel chatLanguageModel; @RegisterExtension static OllamaService OLLAMA = OllamaServiceFactory.createServiceWithConfiguration(() -> MODEL_NAME); @@ -65,7 +65,7 @@ protected CamelContext createCamelContext() throws Exception { return context; } - protected ChatLanguageModel createModel() { + protected ChatModel createModel() { chatLanguageModel = OpenAiChatModel.builder() .apiKey("NO_API_KEY") .modelName(MODEL_NAME) diff --git a/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolNoToolsToBeCalledIT.java b/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolNoToolsToBeCalledIT.java index 15491465cf398..43887d06ce866 100644 --- a/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolNoToolsToBeCalledIT.java +++ b/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolNoToolsToBeCalledIT.java @@ -22,7 +22,7 @@ import dev.langchain4j.data.message.ChatMessage; import dev.langchain4j.data.message.SystemMessage; import dev.langchain4j.data.message.UserMessage; -import dev.langchain4j.model.chat.ChatLanguageModel; +import dev.langchain4j.model.chat.ChatModel; import dev.langchain4j.model.openai.OpenAiChatModel; import org.apache.camel.CamelContext; import org.apache.camel.Exchange; @@ -41,7 +41,7 @@ public class LangChain4jToolNoToolsToBeCalledIT extends CamelTestSupport { public static final String MODEL_NAME = "llama3.1:latest"; - private ChatLanguageModel chatLanguageModel; + private ChatModel chatLanguageModel; @RegisterExtension static OllamaService OLLAMA = OllamaServiceFactory.createServiceWithConfiguration(() -> MODEL_NAME); @@ -65,7 +65,7 @@ protected CamelContext createCamelContext() throws Exception { return context; } - protected ChatLanguageModel createModel() { + protected ChatModel createModel() { chatLanguageModel = OpenAiChatModel.builder() .apiKey("NO_API_KEY") .modelName(MODEL_NAME) diff --git a/parent/pom.xml b/parent/pom.xml index ad5845982b5f5..f0bfd0a6e3160 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -323,7 +323,7 @@ 1.18.1 6.13.3 1.17.0 - 0.33.0 + 1.0.0 1.8 0.12 0.12 From b86c7981effd5dab43179f69b3e17d0a11912f40 Mon Sep 17 00:00:00 2001 From: sbliu Date: Fri, 12 Sep 2025 15:26:11 +0800 Subject: [PATCH 2/3] update camel-langchain4j-tools.tesb.version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b2a018d78eace..287e20a93f443 100644 --- a/pom.xml +++ b/pom.xml @@ -112,7 +112,7 @@ 4.8.1.20250320 4.8.1.20250320 4.1.0.2 - 4.8.1.20250320 + 4.8.1.20250906 4.8.1.20250320 4.8.1.20250320 4.8.1.20250320 From e8bdbc628aab95249cff3877bfd1cf5657d07488 Mon Sep 17 00:00:00 2001 From: sbliu Date: Fri, 12 Sep 2025 16:03:19 +0800 Subject: [PATCH 3/3] add required bean class --- .../langchain4j/tools/JsonSchemaProperty.java | 316 ++++++++++++++++++ 1 file changed, 316 insertions(+) create mode 100644 components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/JsonSchemaProperty.java diff --git a/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/JsonSchemaProperty.java b/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/JsonSchemaProperty.java new file mode 100644 index 0000000000000..6bcf3cc3380fa --- /dev/null +++ b/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/JsonSchemaProperty.java @@ -0,0 +1,316 @@ +/* + * 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.tools; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import dev.langchain4j.model.chat.request.json.JsonArraySchema; +import dev.langchain4j.model.chat.request.json.JsonBooleanSchema; +import dev.langchain4j.model.chat.request.json.JsonEnumSchema; +import dev.langchain4j.model.chat.request.json.JsonIntegerSchema; +import dev.langchain4j.model.chat.request.json.JsonNumberSchema; +import dev.langchain4j.model.chat.request.json.JsonObjectSchema; +import dev.langchain4j.model.chat.request.json.JsonSchemaElement; +import dev.langchain4j.model.chat.request.json.JsonStringSchema; + +import static dev.langchain4j.internal.Utils.quoted; +import static java.util.Collections.singletonMap; + +/** + * Represents a property in a JSON schema. + * + * @deprecated please use the new {@link JsonSchemaElement} API instead to define the schema for tool parameters + */ +@Deprecated(forRemoval = true) +public class JsonSchemaProperty { + + /** + * A property with key "type" and value "string". + * + * @deprecated please use {@link JsonStringSchema#JsonStringSchema()} instead + */ + @Deprecated(forRemoval = true) + public static final JsonSchemaProperty STRING = type("string"); + + /** + * A property with key "type" and value "integer". + * + * @deprecated please use {@link JsonIntegerSchema#JsonIntegerSchema()} instead + */ + @Deprecated(forRemoval = true) + public static final JsonSchemaProperty INTEGER = type("integer"); + + /** + * A property with key "type" and value "number". + * + * @deprecated please use {@link JsonNumberSchema#JsonNumberSchema()} instead + */ + @Deprecated(forRemoval = true) + public static final JsonSchemaProperty NUMBER = type("number"); + + /** + * A property with key "type" and value "object". + * + * @deprecated please use {@link JsonObjectSchema#builder} instead + */ + @Deprecated(forRemoval = true) + public static final JsonSchemaProperty OBJECT = type("object"); + + /** + * A property with key "type" and value "array". + * + * @deprecated please use {@link JsonArraySchema#builder} instead + */ + @Deprecated(forRemoval = true) + public static final JsonSchemaProperty ARRAY = type("array"); + + /** + * A property with key "type" and value "boolean". + * + * @deprecated please use {@link JsonBooleanSchema#JsonBooleanSchema()} instead + */ + @Deprecated(forRemoval = true) + public static final JsonSchemaProperty BOOLEAN = type("boolean"); + + /** + * A property with key "type" and value "null". + */ + @Deprecated(forRemoval = true) + public static final JsonSchemaProperty NULL = type("null"); + + private final String key; + private final Object value; + + /** + * Construct a property with key and value. + * + * @param key the key. + * @param value the value. + * @deprecated please use the new {@link JsonSchemaElement} API instead to define the schema for tool + * parameters + */ + @Deprecated(forRemoval = true) + public JsonSchemaProperty(String key, Object value) { + this.key = key; + this.value = value; + } + + /** + * Get the key. + * + * @return the key. + */ + public String key() { + return key; + } + + /** + * Get the value. + * + * @return the value. + */ + public Object value() { + return value; + } + + @Override + public boolean equals(Object another) { + if (this == another) + return true; + return another instanceof JsonSchemaProperty jsp + && equalTo(jsp); + } + + /** + * Utility method to compare two {@link JsonSchemaProperty} instances. + * + * @param another the other instance. + * @return true if the two instances are equal. + */ + private boolean equalTo(JsonSchemaProperty another) { + if (!Objects.equals(key, another.key)) + return false; + + if (value instanceof Object[] objects && another.value instanceof Object[] objects1) { + return Arrays.equals(objects, objects1); + } + + return Objects.equals(value, another.value); + } + + @Override + public int hashCode() { + int h = 5381; + h += (h << 5) + Objects.hashCode(key); + int v = (value instanceof Object[] os) ? Arrays.hashCode(os) : Objects.hashCode(value); + h += (h << 5) + v; + return h; + } + + @Override + public String toString() { + String valueString = (value instanceof Object[] os) ? Arrays.toString(os) : value.toString(); + return "JsonSchemaProperty {" + + " key = " + quoted(key) + + ", value = " + valueString + + " }"; + } + + /** + * Construct a property with key and value. + * + *

+ * Equivalent to {@code new JsonSchemaProperty(key, value)}. + * + * @param key the key. + * @param value the value. + * @return a property with key and value. + * @deprecated please use the new {@link JsonSchemaElement} API instead to define the schema for tool + * parameters + */ + @Deprecated(forRemoval = true) + public static JsonSchemaProperty from(String key, Object value) { + return new JsonSchemaProperty(key, value); + } + + /** + * Construct a property with key and value. + * + *

+ * Equivalent to {@code new JsonSchemaProperty(key, value)}. + * + * @param key the key. + * @param value the value. + * @return a property with key and value. + * @deprecated please use the new {@link JsonSchemaElement} API instead to define the schema for tool + * parameters + */ + @Deprecated(forRemoval = true) + public static JsonSchemaProperty property(String key, Object value) { + return from(key, value); + } + + /** + * Construct a property with key "type" and value. + * + *

+ * Equivalent to {@code new JsonSchemaProperty("type", value)}. + * + * @param value the value. + * @return a property with key and value. + * @deprecated please use the new {@link JsonSchemaElement} API instead to define the schema for tool + * parameters + */ + @Deprecated(forRemoval = true) + public static JsonSchemaProperty type(String value) { + return from("type", value); + } + + /** + * Construct a property with key "description" and value. + * + *

+ * Equivalent to {@code new JsonSchemaProperty("description", value)}. + * + * @param value the value. + * @return a property with key and value. + * @deprecated please use the new {@link JsonSchemaElement} API instead to define the schema for tool + * parameters + */ + @Deprecated(forRemoval = true) + public static JsonSchemaProperty description(String value) { + return from("description", value); + } + + /** + * Construct a property with key "enum" and value enumValues. + * + * @param enumValues enum values as strings. For example: {@code enums("CELSIUS", "FAHRENHEIT")} + * @return a property with key "enum" and value enumValues + * @deprecated please use {@link JsonEnumSchema} instead + */ + @Deprecated(forRemoval = true) + public static JsonSchemaProperty enums(String... enumValues) { + return from("enum", enumValues); + } + + /** + * Construct a property with key "enum" and value enumValues. + * + *

+ * Verifies that each value is a java class. + * + * @param enumValues enum values. For example: + * {@code enums(TemperatureUnit.CELSIUS, TemperatureUnit.FAHRENHEIT)} + * @return a property with key "enum" and value enumValues + * @deprecated please use {@link JsonEnumSchema} instead + */ + @Deprecated(forRemoval = true) + public static JsonSchemaProperty enums(Object... enumValues) { + List enumNames = new ArrayList<>(); + for (Object enumValue : enumValues) { + if (!enumValue.getClass().isEnum()) { + throw new RuntimeException("Value " + enumValue.getClass().getName() + " should be enum"); + } + enumNames.add(((Enum) enumValue).name()); + } + return from("enum", enumNames); + } + + /** + * Construct a property with key "enum" and all enum values taken from enumClass. + * + * @param enumClass enum class. For example: {@code enums(TemperatureUnit.class)} + * @return a property with key "enum" and values taken from enumClass + * @deprecated please use {@link JsonEnumSchema} instead + */ + @Deprecated(forRemoval = true) + public static JsonSchemaProperty enums(Class enumClass) { + if (!enumClass.isEnum()) { + throw new RuntimeException("Class " + enumClass.getName() + " should be enum"); + } + return enums((Object[]) enumClass.getEnumConstants()); + } + + /** + * Wraps the given type in a property with key "items". + * + * @param type the type + * @return a property with key "items" and value type. + * @deprecated please use {@link JsonArraySchema} instead + */ + @Deprecated(forRemoval = true) + public static JsonSchemaProperty items(JsonSchemaProperty type) { + return from("items", singletonMap(type.key, type.value)); + } + + /** + * @deprecated please use {@link JsonObjectSchema} instead + */ + @Deprecated(forRemoval = true) + public static JsonSchemaProperty objectItems(JsonSchemaProperty type) { + Map map = new HashMap<>(); + map.put("type", "object"); + map.put(type.key, type.value); + return from("items", map); + } +}