Skip to content

Commit

Permalink
LangChain4j: Fix AI services that could not be resolved by interface …
Browse files Browse the repository at this point in the history
…name #6838
  • Loading branch information
aldettinger authored and jamesnetherton committed Dec 3, 2024
1 parent c429b9f commit b6d7903
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 1 deletion.
28 changes: 28 additions & 0 deletions docs/modules/ROOT/pages/reference/extensions/langchain4j.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
```

Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -32,4 +39,13 @@ FeatureBuildItem feature() {
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"));
});
};
}
29 changes: 28 additions & 1 deletion extensions/langchain4j/runtime/src/main/doc/configuration.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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].
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);
}
```
Original file line number Diff line number Diff line change
@@ -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<ChatLanguageModel> {
@Override
public ChatLanguageModel get() {
return (messages) -> new Response<>(new AiMessage("AiServiceResolvedByInterface has been resolved"));
}
}

@UserMessage("Any prompt")
@Handler
String chat(String input);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
}
}

0 comments on commit b6d7903

Please sign in to comment.