はじめに
生成AIの分野では Python が先行している印象がありますが、Javaエコシステムにおいても AI アプリケーション開発を支援するライブラリーやフレームワークが充実してきています。
特に Quarkus には、LLMとの連携を容易にする Quarkus LangChain4j が用意されており、Javaらしい開発スタイルを維持したまま生成AI機能をアプリケーションへ組み込むことができます。
QuarkusのAI関連機能には、LangChain4j、AI Service、Tool Calling、RAG、MCPなど、さまざまな機能や用語があります。
今回は、これらの中でも基本となる AI Service、Prompt、Chat Model の役割を整理します。
また、オープンソースのQuarkusをベースにしたIBM Enterprise Build of Quarkusを使用し、REST APIからLLMを呼び出すところまでを確認します。
なお、本番運用向けには、IBMによるサポートと長期ライフサイクルが提供される製品版も用意されています。
この記事は、Quarkus や生成 AI 関連技術について学習した内容を、自分自身の理解を整理する目的も兼ねてまとめたものです。
今回は以下の機能は扱わず、後続の記事で取り上げる予定です。
- Tool Calling
- Chat Memory
- RAG
- MCP Server
- MCP Client
- Agentic Workflow
なお、Native Image については過去の記事で紹介しています。興味のある方は、こちらもご参照ください。
https://qiita.com/autonomura/items/6af3527658aaf3987abb
https://qiita.com/autonomura/items/79be8a124a39d1b176fe
今回作成するもの
AI Ticket Triage
問い合わせ内容をLLMで分析する「AI Ticket Triage」を作成します。
ユーザーが問い合わせ内容を入力すると、LLMが次の情報を返します。
| フィールド | 内容 |
|---|---|
summary |
問い合わせの要約 |
category |
カテゴリー |
priority |
優先度 |
recommendedAction |
推奨する初動対応 |
assignedTeam |
担当チーム |
例えば、以下のリクエストを送信すると、
POST /tickets/analyze
Content-Type: text/plain
パスワードを忘れてログインできません。
LLM が問い合わせ内容を分析し、業務で扱いやすい構造化データとして返します。
{
"summary": "パスワードを忘れたためログインできない。", "category": "APPLICATION",4 "priority": "LOW", "recommendedAction": "パスワードの再設定手順を案内する。", "assignedTeam": "APPLICATION"
}
処理の流れは次のとおりです。
ユーザー
↓ HTTP リクエスト
REST Resource (TicketResource)
↓ Java メソッド呼び出し
AI Service (TicketAnalyzer)
↓ Prompt を組み立てる
Chat Model
↓ API リクエスト
LLM Provider (OpenAI)
ソースコードはこちらで公開しています。
LangChain4jとMCPの全体像
LangChain4jとは
LangChain4jは、JavaアプリケーションからLLMを利用するためのライブラリーです。主に次の機能を提供します。
- Chat Model
- AI Service
- Prompt Template
- Chat Memory
- Tool Calling
- RAG
- Embedding Model
- Embedding Store
Quarkus LangChain4jは、LangChain4jをQuarkusへ統合するExtensionです。QuarkusのCDIや設定管理などを利用しながら、JavaインターフェースとアノテーションによってAI Serviceを宣言的に定義できます。
MCPとは
MCP(Model Context Protocol)は、AIアプリケーションと検索やファイル操作、業務APIなどの外部機能を共通の方法で接続するためのプロトコルです。
AIアプリケーション
↓ MCP Client
MCP Server
↓
外部機能
Quarkus LangChain4j の主要要素
AI Service
AI Service は、LLM を利用する処理を Java インターフェースとして定義する仕組みです。
例えば、夕食を提案する AI Service は次のように表現できます。
public interface FoodExpert {
List<String> recommendMeals(String mood);
}
通常の Java インターフェースに見えますが、Quarkus LangChain4j が実行時に実装を提供し、次の処理を行います。
- メソッド引数を Prompt へ埋め込む
- Chat Model を呼び出す
- LLM から回答を取得する
- 回答を Java の戻り値へ変換する
AI Service は、LLM とのやり取りを Java のサービス層として扱うための窓口です。
Chat Memory(会話履歴の保持)、Tool Calling(Javaメソッドなどの外部機能の呼び出し)、RAG(外部データを検索して回答に利用する仕組み)なども組み合わせられます。
Prompt
Prompt は、LLM へ渡す指示です。
Quarkus LangChain4j では、主に次のアノテーションと変数記法を使用して Prompt を定義します。
| 記述 | 用途 |
|---|---|
@SystemMessage |
AI の役割や全体的なルールを指定する |
@UserMessage |
ユーザーの要求を指定する |
{{変数名}} |
Java メソッドの引数を Prompt へ埋め込む |
@SystemMessage("""
あなたは夕食のメニューを提案するアシスタントです。
簡潔で実用的な提案をしてください。
""")
@UserMessage("""
気分に合った夕食のメニューを3つ提案してください。
現在の気分は「{{mood}}」です。
各メニューは料理名のみを返してください。
""")
List<String> recommendMeals(String mood);
Chat Model
Chat Model は、実際の LLM Provider へリクエストを送信し、回答を受け取るコンポーネントです。
今回はOpenAI用のquarkus-langchain4j-openai Extensionを使用します。
依存関係とapplication.propertiesを設定すると、OpenAIへ接続するChat ModelがQuarkusによって構成されます。
役割の違い
| 名称 | 役割 |
|---|---|
| AI Service | アプリケーションとしてどのような AI 機能を提供するか |
| Prompt | LLM へ何を指示するか |
| Chat Model | どのモデルへ接続するか |
検証環境
| 項目 | バージョン |
|---|---|
| OS | macOS 26.5.2 |
| Java | OpenJDK 25.0.1 (GraalVM CE) |
| Maven | 3.9.10 |
| Quarkus | IBM Enterprise Build of Quarkus 3.33.2 |
| Quarkus LangChain4j | 1.7.6.redhat-00001 |
| LLM Provider | OpenAI |
| 実行モード | JVM |
プロジェクトの作成
IBM Enterprise Build of Quarkusの Application Configurator にて、以下の依存関係を選択してプロジェクトを生成します。
quarkus-rest-jacksonquarkus-langchain4j-openai
生成された pom.xml の主要な依存関係は次のとおりです。
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkiverse.langchain4j</groupId>
<artifactId>quarkus-langchain4j-openai</artifactId>
</dependency>
API キーの設定
今回は OpenAI を使用します。
OpenAI Quickstart に従い API キーを取得し、環境変数に設定します。
export OPENAI_API_KEY="your_api_key_here"
Chat Model の設定
application.properties に以下を追記します。
quarkus.langchain4j.openai.api-key=${OPENAI_API_KEY}
quarkus.langchain4j.log-requests=true
quarkus.langchain4j.log-responses=true
${OPENAI_API_KEY} は先ほど設定した環境変数を参照します。
log-requests と log-responses は開発中のデバッグに便利なため有効化しています。
AI Service の実装
今回のアプリに登場するクラスは 3 つです。
| クラス | 役割 |
|---|---|
TicketAnalyzer |
AI Service インターフェース |
TicketAnalysis |
LLM の回答を受け取る Java Record |
TicketResource |
REST エンドポイント |
TicketAnalysis — 出力データ定義
package dev.autonomura.ticket;
public record TicketAnalysis(
String summary,
String category,
String priority,
String recommendedAction,
String assignedTeam) {
}
Java Recordで出力形式を定義します。
Quarkus LangChain4jは、LLMの回答をこのRecordへ変換します。
TicketAnalyzer — AI Service
package dev.autonomura.ticket;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import io.quarkiverse.langchain4j.RegisterAiService;
@RegisterAiService
public interface TicketAnalyzer {
@SystemMessage("""
あなたはIT運用チームのサポート担当者です。
問い合わせ内容を分析し、以下の項目を返してください。
- 問い合わせの要約
- カテゴリー
- 優先度
- 推奨される初動対応
- 担当チーム
優先度は以下から選択してください。
LOW, MEDIUM, HIGH, CRITICAL
カテゴリーは以下から選択してください。
APPLICATION, DATABASE, NETWORK, SECURITY, INFRASTRUCTURE, OTHER
問い合わせに記載されていない事実は推測しないでください。
回答は簡潔かつ実務的にしてください。
""")
@UserMessage("""
以下の問い合わせ内容を分析してください。
問い合わせ:
{{ticket}}
""")
TicketAnalysis analyze(String ticket);
}
各アノテーションの役割は次のとおりです。
@RegisterAiService
TicketAnalyzer を Quarkus の AI Service として登録します。
開発者が実装クラスを作成しなくても、Quarkus LangChain4j が実装を自動生成します。
@SystemMessage
LLM の役割と、回答全体に適用するルールを定義します。
今回は優先度(LOW / MEDIUM / HIGH / CRITICAL)とカテゴリー(APPLICATION / DATABASE / ...)の選択肢を明示することで、出力を安定させています。
@UserMessage
ユーザーからの依頼を Prompt として定義します。
{{ticket}}
メソッド引数の ticket を Prompt へ埋め込みます。
TicketAnalysis(戻り値)
LLM の回答を単純な文字列ではなく、Java Record として受け取ります。
Quarkus LangChain4j が JSON 解析と型変換を自動的に行います。
TicketResource — REST API
package dev.autonomura.ticket;
import jakarta.inject.Inject;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/tickets")
@Consumes(MediaType.TEXT_PLAIN)
@Produces(MediaType.APPLICATION_JSON)
public class TicketResource {
@Inject
TicketAnalyzer ticketAnalyzer;
@POST
@Path("/analyze")
public TicketAnalysis analyze(String ticket) {
return ticketAnalyzer.analyze(ticket);
}
}
TicketResource には、LLM への接続処理や Prompt を直接記述しません。
AI 関連処理を TicketAnalyzer に分離することで、REST 層と AI サービス層の役割が明確になります。
処理の流れを改めて整理すると次のとおりです。
- クライアントが
POST /tickets/analyzeを呼び出す -
TicketResourceがリクエストボディの文字列を受け取る - CDI で注入された
TicketAnalyzerを呼び出す - AI Service が Prompt を組み立てる
- Chat Model が LLM Provider へリクエストする
- AI Service が回答を
TicketAnalysisへ変換する - REST API が JSON として返す
アプリケーションの起動
mvn quarkus:dev
正常に起動したら、別のターミナルから呼び出します。
curl -s -X POST \
-H "Content-Type: text/plain" \
--data-binary "Webアプリケーションにログインできません。今朝から複数の利用者で同じ問題が発生しており、画面には「Database connection timeout」と表示されています。" \
http://localhost:8080/tickets/analyze | jq .
応答例(LLM の回答のため、実行ごとに表現が変わる場合があります):
{
"summary": "複数の利用者がWebアプリケーションにログインできず、Database connection timeoutのエラーメッセージが表示されている。",
"category": "DATABASE",
"priority": "HIGH",
"recommendedAction": "データベース接続の状態を確認し、必要に応じて再起動または障害調査を行う。",
"assignedTeam": "DATABASE"
}
実装の振り返り
TicketResource
↓ ticket(問い合わせ文字列)を渡す
TicketAnalyzer
↓ Prompt を生成する
Chat Model
↓ API を呼び出す
LLM Provider
↓ 回答を返す
TicketAnalysis(Java Record)
各クラスの役割
| クラス | 役割 |
|---|---|
TicketResource |
HTTP リクエストの受付とレスポンスの返却 |
TicketAnalyzer |
AI 機能と Prompt の定義 |
TicketAnalysis |
LLM 出力を受け取るデータモデル |
| Chat Model | LLM Provider との通信 |
| Quarkus LangChain4j | AI Service の生成、Prompt の処理、出力の変換 |
Tool CallingとMCPへの発展
今回のアプリでは、AI ServiceからChat Modelを通じてLLMを呼び出しました。
Tool CallingやMCPは使用していません。
Tool Callingを利用すると、LLMの判断に応じてJavaメソッドなどの外部機能を呼び出せます。
また、MCPを利用すると、別のアプリケーションが提供するToolやデータソースへ共通の方法で接続できます。
まとめ
今回は、Quarkus LangChain4jを使用し、Javaインターフェースとして定義したAI ServiceからLLMを呼び出しました。
-
@RegisterAiServiceを使用すると、LLMとの通信処理を直接実装せず、通常のJavaサービスに近い形でAI機能を定義できる -
@SystemMessageと@UserMessageを使用すると、AIの役割とユーザーからの要求を分けて管理できる - Java Recordを戻り値に指定すると、LLMの回答を構造化されたJavaオブジェクトとして受け取れる
今回の実装では、LLMによる問い合わせ内容の分析と構造化までを確認しました。
外部のJavaメソッドや業務システムとの連携は行っていません。
次回はTool Callingを追加し、LLMがユーザーの要求に応じてJavaメソッドを選択・実行する仕組みを確認します。