JavaでAIプログラミングをはじめよう

LangChain4jを使ってみよう

前回はローカルで動く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ファイルにDependencyを追加してください。pom.xmlファイル全体の例としては次のようになります。

<?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>

</think>

日本の首都は **東京** です。

それではコードを見ていきましょう。モデル名にはqwen/qwen3-1.7bを指定しています。

static String MODEL = 
        "qwen/qwen3-1.7b";

LangChain4jでLLMへ接続する基本になるのがChatModelです。LM StudioではOpenAI互換で接続を行うので、OpenAiChatModelを使います。基本的にはbaseUrlmodelNameの指定があれば接続が行えます。

ChatModel model = OpenAiChatModel.builder()
        .baseUrl("http://localhost:1234/v1")
        .modelName(MODEL)
        .build();

ただ、LM StudioへのリクエストにはHTTP/1.1を指定する必要があるので、JdkHttpClientを設定しています。

.httpClientBuilder(JdkHttpClient.builder().httpClientBuilder(
        HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1)))

JdkHttpClientの指定が不要であれば、pom.xmlでの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に貼り付けて「このコードを説明して」と聞けば、Qwen3 1.7Bでも妥当な説明をしてくれるので、興味のある人は聞いてみてください。

チャットで必要になるのは、そこまでの会話の履歴です。⁠日本の首都は?」という質問の」あとで「高い山は?」と聞いたときには、日本の高い山を答えてほしいですよね。そのためには会話履歴を管理する仕組みが必要になりますが、LangChain4jではChatMemoryが会話の管理を行います。

ChatMemory memory = MessageWindowChatMemory.withMaxMessages(50);

まずはチャット全体の制御を行うSystemMessageChatMemoryに追加しています。ここでは/no_thinkを付けることで、チャット全体でThinkingが抑制されるようにしています。

memory.add(new SystemMessage("""
        /no_think
        あなたはユーザーの役にたつアシスタントです。"""));

ボタンが押されたときの処理では、まずUserMessageとして入力をChatMemoryに追加してから、ChatModelchatメソッドにメッセージ全体を渡しています。

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アプリケーションを実装していきます。

おすすめ記事

記事・ニュース一覧