spring-ai-integration について
Spring アプリに LLM 機能を組み込む Java 開発者向けの Spring AI リファレンススキルです。チャット、メモリ、プロンプト、構造化出力、RAG、ストリーミングと GA 版の使い方を扱います。
主な機能
- 01ChatClient の設定と利用
- 02会話メモリとプロンプトテンプレート
- 03構造化出力のマッピング
- 04RAG パイプライン設定
- 05ストリーミング応答
- 06Spring AI GA への移行ガイダンス
活用シーン
- 01Spring AI を使ったチャットサービスの構築
- 02ユーザーごとの会話履歴の分離
- 03検索拡張生成パイプラインの構築
- 04構造化またはストリーミング形式のモデル応答
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ rrezartprebreza/ spring-boot-skills --skill spring-ai-integrationインストール後、Claude Code で/spring-ai-integrationを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
agents/openai.yamlexamples/bad-ai-service.javagood-ai-service.javatemplates/AiServiceTemplate.javaanalyze-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を指定し忘れることがあります。その場合、すべてのユーザーが同じチャット履歴を共有します - エージェントが誤ったモデル文字列を使用することがあります。プロバイダーのドキュメントでモデル名を確認してください
コメント
Markdown 対応