Reputation: 20185
In vanilla langchain4j we can provide tools programatically https://docs.langchain4j.dev/tutorials/tools#specifying-tools-programmatically
I don't seem to find a way to do the same with quarkus-langchain4j, the only examples are for dynamic tools, which is different.
I have my ToolSpecification builder
private ToolSpecification createToolFromAction(Action action) {
var toolname = action.name();
var toolDescription = action.description();
Map<String, JsonSchemaElement> params = new HashMap<>();
action.parameters().forEach(parameter -> {
params.put(parameter.name(), JsonStringSchema.builder().description(parameter.name()).build());
});
return ToolSpecification.builder()
.name(toolname)
.description(toolDescription)
.parameters(JsonObjectSchema.builder().properties(params).build())
.build();
}
public List<ToolSpecification> createToolSpecificationsFromActions() {
return actionsRepository.listActions().stream()
.map(this::createToolFromAction)
.toList();
}
And I can also provide the ToolExecutor, however I have no idea how to provide it along with the AiService with the @RegisterAiService annotation.
UPDATE
Allright, after some deep dive into the codebase, I found a test which shows how to use your own ToolSpecification and ToolExecutor.
@Override
public ToolProviderResult provideTools(ToolProviderRequest request) {
assertNotNull(myServiceWithoutTools);
ToolSpecification toolSpecification = ToolSpecification.builder()
.name("get_booking_details")
.description("Returns booking details")
.build();
ToolExecutor toolExecutor = (t, m) -> "0";
return ToolProviderResult.builder()
.add(toolSpecification, toolExecutor)
.build();
}
However, it breaks all my other interfaces annotated with @RegisterAiService with the following error :
java.lang.IllegalArgumentException: Either the tools or the tool provider can be configured, but not both!
Upvotes: 1
Views: 79
Reputation: 20185
So, it seems you can basically achieve the same using DynamicTools, up to you to make it dynamic or not.
Here is a full example :
package com.ikwattro;
import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.agent.tool.ToolSpecification;
import dev.langchain4j.service.tool.ToolExecutor;
import dev.langchain4j.service.tool.ToolProvider;
import dev.langchain4j.service.tool.ToolProviderRequest;
import dev.langchain4j.service.tool.ToolProviderResult;
import io.quarkiverse.langchain4j.RegisterAiService;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.jboss.resteasy.reactive.RestQuery;
import java.util.function.Supplier;
@Path("/assistant")
public class AiResource {
@Inject FirstAiService firstAiService;
@Inject SecondAiService secondAiService;
@GET
@Path("/one")
public String chatOne(@RestQuery String text) {
return firstAiService.chatOne(text);
}
@GET
@Path("/two")
public String chatTwo(@RestQuery String text) {
return secondAiService.chatTwo(text);
}
@Singleton
public static class Counter {
@Tool("Counts the number of characters in a string")
public int countCharacters(String s) {
return s.length();
}
}
@Singleton
public static class MyToolsProvider implements Supplier<ToolProvider> {
@Override
public ToolProvider get() {
return new ToolsProviderBuilder();
}
public static class ToolsProviderBuilder implements ToolProvider {
@Override
public ToolProviderResult provideTools(ToolProviderRequest toolProviderRequest) {
var spec = ToolSpecification.builder()
.name("return_one")
.description("return the number 1 as string")
.build();
ToolExecutor executor = (toolExecutionRequest, memoryId) -> "one";
return ToolProviderResult.builder().add(spec, executor).build();
}
}
}
@RegisterAiService(tools = {Counter.class})
public interface FirstAiService {
String chatOne(String text);
}
@RegisterAiService(toolProviderSupplier = MyToolsProvider.class)
public interface SecondAiService {
String chatTwo(String text);
}
}
Regarding the issue in the question, it proved to be a bug in quarkus-langchain4j https://github.com/quarkiverse/quarkus-langchain4j/issues/1191
Upvotes: 1