前回はローカルで動くLLM環境のセットアップと、そこにHttpClientを使っての接続を行いました。LLMへのリクエストには、JSONデータの構築が必要でしたが、実際にアプリケーションを作る際にLLMの仕様を把握してJSONを送受信するというのは大変です。利用するAIサービスによってJSONなどを書き換える必要もあります。そこで今回は、LLMへの接続に必要な処理を共通化する定番ライブラリであるLangChain4jを使ってチャットアプリを作っていきます。
LangChain4jの導入
LangChain4jは、Python向けのLLMライブラリであるLangChainを元に作られたJavaライブラリです。実際のコードにあまり共通点はありませんが、全体の概念的な構成には共通点があります。JavaからのLLM操作ではLangChain4jが標準になってきています。
LangChain4jを使ってLM Studioに接続するプログラムを作成する場合、ビルドツールがMavenであれば次のようなDependencyが必要です。
<dependencies>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-http-client-jdk</artifactId>
<version>1.1.0</version>
</dependency>
</dependencies>
IDEなどで作成したpom.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>examples</groupId>
<artifactId>ai02</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-http-client-jdk</artifactId>
<version>1.1.0</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>24</maven.compiler.source>
<maven.compiler.target>24</maven.compiler.target>
</properties>
</project>
基本的なアクセス
それでは、LangChain4jを使ってLLMにアクセスする基本的なコードを見てみましょう。
package examples.ai02;
import dev.langchain4j.http.client.jdk.JdkHttpClient;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import java.net.http.HttpClient;
public class SimpleSample {
static String MODEL =
"qwen/qwen3-1.7b";
public static void main(String[] args) {
ChatModel model = OpenAiChatModel.builder()
.baseUrl("http://localhost:1234/v1")
.modelName(MODEL)
.httpClientBuilder(JdkHttpClient.builder().httpClientBuilder(
HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1)))
.build();
String message = """
/no_think
日本の首都は?""";
String reponse = model.chat(message);
System.out.println(reponse);
}
}
LM Studioを起動した状態で実行すると、/no_
を指定しているので、空の<think>
~</think>
が出力されています。
<think> </think> 日本の首都は **東京** です。
それではコードを見ていきましょう。モデル名にはqwen/
を指定しています。
static String MODEL =
"qwen/qwen3-1.7b";
LangChain4jでLLMへ接続する基本になるのがChatModel
です。LM StudioではOpenAI互換で接続を行うので、OpenAiChatModel
を使います。基本的にはbaseUrl
とmodelName
の指定があれば接続が行えます。
ChatModel model = OpenAiChatModel.builder()
.baseUrl("http://localhost:1234/v1")
.modelName(MODEL)
.build();
ただ、LM StudioへのリクエストにはHTTP/JdkHttpClient
を設定しています。
.httpClientBuilder(JdkHttpClient.builder().httpClientBuilder(
HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1)))
JdkHttpClient
の指定が不要であれば、pom.langchain4j-http-client-jdk
のdependencyも不要です。
baseUrl
の指定を行わない場合はOpenAIのURLが指定されています。apiKey
を指定すればOpenAIにアクセスできるようになります。
ChatModel model = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName(OpenAiChatModelName.GPT_4_O)
.build();
あとは、ChatModel
に対してchat
メソッドを呼び出すと、そのメッセージに対する返答が生成されて返ってきます。
String message = """
/no_think
日本の首都は?""";
String reponse = model.chat(message);
チャットアプリを作る
それでは、LangChain4jを使ってチャットアプリを作ってみましょう。
package examples.ai02;
import dev.langchain4j.data.message.SystemMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.http.client.jdk.JdkHttpClient;
import dev.langchain4j.memory.ChatMemory;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.response.ChatResponse;
import dev.langchain4j.model.openai.OpenAiChatModel;
import java.net.http.HttpClient;
import java.awt.BorderLayout;
import javax.swing.*;
public class GuiChat {
public static void main(String[] args) {
// 画面の構築
JFrame f = new JFrame("チャット");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(800, 600);
JTextArea output = new JTextArea();
output.setFocusable(false);
output.setLineWrap(true);
f.add(new JScrollPane(output,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER));
JPanel p = new JPanel();
JTextField tf = new JTextField(30);
JButton b = new JButton("送信");
p.add(tf);
p.add(b);
f.add(BorderLayout.SOUTH, p);
f.setVisible(true);
// LLMへの接続処理
String MODEL = "qwen/qwen3-1.7b";
var model = OpenAiChatModel.builder()
.baseUrl("http://localhost:1234/v1")
.modelName(MODEL)
.httpClientBuilder(JdkHttpClient.builder().httpClientBuilder(
HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1)))
.build();
ChatMemory memory = MessageWindowChatMemory.withMaxMessages(50);
memory.add(new SystemMessage("""
/no_think
あなたはユーザーの役にたつアシスタントです。"""));
b.addActionListener(ae -> {
String text = tf.getText();
if (text.isBlank()) return;
tf.setText("");
output.append("> %s\n".formatted(text));
memory.add(new UserMessage(text));
ChatResponse result = model.chat(memory.messages());
String reply = result.aiMessage().text()
.replaceFirst("^<think>\\s*</think>\\s*", "");
output.append(reply + "\n\n");
memory.add(result.aiMessage());
});
tf.addActionListener(b.getActionListeners()[0]);
}
}
実行すると、次のようにLLMとのチャットが行えます。

「日本の首都は?」
今回、画面構成のためのSwingに関する説明は省略します。コードをLM Studioに貼り付けて

チャットで必要になるのは、そこまでの会話の履歴です。ChatMemory
が会話の管理を行います。
ChatMemory memory = MessageWindowChatMemory.withMaxMessages(50);
まずはチャット全体の制御を行うSystemMessage
をChatMemory
に追加しています。ここでは/no_
を付けることで、チャット全体でThinkingが抑制されるようにしています。
memory.add(new SystemMessage("""
/no_think
あなたはユーザーの役にたつアシスタントです。"""));
ボタンが押されたときの処理では、まずUserMessage
として入力をChatMemory
に追加してから、ChatModel
のchat
メソッドにメッセージ全体を渡しています。
memory.add(new UserMessage(text));
ChatResponse result = model.chat(memory.messages());
返答からは空の<think>
~</think>
を削除して表示しています。
String reply = result.aiMessage().text()
.replaceFirst("^<think>\\s*</think>\\s*", "");
output.append(reply + "\n\n");
また、LLMからの返答もChatMemory
に追加します。
memory.add(result.aiMessage());
返答はAiMessage
クラスのオブジェクトになっています。メッセージのJSONに指定するrole
とJavaクラスとの関係をまとめると次のようになります。
role | Javaクラス | 用途 |
---|---|---|
system | SystemMessage | チャットの設定の指定 |
user | UserMessage | ユーザーの入力 |
assistant | AiMessage | LLMからの出力 |
ストリーミング
ここまでの例では、LLMからの出力がすべて終わってから表示が行われました。そのため、出力が長いときには長時間待たされることになります。
この制約を解消するためにStreamingChatModel
を使います。トークンの出力ごとに処理を行えるようになり、LLMからの出力をリアルタイムに表示可能になります。OpenAI互換のアクセスでは、OpenAiChatModel
の代わりにOpenAiStreamingChatModel
を使います。
StreamingChatModel model = OpenAiStreamingChatModel.builder()
.baseUrl("http://localhost:1234/v1")
chat
メソッドの呼び出しで、StreamingChatResponseHandler
を渡して、ここに出力ごとの処理を記述します。
model.chat(memory.messages(), new StreamingChatResponseHandler(){
...
});
onPartialResponse
はトークンごとの出力で呼び出されるメソッドです。ここでテキストエリアへの出力を行います。
@Override
public void onPartialResponse(String partialResponse) {
output.append(partialResponse);
}
ただ、<think>
~</think>
を省こうとすると少し込み入った処理が必要になるので、今回はそのまま表示しています。
onCompleteResponse
メソッドは、出力が終わったときに呼び出されるメソッドです。ここでChatMemory
への追加を行っています。
@Override
public void onCompleteResponse(ChatResponse completeResponse) {
output.append("\n\n");
memory.add(completeResponse.aiMessage());
}
onError
メソッドでは例外が発生した場合の処理を行います。今回は何もしていません。
@Override
public void onError(Throwable error) {
}
今回のクラスやインタフェースを使うには、次のようなimport
が必要です。
import dev.langchain4j.model.chat.StreamingChatModel;
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
import dev.langchain4j.model.openai.OpenAiStreamingChatModel;
このようにすることで、次のようにリアルタイムに出力されるようになりました。

次回はAiService
を使って、Function Calling (Tool Use)やRAGを扱う、より高度なAIアプリケーションを実装していきます。