spring-ai について
生成 AI 機能を構築する Spring 開発者向けの Spring AI リファレンススキルです。チャット、構造化出力、ストリーミング、関数呼び出し、埋め込み、ベクトルストアを扱います。
主な機能
- 01ChatClient によるチャット呼び出し
- 02構造化出力とストリーミング
- 03関数呼び出し
- 04テキスト埋め込み
- 05ベクトルストアと RAG
活用シーン
- 01Spring サービスへのモデルチャットの追加
- 02構造化されたモデル応答の生成
- 03長い応答のストリーミング処理
- 04ベクトルストアを使った RAG クエリの構築
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ claude-dev-suite/ claude-dev-suite --skill spring-aiインストール後、Claude Code で/spring-aiを入力して読み込まれたか確認します。
同梱ファイルadvanced.md
詳細
Spring AI - クイックリファレンス
完全なリファレンス:画像生成、マルチモーダル/ビジョン、アドバイザー/ミドルウェア、テストパターン、プロンプトテンプレートについては、advanced.md を参照してください。
詳細な知識:包括的なドキュメントについては、technology に
spring-aiを指定してmcp__documentation__fetch_docsを使用してください。
依存関係
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-azure-openai-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-pgvector-store-spring-boot-starter</artifactId>
</dependency>
設定
OpenAI
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
chat:
options:
model: gpt-4o
temperature: 0.7
max-tokens: 1000
embedding:
options:
model: text-embedding-3-small
Azure OpenAI
spring:
ai:
azure:
openai:
api-key: ${AZURE_OPENAI_KEY}
endpoint: ${AZURE_OPENAI_ENDPOINT}
chat:
options:
deployment-name: gpt-4o
temperature: 0.7
Ollama(ローカル)
spring:
ai:
ollama:
base-url: http://localhost:11434
chat:
options:
model: llama3
temperature: 0.7
基本的なチャット
@Service
@RequiredArgsConstructor
public class ChatService {
private final ChatClient chatClient;
public String chat(String message) {
return chatClient.prompt()
.user(message)
.call()
.content();
}
// With system prompt
public String chatWithContext(String message) {
return chatClient.prompt()
.system("You are a helpful assistant specialized in Spring Boot.")
.user(message)
.call()
.content();
}
// With parameters
public String chatWithParams(String message, String topic) {
return chatClient.prompt()
.system(s -> s.text("You are an expert in {topic}.")
.param("topic", topic))
.user(message)
.call()
.content();
}
}
ChatClient ビルダー
@Configuration
public class ChatClientConfig {
@Bean
public ChatClient chatClient(ChatClient.Builder builder) {
return builder
.defaultSystem("You are a helpful AI assistant.")
.defaultOptions(ChatOptionsBuilder.builder()
.withTemperature(0.7)
.withMaxTokens(1000)
.build())
.build();
}
}
構造化出力
public record BookRecommendation(
String title,
String author,
String genre,
String summary,
int rating
) {}
@Service
public class BookService {
private final ChatClient chatClient;
public BookRecommendation getRecommendation(String preferences) {
return chatClient.prompt()
.user("Recommend a book based on: " + preferences)
.call()
.entity(BookRecommendation.class);
}
public List<BookRecommendation> getRecommendations(String preferences, int count) {
return chatClient.prompt()
.user("Recommend " + count + " books based on: " + preferences)
.call()
.entity(new ParameterizedTypeReference<List<BookRecommendation>>() {});
}
}
ストリーミング
@Service
public class StreamingChatService {
private final ChatClient chatClient;
public Flux<String> streamChat(String message) {
return chatClient.prompt()
.user(message)
.stream()
.content();
}
// WebFlux controller
@GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> streamResponse(@RequestParam String message) {
return streamChat(message);
}
}
関数呼び出し
@Configuration
public class FunctionConfig {
@Bean
@Description("Get current weather for a location")
public Function<WeatherRequest, WeatherResponse> currentWeather() {
return request -> weatherService.getWeather(request.location());
}
@Bean
@Description("Search for products by name")
public Function<ProductSearchRequest, List<Product>> searchProducts() {
return request -> productService.search(request.query(), request.maxResults());
}
}
public record WeatherRequest(String location) {}
public record WeatherResponse(String location, double temperature, String conditions) {}
@Service
public class AssistantService {
private final ChatClient chatClient;
public String assistWithFunctions(String message) {
return chatClient.prompt()
.user(message)
.functions("currentWeather", "searchProducts")
.call()
.content();
}
}
埋め込み
@Service
@RequiredArgsConstructor
public class EmbeddingService {
private final EmbeddingModel embeddingModel;
public float[] getEmbedding(String text) {
EmbeddingResponse response = embeddingModel.embedForResponse(List.of(text));
return response.getResult().getOutput();
}
public List<float[]> getEmbeddings(List<String> texts) {
EmbeddingResponse response = embeddingModel.embedForResponse(texts);
return response.getResults().stream()
.map(e -> e.getOutput())
.toList();
}
}
ベクトルストア(RAG)
設定
spring:
ai:
vectorstore:
pgvector:
dimensions: 1536
index-type: HNSW
distance-type: COSINE_DISTANCE
RAG クエリ
@Service
@RequiredArgsConstructor
public class RagService {
private final VectorStore vectorStore;
private final ChatClient chatClient;
public String queryWithContext(String question) {
// Retrieve relevant documents
List<Document> relevantDocs = vectorStore.similaritySearch(
SearchRequest.query(question)
.withTopK(5)
.withSimilarityThreshold(0.7)
);
// Build context
String context = relevantDocs.stream()
.map(Document::getContent)
.collect(Collectors.joining("\n\n"));
// Generate response with context
return chatClient.prompt()
.system("""
You are a helpful assistant. Answer questions based on the provided context.
If the answer is not in the context, say "I don't have information about that."
Context:
{context}
""")
.user(question)
.call()
.content();
}
}
QuestionAnswerAdvisor
@Configuration
public class RagConfig {
@Bean
public ChatClient ragChatClient(ChatClient.Builder builder, VectorStore vectorStore) {
return builder
.defaultAdvisors(new QuestionAnswerAdvisor(vectorStore))
.build();
}
}
// Usage is simple - advisor handles RAG automatically
@Service
public class SimpleRagService {
private final ChatClient ragChatClient;
public String answer(String question) {
return ragChatClient.prompt()
.user(question)
.call()
.content();
}
}
ベストプラクティス
| 推奨事項 | 非推奨事項 |
|---|---|
| 予測可能な結果を得るために構造化出力を使用する | 自由形式のテキストを手動で解析する |
| 適切なエラー処理を実装する | API の失敗を無視する |
| 長い応答にはストリーミングを使用する | 大規模な生成処理をブロックする |
| 可能な場合は埋め込みをキャッシュする | 埋め込みを繰り返し再生成する |
| 適切なトークン上限を設定する | トークン数を無制限にする |
本番環境チェックリスト
- API キーを安全に管理する(環境変数)
- レート制限を実装する
- エラー処理とリトライを設定する
- トークン使用量を監視する
- 必要に応じて応答をキャッシュする
- ベクトルストアに適切なインデックスを作成する
- 埋め込みの次元数に一貫性を持たせる
- プロンプトインジェクションを防止する
- コストを監視し、アラートを設定する
- フォールバックモデルを設定する
このスキルを使用すべきでない場合
- OpenAI/Anthropic の生 API - それぞれの SDK を直接使用してください
- ML モデルのトレーニング - Python フレームワーク(PyTorch、TensorFlow)を使用してください
- Spring 以外のアプリケーション - LangChain またはネイティブ SDK を使用してください
- 単純なテキスト生成 - 些細なユースケースには過剰な場合があります
アンチパターン
| アンチパターン | 問題 | 解決策 |
|---|---|---|
| API キーをハードコードする | セキュリティリスク | 環境変数を使用する |
| トークン上限がない | コストの急増 | max-tokens を適切に設定する |
| 長いリクエストに同期処理を使う | スレッドのブロック | ストリーミングを使用する |
| レート制限を無視する | API エラー、利用停止 | バックオフ付きのリトライを実装する |
| 埋め込みをキャッシュしない | 高コスト | 埋め込みをローカルにキャッシュする |
| プロンプトインジェクションの脆弱性 | セキュリティリスク | ユーザー入力をサニタイズする |
トラブルシューティング(クイックガイド)
| 問題 | 診断 | 解決策 |
|---|---|---|
| API キーが無効 | エラーメッセージを確認する | OPENAI_API_KEY 環境変数を確認する |
| レート制限を超過した | 429 エラー | リトライロジックを追加し、リクエスト数を減らす |
| 大きなプロンプトでタイムアウトする | 接続タイムアウト | ストリーミングを使用し、タイムアウトを延長する |
| 埋め込みの次元数が一致しない | ベクトルストアのエラー | 埋め込みモデルの次元数を一致させる |
| 構造化出力に失敗する | JSON 解析エラー | スキーマを簡素化し、例を追加する |
リファレンスドキュメント
コメント
Markdown 対応