Large Language Models elegant in Java nutzen mit LangChain4j

In früheren Artikeln haben wir grundlegende Themen für den programmatischen Zugriff auf Large Language Models (LLMs) angeschaut und diese sowohl mittels Core Python als auch mittels Core Java implementiert. Nachdem wir diese Konzepte in den letzten beiden Artikeln (LangChain_1 und LangChain_2) mit der Bibliothek LangChain in Python umgesetzt haben, möchten wir nun in den nächsten beiden Artikeln dieselben Beispiele in Java mit der Bibliothek LangChain4j implementieren.
Was ist LangChain4j?
LangChain4j ist eine Java-Bibliothek, die Large Language Models (LLMs) in Java-Anwendungen integriert. Obwohl LangChain (Python) als Inspiration diente, hat das LangChain4j-Team die Bibliothek eigenständig für das Java-Ökosystem neu entwickelt.
Während LangChain stark auf das Chain-Paradigma setzt – man baut also explizit Pipelines aus Komponenten zusammen – ist LangChain4j keine direkte Portierung, sondern eine eigenständige Neuinterpretation dieser Ideen. Da sie für die Java-Welt konzipiert wurde, profitiert man von starker Typisierung, Dependency Injection und Standard-Java-Tools.
Darüber hinaus bietet LangChain4j einige besonders hervorzuhebende, Java-spezifische Funktionen:
@AiService– Definiert KI-Verhalten als einfache Java-Schnittstelle; die Bibliothek implementiert diese automatisch.- Deklarative Annotationen wie
@SystemMessageund@UserMessage
Java-Spezifika
Installation von Libraries
Da LangChain4j als Maven– oder Gradle-Bibliothek verfügbar ist, können die benötigten Dependencies bequem eingebunden werden. In den Beispielen dieses Artikels verwenden wir Maven und LangChain4j in der Version 1.12.2. Da sich LangChain4j schnell weiterentwickelt, ist es empfehlenswert immer nach der aktuellen Version zu schauen.
API Keys
Weil kommerzielle LLMs wie Claude, OpenAI und Gemini kostenpflichtig sind, benötigt man für den Zugriff einen API Key, der mit der eigenen Kreditkarte verknüpft ist.
Unter macOS und Unix-Systemen trägt man die Keys direkt im Konfigurationsfile der verwendeten Shell ein. Dabei ersetzt man YOUR_API_KEY durch den eigenen Key:
export ANTHROPIC_API_KEY=YOUR_API_KEY export OPENAI_API_KEY=YOUR_API_KEY export GEMINI_API_KEY=YOUR_API_KEY
Unter Windows legt man die Keys als System Properties an.
Requests mit LangChain4j
Abstraktion des Chat-Modells
Die zentrale Abstraktion ist das Interface ChatModel, das die Interaktion mit dem LLM via chat()-Methoden kapselt. Da jede Variante eine Default-Implementierung besitzt, muss man nur die tatsächlich benötigten Methoden überschreiben.
public interface ChatModel {
default ChatResponse chat(ChatRequest chatRequest) { ... }
default ChatResponse doChat(ChatRequest chatRequest) { ... }
default String chat(String userMessage) { ... }
default ChatResponse chat(ChatMessage... messages) { ... }
default ChatResponse chat(List<ChatMessage> messages) { ... }
...
}
Das Interface liegt in dev.langchain4j.model.chat.ChatModel, weshalb man folgende Maven-Dependency einbinden muss:
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-core</artifactId>
<version>1.12.2</version>
<scope>compile</scope>
</dependency>
Da jeder LLM-Anbieter seine eigene Implementierung mitbringt, muss man zusätzlich jeweils eine spezifische Bibliothek via Maven einbinden.
Ollama
Für den Zugriff via Ollama greift man auf dev.langchain4j.model.ollama.OllamaChatModel zurück. Man bindet dazu zunächst folgende Maven-Dependency ein:
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-ollama</artifactId>
<version>1.12.2</version>
</dependency>
Damit haben wir alles beisammen. Im folgenden Beispiel fragen wir das llama3.1-Modell, was LangChain4j ist:
RequestOllama.java
public class RequestOllama {
private static final String OLLAM_MODEL_NAME = "llama3.1";
public static final String OLLAMA_BASE_URL = "http://localhost:11434";
void simpleQuery(String prompt) {
ChatModel model = OllamaChatModel.builder()
.baseUrl(OLLAMA_BASE_URL)
.modelName(OLLAM_MODEL_NAME)
.build();
String response = model.chat(prompt);
printRequestResponseInfo(prompt, OLLAM_MODEL_NAME, response);
}
void main() {
var prompt = "What is LangChain4j? Please answer in max 1 sentences.";
new RequestOllama().simpleQuery(prompt);
}
}
Ollama liefert uns folgendes Resultat:
LangChain4j is a Java library that enables developers to build conversational AI applications, including chatbots and voice assistants, by providing a unified interface for integrating various natural language processing (NLP) tools and services.
Zum Code:
- Via
OllamaChatModel-Builderwird einChatModelerstellt. DaOllamaChatModeldas Basis-Interface ChatModel erweitert, überschreibt es diverse Methoden mit Ollama-spezifischem Code. - Die Builder-Parameter sind eine URL auf die laufende Ollama-Instanz sowie der Name des gewünschten LLMs.
- Die
chat()-Methode macht den eigentlichen Aufruf an das LLM und liefert das Resultat als String zurück. Zusätzlich bietet das ChatModel-Interface Varianten, die stattdessen eineChatResponsezurückgeben.
Anthropic Claude
Da das Ollama-Beispiel und das Anthropic-Beispiel dieselbe ChatModel-Abstraktion verwenden, sind nur wenige Anpassungen nötig:
- Eine Anthropic-spezifische Maven-Dependency hinzufügen.
- Einen Anthropic API Key erstellen.
- Statt OllamaChatModel das
AnthropicChatModelverwenden. - Den API Key über
System.getenv()aus den Umgebungsvariablen lesen, da er nicht im Code hinterlegt werden sollte.
Maven Dependency:
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-anthropic</artifactId>
<version>1.12.2</version>
</dependency>
RequestAnthropic.java
public class RequestAnthropic {
private static final AnthropicChatModelName MODEL_NAME = AnthropicChatModelName.CLAUDE_SONNET_4_6;
void simpleQuery(String prompt) {
ChatModel model = AnthropicChatModel.builder()
.apiKey(System.getenv("ANTHROPIC_API_KEY"))
.modelName(MODEL_NAME)
.build();
String response = model.chat(prompt);
printRequestResponseInfo(prompt, MODEL_NAME.name(), response);
}
void main() {
var prompt = "What is LangChain4j? Please answer in 1 sentence.";
var claude = new RequestAnthropic();
claude.simpleQuery(prompt);
}
}
OpenAI GPT
Damit man auf die GPT-Modelle von OpenAI zugreifen kann, benötigt man zusätzlich die OpenAI-spezifische Bibliothek sowie einen OpenAI API Key. Anstatt OllamaChatModel verwendet man hier das OpenAiChatModel:
Maven Dependency:
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
<version>1.12.2</version>
<scope>compile</scope>
</dependency>
RequestOpenAI.java
public class RequestOpenAI {
private static final OpenAiChatModelName MODEL_NAME = OpenAiChatModelName.GPT_4_1_NANO;
void simpleQuery(String prompt) {
ChatModel model = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName(MODEL_NAME)
.logRequests(true)
.logResponses(true)
.build();
String response = model.chat(prompt);
printRequestResponseInfo(prompt, MODEL_NAME.name(), response);
}
void main() {
var prompt = "What is LangChain4j? Please answer in max 1 sentences.";
new RequestOpenAI().simpleQuery(prompt);
}
}
Google Gemini
Auch für die Google-Gemini-Modelle sind nur wenige Anpassungen nötig:
- Zunächst die Google-spezifische Bibliothek via Maven einbinden.
- Anschliessend einen Google API Key erstellen.
- Schliesslich das
GoogleAiGeminiChatModelverwenden.
Maven Dependency:
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-google-ai-gemini</artifactId>
<version>1.12.2</version>
</dependency>
RequestGoogle.java
public class RequestGoogle {
private static final String MODEL_NAME = "gemini-2.5-flash-lite";
void simpleQuery(String prompt) {
ChatModel model = GoogleAiGeminiChatModel.builder()
.apiKey(System.getenv("GEMINI_API_KEY"))
.modelName(MODEL_NAME)
.build();
String response = model.chat(prompt);
printRequestResponseInfo(prompt, MODEL_NAME, response);
}
void main() {
var prompt = "What is LangChain4j? Please answer in max 1 sentences.";
new RequestGoogle().simpleQuery(prompt);
}
}
Loggen von Requests und Responses
Damit man Anfragen und Antworten nachvollziehen kann, lässt sich das Logging bequem über logRequests(true) und logResponses(true) am ChatModel-Builder aktivieren. Für Claude sieht das so aus:
ChatModel model = AnthropicChatModel.builder()
.apiKey(System.getenv("ANTHROPIC_API_KEY"))
.modelName(MODEL_NAME)
.logRequests(true)
.logResponses(true)
.build();
⚠️ Stolperfalle: Der Code kompiliert und läuft, aber es wird nichts geloggt. Logging muss zusätzlich konfiguriert werden. Dazu muss nur die Dependency ch.qos.logback in Maven angegeben werden .. und schon wird geloggt.
Maven Dependency:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.13</version>
</dependency>
Hier ein Beispiel, wie der Log Output aussehen könnte:
Request
13:59:53 [main] INFO d.l.h.client.log.LoggingHttpClient - HTTP request:
- method: POST
- url: https://api.anthropic.com/v1/messages
- headers: [x-api-key: sk-an...AA], [anthropic-version: 2023-06-01], [Content-Type: application/json]
- body: {
"model" : "claude-sonnet-4-6",
"messages" : [ {
"role" : "user",
"content" : [ {
"type" : "text",
"text" : "What is LangChain4j? Please answer in 1 sentence."
} ]
} ],
"max_tokens" : 1024,
"stream" : false
}
Response
{
"model":"claude-sonnet-4-6",
"id":"msg_01BiFdgPw83CrHpgAYEoVJeX",
"type":"message",
"role":"assistant",
"content":[
{
"type":"text",
"text":"LangChain4j is a Java library that simplifies the integration of large language models (LLMs) and AI capabilities into Java applications."
}
],
"stop_reason":"end_turn",
"stop_sequence":null,
"stop_details":null,
"usage":{
"input_tokens":24,
"cache_creation_input_tokens":0,
"cache_read_input_tokens":0,
"cache_creation":{
"ephemeral_5m_input_tokens":0,
"ephemeral_1h_input_tokens":0
},
"output_tokens":34,
"service_tier":"standard",
"inference_geo":"global"
}
}
Weitere chat()-Methoden
Bisher haben wir chat() mit einem einfachen String als Input verwendet. Da es jedoch auch komplexere Anwendungsfälle gibt, existiert eine weitere Variante, die einen Vararg von ChatMessages entgegennimmt und dafür eine ChatResponse zurückgibt.
Use Case 1 – Metadaten
Da die ChatResponse nicht nur den Antworttext enthält, können daraus auch nützliche Metadaten extrahiert werden – insbesondere die Token-Usage und die FinishReason:
RequestAnthropic.simpleQueryWithResponseMetaData()
ChatModel model = AnthropicChatModel.builder()
.apiKey(System.getenv("ANTHROPIC_API_KEY"))
.modelName(MODEL_NAME)
.build();
UserMessage userMessage = UserMessage.from(prompt);
ChatResponse response = model.chat(userMessage);
String content = response.aiMessage().text();
println("Response with Metadata:");
println("Content: " + content);
printTokenUsage(response);
printFinishReason(response);
Und die print() Helper Methoden dazu:
public static void printTokenUsage(ChatResponse response) {
if (response.tokenUsage() != null) {
println("Token Usage: " + response.tokenUsage());
println(" Input Tokens : " + response.tokenUsage().inputTokenCount());
println(" Output Tokens: " + response.tokenUsage().outputTokenCount());
println(" Total Tokens : " + response.tokenUsage().totalTokenCount());
} else {
println("Token Usage: Not available");
}
print_line();
}
public static void printFinishReason(ChatResponse response) {
if (response.finishReason() != null) {
println("Finish Reason: " + response.finishReason());
} else {
println("Finish Reason: Not available");
}
print_line();
}
Der Output hat folgendes Format:
Response with Metadata:
Content: LangChain4j is a Java library that simplifies the integration of large language models (LLMs) and AI capabilities into Java applications.
Token Usage: AnthropicTokenUsage { inputTokenCount = 24, outputTokenCount = 34, totalTokenCount = 58, cacheCreationInputTokens = 0, cacheReadInputTokens = 0 }
Input Tokens : 24
Output Tokens: 34
Total Tokens : 58
==================================================
Finish Reason: STOP
==================================================
Use Case 2 – System Messages
Da chat() mehrere Messages verschiedener Typen akzeptiert, ist es möglich, zusätzlich zur UserMessage auch eine SystemMessage mitzugeben. Dadurch lässt sich das Verhalten des LLMs gezielt steuern.
RequestAnthropic.simpleQueryWithSystemMessage()
ChatModel model = AnthropicChatModel.builder()
.apiKey(System.getenv("ANTHROPIC_API_KEY"))
.modelName(MODEL_NAME)
.build();
SystemMessage systemMessage = SystemMessage.from("You are a software developer expert.");
UserMessage userMessage = UserMessage.from(prompt);
ChatResponse response = model.chat(systemMessage, userMessage);
printRequestResponseInfo(prompt, MODEL_NAME.name(), response.aiMessage().text());
Prompt Templates und StructuredPrompt
Bisher haben wir für den Prompt immer einen einfachen String verwendet. Während das für einfache Anfragen genügt, stellt sich die Frage: Was ist, wenn man denselben Prompt mit verschiedenen Platzhaltern mehrmals verwenden möchte?
Ansatz 1 – Plain Strings
Der einfachste Ansatz ist die manuelle Zusammensetzung des Prompts via String-Konkatenation. Diese Lösung ist jedoch nicht empfehlenswert, da sie weder typisiert noch gut lesbar ist und sich zudem schwer pflegen lässt.
Hier einkleines Beispiel dazu: wir möchten einen Text (der Platzhalter im Prompt hat) nach französisch übersetzen lassen.
StringConcatExample
private static String createPrompt(String text) {
return "Translate the following text to french:" + text;
}
ChatModel model = ...
String promptHello = createPrompt("Hello World");
String response = model.chat(promptHello);
String promptBye = createPrompt("Bye World");
String responseBye = model.chat(promptBye);
Ansatz 2 – PromptTemplate
Da die manuelle String-Konkatenation unpraktisch ist, bietet LangChain4j mit PromptTemplate eine elegantere Lösung. Ein PromptTemplate ist eine Zeichenkette mit benannten Platzhaltern in der Form {{variable}}. LangChain4j löst diese Platzhalter zur Laufzeit über eine Variablenzuordnung auf und gibt ein typisiertes Prompt-Objekt zurück.
Das gleiche Beispiel mit Hilfe von PromptTemplates:
- ein PromptTemplate kann aus einem String erstellt werden, wobei der Platzhalter als Variablennamen in
{{ }}definiert ist. - Füllen kann man das PromptTemplate mit konkreten Werten für die Platzhalter mit der
apply()Methode. apply()nimmt als Parameter eine Map, wobei der Key der Name des Platzhalters ist und Value der Wert.- Mit Hilfe von
toUserMessage()kann der Prompt in eine User Message umgewandelt werden und derchat()Methode übergeben werden.
PromptTemplateWithNamedVariable
var stringTemplate = "Translate the following text to french: {{text}}";
PromptTemplate promptTemplate = PromptTemplate.from(stringTemplate);
ChatModel model = ...
Prompt promptHello = promptTemplate.apply(Map.of("text", "Hello World"));
ChatResponse responseHello = model.chat(promptHello.toUserMessage());
Prompt promptBye = promptTemplate.apply(Map.of("text", "Bye World"));
ChatResponse responseBye = model.chat(promptBye.toUserMessage());
Warum ist das besser als einfache String-Konkatenation?
- Wiederverwendbar – da das Template einmal definiert wird, kann es mit verschiedenen Variablen überall angewendet werden.
- Lesbar – weil die Template-Intention klar und von den Daten getrennt ist.
- Sicherer – da LangChain4j eine Exception wirft, wenn ein Platzhalter fehlt oder nicht aufgelöst wird.
- Typisiert – denn statt eines einfachen Strings liefert LangChain4j ein typisiertes Prompt-Objekt zurück.
Ausserdem können PromptTemplates auch mehrere Platzhalter haben, sodass sich z. B. sowohl Zielsprache als auch Text als Parameter übergeben lassen:
PromptTemplateWithMultipleVariables
var stringTemplate = "Translate the following text to {{language}}: {{text}}";
PromptTemplate promptTemplate = PromptTemplate.from(stringTemplate);
ChatModel model = ...
Prompt prompt = promptTemplate.apply(Map.of(
"text", "Hello World",
"language", "French"
));
ChatResponse response = model.chat(prompt.toUserMessage());
Ansatz 3 – StructuredPrompt
Während PromptTemplate bereits eine deutliche Verbesserung darstellt, geht @StructuredPrompt noch einen Schritt weiter. Indem man das Template als Annotation einer Klasse oder eines Java Records definiert, werden die Variablen zu Feldern – was den Code noch typsicherer und wartbarer macht.
Hierzu das Translation Beispiel mit StructuredPrompts.
- Es wird der Java Record TranslationPrompt mit den Feldern
languageundtextdefiniert. - Der Record hat eine Annotation
@StructeredPrompt. - Die Translation Anweisung mit den Platzhaltern ist der Parameter der Annotation.
- Mit einem
StructuredPromptProcessorkann derTranslationPromptin einen normalenPromptumgewandelt werden. Dieser Prompt wird dann als Argument für den Aufruf derchat()Methode verwendet.
StructuredPromptExample
@StructuredPrompt("Translate the following text to {{language}}: {{text}}")
record TranslationPrompt(String language, String text) {}
ChatModel model = ...
Prompt promptFrench = StructuredPromptProcessor.toPrompt(
new TranslationPrompt("French", "Hello, world!")
);
ChatResponse responseFrench = model.chat(promptFrench.toUserMessage());
Prompt promptGerman = StructuredPromptProcessor.toPrompt(
new TranslationPrompt("German", "Hello, world!")
);
ChatResponse responseGerman = model.chat(promptGerman.toUserMessage());
Gegenüber dem einfachen PromptTemplate bietet @StructuredPrompt noch weitere Vorteile:
- Struktur zur Kompilierzeit – da Felder explizit typisiert sind, ist Refactoring problemlos möglich.
- Kapselung – weil Prompt-Logik und Daten als ein einziges Objekt modelliert sind.
- Testbarkeit – da sich Prompt-Objekte einfach isoliert testen lassen.
Zusammenfassung
Für einfache Prompts ohne Parameter genügt ein String. Sobald jedoch Platzhalter ins Spiel kommen, empfehlen sich PromptTemplates. Für wiederverwendbare, komplexe Prompts mit mehreren Eingabefeldern sind @StructuredPrompts die beste Wahl, da sie typsicher und gut testbar sind.
AI Services
AI Services sind eine höhere Abstraktionsschicht für den Zugriff auf LLMs. Anstatt den Prompt manuell zu erstellen, das LLM aufzurufen und die Antworten zu parsen, legt man lediglich fest, WAS die KI tun soll – während LangChain4j sich vollständig um das WIE kümmert. Dabei übernimmt LangChain4j alle Low-Level-Details: Prompt-Erstellung, Modellaufruf, JSON-Parsing und Fehlerbehandlung.
Damit AI Services genutzt werden können, benötigt man zunächst die folgende zusätzliche Maven-Dependency:
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>1.12.2</version>
</dependency>
Damit lässt sich das Übersetzungsbeispiel nun so implementieren:
AiServiceExample
ChatModel model = ...
@StructuredPrompt("Translate the following text to {{language}}: {{text}}")
record TranslationPrompt(String language, String text) {
}
interface AssistantService {
String translate(@UserMessage TranslationPrompt prompt);
}
AssistantService assistant = AiServices.create(AssistantService.class, model);
String replyFrench = assistant.translate(
new TranslationPrompt("french", "Hello World!")
);
String replyGerman = assistant.translate(
new TranslationPrompt("german", "Hello World!")
);
Erläuterungen zum Code:
- Zunächst richtet man das Modell wie in den früheren Beispielen ein.
- Anschliessend legt man den AssistantService als Interface an, das die Schnittstelle zur Interaktion mit dem LLM beschreibt – in diesem Fall eine
translate()-Methode. - Den TranslationPrompt definieren wir als StructuredPrompt, sodass man die Parameter
languageundtexttypsicher übergibt. - Da
AiServices.create()eine vollständige Implementierung des Interfaces erstellt, entfällt jeglicher Boilerplate-Code – wirklich elegant!
Alle Low-Level-Details verschwinden im Hintergrund. Man definiert die Schnittstelle zum LLM vollständig selbst – AI Services ist ein wirklich schönes Konzept in LangChain4j!
Structured Data
In einem früheren Artikel haben wir die Frage gestellt: “What are the most popular programming libraries for accessing LLMs?”. Da dieses Beispiel strukturierte Ausgaben erfordert, ist es besonders geeignet, um zu zeigen, wie LangChain4j auch damit elegant umgeht.
Datenstruktur
Da wir eine Liste von Libraries mit mehreren Attributen pro Eintrag benötigen, definieren wir die Datenstruktur zunächst als Java Records. Folgende Informationen interessieren uns pro Library:
- Name der Library
- Provider (Hersteller)
- URL zur Dokumentation oder zum Repository
- Unterstützte Programmiersprachen
- Aktuelle Version
Diese können wir in Java elegant mittels Records definieren:
public record Library(String name, String provider, String url, String language, String version) {
@Override
public String toString() {
return name + " | " + provider +
", url='" + url + '\'' +
", language='" + language + '\'' +
", version='" + version + '\'' +
'}';
}
}
public record Libraries(List<Library> libraries) {
@Override
public String toString() {
return libraries.stream()
.map(Library::toString)
.collect(Collectors.joining("\n"));
}
}
Anfrage mit AI Services
Da AI Services die gesamte Komplexität verbergen, gestaltet sich die Anfrage an das LLM fast trivial:
GetLibrariesFromAnthropic
interface LibraryService {
@SystemMessage("You are an ai developer expert.")
Libraries getLibraries(@UserMessage LibraryPrompt query);
}
@StructuredPrompt("What are the {{numOfItems}} most popular programming libraries in {{language}} for accessing LLMs?")
record LibraryPrompt(int numOfItems, String language) {
}
ChatModel model = ...
LibraryService service = AiServices.builder(LibraryService.class)
.chatModel(model)
.build();
var prompt = new LibraryPrompt(noOfItems, language);
Libraries libraries = service.getLibraries(prompt);
AI Services ist eines der schönsten Konzepte in LangChain4j. Es abstrahiert alle technischen Details und erlaubt es, die Schnittstelle zu einem LLM völlig nach eigenen Bedürfnissen zu gestalten.
Fazit
Dieser Artikel hat gezeigt, wie man LLMs mit LangChain4j elegant in Java-Anwendungen einbindet. Die Bibliothek bietet dabei eine klare Abstraktionsleiter – von einfachen direkten Aufrufen bis hin zu vollständig deklarierten AI-Schnittstellen.
| Thema | |
| ChatModel | Zentrale Abstraktion für alle LLM-Aufrufe. Einheitliches Interface für Ollama, Anthropic Claude, OpenAI GPT und Google Gemini. |
| LLM-Anbindung | Pro LLM-Anbieter gibt es eine eigene Maven-Dependency und eine eigene ChatModel-Implementierung. Der eigentliche Aufruf via chat() ist immer gleich. |
| Prompt-Strategien | Drei Stufen: Plain String (einfach, fragil) → PromptTemplate (typisiert, wiederverwendbar) → @StructuredPrompt (typsicher, gut testbar, empfohlen für komplexe Prompts). |
| AI Services | Höchste Abstraktionsstufe. Man deklariert das KI-Verhalten als Java-Interface – LangChain4j übernimmt die gesamte Implementierung. Ideal für produktionsreifen, wartbaren Code. |
| Logging | logRequests(true) und logResponses(true) aktivieren das Logging im Builder. Achtung: zusätzlich muss ch.qos.logback als Maven-Dependency hinzugefügt werden. |
Da LangChain4j einen konsequent Java-nativen Ansatz verfolgt, fühlen sich starke Typisierung, deklarative Annotationen und das durchgängige Builder-Pattern sofort vertraut an. Weil LangChain4j zudem jeden LLM-Anbieter hinter demselben ChatModel-Interface kapselt, erfordert der Wechsel zwischen verschiedenen Anbietern nur minimale Codeänderungen – ein grosser Vorteil für die Flexibilität und Wartbarkeit von Projekten.
Während dieser Artikel die grundlegenden Konzepte abgedeckt hat, werden wir im nächsten Artikel fortgeschrittene LangChain4j-Themen betrachten: Parallele Anfragen, Memory (LLM Context), Tool-Verwendung (Function Calling) sowie Implementation eines einfachen Chatbots mit UI.
Links
Artikel
Repository
Frameworks
Workshop
- Agentic Coding with Claude Code, Ken Kousen, O’Reilly Media, Inc.