Im letzten Blog-Artikel zum Thema AI haben wir uns die Tools-Unterstützung mit Spring und Spring AI angeschaut (siehe hier). In diesem Artikel schauen wir uns nun dasselbe Beispiel mit Quarkus und LangChain4j an. Die Bibliothek ist zunächst mal an kein bestimmtes Framework gebunden, es gibt aber optionale Integrationen um die Verwendung mit Spring Boot oder Quarkus zu vereinfachen. In unserem Beispielprojekt verwenden wir wieder das Modell mistral (lokal mit Ollama) und ermöglichen dem LLM aktuelle Wetter- und Verkehrsinformationen abzurufen. Für dieses Beispiel füllen wir diese Schnittstellen aber zunächst nur mit Beispieldaten.
Im Gegensatz zur Umsetzung mit Spring AI kann man bei Langchain4j auch einen eher deklarativen Ansatz auf Basis von Annotationen verwenden um die LLM-Schnittstelle zu definieren. In den Code-Beispielen fokussieren wir uns diesmal auf die Wetterinformationen. Die Schnittstellen für die Verkehrsinformationen sind analog implementiert.
Tools (also Funktionalitäten, die das LLM verwenden kann) können mit @Tool
und einer Beschreibung der Schnittstelle annotiert werden. Man kann auch hier beliebige Java-Typen als Parameter angeben, LLMs kommen aber mit einer flacheren Struktur oft besser klar. Mit der @P
Annotation können die Parameter hier noch weiter beschrieben und als optional gekennzeichnet werden.
@ApplicationScoped
public class AiTools {
@Tool("Get current weather information for location.")
public WeatherResponse getCurrentWeather(@P("Request location") String location,
@P(value = "Request unit", required = false) TempUnit unit) {
return new MockWeatherInformationService().apply(new WeatherRequest(location, unit));
}
[...]
}
Die Typen sind hier dieselben wie bereits im Spring AI Beispiel.
public record WeatherRequest(String location, TempUnit unit) {}
public sealed interface WeatherResponse {
record Success(String location, double temp, TempUnit unit) implements WeatherResponse { [...] }
record Failure(String failureMessage) implements WeatherResponse {
}
}
public enum TempUnit{ CELSIUS, FAHRENHEIT; [...] }
Die Mock-Schnittstelle liefert dann ein paar vordefinierte Wetterinformationen.
public class MockWeatherInformationService {
private final Map<String, WeatherResponse> temperatureMap = Map.ofEntries(
Map.entry("Bielefeld", new WeatherResponse.Success("Bielefeld", 20.1, TempUnit.CELSIUS)),
Map.entry("Berlin", new WeatherResponse.Success("Berlin", 25.6, TempUnit.CELSIUS)),
Map.entry("Köln", new WeatherResponse.Success("Köln", 27.2, TempUnit.CELSIUS))
);
public WeatherResponse apply(WeatherRequest request) {
if (request == null) return new WeatherResponse.Failure("Error: Could not parse request.");
WeatherResponse weatherResponse = temperatureMap.getOrDefault(request.location(), null);
return switch (weatherResponse) {
case null -> new WeatherResponse.Failure("Error: No weather information for location " + request.location() + " available.");
case WeatherResponse.Failure failure -> failure;
case WeatherResponse.Success success -> success.withTempUnit(request.unit());
};
}
}
Unsere Anwendung beinhaltet auch eine einfache HTTP-Schnittstelle zum Testen, in der eine Anfrage an das LLM gesendet wird. Die Antwort bekommen wir dann entsprechend zurück.
@Path("ai")
public class ToolsAiResource{
@Inject
ToolsAiService toolsAiService;
@POST
@Path("/tools")
@Consumes(MediaType.TEXT_PLAIN)
@Produces(MediaType.TEXT_PLAIN)
public Response tools(String question) {
return Response.ok(toolsAiService.chat(question)).build();
}
}
Der ToolsAiService kann durch die Quarkus Integration über Annotationen bereitgestellt und konfiguriert werden.
@RegisterAiService(tools = AiTools.class)
public interface ToolsAiService {
String chat(@UserMessage String question);
}
Eine beispielhafte Interaktion kann dann so aussehen.
Q: What is the current weather in Bielefeld?
A: Currently in Bielefeld, the temperature is 20.1 degrees Celsius.
Im Gegensatz zu Spring AI können wir hier Logging für die HTTP Kommunikation mit dem LLM einfach über die Properties quarkus.langchain4j.ollama.chat-model.log-requests
und quarkus.langchain4j.ollama.chat-model.log-responses
aktivieren.
Das Beispiel gibt es wie immer auf GitHub.