This is an automated email from the ASF dual-hosted git repository. jamesnetherton pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git
The following commit(s) were added to refs/heads/main by this push: new b6d7903dd4 LangChain4j: Fix AI services that could not be resolved by interface name #6838 b6d7903dd4 is described below commit b6d7903dd4fe3695ddfd5e653bd8cfd683f0b421 Author: aldettinger <aldettin...@gmail.com> AuthorDate: Tue Dec 3 14:59:28 2024 +0100 LangChain4j: Fix AI services that could not be resolved by interface name #6838 --- .../pages/reference/extensions/langchain4j.adoc | 28 ++++++++++++++++++++ .../chat/deployment/LangChain4jProcessor.java | 16 ++++++++++++ .../runtime/src/main/doc/configuration.adoc | 29 ++++++++++++++++++++- ...oute.java => AiServiceResolvedByInterface.java} | 30 ++++++++++++++-------- .../langchain/it/LangChain4jResource.java | 8 ++++++ .../component/langchain/it/LangChain4jRoute.java | 3 +++ .../component/langchain4jit/LangChain4jTest.java | 8 ++++++ 7 files changed, 110 insertions(+), 12 deletions(-) diff --git a/docs/modules/ROOT/pages/reference/extensions/langchain4j.adoc b/docs/modules/ROOT/pages/reference/extensions/langchain4j.adoc index 53cf91d22f..0287231bda 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 9709abdf52..4d40dc4c95 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 @@ class LangChain4jProcessor { MethodParameterAllowedAnnotationsBuildItem camelAnnotatedParametersCouldBeUsedAsTemplateVariable() { return new MethodParameterAllowedAnnotationsBuildItem(anno -> anno.name().toString().startsWith("org.apache.camel")); }; + + @BuildStep + void markAiServicesAsUnremovable( + List<DeclarativeAiServiceBuildItem> aiServices, BuildProducer<UnremovableBeanBuildItem> 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 51aa839b59..cf06882e5c 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/LangChain4jRoute.java b/integration-tests/langchain4j/src/main/java/org/apache/camel/quarkus/component/langchain/it/AiServiceResolvedByInterface.java similarity index 52% copy from integration-tests/langchain4j/src/main/java/org/apache/camel/quarkus/component/langchain/it/LangChain4jRoute.java copy to integration-tests/langchain4j/src/main/java/org/apache/camel/quarkus/component/langchain/it/AiServiceResolvedByInterface.java index a7ee74f31a..ef81c28b2d 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/AiServiceResolvedByInterface.java @@ -16,20 +16,28 @@ */ 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 jakarta.inject.Inject; -import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.Handler; @ApplicationScoped -public class LangChain4jRoute extends RouteBuilder { - - @Inject - MirrorAiService mirrorAiService; +@RegisterAiService(chatLanguageModelSupplier = AiServiceResolvedByInterface.AiServiceResolvedByInterfaceModelSupplier.class) +public interface AiServiceResolvedByInterface { - @Override - public void configure() { - from("direct:camel-annotations-should-work-as-expected") - .setHeader("headerName", constant("headerValue")) - .bean(mirrorAiService); + public static class AiServiceResolvedByInterfaceModelSupplier implements Supplier<ChatLanguageModel> { + @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 d3c9d30a68..bbe77035a4 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 @@ package org.apache.camel.quarkus.component.langchain.it; 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 class LangChain4jResource { 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 a7ee74f31a..c7aa1a8df6 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 class LangChain4jRoute extends RouteBuilder { 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 26d5642894..85e73deb1f 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 @@ class LangChain4jTest { .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")); + } }