This is an automated email from the ASF dual-hosted git repository. fmariani pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
commit 76718ccf446a184babe709873d33b3b98dd98de3 Author: Croway <federico.mariani.1...@gmail.com> AuthorDate: Fri May 31 18:36:28 2024 +0200 CAMEL-20822: Implement OpenAI Tool Capabilities natively in Camel --- components/camel-ai/camel-langchain4j-chat/pom.xml | 16 ++++ .../src/main/docs/langchain4j-chat-component.adoc | 34 ++++++++ .../langchain4j/chat/LangChain4jChatConsumer.java | 28 +++++++ .../langchain4j/chat/LangChain4jChatEndpoint.java | 83 ++++++++++++++++++- .../langchain4j/chat/LangChain4jChatProducer.java | 56 ++++++++++++- .../chat/tool/CamelSimpleToolParameter.java | 42 ++++++++++ .../chat/tool/CamelToolExecutorCache.java | 59 ++++++++++++++ .../chat/tool/CamelToolSpecification.java | 51 ++++++++++++ .../chat/tool/NamedJsonSchemaProperty.java | 40 ++++++++++ .../langchain4j.chat/LangChain4jConsumerIT.java | 93 ++++++++++++++++++++++ 10 files changed, 498 insertions(+), 4 deletions(-) diff --git a/components/camel-ai/camel-langchain4j-chat/pom.xml b/components/camel-ai/camel-langchain4j-chat/pom.xml index 604500a8feb..bc1e423b84a 100644 --- a/components/camel-ai/camel-langchain4j-chat/pom.xml +++ b/components/camel-ai/camel-langchain4j-chat/pom.xml @@ -43,6 +43,16 @@ <groupId>org.apache.camel</groupId> <artifactId>camel-langchain4j-core</artifactId> </dependency> + <dependency> + <groupId>dev.langchain4j</groupId> + <artifactId>langchain4j</artifactId> + <version>${langchain4j-version}</version> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + <version>${jackson2-version}</version> + </dependency> <!-- for testing --> <dependency> @@ -63,6 +73,12 @@ <type>test-jar</type> <scope>test</scope> </dependency> + <dependency> + <groupId>dev.langchain4j</groupId> + <artifactId>langchain4j-open-ai</artifactId> + <version>${langchain4j-version}</version> + <scope>test</scope> + </dependency> </dependencies> diff --git a/components/camel-ai/camel-langchain4j-chat/src/main/docs/langchain4j-chat-component.adoc b/components/camel-ai/camel-langchain4j-chat/src/main/docs/langchain4j-chat-component.adoc index 441f3f796f9..792d6c209ba 100644 --- a/components/camel-ai/camel-langchain4j-chat/src/main/docs/langchain4j-chat-component.adoc +++ b/components/camel-ai/camel-langchain4j-chat/src/main/docs/langchain4j-chat-component.adoc @@ -143,3 +143,37 @@ messages.add(new SystemMessage("You are asked to provide recommendations for a r String response = template.requestBody("direct:send-multiple", messages, String.class); ---- + +== Chat with Tool +Camel langchain4j-chat used as a consumer can be used to implement a Tool, +right now Tools are supported only via the OpenAiChatModel +backed by OpenAI APIs. + +Tool Input parameter can be defined via parameterName and parameterType in case of only one parameter, +or via the endpoint option camelToolParameter in case of multiple parameters. +The parameters can be found as headers in the consumer route, in particular, if you define a `parameterName=userId`, +in the consumer route `${header.userId}` can be used. + +Example of a producer and a consumer: +[source, java] +---- +from("direct:test") + .to("langchain4j-chat:test1?chatOperation=CHAT_MULTIPLE_MESSAGES"); + +from("langchain4j-chat:test1?description=Query user database by number¶meterName=number¶meterType=integer") + .to("sql:SELECT name FROM users WHERE id = :#number"); +---- + +Example of usage: +[source, java] +---- +List<ChatMessage> messages = new ArrayList<>(); + messages.add(new SystemMessage(""" + You provide information about specific user name querying the database given a number. + """)); + messages.add(new UserMessage(""" + What is the name of the user 1? + """)); + + Exchange message = fluentTemplate.to("direct:test").withBody(messages).request(Exchange.class); +---- \ No newline at end of file diff --git a/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/LangChain4jChatConsumer.java b/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/LangChain4jChatConsumer.java new file mode 100644 index 00000000000..c88c6f0d20e --- /dev/null +++ b/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/LangChain4jChatConsumer.java @@ -0,0 +1,28 @@ +/* + * 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.chat; + +import org.apache.camel.Endpoint; +import org.apache.camel.Processor; +import org.apache.camel.support.DefaultConsumer; + +public class LangChain4jChatConsumer extends DefaultConsumer { + + public LangChain4jChatConsumer(Endpoint endpoint, Processor processor) { + super(endpoint, processor); + } +} diff --git a/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/LangChain4jChatEndpoint.java b/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/LangChain4jChatEndpoint.java index c8324d32af5..b8598b45852 100644 --- a/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/LangChain4jChatEndpoint.java +++ b/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/LangChain4jChatEndpoint.java @@ -16,10 +16,18 @@ */ package org.apache.camel.component.langchain4j.chat; +import java.util.UUID; + +import dev.langchain4j.agent.tool.JsonSchemaProperty; +import dev.langchain4j.agent.tool.ToolSpecification; import org.apache.camel.Category; import org.apache.camel.Consumer; import org.apache.camel.Processor; import org.apache.camel.Producer; +import org.apache.camel.component.langchain4j.chat.tool.CamelSimpleToolParameter; +import org.apache.camel.component.langchain4j.chat.tool.CamelToolExecutorCache; +import org.apache.camel.component.langchain4j.chat.tool.CamelToolSpecification; +import org.apache.camel.component.langchain4j.chat.tool.NamedJsonSchemaProperty; import org.apache.camel.spi.Metadata; import org.apache.camel.spi.UriEndpoint; import org.apache.camel.spi.UriParam; @@ -41,6 +49,22 @@ public class LangChain4jChatEndpoint extends DefaultEndpoint { @UriParam private LangChain4jChatConfiguration configuration; + @Metadata(label = "consumer") + @UriParam(description = "simple Tool description") + private String description; + + @Metadata(label = "consumer") + @UriParam(description = "simple Tool paramenter name") + private String parameterName; + + @Metadata(label = "consumer") + @UriParam(description = "Simple Tool parameter type", enums = "string,integer,number,object,array,boolean,null") + private String parameterType; + + @Metadata(label = "consumer,advanced") + @UriParam(description = "Tool's Parameters, to be used in case of multiple arguments") + private CamelSimpleToolParameter camelToolParameter; + public LangChain4jChatEndpoint(String uri, LangChain4jChatComponent component, String chatId, LangChain4jChatConfiguration configuration) { super(uri, component); @@ -55,7 +79,33 @@ public class LangChain4jChatEndpoint extends DefaultEndpoint { @Override public Consumer createConsumer(Processor processor) throws Exception { - throw new UnsupportedOperationException("Cannot consume from an LangChain4j chat Endpoint: " + getEndpointUri()); + ToolSpecification.Builder toolSpecificationBuilder = ToolSpecification.builder(); + toolSpecificationBuilder.name(UUID.randomUUID().toString()); + if (camelToolParameter != null) { + toolSpecificationBuilder.description(camelToolParameter.getDescription()); + + for (NamedJsonSchemaProperty namedJsonSchemaProperty : camelToolParameter.getProperties()) { + toolSpecificationBuilder.addParameter(namedJsonSchemaProperty.getName(), + namedJsonSchemaProperty.getProperties()); + } + } else if (description != null) { + toolSpecificationBuilder.description(description); + + if (parameterName != null) { + toolSpecificationBuilder.addParameter(parameterName, JsonSchemaProperty.type(parameterType)); + } + } else { + // Consumer without toolParameter or description + throw new IllegalArgumentException( + "In order to use the langchain4j component as a consumer, you need to specify at least description, or a camelToolParameter"); + } + ToolSpecification toolSpecification = toolSpecificationBuilder.build(); + + CamelToolSpecification camelToolSpecification + = new CamelToolSpecification(toolSpecification, new LangChain4jChatConsumer(this, processor)); + CamelToolExecutorCache.getInstance().put(chatId, camelToolSpecification); + + return camelToolSpecification.getConsumer(); } /** @@ -71,4 +121,35 @@ public class LangChain4jChatEndpoint extends DefaultEndpoint { return configuration; } + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getParameterName() { + return parameterName; + } + + public void setParameterName(String parameterName) { + this.parameterName = parameterName; + } + + public String getParameterType() { + return parameterType; + } + + public void setParameterType(String parameterType) { + this.parameterType = parameterType; + } + + public CamelSimpleToolParameter getCamelToolParameter() { + return camelToolParameter; + } + + public void setCamelToolParameter(CamelSimpleToolParameter camelToolParameter) { + this.camelToolParameter = camelToolParameter; + } } diff --git a/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/LangChain4jChatProducer.java b/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/LangChain4jChatProducer.java index 08033def4ae..f17e9530768 100644 --- a/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/LangChain4jChatProducer.java +++ b/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/LangChain4jChatProducer.java @@ -18,9 +18,15 @@ package org.apache.camel.component.langchain4j.chat; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import dev.langchain4j.agent.tool.ToolExecutionRequest; +import dev.langchain4j.agent.tool.ToolSpecification; 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.input.Prompt; import dev.langchain4j.model.input.PromptTemplate; @@ -28,6 +34,8 @@ import dev.langchain4j.model.output.Response; import org.apache.camel.Exchange; import org.apache.camel.InvalidPayloadException; import org.apache.camel.NoSuchHeaderException; +import org.apache.camel.component.langchain4j.chat.tool.CamelToolExecutorCache; +import org.apache.camel.component.langchain4j.chat.tool.CamelToolSpecification; import org.apache.camel.support.DefaultProducer; import org.apache.camel.util.ObjectHelper; @@ -37,6 +45,8 @@ public class LangChain4jChatProducer extends DefaultProducer { private ChatLanguageModel chatLanguageModel; + private final ObjectMapper objectMapper = new ObjectMapper(); + public LangChain4jChatProducer(LangChain4jChatEndpoint endpoint) { super(endpoint); this.endpoint = endpoint; @@ -85,7 +95,7 @@ public class LangChain4jChatProducer extends DefaultProducer { @SuppressWarnings("unchecked") private void processMultipleMessages(Exchange exchange) throws InvalidPayloadException { List<ChatMessage> messages = exchange.getIn().getMandatoryBody(List.class); - populateResponse(sendListChatMessage(messages), exchange); + populateResponse(sendListChatMessage(messages, exchange), exchange); } @Override @@ -126,8 +136,48 @@ public class LangChain4jChatProducer extends DefaultProducer { * @param chatMessages * @return */ - private String sendListChatMessage(List<ChatMessage> chatMessages) { - Response<AiMessage> response = this.chatLanguageModel.generate(chatMessages); + private String sendListChatMessage(List<ChatMessage> chatMessages, Exchange exchange) { + LangChain4jChatEndpoint langChain4jChatEndpoint = (LangChain4jChatEndpoint) getEndpoint(); + + List<ToolSpecification> toolSpecifications = CamelToolExecutorCache.getInstance().getTools() + .get(langChain4jChatEndpoint.getChatId()).stream() + .map(camelToolSpecification -> camelToolSpecification.getToolSpecification()) + .collect(Collectors.toList()); + + Response<AiMessage> response = this.chatLanguageModel.generate(chatMessages, toolSpecifications); + + if (response.content().hasToolExecutionRequests()) { + chatMessages.add(response.content()); + + for (ToolExecutionRequest toolExecutionRequest : response.content().toolExecutionRequests()) { + String toolName = toolExecutionRequest.name(); + CamelToolSpecification camelToolSpecification = CamelToolExecutorCache.getInstance().getTools() + .get(langChain4jChatEndpoint.getChatId()).stream() + .filter(cts -> cts.getToolSpecification().name().equals(toolName)) + .findFirst().orElseThrow(() -> new RuntimeException("Tool " + toolName + " not found")); + try { + // Map Json to Header + JsonNode jsonNode = objectMapper.readValue(toolExecutionRequest.arguments(), JsonNode.class); + + jsonNode.fieldNames() + .forEachRemaining(name -> exchange.getMessage().setHeader(name, jsonNode.get(name))); + + // Execute the consumer route + camelToolSpecification.getConsumer().getProcessor().process(exchange); + } catch (Exception e) { + // How to handle this exception? + exchange.setException(e); + } + + chatMessages.add(new ToolExecutionResultMessage( + toolExecutionRequest.id(), + toolExecutionRequest.name(), + exchange.getIn().getBody(String.class))); + } + + response = this.chatLanguageModel.generate(chatMessages); + } + return extractAiResponse(response); } diff --git a/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/tool/CamelSimpleToolParameter.java b/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/tool/CamelSimpleToolParameter.java new file mode 100644 index 00000000000..0751c15240d --- /dev/null +++ b/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/tool/CamelSimpleToolParameter.java @@ -0,0 +1,42 @@ +/* + * 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.chat.tool; + +import java.util.List; + +/** + * langchain4j Simple Tool parameter implementation, this class can be used to provide multiple properties/input + * parameters to the tool itself, the NamedJsonSchemaProperty can be then found as headers into the consumer route + */ +public class CamelSimpleToolParameter { + + private final String description; + private final List<NamedJsonSchemaProperty> properties; + + public CamelSimpleToolParameter(String description, List<NamedJsonSchemaProperty> properties) { + this.description = description; + this.properties = properties; + } + + public List<NamedJsonSchemaProperty> getProperties() { + return properties; + } + + public String getDescription() { + return description; + } +} diff --git a/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/tool/CamelToolExecutorCache.java b/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/tool/CamelToolExecutorCache.java new file mode 100644 index 00000000000..20c139cb5d2 --- /dev/null +++ b/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/tool/CamelToolExecutorCache.java @@ -0,0 +1,59 @@ +/* + * 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.chat.tool; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Caches Tools Specification and Consumer route reference by the chatId, so that different chats can have different + * Tool implementation + */ +public final class CamelToolExecutorCache { + + private static CamelToolExecutorCache INSTANCE; + private Map<String, List<CamelToolSpecification>> tools; + + private CamelToolExecutorCache() { + tools = new ConcurrentHashMap<>(); + } + + public synchronized static CamelToolExecutorCache getInstance() { + if (INSTANCE == null) { + INSTANCE = new CamelToolExecutorCache(); + } + + return INSTANCE; + } + + public void put(String chatId, CamelToolSpecification specification) { + if (tools.get(chatId) != null) { + tools.get(chatId).add(specification); + } else { + List<CamelToolSpecification> camelToolSpecifications = new ArrayList<>(); + camelToolSpecifications.add(specification); + tools.put(chatId, camelToolSpecifications); + } + } + + public Map<String, List<CamelToolSpecification>> getTools() { + return tools; + } + +} diff --git a/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/tool/CamelToolSpecification.java b/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/tool/CamelToolSpecification.java new file mode 100644 index 00000000000..9b2d402916d --- /dev/null +++ b/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/tool/CamelToolSpecification.java @@ -0,0 +1,51 @@ +/* + * 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.chat.tool; + +import dev.langchain4j.agent.tool.ToolSpecification; +import org.apache.camel.component.langchain4j.chat.LangChain4jChatConsumer; + +/** + * Holds ToolSpecification needed by langchain4j and the associated Camel Consumer. In this way, a specific route can be + * invoked by a specific Tool + */ +public class CamelToolSpecification { + + private ToolSpecification toolSpecification; + private LangChain4jChatConsumer consumer; + + public CamelToolSpecification(ToolSpecification toolSpecification, LangChain4jChatConsumer consumer) { + this.toolSpecification = toolSpecification; + this.consumer = consumer; + } + + public ToolSpecification getToolSpecification() { + return toolSpecification; + } + + public void setToolSpecification(ToolSpecification toolSpecification) { + this.toolSpecification = toolSpecification; + } + + public LangChain4jChatConsumer getConsumer() { + return consumer; + } + + public void setConsumer(LangChain4jChatConsumer consumer) { + this.consumer = consumer; + } +} diff --git a/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/tool/NamedJsonSchemaProperty.java b/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/tool/NamedJsonSchemaProperty.java new file mode 100644 index 00000000000..d187c780696 --- /dev/null +++ b/components/camel-ai/camel-langchain4j-chat/src/main/java/org/apache/camel/component/langchain4j/chat/tool/NamedJsonSchemaProperty.java @@ -0,0 +1,40 @@ +/* + * 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.chat.tool; + +import java.util.List; + +import dev.langchain4j.agent.tool.JsonSchemaProperty; + +public class NamedJsonSchemaProperty { + + private final String name; + private final List<JsonSchemaProperty> properties; + + public NamedJsonSchemaProperty(String name, List<JsonSchemaProperty> properties) { + this.name = name; + this.properties = properties; + } + + public String getName() { + return name; + } + + public List<JsonSchemaProperty> getProperties() { + return properties; + } +} diff --git a/components/camel-ai/camel-langchain4j-chat/src/test/java/org/apache/camel/component/langchain4j.chat/LangChain4jConsumerIT.java b/components/camel-ai/camel-langchain4j-chat/src/test/java/org/apache/camel/component/langchain4j.chat/LangChain4jConsumerIT.java new file mode 100644 index 00000000000..de4018ce1a3 --- /dev/null +++ b/components/camel-ai/camel-langchain4j-chat/src/test/java/org/apache/camel/component/langchain4j.chat/LangChain4jConsumerIT.java @@ -0,0 +1,93 @@ +/* + * 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.chat; + +import java.util.ArrayList; +import java.util.List; + +import dev.langchain4j.agent.tool.JsonSchemaProperty; +import dev.langchain4j.data.message.ChatMessage; +import dev.langchain4j.data.message.SystemMessage; +import dev.langchain4j.data.message.UserMessage; +import dev.langchain4j.model.openai.OpenAiChatModel; +import org.apache.camel.CamelContext; +import org.apache.camel.Exchange; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.langchain4j.chat.tool.CamelSimpleToolParameter; +import org.apache.camel.component.langchain4j.chat.tool.NamedJsonSchemaProperty; +import org.apache.camel.test.junit5.CamelTestSupport; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; + +@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".*") +public class LangChain4jConsumerIT extends CamelTestSupport { + + private final String nameFromDB = "pippo"; + + @Override + protected CamelContext createCamelContext() throws Exception { + CamelContext context = super.createCamelContext(); + + LangChain4jChatComponent component + = context.getComponent(LangChain4jChat.SCHEME, LangChain4jChatComponent.class); + + component.getConfiguration().setChatModel( + OpenAiChatModel.withApiKey(System.getenv("OPENAI_API_KEY"))); + + return context; + } + + @Override + protected RouteBuilder createRouteBuilder() { + return new RouteBuilder() { + public void configure() { + + NamedJsonSchemaProperty namedJsonSchemaProperty + = new NamedJsonSchemaProperty("name", List.of(JsonSchemaProperty.STRING)); + CamelSimpleToolParameter camelToolParameter = new CamelSimpleToolParameter( + "This is a tool description", + List.of(namedJsonSchemaProperty)); + context().getRegistry().bind("parameters", camelToolParameter); + + from("direct:test") + .to("langchain4j-chat:test1?chatOperation=CHAT_MULTIPLE_MESSAGES"); + + from("langchain4j-chat:test1?description=Query user database by number¶meterName=number¶meterType=integer") + .process(exchange -> exchange.getIn().setBody(nameFromDB)); + + from("langchain4j-chat:test1?camelToolParameter=#parameters") + .setBody(constant("Hello World")); + } + }; + } + + @Test + public void testSimpleInvocation() { + List<ChatMessage> messages = new ArrayList<>(); + messages.add(new SystemMessage(""" + You provide information about specific user name querying the database given a number. + """)); + messages.add(new UserMessage(""" + What is the name of the user 1? + """)); + + Exchange message = fluentTemplate.to("direct:test").withBody(messages).request(Exchange.class); + + Assertions.assertTrue(message.getMessage().getBody().toString().contains(nameFromDB)); + } +}