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

JavaでLLMにアクセスしてみよう

2022年のChatGPTをきっかけに広まりだしたAIは、今年に入ってさまざまな領域で実用的になってきているように見えます。現在のAIは、LLM(Large Language Model; 大規模言語モデル)を中心としたシステムです。LLMを使って自律的に動くAIシステムはAIエージェントと呼ばれて、注目を集め始めています。

今回の短期連載では数回にわけて、JavaでAIエージェントを作る基本になるLLM操作のプログラムを作っていきます。

まずは手元でAIを動かそう

AIプログラミングをするときに、課金が必要なAPIを使うとちょっと試すことにも躊躇してしまいますね。そこで、プログラムが正しくLLMにアクセスできているかを確認するために、手元でLLMが動くようにしてみましょう。

手元でLLMを動かす環境としては、GUIベースのLM StudioやCUIベースのOllamaが代表的ですが、ここでは導入がわかりやすいLM Studioを使います。

LM Studioのインストール

次のURLにアクセスして、LM Studioをダウンロード、インストールしてください。

https://lmstudio.ai/

起動すると次のようになります。ここでは実行するLLMを指定したいので、右上の「Skip onboarding」を押してオンボーディングを飛ばします。

LLMモデルのダウンロード

左のツールアイコンの虫眼鏡のDiscover(探索)を選ぶと、モデルの検索画面が開きます。

ローカルで動く手ごろな大きさのLLMとしては、Alibabaの開発したQwen3かGoogleの開発したGemma3がおすすめです。今回はQwen3 1.7Bを使います。1.7Bというのがモデルの大きさを表していて、簡単にいうとBをGに置き換えて1.7Gバイトのサイズになります。

Qwen3には0.6Bというもっと小さいモデルや、4Bや8B、14B、32B、また、MoE(Mixture of Experts)モデルの30B-A3BやA235B-A22Bといったもっと大きいモデルもあります。今回の連載のサンプルは0.6Bでも一応動きますが、安定した動作のために1.7Bを使っています。より大きなモデルのほうが返答が的確になるので、ハードウェアに余裕があれば、もっと大きいモデルを使っても大丈夫です。

検索に「Qwen3 1.7b」を入力すると一番上に推奨のモデルが表示されます。ここをクリックしてDownload Optionsのところを見るといろいろなサイズのものがありますが、今回は「Q4_K_M」を使います。

ファイルのダウンロードが終わると通知が出るので「Load Model」を押すとモデルの読み込みが始まります。

モデル読み込みの設定の指定が表示されますが、そのまま「モデルを読み込む」とすればモデルが読み込まれます。

LLMの動作を確認する

では、LM Studioのチャット画面で「JavaのSwingのサンプルを作って」と聞いてみましょう。

Qwen3はReasoningモデルといって、一旦Thinkingフェーズで考慮を行ってから返答を返すモデルになっています。現在のLLMはあくまで文章の続きを生成する仕組みなので、LLMの持つ知識を文章に書きだしておいてThinkingを開くと、何か英語で考え事をしていることがわかりますね。

しばらく待つと、考え事が終わって返答が日本語で始まります。

ここで出力されたJavaコードをファイルに保存してjavaコマンドで起動すると、運がよければ一発で起動しますが、1.7Bモデルだと恐らくimport不足でエラーが出ます。今回はコンパイルエラーが出たので、そのエラーを貼り付けて、修正してもらいます。

実際にはjava.awt.BorderLayoutのimportが必要なので、もう一度修正してもらうと、ボタンを押すとメッセージが表示されるプログラムが動きました。

LLMからの返答はランダムなので、みなさんの手元では、別のプログラムが出力されていると思います。エラーが出たらエラーメッセージを渡してなるべくLLMに直させるようにしてみてください。

もっと具体的な指示を行えば、プログラムの確実度はあがります。例えば次のように指示してみます。

JavaのSwingでFlowLayoutとBorderLayoutを組み合わせて、ボタンを押すとテキストフィールドに入力した文字列がテキストエリアに表示されるサンプルを作って

そうすると、importの問題があったものの次のようなプログラムができました。思ったものとは違いますが、条件通りの動きになっています。

AIにプログラムを作らせるときは、動きや構造を具体的に指定したほうが求めるものが生成される可能性が高くなります。さすがに1.7Bで狙ったプログラムを作るのは難しく、Qwen3 4Bになると次のようなプログラムが一発でできました。

また、Qwen3は/no_thinkで始めると、Thinkingを行わずに返答が始まります。小さなモデルではThinkingが長くなってトークンを消費すると求める出力になりにくくなるので、論理的な問題を解くとき以外は/no_thinkをつけるほうがいいかもしれません。

Javaからアクセスしてみる

それでは、LM Studioで読み込んだモデルをJavaのコードから呼び出してみましょう。

サーバー設定と確認

Developerタブを表示して、左上のStatusがRunningになっていることを確認してください。Stoppedになっていたら、トグルをクリックしてRunningになるようにします。

ブラウザで「http://localhost:1234/v1/models」にアクセスして、次のように返ってくれば、サーバーが起動しています。

Javaプログラムからのアクセス

今回はライブラリを使わず、直接HTTPアクセスをして試してみます。LM StudioではOpenAIと同じ形式でのAPI呼び出しが行えます。

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpRequest.BodyPublishers;
import java.nio.charset.StandardCharsets;

public class AIClient {

    public static void main(String[] args) throws Exception {
        // String endpoint = "https://api.openai.com/v1/chat/completions";
        String endpoint = "http://localhost:1234/v1/chat/completions";

        String jsonBody = """
            {
              "model": "qwen/qwen3-1.7b",
              "messages": [
                { "role": "system", "content": "/no_think\\nあなたはユーザーの役にたつアシスタントです。" },
                { "role": "user", "content": "日本の首都は?" }
              ],
              "temperature": 0.7
            }
        """;

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(endpoint))
            // .header("Authorization", "Bearer " + API_KEY) // OpenAIにアクセスする場合はAPI_KEYが必要
            .header("Content-Type", "application/json")
            .POST(BodyPublishers.ofString(jsonBody, StandardCharsets.UTF_8))
            .build();

        HttpClient client = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build();
        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

        System.out.println("Response Body:\n" + response.body());
    }
}

この内容をAIClient.javaとして保存してください。このコードを確認していきます。

LM Studioでは1234ポートで待ち受けを行うので、次のようなURLでアクセスします。

String endpoint = "http://localhost:1234/v1/chat/completions";

localhost:1234ではなくapi.openai.comにつなぐようにすれば、OpenAIのAPIにアクセスできます。

リクエストはJSONで指定します。

{
  "model": "qwen/qwen3-1.7b",
  "messages": [
    { "role": "system", "content": "/no_think\\nあなたはユーザーの役にたつアシスタントです。" },
    { "role": "user", "content": "日本の首都は?" }
  ],
  "temperature": 0.7
}

modelに指定するモデル名は、LM Studioの開発者タブでコピーボタンを押すとクリップボードにコピーされるものを使います。

OpenAIにアクセスする場合にはgpt-4oなどになります。

messagesには会話の履歴を与えます。ここでそれぞれのメッセージにはroleとして役割が、contentとしてメッセージ内容を指定します。

roleには次のようなものを指定します。

role 用途
system チャットの設定の指定
user ユーザーの入力
assistant AIからの出力

また、ここでシステムプロンプトに/no_thinkを指定していますがQwen3以外のモデルを使う場合には不要です。

temperatureは返答を選ぶ幅で、0に近ければ確率の高いものだけを選び、1に近づくほど確率の低いものも選ぶようになり返答の多様性があがります。

アクセスにはHttpClientを使います。HttpClientは最初にHTTP/2を試すのですが、この動きにLM Studioが対応してないので、明示的にHTTP/1.1を指定する必要があります。

HttpClient client = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build();

リクエストはPOSTで行います。OpenAIにアクセスする場合には、APIキーの指定が必要です。ただ、なるべくプログラムに埋め込まないよう、環境変数から取ってくるようにしてください。

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create(endpoint))
    // .header("Authorization", "Bearer " + API_KEY) // OpenAIにアクセスする場合はAPI_KEYが必要
    .header("Content-Type", "application/json")
    .POST(BodyPublishers.ofString(jsonBody, StandardCharsets.UTF_8))
    .build();

AiClient.javaを実行すると次のようなJSONが返ってくるはずです。contentの内容は異なるはずです。

>java AiClient.java
Response Body:
{
  "id": "chatcmpl-elu0ckl7oswp9k7pwdklh",
  "object": "chat.completion",
  "created": 1752345165,
  "model": "qwen/qwen3-1.7b",
  "choices": [
    {
      "index": 0,
      "logprobs": null,
      "finish_reason": "stop",
      "message": {
        "role": "assistant",
        "content": "<think>\n\n</think>\n\n日本の首都は**東京都**です。"
      }
    }
  ],
  "usage": {
    "prompt_tokens": 35,
    "completion_tokens": 13,
    "total_tokens": 48
  },
  "stats": {},
  "system_fingerprint": "qwen/qwen3-1.7b"
}

このJSONを処理してアプリケーションを作っていけばいいのですが、それは手間なので、次回はLangChain4Jを使ってLLMの操作を行っていきます。

おすすめ記事

記事・ニュース一覧