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 <[email protected]>
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"));
+ }
}