はじめに
生成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 については過去の記事で紹介しています。興味のある方は、こちらもご参照ください。
今回作成するもの
本記事では、問い合わせ内容を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)
このアプリケーションを実装しながら、Quarkus LangChain4jにおけるAI Service、Prompt、Chat Modelの役割を確認します。
ソースコードは以下で公開しています。
LangChain4jとMCP
QuarkusのAI関連機能を調べると、LangChain4jとMCPという2つの名前が登場します。どちらもAIアプリケーション開発に関係しますが、両者は競合する技術でも、どちらか一方を選択するものでもありません。
LangChain4jは、JavaアプリケーションへLLMを利用した機能を組み込むためのライブラリーです。一方、MCPは、AIアプリケーションと外部のToolやデータソースを共通の方法で接続するためのプロトコルです。
今回作成する「AI Ticket Triage」では、まずLangChain4jを使用して、JavaアプリケーションからLLMを呼び出す基本構成を確認します。MCPは使用しませんが、今後Tool Callingや外部システム連携へ発展させる際の位置付けを明確にするため、ここで両者の役割を整理します。
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を宣言的に定義できます。
今回の記事では、Quarkus LangChain4jが提供する機能のうち、次の3つを中心に扱います。
- AI Service:アプリケーションが提供するAI機能をJavaインターフェースとして定義する
- Prompt:LLMへ渡す役割や指示を定義する
- Chat Model:OpenAIなどのLLM Providerとの通信を担当する
Javaアプリケーション
↓ AI Service
Prompt
↓ Chat Model
LLM Provider
MCPとは
MCP(Model Context Protocol)は、AIアプリケーションと検索やファイル操作、業務APIなどの外部機能を共通の方法で接続するためのプロトコルです。
MCPでは、外部機能を提供する側をMCP Server、それを利用するAIアプリケーション側をMCP Clientと呼びます。
AIアプリケーション
↓ MCP Client
MCP Server
↓
外部機能
LangChain4jだけでも、JavaメソッドをToolとしてLLMから呼び出すことができます。一方、MCPを利用すると、Toolを別のアプリケーションとして公開し、複数のAIアプリケーションから共通の方法で利用できるようになります。
このように、LangChain4jはJavaアプリケーションにAI機能を組み込むためのライブラリーであり、MCPはAIアプリケーションと外部機能の接続方法を標準化するプロトコルです。
両者は目的が異なり、必要に応じて組み合わせて使用できます。
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のAI Serviceを使用し、Javaインターフェースを定義するだけでLLMを呼び出すアプリケーションを実装しました。
Quarkus LangChain4jを利用することで、LLMとの通信やレスポンスの変換といった処理を個別に実装することなく、既存のJavaアプリケーションに近い開発スタイルでAI機能を組み込めます。
今回確認した主なポイントは、以下のとおりです。
-
@RegisterAiServiceにより、LLMとの通信処理を抽象化し、AI機能をJavaサービスとして宣言的に定義できる -
@SystemMessageと@UserMessageにより、AIの役割とユーザーからの要求をJavaコード上で分けて管理できる - Java Recordを戻り値に指定することで、LLMの回答を型付けされたJavaオブジェクトとして受け取れる
今回は、LLMによる問い合わせ内容の分析と構造化までを確認しました。次回はTool Callingを追加し、LLMがユーザーの要求に応じてJavaメソッドを選択・実行する仕組みを確認します。
最後までご覧いただき、ありがとうございました。
本記事が、Quarkusアプリケーション開発の参考になれば幸いです。