diff --git a/docs/modules/ROOT/pages/reference/extensions/langchain4j.adoc b/docs/modules/ROOT/pages/reference/extensions/langchain4j.adoc index 53cf91d22f5d..0287231bda34 100644 --- a/docs/modules/ROOT/pages/reference/extensions/langchain4j.adoc +++ b/docs/modules/ROOT/pages/reference/extensions/langchain4j.adoc @@ -83,3 +83,31 @@ public interface CustomAiService { You can find more details about Camel Parameter Binding annotations in the xref:manual::parameter-binding-annotations.adoc[manual]. +[id="extensions-langchain4j-configuration-resolving-ai-services-by-interface-name"] +=== Resolving AI services by interface name + +With the `camel-quarkus-langchain4j` extension, the AI services are resolvable by interface name when called from a `bean` statement. + +For instance, let's define an AI service below: + +``` +@RegisterAiService +@ApplicationScoped +public interface MyAiService { + + @UserMessage("My Prompt") + @Handler + String chat(String question); +} +``` + +The AI service could then be invoked from a Camel route like this: + +``` +@Override +public void configure() { + from("...") + .bean(MyAiService.class); +} +``` + diff --git a/extensions/langchain4j/deployment/src/main/java/org/apache/camel/quarkus/component/langchain/chat/deployment/LangChain4jProcessor.java b/extensions/langchain4j/deployment/src/main/java/org/apache/camel/quarkus/component/langchain/chat/deployment/LangChain4jProcessor.java index 9709abdf5248..4d40dc4c951e 100644 --- a/extensions/langchain4j/deployment/src/main/java/org/apache/camel/quarkus/component/langchain/chat/deployment/LangChain4jProcessor.java +++ b/extensions/langchain4j/deployment/src/main/java/org/apache/camel/quarkus/component/langchain/chat/deployment/LangChain4jProcessor.java @@ -16,10 +16,17 @@ */ package org.apache.camel.quarkus.component.langchain.chat.deployment; +import java.util.List; + +import io.quarkiverse.langchain4j.deployment.DeclarativeAiServiceBuildItem; import io.quarkiverse.langchain4j.deployment.items.MethodParameterAllowedAnnotationsBuildItem; +import io.quarkus.arc.deployment.UnremovableBeanBuildItem; +import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.FeatureBuildItem; +import static io.quarkus.arc.deployment.UnremovableBeanBuildItem.beanClassNames; + class LangChain4jProcessor { private static final String FEATURE = "camel-quarkus-langchain4j"; @@ -32,4 +39,13 @@ FeatureBuildItem feature() { MethodParameterAllowedAnnotationsBuildItem camelAnnotatedParametersCouldBeUsedAsTemplateVariable() { return new MethodParameterAllowedAnnotationsBuildItem(anno -> anno.name().toString().startsWith("org.apache.camel")); }; + + @BuildStep + void markAiServicesAsUnremovable( + List aiServices, BuildProducer unremovableBeans) { + aiServices.stream().forEach(ai -> { + unremovableBeans.produce( + beanClassNames(ai.getServiceClassInfo().name().toString() + "$$QuarkusImpl")); + }); + }; } diff --git a/extensions/langchain4j/runtime/src/main/doc/configuration.adoc b/extensions/langchain4j/runtime/src/main/doc/configuration.adoc index 51aa839b5914..cf06882e5cfd 100644 --- a/extensions/langchain4j/runtime/src/main/doc/configuration.adoc +++ b/extensions/langchain4j/runtime/src/main/doc/configuration.adoc @@ -15,4 +15,31 @@ public interface CustomAiService { } ``` -You can find more details about Camel Parameter Binding annotations in the xref:manual::parameter-binding-annotations.adoc[manual]. \ No newline at end of file +You can find more details about Camel Parameter Binding annotations in the xref:manual::parameter-binding-annotations.adoc[manual]. + +=== Resolving AI services by interface name + +With the `camel-quarkus-langchain4j` extension, the AI services are resolvable by interface name when called from a `bean` statement. + +For instance, let's define an AI service below: + +``` +@RegisterAiService +@ApplicationScoped +public interface MyAiService { + + @UserMessage("My Prompt") + @Handler + String chat(String question); +} +``` + +The AI service could then be invoked from a Camel route like this: + +``` +@Override +public void configure() { + from("...") + .bean(MyAiService.class); +} +``` \ No newline at end of file diff --git a/integration-tests/langchain4j/src/main/java/org/apache/camel/quarkus/component/langchain/it/AiServiceResolvedByInterface.java b/integration-tests/langchain4j/src/main/java/org/apache/camel/quarkus/component/langchain/it/AiServiceResolvedByInterface.java new file mode 100644 index 000000000000..ef81c28b2d8b --- /dev/null +++ b/integration-tests/langchain4j/src/main/java/org/apache/camel/quarkus/component/langchain/it/AiServiceResolvedByInterface.java @@ -0,0 +1,43 @@ +/* + * 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.quarkus.component.langchain.it; + +import java.util.function.Supplier; + +import dev.langchain4j.data.message.AiMessage; +import dev.langchain4j.model.chat.ChatLanguageModel; +import dev.langchain4j.model.output.Response; +import dev.langchain4j.service.UserMessage; +import io.quarkiverse.langchain4j.RegisterAiService; +import jakarta.enterprise.context.ApplicationScoped; +import org.apache.camel.Handler; + +@ApplicationScoped +@RegisterAiService(chatLanguageModelSupplier = AiServiceResolvedByInterface.AiServiceResolvedByInterfaceModelSupplier.class) +public interface AiServiceResolvedByInterface { + + public static class AiServiceResolvedByInterfaceModelSupplier implements Supplier { + @Override + public ChatLanguageModel get() { + return (messages) -> new Response<>(new AiMessage("AiServiceResolvedByInterface has been resolved")); + } + } + + @UserMessage("Any prompt") + @Handler + String chat(String input); +} diff --git a/integration-tests/langchain4j/src/main/java/org/apache/camel/quarkus/component/langchain/it/LangChain4jResource.java b/integration-tests/langchain4j/src/main/java/org/apache/camel/quarkus/component/langchain/it/LangChain4jResource.java index d3c9d30a6878..bbe77035a44c 100644 --- a/integration-tests/langchain4j/src/main/java/org/apache/camel/quarkus/component/langchain/it/LangChain4jResource.java +++ b/integration-tests/langchain4j/src/main/java/org/apache/camel/quarkus/component/langchain/it/LangChain4jResource.java @@ -18,6 +18,7 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; @@ -38,4 +39,11 @@ public String camelAnnotationsShouldWorkAsExpected(String json) { return producerTemplate.requestBody("direct:camel-annotations-should-work-as-expected", json, String.class); } + @Path("/ai-service-should-be-resolvable-by-interface") + @GET + @Produces(MediaType.TEXT_PLAIN) + public String aiServiceShouldBeResolvableByInterface() { + return producerTemplate.requestBody("direct:ai-service-should-be-resolvable-by-interface", "dummy-body", String.class); + } + } diff --git a/integration-tests/langchain4j/src/main/java/org/apache/camel/quarkus/component/langchain/it/LangChain4jRoute.java b/integration-tests/langchain4j/src/main/java/org/apache/camel/quarkus/component/langchain/it/LangChain4jRoute.java index a7ee74f31a22..c7aa1a8df6cf 100644 --- a/integration-tests/langchain4j/src/main/java/org/apache/camel/quarkus/component/langchain/it/LangChain4jRoute.java +++ b/integration-tests/langchain4j/src/main/java/org/apache/camel/quarkus/component/langchain/it/LangChain4jRoute.java @@ -31,5 +31,8 @@ public void configure() { from("direct:camel-annotations-should-work-as-expected") .setHeader("headerName", constant("headerValue")) .bean(mirrorAiService); + + from("direct:ai-service-should-be-resolvable-by-interface") + .bean(AiServiceResolvedByInterface.class); } } diff --git a/integration-tests/langchain4j/src/test/java/org/apache/camel/quarkus/component/langchain4jit/LangChain4jTest.java b/integration-tests/langchain4j/src/test/java/org/apache/camel/quarkus/component/langchain4jit/LangChain4jTest.java index 26d564289414..85e73deb1f48 100644 --- a/integration-tests/langchain4j/src/test/java/org/apache/camel/quarkus/component/langchain4jit/LangChain4jTest.java +++ b/integration-tests/langchain4j/src/test/java/org/apache/camel/quarkus/component/langchain4jit/LangChain4jTest.java @@ -36,4 +36,12 @@ void camelAnnotationsShouldWorkAsExpected() { .body("fromHeader", is("headerValue")); } + @Test + void aiServiceShouldBeResolvedByInterface() { + RestAssured.given() + .get("/langchain4j/ai-service-should-be-resolvable-by-interface") + .then() + .statusCode(200) + .body(is("AiServiceResolvedByInterface has been resolved")); + } }