QuarkusとGraniteを使ったAIアプリ開発

Red Hat で Quarkus を担当している伊藤ちひろです。

この記事では、PC 上で LLM に接続するアプリケーションの開発の流れを紹介します。 今回は、LLM としてGraniteを使用し、そこへアクセスするアプリケーションは Quarkus を使用して開発します。 このGraniteのモデルはInstructLabでチューニングされたものを使用しています。 Graniteのモデルは Podman Desktop 上に構築しています。

最終的に完成するアプリはこちらです。

質問すると、AIっぽい返答がもらえます。

Podman の AI 拡張機能をインストール

まずは、Podman Desktop に AI の拡張機能をインストールします。 左のメニューのパズルのポースのようなアイコンを押して、Catalog (カタログ) を選択すると、Podman AI Labというのが見つかります。

Podman AI Lab の右側にある下向き矢印(↓)をクリックして、Podman AI Labをインストールします。Podman AI Lab のインストールが終わると、左側のメニューにPodman AI Labのアイコンが表示されます。

Graniteをインストール

次に、Graniteをインストールします。 Podman AI LabCatalogを選択すると、使用可能なモデルの一覧が表示されます。 今回は一番へのinstructlab/graniteを使用します。 右側にある下向き矢印(↓)をクリックしてダウンロードしましょう。

ダウンロードが終わると、instructlab/graniteのアイコンが黄緑になります。

それではinstructlab/graniteを起動しましょう。 右側のロケットのアイコンをクリックすると、モデルのサービスを作成する画面に移動します。

設定できる項目はコンテナのポートのみです。 Create service ボタンをクリックすると、モデルのサービスが作成されます。

ここまでで、Podman 上に モデルが構築できました。

最後に、実際に、モデルへ問い合わせしてみましょう。 モデルのサービスが作成されると、そのサービスへアクセスする方法が表示されます。 デフォルトではcURLを使ったアクセス方法が表示されています。 cURL のある環境から次のコマンドを実行してください。 レスポンスは JSON 形式のため、最後に jq コマンドを追加すると良いでしょう。

> curl --location 'http://localhost:35000/v1/chat/completions' --header 'Content-Type: application/json' --data '{
  "messages": [
    {
      "content": "東京の首都はどこですか?",
       role": "user"
    }
  ]
}' | jq

レスポンスは以下のようになります。結果の文は実行のたびに異なります。

{
  "id": "chatcmpl-c6572f33-52bc-41bb-a200-27cc7d88f73c",
  "object": "chat.completion",
  "created": 1718946977,
  "model": "/models/granite-7b-lab-Q4_K_M.gguf",
  "choices": [
    {
      "index": 0,
      "message": {
        "content": "東京は日本の首都です。また、世界の最大の都市です。",
        "role": "assistant"
      },
      "logprobs": null,
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 66,
    "completion_tokens": 34,
    "total_tokens": 100
  }
}

Quarkus からモデルへ接続

Podman AI Labは、cURLだけでなく、Quarkus からモデルへ接続する方法も提供します。 ドロップダウンリストをJavaQuarkus Langchain4Jに変更します。 これによって、モデルへ接続する為に必要な設定やコードが出力されます。

ここで変更するファイルは以下の 3 つです。

  • pom.xml
  • src/main/resources/application.properties
  • src/main/java/io/podman/desktop/quarkus/langchain4j/AiService.java

最初の 2 つはすでにあるものに追記します。 最後のファイルは新たに追加します。

これでQuarkus がモデルへ接続する準備はできました。

Quarkus アプリとの統合

最後に、Quarkusアプリからモデルへ接続してみましょう。 今回は、Qute を使用して画面を作成します。

pom.xml に Qute の依存関係を追加します。

pom.xml

        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-rest-qute</artifactId>
        </dependency>

画面を作成します。

src/resources/templates/ai.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Quarkus + Granite</title>
</head>
<body>
<h1>Quarkus + Granite Demo</h1>
<form action="/ai/query" method="get">
    <div>
        <label for="question">質問を入力してください</label><br/>
        <input type="text" style="width: 500px" name="question" id="question" value="{question ?: ''}" required/>
        <input type="submit" value="送信"/>
    </div>
</form>
<p>{answer ?: ''}</p>
</body>
</html>

最後に、画面とモデルの橋渡しをする部分を作成します。

src/main/java/ai/AiResource.java

package ai;

import io.podman.desktop.quarkus.langchain4j.AiService;
import io.quarkus.qute.Template;
import io.quarkus.qute.TemplateInstance;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;

@Path("/ai")
public class AiResource {

    @Inject
    Template ai;

    @Inject
    AiService aiService;

    @GET
    @Produces(MediaType.TEXT_HTML)
    public TemplateInstance index() {
        return ai.instance();
    }

    @GET
    @Path("/query")
    @Produces(MediaType.TEXT_HTML)
    public TemplateInstance get(@QueryParam("question") String question) {
        String answer = aiService.request(question);
        return ai.data("question", question).data("answer", answer);
    }
}

これで完成です。

きちんと動くか試してみましょう。 Quarkus アプリケーションを起動して、https://localhost:8080/ai にアクセスします。 テキストフィールドに、質問を入力して「送信」ボタンを押してみましょう。 返事が下に表示されれば成功です。

10秒以上かかるとタイムアウトします。 タイムアウトの時間を延ばすにはapplication.propertiesにquarkus.langchain4j.openai.timeout=30sのように追加してください。

* 各記事は著者の見解によるものでありその所属組織を代表する公式なものではありません。その内容については非公式見解を含みます。