【Java開発者向け】Spring AIとは?GitHubで始めよう

はい、承知いたしました。Java開発者向けにSpring AIの詳細な説明を含む約5000語の記事を作成します。GitHubでの始め方に重点を置きつつ、主要な機能、概念、応用例を網羅します。


【Java開発者向け】Spring AIとは?GitHubで始めよう

はじめに:AIとJava開発の交差点

近年、生成AI、特に大規模言語モデル(LLM)の進化は目覚ましく、様々なアプリケーションへの応用が進んでいます。チャットボット、コンテンツ生成、要約、翻訳、コード生成など、その可能性は日々広がっています。

Java開発者として、このエキサイティングなAIの波に乗り遅れたくない、自分のアプリケーションにAIの能力を組み込みたい、そうお考えの方も多いのではないでしょうか?しかし、AIモデルを扱うには、専門的なライブラリの使い方を学んだり、API連携のための複雑なコードを書いたり、モデルのプロバイダーごとに異なるインターフェースに対応したりと、いくつかのハードルが存在します。

ここで登場するのが Spring AI です。Spring AIは、Springエコシステムの一部として開発されており、Java開発者が慣れ親しんだSpringのスタイルで、AIモデルとの連携を簡単に行えるように設計されています。主要なAIモデルプロバイダー(OpenAI, Azure OpenAI, Google AI, Huging Faceなど)や、ベクトルデータベース(Vector Database)との統合を抽象化し、一貫性のあるシンプルなプログラミングモデルを提供します。

この記事では、Java開発者向けにSpring AIの基本から応用までを詳細に解説します。特に、GitHubでプロジェクトを始め、Spring AIを導入する手順、主要な機能(テキスト生成、埋め込み、ベクトルデータベース連携、関数呼び出しなど)、そして実践的なコード例を中心に説明します。読み終える頃には、Spring AIを使ってあなたのJavaアプリケーションにAIの力を解き放つ準備ができているはずです。

Spring AIとは? なぜ今、Spring AIなのか?

Spring AIは、Javaアプリケーションに人工知能機能を簡単に組み込むためのオープンソースプロジェクトです。Springフレームワークの哲学に基づき、以下のような特徴を持っています。

  1. 抽象化と統一インターフェース: 様々なAIモデルプロバイダー(OpenAI, Azure OpenAI, Google AI, Hugging Faceなど)に対して、共通のAPIを提供します。これにより、特定のプロバイダーにロックインされることなく、設定を変更するだけで容易に切り替えたり、複数のプロバイダーを組み合わせたりすることが可能です。
  2. Springエコシステムとの親和性: Spring Bootの自動設定機能や依存性注入(Dependency Injection)を最大限に活用しています。SpringアプリケーションにAI機能を組み込むことが、まるで他のSpringコンポーネントを追加するのと同じくらい自然に行えます。
  3. シンプルなプログラミングモデル: テキスト生成、埋め込み(Embeddings)、ベクトルデータベースとの連携、関数呼び出し(Function Calling)といった主要なAI機能を、直感的で理解しやすいAPIを通じて提供します。
  4. モジュール性: 必要に応じて機能を選択して利用できます。テキスト生成だけ、埋め込みだけ、といった使い方が可能です。
  5. コミュニティ: Springエコシステムは巨大で活発なコミュニティを持っています。問題が発生した際にサポートを得やすく、継続的な開発と改善が期待できます。

これらの特徴により、Spring AIはJava開発者がAI開発に参入する際の障壁を大きく下げてくれます。AIモデルの複雑なAPIを直接扱う必要がなくなり、ビジネスロジックに集中できるようになります。

Spring AIのコアコンセプト

Spring AIを理解するためには、いくつかの主要な概念を把握しておく必要があります。

  1. AI Client: AIモデルと通信するためのインターフェースです。例えば、ChatClientはテキスト生成のためのインターフェース、EmbeddingClientはテキストを数値ベクトルに変換するためのインターフェースです。プロバイダーごとに実装が提供されますが、開発者はこの抽象インターフェースを使ってコードを書きます。
  2. Model: 実際にタスクを実行するAIモデルそのものです。例えば、OpenAIのGPT-4o、GoogleのGemini、Hugging FaceのMistralなどがあります。Spring AIはこれらのモデルとの連携をサポートします。
  3. Prompt: AIモデルへの入力として与えられるテキストや情報のことです。ユーザーからの直接の質問だけでなく、システムの役割設定(System Message)、会話履歴、参照情報(RAGの場合)などが含まれます。
  4. Response: AIモデルからの出力です。生成されたテキスト、関数の呼び出し指示、その他の構造化された情報などが含まれます。
  5. Embedding: テキストやその他のデータを低次元の数値ベクトルに変換したものです。意味的に近いテキストはベクトル空間上で近くに配置されるため、類似性検索などに利用されます。
  6. Vector Database (Vector Store): 埋め込みベクトルを効率的に保存し、高速な類似性検索(Nearest Neighbor Search)を実行することに特化したデータベースです。RAG (Retrieval Augmented Generation) アプリケーションにおいて、外部知識を取り込むために不可欠な要素です。
  7. Function Calling: LLMが外部のツールやサービスを呼び出す機能です。LLMが「この情報が必要だ」「この操作を実行したい」と判断した場合に、定義された関数を呼び出すための指示を生成します。アプリケーション側はこの指示を受け取り、実際の関数を実行して結果をLLMにフィードバックします。
  8. RAG (Retrieval Augmented Generation): 生成(Generation)を行う前に、外部ソース(ドキュメント、データベースなど)から関連情報を検索(Retrieval)し、その情報をプロンプトに含めてLLMに与える手法です。これにより、LLMは学習データにない最新の情報や特定のドメイン知識に基づいて応答を生成できるようになります。Spring AIは、埋め込みとベクトルデータベースの統合を通じてRAGの実装を支援します。

これらの概念がSpring AIの各コンポーネントにどのようにマッピングされ、連携するのかを理解することが、効果的なSpring AIアプリケーション開発の鍵となります。

GitHubで始めよう:Spring AIプロジェクトのセットアップ

さあ、実際にSpring AIを使った開発を始めましょう。最も一般的な方法は、Spring BootプロジェクトにSpring AIの依存関係を追加することです。

前提条件

  • Java 17以上(Spring AIの最新バージョンはJava 17を要求します)
  • Apache Maven または Gradle
  • お好みのIDE (IntelliJ IDEA, Eclipse, VS Codeなど)
  • AIプロバイダーのAPIキー (例: OpenAI APIキー)

ステップ 1: Spring Bootプロジェクトの作成

Spring Initializr (https://start.spring.io/) を使うのが最も簡単です。

  • Project: Maven Project または Gradle Project
  • Language: Java
  • Spring Boot: 最新の安定板を選択 (例: 3.2.x)
  • Project Metadata: グループ、アーティファクト名などを適宜設定
  • Dependencies:
    • Spring Web: REST APIを作成する場合に便利です。
    • Spring Boot DevTools: 開発時のホットリロードに便利です。
    • Spring AI (Snapshot): 重要な注意点: Spring AIはまだマイルストーン版またはスナップショット版として開発が進んでいます。Spring Initializrにはデフォルトで表示されないため、プロジェクト生成後に手動で依存関係を追加する必要があります。
    • 使用するAIプロバイダーの依存関係 (例: spring-ai-openai-spring-boot-starter) も後で手動で追加します。

プロジェクト情報を入力したら、「Generate」ボタンをクリックしてプロジェクトアーカイブをダウンロードし、解凍してIDEで開きます。

ステップ 2: Spring AI依存関係の追加

ダウンロードしたプロジェクトの pom.xml (Mavenの場合) または build.gradle (Gradleの場合) を編集します。

Spring AIはまだ活発に開発されているため、多くの場合、Spring MilestoneまたはSnapshotリポジトリから取得する必要があります。

Maven (pom.xml) の場合:

まず、Spring AIのBom (Bill of Materials) と、使用するプロバイダー(例: OpenAI)のスターター依存関係を追加します。また、Milestone/Snapshotリポジトリも追加します。

“`xml


4.0.0 org.springframework.boot
spring-boot-starter-parent
3.2.5
com.example
spring-ai-demo
0.0.1-SNAPSHOT
spring-ai-demo
Demo project for Spring AI

<properties>
    <java.version>17</java.version>
    <!-- Spring AIのバージョンを指定 -->
    <spring-ai.version>0.8.0</spring-ai.version> <!-- 最新のMilestone/Snapshotバージョンを確認 -->
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <!-- Spring AI BOM -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-bom</artifactId>
        <version>${spring-ai.version}</version>
        <type>pom</type>
        <scope>import</scope>
    </dependency>

    <!-- 使用するプロバイダーのスターター依存関係 -->
    <!-- 例: OpenAI -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
        <!-- バージョンはBOMで管理されるため指定不要 -->
    </dependency>

    <!-- 他のプロバイダーを使用する場合はコメントアウトを解除または追加 -->
    <!-- 例: Azure OpenAI -->
    <!--
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-azure-openai-spring-boot-starter</artifactId>
    </dependency>
    -->
    <!-- 例: Google AI -->
    <!--
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-google-ai-spring-boot-starter</artifactId>
    </dependency>
    -->
    <!-- 例: Hugging Face -->
    <!--
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-huggingface-spring-boot-starter</artifactId>
    </dependency>
    -->
    <!-- 例: Ollama (ローカルモデル) -->
    <!--
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
    </dependency>
    -->

</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

<!-- Spring Milestone/Snapshot リポジトリの追加 -->
<repositories>
    <repository>
        <id>spring-milestones</id>
        <name>Spring Milestones</name>
        <url>https://repo.spring.io/milestone</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository>
    <repository>
        <id>spring-snapshots</id>
        <name>Spring Snapshots</name>
        <url>https://repo.spring.io/snapshot</url>
        <releases>
            <enabled>false</enabled>
        </releases>
    </repository>
</repositories>
<pluginRepositories>
    <pluginRepository>
        <id>spring-milestones</id>
        <name>Spring Milestones</name>
        <url>https://repo.spring.io/milestone</url>
        <snapshots>
            <enabled>false</semantics>
        </snapshots>
    </pluginRepository>
    <pluginRepository>
        <id>spring-snapshots</id>
        <name>Spring Snapshots</name>
        <url>https://repo.spring.io/snapshot</url>
        <releases>
            <enabled>false</releases>
        </releases>
    </pluginRepository>
</pluginRepositories>

“`

Gradle (build.gradle) の場合:

“`gradle
plugins {
id ‘java’
id ‘org.springframework.boot’ version ‘3.2.5’ // または使用するSpring Bootバージョン
id ‘io.spring.dependency-management’ version ‘1.1.4’
}

group = ‘com.example’
version = ‘0.0.1-SNAPSHOT’

java {
sourceCompatibility = ’17’
}

repositories {
mavenCentral()
// Spring Milestone/Snapshot リポジトリの追加
maven { url ‘https://repo.spring.io/milestone’ }
maven { url ‘https://repo.spring.io/snapshot’ }
}

// Spring AIのバージョンを指定
ext[‘spring-ai.version’] = ‘0.8.0’ // 最新のMilestone/Snapshotバージョンを確認

dependencies {
implementation ‘org.springframework.boot:spring-boot-starter-web’
developmentOnly ‘org.springframework.boot:spring-boot-devtools’
testImplementation ‘org.springframework.boot:spring-boot-starter-test’

// Spring AI BOM (dependency-management プラグインが扱います)
implementation platform("org.springframework.ai:spring-ai-bom:${spring-ai.version}")

// 使用するプロバイダーのスターター依存関係
// 例: OpenAI
implementation 'org.springframework.ai:spring-ai-openai-spring-boot-starter'

// 他のプロバイダーを使用する場合はコメントアウトを解除または追加
// 例: Azure OpenAI
// implementation 'org.springframework.ai:spring-ai-azure-openai-spring-boot-starter'
// 例: Google AI
// implementation 'org.springframework.ai:spring-ai-google-ai-spring-boot-starter'
// 例: Hugging Face
// implementation 'org.springframework.ai:spring-ai-huggingface-spring-boot-starter'
// 例: Ollama (ローカルモデル)
// implementation 'org.springframework.ai:spring-ai-ollama-spring-boot-starter'

}

tasks.named(‘test’) {
useJUnitPlatform()
}
“`

spring-ai.version は、Spring AIのGitHubリポジトリやドキュメントで最新のバージョンを確認してください。通常、プロジェクトのREADMEやリリースページに記載されています。

依存関係を追加したら、IDEでMaven/Gradleプロジェクトをリロードします。

ステップ 3: APIキーの設定

AIプロバイダーと通信するためには、通常、APIキーが必要です。これは application.properties または application.yml ファイルに設定します。セキュリティ上の理由から、本番環境では環境変数や外部設定サービスを利用することを推奨します。

src/main/resources/application.properties に以下の設定を追加します(例: OpenAIの場合)。

“`properties

OpenAI API Key

spring.ai.openai.api-key=${OPENAI_API_KEY}

(Optional) OpenAI API Base URL if you use a proxy or self-hosted service

spring.ai.openai.base-url=https://api.openai.com/v1

(Optional) Default chat model

spring.ai.openai.chat.options.model=gpt-4o

spring.ai.openai.chat.options.temperature=0.7

(Optional) Default embedding model

spring.ai.openai.embedding.options.model=text-embedding-ada-002

“`

${OPENAI_API_KEY} の部分は、環境変数 OPENAI_API_KEY からAPIキーを読み込む設定です。ローカル開発の場合、OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxx のように直接記述することも可能ですが、APIキーの漏洩には十分注意してください。

他のプロバイダーを使用する場合も同様に設定します。例えば、Azure OpenAIの場合は以下のようになります。

properties
spring.ai.azure.openai.api-key=${AZURE_OPENAI_KEY}
spring.ai.azure.openai.endpoint=${AZURE_OPENAI_ENDPOINT}
spring.ai.azure.openai.deployment-id=${AZURE_OPENAI_DEPLOYMENT_ID} # デプロイ名 (モデル名とは異なる場合がある)

Google AIの場合は以下のようになります。

“`properties
spring.ai.google.ai.api-key=${GOOGLE_AI_KEY}

spring.ai.google.ai.chat.options.model=gemini-pro

“`

ステップ 4: シンプルなテキスト生成アプリケーションの作成

依存関係と設定が完了したら、Spring AIの ChatClient を使ってテキスト生成を試す簡単なRESTコントローラーを作成します。

“`java
package com.example.springaidemo.controller;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ChatController {

private final ChatClient chatClient;

// Spring BootのDIによりChatClientが自動的に注入される
public ChatController(ChatClient chatClient) {
    this.chatClient = chatClient;
}

@GetMapping("/ai/generate")
public String generate(@RequestParam(value = "message", defaultValue = "Tell me a joke about programming.") String message) {
    // ChatClientを使用してテキストを生成
    String response = chatClient.prompt()
                                .user(message) // ユーザーからのメッセージを設定
                                .call()        // AIモデルを呼び出し
                                .content();   // 生成されたテキストコンテンツを取得
    return response;
}

}
“`

このコントローラーは /ai/generate エンドポイントを提供し、message パラメータで受け取ったテキストをAIモデルに送信し、その応答を返します。

ステップ 5: アプリケーションの実行

Spring Bootアプリケーションのメインクラスを実行します。

“`java
package com.example.springaidemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringAiDemoApplication {

public static void main(String[] args) {
    SpringApplication.run(SpringAiDemoApplication.class, args);
}

}
“`

アプリケーションが起動したら、WebブラウザやcURLなどのツールを使ってエンドポイントにアクセスしてみます。

bash
curl "http://localhost:8080/ai/generate?message=こんにちは、AIさん!自己紹介してください。"

APIキーと設定が正しければ、AIモデルからの応答が表示されるはずです。これで、Spring AIを使ったAI連携の第一歩を踏み出しました。

Spring AIの主要機能詳細

1. テキスト生成 (Chat Client)

ChatClient は、チャット形式でのテキスト生成を行うための主要なインターフェースです。単なる質問応答だけでなく、複雑なプロンプト構成や会話履歴の管理も可能です。

基本的な使い方:

上記の例のように、chatClient.prompt().user(message).call().content() の形式で簡単にテキストを生成できます。

プロンプトの構成:

より高度なプロンプトを作成するために、Spring AIは Prompt クラスと Message クラスを提供します。Message には SystemMessage, UserMessage, AssistantMessage などの種類があり、会話の役割を明示的に指定できます。

“`java
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Map;

@RestController
public class AdvancedChatController {

private final ChatClient chatClient;

public AdvancedChatController(ChatClient chatClient) {
    this.chatClient = chatClient;
}

@GetMapping("/ai/generate/advanced")
public String generateAdvanced(@RequestParam(value = "topic", defaultValue = "Spring Framework") String topic) {
    // システムメッセージとユーザーメッセージを含むプロンプトを作成
    var systemMessage = new SystemMessage("You are a helpful assistant that provides concise and informative answers.");
    var userMessage = new UserMessage("Tell me 3 key benefits of using " + topic + ".");

    var response = chatClient.prompt()
                             .messages(systemMessage, userMessage)
                             .call()
                             .content(); // または .chatResponse() でより詳細な応答を取得

    return response;
}

@GetMapping("/ai/generate/templated")
public String generateTemplated(@RequestParam(value = "subject", defaultValue = "Java") String subject) {
    // PromptTemplate を使用したプロンプト
    var response = chatClient.prompt()
                             .user("""
                                   Tell me about the history of {subject}.
                                   Keep the answer to under 100 words.
                                   """)
                             .variables(Map.of("subject", subject))
                             .call()
                             .content();
    return response;
}

}
“`

  • SystemMessage: AIモデルに特定の役割や指示を与えるために使用します。「あなたはプログラミングに関するジョークを言う専門家です。」のように設定できます。
  • UserMessage: ユーザーからの入力や質問です。
  • AssistantMessage: 会話履歴において、以前のAIモデルの応答を表します。チャットボットのような状態を持つアプリケーションで重要になります。
  • PromptTemplate: プレースホルダー(例: {subject})を含むテキストからプロンプトを生成するための便利なクラスです。variables() メソッドでプレースホルダーに値をバインドします。

モデルオプションの設定:

温度 (temperature)、最大トークン数 (maxTokens)、トップP (topP) などのモデル固有のパラメータを設定することで、応答の創造性や長さを調整できます。

“`java
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatOptions;
import org.springframework.ai.openai.OpenAiChatOptions; // プロバイダー固有のオプションクラス

@RestController
public class OptionsChatController {

private final ChatClient chatClient;

public OptionsChatController(ChatClient chatClient) {
    this.chatClient = chatClient;
}

@GetMapping("/ai/generate/creative")
public String generateCreative(@RequestParam(value = "prompt", defaultValue = "Write a short poem about a flying cat.") String prompt) {

    // OpenAI固有のオプションを設定 (他のプロバイダーも同様のクラスを持つ)
    OpenAiChatOptions options = OpenAiChatOptions.builder()
                                                    .withTemperature(0.9) // 高いほど創造的
                                                    .withTopP(0.9)
                                                    .withModel("gpt-4o") // 使用するモデルを指定
                                                    .build();

    String response = chatClient.prompt()
                                .user(prompt)
                                .options(options) // オプションを設定
                                .call()
                                .content();
    return response;
}

}
“`

ChatOptions はインターフェースであり、各プロバイダーが固有の実装を提供します (OpenAiChatOptions, AzureOpenAiChatOptions, GoogleAiChatOptions など)。

ストリーミング応答:

長い応答やリアルタイム性を重視する場合、ストリーミング機能が便利です。応答が単語またはトークンごとに順次送られてきます。Spring AIは Flux を使用してストリーミングをサポートします。

“`java
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

@RestController
public class StreamChatController {

private final ChatClient chatClient;

public StreamChatController(ChatClient chatClient) {
    this.chatClient = chatClient;
}

@GetMapping(value = "/ai/generate/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> generateStream(@RequestParam(value = "message", defaultValue = "Tell me a long story about a brave knight.") String message) {
    // ストリーミングで応答を取得
    return chatClient.prompt()
                     .user(message)
                     .stream() // stream() メソッドを呼び出す
                     .content(); // Flux<String> を返す
}

}
“`

stream() メソッドは ChatResponse のストリーム (Flux<ChatResponse>) を返し、.content() はその中からテキストコンテンツ (Flux<String>) を抽出します。WebFluxを使用している場合は、produces = MediaType.TEXT_EVENT_STREAM_VALUE を指定することで、サーバーセントイベント(SSE)としてクライアントにストリーミングできます。

2. 埋め込み (Embeddings) とベクトルデータベース連携

埋め込みは、テキストの意味を数値ベクトルで表現する技術です。これにより、テキスト間の類似性を数値的に計算したり、ベクトルデータベースで効率的に検索したりできるようになります。これは、RAGアプリケーションの中核を成す要素です。

埋め込みの生成:

EmbeddingClient を使用して、テキストのリストを埋め込みベクトルに変換できます。

“`java
import org.springframework.ai.embedding.EmbeddingClient;
import org.springframework.ai.embedding.EmbeddingResponse;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Map;

@RestController
public class EmbeddingController {

private final EmbeddingClient embeddingClient;

public EmbeddingController(EmbeddingClient embeddingClient) {
    this.embeddingClient = embeddingClient;
}

@GetMapping("/ai/embed")
public Map<String, List<Double>> embed(@RequestParam(value = "message", defaultValue = "Hello World") String message) {
    // テキストを埋め込みベクトルに変換
    EmbeddingResponse embeddingResponse = this.embeddingClient.embedForResponse(List.of(message));

    // 応答から埋め込みベクトルを取得
    List<Double> embedding = embeddingResponse.getOutput().get(0).getEmbedding();

    return Map.of(message, embedding);
}

@GetMapping("/ai/embed-list")
public Map<String, List<List<Double>>> embedList(@RequestParam(value = "messages", defaultValue = "Hello World,Spring AI") List<String> messages) {
     // 複数のテキストを埋め込みベクトルに変換
    EmbeddingResponse embeddingResponse = this.embeddingClient.embedForResponse(messages);

    // 応答から各埋め込みベクトルを取得
    List<List<Double>> embeddings = embeddingResponse.getOutput().stream()
                                    .map(e -> e.getEmbedding())
                                    .toList();

    return Map.of("embeddings", embeddings);
}

}
“`

EmbeddingClient もプロバイダーごとに実装が提供されます(OpenAiEmbeddingClient, AzureOpenAiEmbeddingClient など)。設定は application.properties で行います(例: spring.ai.openai.embedding.options.model=text-embedding-ada-002)。

ベクトルデータベースとの連携 (VectorStore):

Spring AIは VectorStore インターフェースを提供し、様々なベクトルデータベースや検索インデックスとの連携を可能にします。これにより、埋め込みベクトルを保存し、クエリベクトルに対する類似ドキュメントを検索できます。

主要なサポートされている VectorStore 実装例:

  • Chroma
  • Pinecone
  • Weaviate
  • Redis
  • PostgreSQL + pgvector
  • Milvus
  • Qdrant
  • SimpleVectorStore (インメモリ、テスト/デモ用)
  • Elasticsearch (ベクトル検索機能を利用)

ベクトルデータベースを使用するには、対応するSpring AIスターター依存関係を追加し、設定を行います。例として、インメモリの SimpleVectorStore を使用した簡単な例を示します。

“`java
package com.example.springaidemo.rag;

import org.springframework.ai.document.Document;
import org.springframework.ai.embedding.EmbeddingClient;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.SimpleVectorStore;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Map;

@Configuration // VectorStore Bean を定義するために Configuration クラスとしてマーク
@RestController
public class VectorStoreController {

private final VectorStore vectorStore;

// EmbeddingClient は自動注入される
public VectorStoreController(VectorStore vectorStore) {
    this.vectorStore = vectorStore;
}

// SimpleVectorStore の Bean 定義
// 本番環境では実際の Vector Database の Bean を定義する
@Bean
public VectorStore simpleVectorStore(EmbeddingClient embeddingClient) {
    return new SimpleVectorStore(embeddingClient);
}

@PostMapping("/ai/vector/add")
public String addDocument(@RequestBody String text) {
    Document document = new Document(text);
    vectorStore.add(List.of(document)); // ドキュメント(テキスト)をベクトル化してストアに追加
    return "Document added";
}

@GetMapping("/ai/vector/search")
public List<Document> search(@RequestParam("query") String query) {
    // クエリテキストをベクトル化し、ストア内で類似ドキュメントを検索
    // similarityThreshold や topK などのオプションを設定可能
    List<Document> results = vectorStore.similaritySearch(query);
    return results;
}

@GetMapping("/ai/vector/search-advanced")
public List<Document> searchAdvanced(@RequestParam("query") String query,
                                     @RequestParam(value = "topK", defaultValue = "3") int topK,
                                     @RequestParam(value = "threshold", defaultValue = "0.5") double threshold) {
    // より詳細な検索オプションを指定
    SearchRequest searchRequest = SearchRequest.query(query)
                                                .withTopK(topK)
                                                .withSimilarityThreshold(threshold);

    List<Document> results = vectorStore.similaritySearch(searchRequest);
    return results;
}

}
“`

この例では、SimpleVectorStore@Bean として定義しています。Spring Bootは、EmbeddingClient を自動的に注入して VectorStore Bean を作成します。POST /ai/vector/add でテキストを送信すると、そのテキストが埋め込みベクトルに変換されてストアに保存されます。GET /ai/vector/search でクエリを送信すると、類似のドキュメントが検索されて返されます。

3. RAG (Retrieval Augmented Generation)

埋め込みとベクトルデータベースを組み合わせることで、RAGアプリケーションを構築できます。これは、LLMが学習データにはない外部知識に基づいて応答を生成するために非常に強力な手法です。

RAGの基本的な流れ:

  1. 文書のロード: 外部ソース(ファイル、データベースなど)からドキュメントを読み込みます。Spring AIは様々な DocumentReader を提供予定です。
  2. チャンキング: 長いドキュメントを、AIモデルのコンテキストウィンドウや埋め込みモデルの入力サイズに収まる小さな塊(チャンク)に分割します。Spring AIは TextSplitter を提供します。
  3. 埋め込みと保存: 各チャンクを EmbeddingClient で埋め込みベクトルに変換し、VectorStore に保存します。
  4. クエリの埋め込み: ユーザーからのクエリを EmbeddingClient で埋め込みベクトルに変換します。
  5. 類似性検索: クエリベクトルを使って VectorStore から最も類似性の高いドキュメントチャンクを検索します。
  6. プロンプトの構成: 検索で得られた関連ドキュメントチャンクを、ユーザーのクエリとともにプロンプトに含めます。
  7. 生成: 構成されたプロンプトを ChatClient に与え、応答を生成させます。

Spring AIは、これらのステップを支援するためのコンポーネントを提供します。SimpleVectorStore を使った簡易的なRAGの例を以下に示します。

“`java
package com.example.springaidemo.rag;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@RestController
public class RagController {

private final ChatClient chatClient;
private final VectorStore vectorStore; // SimpleVectorStoreBeanが注入される

public RagController(ChatClient chatClient, VectorStore vectorStore) {
    this.chatClient = chatClient;
    this.vectorStore = vectorStore;
}

@GetMapping("/ai/rag")
public String generate(@RequestParam(value = "message", defaultValue = "Tell me about the documents.") String message) {

    // 1. ユーザーからのクエリに基づいて関連ドキュメントを検索
    List<Document> similarDocuments = vectorStore.similaritySearch(message);

    // 2. 検索で得られたドキュメントの内容を抽出してプロンプトに含める
    String documentContent = similarDocuments.stream()
            .map(Document::getContent)
            .collect(Collectors.joining("\n\n")); // ドキュメント間を改行で区切る

    // 3. RAG用のプロンプトテンプレートを定義
    PromptTemplate promptTemplate = new PromptTemplate("""
        Based on the following documents, answer the user's question.

        Documents:
        {documents}

        User Question:
        {message}
        """);

    // 4. プロンプトを構成し、AIモデルに送信
    promptTemplate.add("documents", documentContent);
    promptTemplate.add("message", message);

    String response = chatClient.prompt(promptTemplate.create())
                                .call()
                                .content();

    return response;
}

}
“`

この例では、VectorStoreController で追加されたドキュメントを前提としています。/ai/rag エンドポイントにクエリを送信すると、まず vectorStore で類似ドキュメントが検索され、その内容がプロンプトに追加されてからLLMに送信されます。これにより、LLMは提供されたドキュメントに基づいて応答を生成します。

4. 関数呼び出し (Function Calling)

関数呼び出しは、LLMが外部ツール(APIなど)と連携するための強力な機能です。LLMがユーザーの指示を理解し、外部の情報取得や操作が必要だと判断した場合に、呼び出すべき関数名と引数を構造化された形式で出力します。アプリケーションはその出力を受け取り、実際の関数を実行し、結果をLLMに返すことで、LLMが最終的な応答を生成できるようにします。

Spring AIでは、Springの @Bean@Service として定義されたメソッドをLLMが呼び出せるように設定できます。

ステップ 1: 呼び出し可能な関数を定義する

Spring Beanとして、関数として公開したいメソッドを持つクラスを作成します。メソッドはLLMからの引数を受け取り、結果を返します。

“`java
package com.example.springaidemo.function;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.function.Function;

@Configuration // Bean定義のためにConfigurationクラスとしてマーク
public class FunctionConfig {

// 例: 天気情報を取得する関数
@Bean
public Function<WeatherService.Request, WeatherService.Response> weatherService() {
    return new WeatherService();
}

}
“`

“`java
package com.example.springaidemo.function;

import java.util.function.Function;

// 関数として公開するクラス
public class WeatherService implements Function {

// LLMからの入力パラメータを表すレコード(またはPOJO)
public record Request(String location, String unit) {
    // コンストラクタやゲッターはLombokまたはIDE機能で生成可能
}

// 関数からの出力を表すレコード(またはPOJO)
public record Response(String weather, String location, String unit) {
    // コンストラクタやゲッターはLombokまたはIDE機能で生成可能
}

@Override
public Response apply(Request request) {
    // ここで実際の天気情報を取得する処理を実装
    // 例: 外部APIを呼び出す、DBを検索するなど
    System.out.println("Calling WeatherService for location: " + request.location() + ", unit: " + request.unit());

    // ダミーの応答を返す
    String weather = "晴れ時々曇り";
    if (request.location().contains("東京")) {
        weather = "晴れ";
    } else if (request.location().contains("大阪")) {
        weather = "曇り";
    }

    return new Response(weather, request.location(), request.unit());
}

}
“`

  • 関数は java.util.function.Function インターフェースを実装するのが一般的です。入力と出力の型を定義します。
  • 入力と出力の型は、LLMがJSONとして理解・生成できる構造(レコードまたはPOJO)である必要があります。フィールド名と型はLLMがAPIを理解するために重要です。
  • @Bean として登録することで、Spring AIがこの関数を認識できるようになります。

ステップ 2: ChatClientで関数を有効にする

ChatClient を使用する際に、どの関数をLLMに提示するかを設定します。

“`java
package com.example.springaidemo.controller;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class FunctionCallingController {

private final ChatClient chatClient;

// ChatClient Bean は FunctionCallback レジストリを持つインスタンスとして注入される
public FunctionCallingController(ChatClient.Builder builder) {
    // ChatClient をビルドする際に、利用可能な関数コールバックを登録
    // FunctionConfig で定義した weatherService Bean が FunctionCallback レジストリに登録されている前提
    this.chatClient = builder.build();
}

@GetMapping("/ai/function")
public String generateWithFunction(@RequestParam(value = "message", defaultValue = "大阪の天気は?") String message) {
    // プロンプトに加えて、利用可能な関数を指定
    // ChatClient.Builder によって、FunctionConfig で定義した関数が自動的に利用可能になる
    String response = chatClient.prompt()
                                .user(message)
                                // .functions("weatherService") // または特定の関数名を指定
                                .call()
                                .content();

    // LLMが関数呼び出しを要求した場合、Spring AIが自動的に関数を実行し、その結果をLLMに返して最終応答を取得する。
    // この一連のやり取りはSpring AIの内部で行われるため、開発者は最終応答を受け取るだけで済む。

    return response;
}

}
“`

ChatClient.Builder を使用して ChatClient を構築すると、Springコンテキスト内の利用可能な Function Bean が自動的に FunctionCallback として登録されます。prompt().user(message).call() を実行する際に、LLMはプロンプトと利用可能な関数の情報を基に、応答を生成するか、関数呼び出しを要求するかを判断します。

LLMが関数呼び出しを要求した場合:

  1. Spring AIはLLMからの関数呼び出し指示(関数名と引数)を解析します。
  2. 対応するSpring Bean (この例では weatherService) を検索します。
  3. Beanのメソッド(apply メソッド)を、LLMが生成した引数で呼び出します。
  4. メソッドの実行結果を取得します。
  5. 取得した結果をLLMへの追加メッセージとして送信します(元のプロンプト+LLMの関数呼び出し指示+関数の実行結果)。
  6. LLMは結果を受け取り、最終的な自然言語応答を生成します。
  7. Spring AIはその最終応答を取得し、開発者に返します。

この一連の流れがSpring AIによって自動的に処理されるため、開発者は複雑な関数呼び出しのハンドリングロジックを書く必要がありません。

5. 出力形式の制御 (Output Parsing)

LLMは基本的にテキストを生成しますが、アプリケーションで利用しやすいように構造化された形式(JSONや特定のデータクラス)で応答が必要な場合があります。Spring AIは OutputParser を提供しており、これを活用できます。

“`java
package com.example.springaidemo.output;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.parser.BeanOutputParser;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@RestController
public class OutputParsingController {

private final ChatClient chatClient;

public OutputParsingController(ChatClient chatClient) {
    this.chatClient = chatClient;
}

// 抽出したい情報を保持するレコード(またはPOJO)を定義
public record ActorFilms(String actor, String[] movies) {}

@GetMapping("/ai/parse/actorfilms")
public ActorFilms generateActorFilms(@RequestParam(value = "actor", defaultValue = "Tom Hanks") String actor) {

    // BeanOutputParser を作成し、ターゲットとなるクラスを指定
    var outputParser = new BeanOutputParser<>(ActorFilms.class);

    // プロンプトテンプレートを定義し、出力フォーマットの指示を含める
    var promptTemplate = """
        Generate the filmography for the actor {actor}.
        {format}
        """;

    // プロンプトを作成
    var prompt = chatClient.prompt()
                           .user(promptTemplate)
                           .variables(Map.of("actor", actor))
                           // OutputParser が要求するフォーマット指示をプロンプトに追加
                           .call(outputParser.getFormat());

    // AIモデルを呼び出し、OutputParser を適用して結果を取得
    var response = chatClient.prompt(prompt)
                             .call()
                             .content(); // LLMからの生テキスト応答を取得

    // 生テキスト応答を定義した Bean にパース
    ActorFilms actorFilms = outputParser.parse(response);

    return actorFilms;
}

}
“`

  • 抽出したい情報を保持するレコード(またはPOJO)を定義します。
  • BeanOutputParser を使用し、ターゲットクラスを指定してインスタンスを作成します。
  • outputParser.getFormat() メソッドを呼び出すと、LLMに指定したクラス構造で応答を生成するよう指示するためのテキストが得られます。これをプロンプトに含めるのが重要です。
  • chatClient.prompt(...).call().content() でLLMからの生テキスト応答を取得します。
  • 取得した生テキスト応答を outputParser.parse(response) メソッドで指定したレコード(またはPOJO)にパースします。

これにより、LLMからの応答を手動でパースする手間が省け、型安全に扱うことができます。JSON形式だけでなく、区切り文字によるリストなど、様々な形式に対応する OutputParser が提供されています。

6. 異なるプロバイダー/モデルの利用

Spring AIの大きな利点は、プロバイダーやモデルの切り替えが容易な点です。application.properties の設定を変更するだけで、OpenAI、Azure OpenAI、Google AI、Hugging Faceなど、様々なプロバイダーや、プロバイダーが提供する異なるモデルを利用できます。

例: Google AIに切り替える場合

  1. Gradle (build.gradle) または Maven (pom.xml) に spring-ai-google-ai-spring-boot-starter 依存関係を追加します。
  2. application.properties でOpenAIの設定をコメントアウトし、Google AIの設定を追加します。
    “`properties
    # OpenAI settings (commented out)
    # spring.ai.openai.api-key=${OPENAI_API_KEY}

    Google AI settings

    spring.ai.google.ai.api-key=${GOOGLE_AI_KEY}

    spring.ai.google.ai.chat.options.model=gemini-pro # 使用するモデルを指定

    ``
    3. コード内の
    ChatClient,EmbeddingClient` はインターフェースを使用しているため、コードの変更は不要です。アプリケーションを再起動するだけで、Google AIモデルが使用されるようになります。

これは、プロバイダーのコスト比較、性能評価、特定のタスクに最適なモデルの選択などに非常に便利です。

7. ローカルモデルの利用 (Ollamaなど)

外部APIに依存せず、ローカル環境やプライベートなネットワークでAIモデルを実行したい場合もあります。Spring AIは、OllamaなどのローカルAI実行環境との連携もサポートしています。

Ollamaを使用する場合:

  1. Ollamaをインストールし、使用したいモデル(例: llama2)をダウンロードします(ollama pull llama2)。
  2. Gradle (build.gradle) または Maven (pom.xml) に spring-ai-ollama-spring-boot-starter 依存関係を追加します。
  3. application.properties で以下のように設定します。
    properties
    # Ollama settings
    spring.ai.ollama.chat.options.model=llama2 # 使用するローカルモデル名を指定
    # spring.ai.ollama.base-url=http://localhost:11434 # OllamaのURL (デフォルト)
  4. アプリケーションを起動すると、Ollama経由でローカルモデルが使用されます。

これは、データプライバシーが重要なアプリケーションや、開発/テスト時にコストを抑えたい場合に有効です。

実践的な応用例

ここまで見てきたSpring AIの機能を使って、より実践的なアプリケーションのアイデアをいくつかご紹介します。

  1. 高度なチャットボット: 会話履歴を管理し、RAGを使って組織内のドキュメントに基づいて応答するチャットボット。関数呼び出しを利用して、ユーザーからの指示(例:「〇〇の情報を検索して」「会議の予約を入れて」)に対応できます。
  2. インテリジェントな検索: ユーザーの自然言語クエリを埋め込み検索と組み合わせ、関連性の高い情報をデータベースやドキュメントストアから取得するアプリケーション。単なるキーワード一致ではなく、意味的な類似性に基づいた検索が可能になります。
  3. コンテンツ自動生成・編集: 入力された情報やテンプレートに基づいて、ブログ記事、メール、レポートなどを自動生成したり、既存のテキストを要約、翻訳、校正したりするツール。
  4. データ分析・可視化の補佐: データセットに関する自然言語での質問に対して、関連する統計情報を提供したり、適切なグラフの種類を提案したりする機能。
  5. 異常検知・監視: ログデータやセンサーデータなどを分析し、異常なパターンを検出した場合に自然言語で通知したり、原因の可能性を説明したりするシステム。

これらの応用例において、Spring AIはAIモデルとの複雑な連携部分を抽象化し、Spring開発者が慣れた手法でビジネスロジックの実装に集中できるようにします。

Spring AI開発における考慮事項

Spring AIを使ってアプリケーションを開発する際に考慮すべき点をいくつか挙げます。

  • コスト管理: LLMのAPI利用にはコストがかかります。開発段階や本番環境での利用量とコストを把握し、必要に応じてモデルの選択(より安価なモデルやローカルモデル)、プロンプトの最適化、キャッシュ戦略などを検討する必要があります。
  • レイテンシ: AIモデルへのリクエストは、ネットワークの遅延やモデルの処理時間により、従来のAPI呼び出しよりも時間がかかる場合があります。非同期処理(ストリーミングなど)やレスポンスタイムの最適化を考慮する必要があります。
  • 信頼性とエラーハンドリング: 外部APIへの依存は、APIの可用性やレート制限の影響を受けます。適切なエラーハンドリング、リトライメカニズム、フォールバック戦略の実装が重要です。
  • セキュリティ: APIキーなどの認証情報は安全に管理する必要があります。環境変数やSpring Cloud Configなどの外部設定サービスを利用することを強く推奨します。ユーザーからの入力やAIからの出力に含まれる可能性のある機密情報や個人情報の取り扱いにも注意が必要です。
  • プロンプトエンジニアリング: 効果的なAI応答を得るためには、適切なプロンプトを作成することが重要です。AIモデルの特性を理解し、試行錯誤を通じてプロンプトを改善していく必要があります。
  • モデルの選択: タスクの内容や要求される性能(精度、速度、コスト)に応じて、適切なAIモデルを選択する必要があります。Spring AIはプロバイダー切り替えを容易にするため、比較検討がしやすいです。
  • 評価とテスト: AIモデルの応答は非決定的である場合があります。期待通りの応答が得られるか、様々な入力に対して頑健であるかなどを評価し、テスト戦略を検討する必要があります。
  • 規制と倫理: AIの利用には、プライバシー、透明性、公平性などの倫理的・法的な考慮が必要です。生成されたコンテンツが誤情報を含まないか、バイアスがないかなどを検証する仕組みも重要になる場合があります。

Spring AI vs. その他のAIライブラリ

Javaエコシステムには、AIモデルを扱うための他のライブラリも存在します。例えば、LangChainのJava/Spring実装(LangChain4j)などがあります。

Spring AIの主な強みは、その名前が示す通り、Springエコシステムとの緊密な統合です。Spring Bootの自動設定、依存性注入、抽象化といったSpring開発者が慣れ親しんだパターンでAI機能を扱える点が、Spring AIの最大の差別化要因です。SpringアプリケーションにAI機能を後から追加したり、既存のSpringコンポーネントと連携させたりする場合に、非常にスムーズに導入できます。

一方、LangChainのようなライブラリは、AIアプリケーション開発のためのより汎用的なフレームワークや抽象化を提供しており、言語にとらわれない概念やツール(エージェント、チェーンなど)が豊富に用意されている場合があります。

どちらを選択するかは、プロジェクトの既存技術スタック、開発チームのSpringに関する習熟度、必要とされるAI機能の複雑さなどによって判断することになるでしょう。Springエコシステムを中心に開発している、あるいはこれからSpringでAI機能を組み込みたい、という場合は、Spring AIが最も自然な選択肢となる可能性が高いです。

Spring AIのコミュニティと将来

Spring AIは現在も活発に開発が続けられているオープンソースプロジェクトです。GitHubリポジトリ(https://github.com/spring-projects/spring-ai)で開発状況を確認したり、コントリビュートしたりすることができます。

まだ比較的新しいプロジェクトであり、今後の発展に期待が持てます。より多くのAIモデルやプロバイダーへの対応、高度なエージェント機能のサポート、開発者体験のさらなる向上などがロードマップに含まれる可能性があります。

Spring開発者として、Spring AIのコミュニティに参加し、最新情報を追いかけ、フィードバックを提供することは、プロジェクトの成長を支援し、自身のスキルアップにも繋がるでしょう。

まとめ

この記事では、Java開発者向けにSpring AIについて詳細に解説しました。

  • Spring AIは、Java開発者がSpringの流儀でAIモデル(特にLLM)と連携するためのフレームワークです。
  • 主要なAIプロバイダーやベクトルデータベースを抽象化し、一貫性のあるAPIを提供します。
  • Spring Bootとの連携が容易で、依存関係を追加し、APIキーを設定するだけで利用を開始できます。
  • テキスト生成 (ChatClient)、埋め込み (EmbeddingClient)、ベクトルデータベース連携 (VectorStore)、関数呼び出し (Function Calling)、出力パース (OutputParser) といった主要な機能を提供します。
  • RAGのような高度な応用も、提供されるコンポーネントを組み合わせて実現できます。
  • プロバイダーやモデルの切り替えが容易で、ローカルモデルの利用もサポートしています。
  • Springエコシステムとの親和性が最大の強みであり、既存のSpringアプリケーションへの導入がスムーズに行えます。

約5000語を費やしてSpring AIの多岐にわたる機能と概念を掘り下げてきましたが、最も重要なのは、実際に手を動かしてみることです。GitHubからプロジェクトをクローンしたり、Spring Initializrで新しいプロジェクトを作成したりして、この記事で紹介したコード例を試してみてください。

AI技術は急速に進化しており、Java開発者がAIの力を活用できる機会はますます増えています。Spring AIは、そのための強力な味方となるでしょう。ぜひ、あなたの次のプロジェクトでSpring AIを試してみてください!


コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール