mcpField
S
spring-ai-integration

spring-ai-integration

github.com/rrezartprebreza開発328100 回インストール6 日前に更新
リポジトリ

spring-ai-integration について

Spring アプリに LLM 機能を組み込む Java 開発者向けの Spring AI リファレンススキルです。チャット、メモリ、プロンプト、構造化出力、RAG、ストリーミングと GA 版の使い方を扱います。

主な機能

  1. 01ChatClient の設定と利用
  2. 02会話メモリとプロンプトテンプレート
  3. 03構造化出力のマッピング
  4. 04RAG パイプライン設定
  5. 05ストリーミング応答
  6. 06Spring AI GA への移行ガイダンス

活用シーン

  1. 01Spring AI を使ったチャットサービスの構築
  2. 02ユーザーごとの会話履歴の分離
  3. 03検索拡張生成パイプラインの構築
  4. 04構造化またはストリーミング形式のモデル応答

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/rrezartprebreza/spring-boot-skills --skill spring-ai-integration

インストール後、Claude Code で/spring-ai-integrationを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

agents/
openai.yaml
examples/
bad-ai-service.java
good-ai-service.java
templates/
AiServiceTemplate.java
analyze-order.st

詳細

Spring AI 統合

依存関係

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>1.0.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-model-anthropic</artifactId>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-model-openai</artifactId>
    </dependency>

    
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-vector-store-pgvector</artifactId>
    </dependency>
</dependencies>

アーティファクト名に注意してください。 1.0 GA では、以前の spring-ai-<x>-spring-boot-starter 座標が廃止されました。現在のパターンは spring-ai-starter-model-<provider>(例:-model-anthropic、 -model-openai)と spring-ai-starter-vector-store-<store> です。GA 前の Spring AI でトレーニングされたエージェントは 存在しない名前を出力することがあり、Maven Central では何も解決されません。

ChatClient — 基本的な使い方

@Service
@RequiredArgsConstructor
public class DocumentSummaryService {

    private final ChatClient chatClient;

    public String summarize(String conversationId, String content) {
        return chatClient.prompt()
            .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId))
            .user(u -> u.text("Summarize the following document in 3 bullet points:\n\n{content}")
                .param("content", content))
            .call()
            .content();
    }

    // With system prompt
    public String analyzeFinancial(String conversationId, String document, String language) {
        return chatClient.prompt()
            .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId))
            .system("You are a financial analyst. Respond in {language}.")
            .system(s -> s.param("language", language))
            .user(document)
            .call()
            .content();
    }
}

設定済みのメモリーアドバイザーを使用する呼び出しでは、必ずユーザー単位またはセッション単位の ChatMemory.CONVERSATION_ID を指定してください。すべてのユーザーで共通の会話 ID を使用しないでください。

ChatClient Bean の設定

@Configuration
public class AiConfig {

    @Bean
    public ChatMemory chatMemory() {
        // 1.0 GA: InMemoryChatMemory is GONE. Use MessageWindowChatMemory —
        // it caps history to a sliding window and defaults to an in-memory repository.
        return MessageWindowChatMemory.builder()
            .maxMessages(20)
            .build();
    }

    @Bean
    public ChatClient chatClient(ChatClient.Builder builder, ChatMemory chatMemory) {
        return builder
            .defaultSystem("You are a helpful assistant for an e-commerce platform.")
            .defaultAdvisors(
                MessageChatMemoryAdvisor.builder(chatMemory).build(), // GA: builder, not new(...)
                new SimpleLoggerAdvisor() // logs prompts/responses
            )
            .build();
    }
}

プロンプトテンプレート(外部化)

// src/main/resources/prompts/analyze-order.st
// Analyze this order and identify any anomalies:
// Customer: {customerEmail}
// Items: {items}
// Total: {total}
// Flag any unusual patterns.

@Service
@RequiredArgsConstructor
public class OrderAnalysisService {

    private final ChatClient chatClient;

    @Value("classpath:prompts/analyze-order.st")
    private Resource promptTemplate;

    public String analyzeOrder(String conversationId, Order order) {
        return chatClient.prompt()
            .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId))
            .user(u -> u.text(promptTemplate)
                .param("customerEmail", order.getCustomerEmail()) // must match {customerEmail} in the .st
                .param("items", order.getItems().toString())
                .param("total", order.getTotal()))
            .call()
            .content();
    }
}

構造化出力

// Define the target record
public record OrderClassification(
    String category,
    String priority,
    List<String> tags,
    boolean requiresManualReview
) {}

@Service
public class OrderClassifier {

    public OrderClassification classify(String orderDescription) {
        return chatClient.prompt()
            .user("Classify this order: " + orderDescription)
            .call()
            .entity(OrderClassification.class); // Spring AI handles JSON parsing
    }
}

RAG パイプライン

@Configuration
public class RagConfig {

    // No manual VectorStore bean — the spring-ai-starter-vector-store-pgvector
    // starter auto-configures one. Just inject it. (The old `new PgVectorStore(...)`
    // constructor is removed in GA; if you must build one, use PgVectorStore.builder(...).)

    @Bean
    public ChatClient ragChatClient(ChatClient.Builder builder, VectorStore vectorStore) {
        return builder
            .defaultAdvisors(
                QuestionAnswerAdvisor.builder(vectorStore)
                    .searchRequest(SearchRequest.builder().topK(5).build()) // GA: builder, not defaults().withTopK()
                    .build()
            )
            .build();
    }
}

@Service
@RequiredArgsConstructor
public class KnowledgeService {

    private final VectorStore vectorStore;
    private final ChatClient ragChatClient;

    // Ingest documents
    public void ingest(List<String> documents) {
        List<Document> docs = documents.stream()
            .map(content -> new Document(content))
            .toList();
        vectorStore.add(docs);
    }

    // Query with RAG
    public String ask(String question) {
        return ragChatClient.prompt()
            .user(question)
            .call()
            .content();
    }
}

ストリーミング応答

@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> stream(@RequestParam String prompt) {
    return chatClient.prompt()
        .user(prompt)
        .stream()
        .content();
}

application.yml

spring:
  ai:
    anthropic:
      api-key: ${ANTHROPIC_API_KEY}
      chat:
        options:
          model: ${ANTHROPIC_MODEL}
          max-tokens: 2048
          temperature: 0.7
    # OR for OpenAI:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        options:
          model: ${OPENAI_MODEL}
    vectorstore:
      pgvector:
        initialize-schema: true
        dimensions: 1536

注意点

  • エージェントが .st ファイルで {customerEmail} と指定されているのに .param("customer", ...) をバインドすることがあります。デフォルトのレンダラーでは「Not all variables were replaced」エラーになるため、パラメーター名とテンプレートのプレースホルダーを一致させてください
  • エージェントが GA 前のアーティファクト名(spring-ai-anthropic-spring-boot-starter)を使用することがあります。GA 版は spring-ai-starter-model-anthropic です
  • エージェントが new MessageChatMemoryAdvisor(new InMemoryChatMemory()) を記述することがあります。GA では両方とも削除されています。MessageChatMemoryAdvisor.builder(chatMemory) と MessageWindowChatMemory を使用してください
  • エージェントが SearchRequest.defaults().withTopK(n) を記述することがあります。GA 版は SearchRequest.builder().topK(n).build() です
  • エージェントが API キーをハードコードすることがあります。必ず環境変数または ${...} を使用してください
  • エージェントがプロバイダーのモデル ID をハードコードすることがあります。モデルカタログは変更されるため、外部で設定してください
  • エージェントが文字列連結でプロンプトを組み立てることがあります。.param() のテンプレート変数を使用してください
  • エージェントがプロンプトをコード内に直接記述することがあります。src/main/resources/prompts/ に外部化してください
  • エージェントが構造化出力を無視することがあります。手動で解析するのではなく、.entity(MyClass.class) を使用してください
  • エージェントがリストに .entity(List.class) を使用することがあります。ジェネリクスは消去されるため、new ParameterizedTypeReference<List<X>>() {} を渡してください
  • エージェントが API 呼び出しのエラー処理を省略することがあります。try/catch で囲み、NonTransientAiException(再試行しない)と TransientAiException(再試行する)を処理してください
  • エージェントがメモリーアドバイザーにユーザーごとの conversationId を指定し忘れることがあります。その場合、すべてのユーザーが同じチャット履歴を共有します
  • エージェントが誤ったモデル文字列を使用することがあります。プロバイダーのドキュメントでモデル名を確認してください

コメント