AI API を活用したシステムにおいて、外部サービスへの依存は避けて通れません。しかし、急激なトラフィック増加やサービス障害发生时、システム全体への波及效应を防ぐ必要があります。本稿では、Java/Spring Boot 環境で AI API 调用のilience を確保するために、resilience4j を活用したサーキットブレーカーとグレードダウン策略について、実戦経験を交えながら解説いたします。
なぜ AI API にサーキットブレーカーが必要か
私は以前、電子商取引プラットフォームの AI カスタマーサービスシステムを構築していたとき、週末に予想外のトラフィック急増が発生し、AI API への过多要求而导致応答遅延が频発しました。この経験が、サーキットブレーカー実装の重要性を実感させたきっかけです。
AI API の特徴として、通常の REST API と比较して以下の点が異なります:
- レスポンスタイムの変動が大きい:LLM の推論時間は入力トークン数によって大きく変動します
- コスト構造が複雑:トークン単位で料金が発生するた め、無駄なリクエスト削減が直接コストに反映されます
- レートリミットが厳格:多くの AI プロバイダーは分間または日あたりのリクエスト数に上限を設定しています
このような环境下で、サーキットブレーカーとグレードダウン机制を実装することで、以下の效果が得られます:
- 障害時の迅速な failover
- 上游サービスへの負荷軽減
- フォールバック responses によるユーザー体験の维持
- コストの急激な上昇防止
resilience4j の基本設定
Spring Boot プロジェクトで resilience4j を利用するための設定부터 살펴していきましょう。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>ai-api-resilience</artifactId>
<version>1.0.0</version>
<properties>
<java.version>17</java.version>
<resilience4j.version>2.2.0</resilience4j.version>
</properties>
<dependencies>
<!-- resilience4j core -->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot3</artifactId>
<version>${resilience4j.version}</version>
&;
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- HTTP Client: OpenFeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
</project>
次に application.yml での設定例を示します。
resilience4j:
circuitbreaker:
instances:
aiChat:
register-health-indicator: true
sliding-window-type: COUNT_BASED
sliding-window-size: 10
minimum-number-of-calls: 5
failure-rate-threshold: 50
wait-duration-in-open-state: 60s
permitted-number-of-calls-in-half-open-state: 3
automatic-transition-from-open-to-half-open-enabled: true
record-exceptions:
- java.io.IOException
- java.util.concurrent.TimeoutException
- com.example.ai.exception.AIRateLimitException
- feign.FeignException.ServiceUnavailable
retry:
instances:
aiChat:
max-attempts: 3
wait-duration: 2s
exponential-backoff-multiplier: 2
retry-exceptions:
- java.io.IOException
- feign.FeignException.ServiceUnavailable
timelimiter:
instances:
aiChat:
timeout-duration: 30s
cancel-running-future: true
HolySheep AI API 設定
ai:
api:
base-url: https://api.holysheep.ai/v1
api-key: ${HOLYSHEEP_API_KEY:YOUR_HOLYSHEEP_API_KEY}
model: gpt-4.1
timeout: 30000
AI API クライアントの実装
HolySheep AI への API 调用を実装します。HolySheep AI は¥1=$1のレート(公式¥7.3=$1比85%節約)で提供されており、今すぐ登録して無料クレジットを獲得できます。
@Configuration
@EnableFeignClients
public class AIApiConfig {
@Bean
public feign.Client feignClient() {
return new feign.Client.OkHttpClient(new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build());
}
}
@FeignClient(
name = "holySheepAI",
url = "${ai.api.base-url}",
configuration = AIApiConfig.class,
fallbackFactory = AIChatFallbackFactory.class
)
public interface AIChatClient {
@Headers("Authorization: Bearer {apiKey}")
@RequestLine("POST /chat/completions")
ChatCompletionResponse chatCompletions(
@Param("apiKey") String apiKey,
@RequestBody ChatCompletionRequest request
);
}
// リクエスト DTO
public record ChatCompletionRequest(
String model,
List<ChatMessage> messages,
Double temperature,
Integer maxTokens
) {}
// レスポンス DTO
public record ChatCompletionResponse(
String id,
String model,
List<Choice> choices,
Usage usage
) {}
public record ChatMessage(String role, String content) {}
public record Choice(Integer index, ChatMessage message, String finishReason) {}
public record Usage(Integer promptTokens, Integer completionTokens, Integer totalTokens) {}
次にグレードダウン(フォールバック)の実装例を示します。
@Service
@Slf4j
public class AIChatService {
private final AIChatClient aiChatClient;
private final AIChatFallbackFactory fallbackFactory;
@Autowired
public AIChatService(
AIChatClient aiChatClient,
AIChatFallbackFactory fallbackFactory) {
this.aiChatClient = aiChatClient;
this.fallbackFactory = fallbackFactory;
}
/**
* サーキットブレーカーとグレードダウンを適用した AI チャットメソッド
* HolySheep AI API を使用して会話を処理します
*/
@CircuitBreaker(name = "aiChat", fallbackMethod = "chatFallback")
@TimeLimiter(name = "aiChat")
@Retry(name = "aiChat")
public CompletableFuture<String> chat(String userMessage) {
return CompletableFuture.supplyAsync(() -> {
try {
ChatCompletionRequest request = new ChatCompletionRequest(
"gpt-4.1",
List.of(
new ChatMessage("system", "あなたは有帮助なアシスタントです。"),
new ChatMessage("user", userMessage)
),
0.7,
1000
);
ChatCompletionResponse response = aiChatClient.chatCompletions(
"YOUR_HOLYSHEEP_API_KEY",
request
);
return response.choices().get(0).message().content();
} catch (FeignException e) {
log.error("HolySheep AI API エラー: status={}, message={}",
e.status(), e.getMessage());
throw new RuntimeException("AI API 调用失敗", e);
}
});
}
/**
* フォールバックメソッド:サーキットブレーカー开启時またはエラー発生時に実行
*/
private CompletableFuture<String> chatFallback(String userMessage, Throwable t) {
log.warn("AI API グレードダウン発動 - フォールバック応答を返します: {}",
t.getMessage());
// フォールバック応答の生成
String fallbackResponse = generateFallbackResponse(userMessage);
return CompletableFuture.completedFuture(fallbackResponse);
}
/**
* フォールバック応答の生成ロジック
*/
private String generateFallbackResponse(String userMessage) {
// 簡易的なフォールバック応答
if (userMessage.contains("価格") || userMessage.contains("料金")) {
return "只今、AIサービスが不安定のため、正確なお答えを提供できません。" +
"恐れ入りますが、後ほど再度お試しいただくか、" +
"[email protected] までご連絡ください。";
}
if (userMessage.contains("注文") || userMessage.contains("キャンセル")) {
return "注文に関するお问い合わせは、0120-XXX-XXXX(対応時間 9:00-18:00)" +
"まで、お気軽にお問い合わせください。";
}
return "只今込み合っております。お答えにはお時間をいただく場合があります。" +
"少々お待ちいただいた後、もう一度お試しください。";
}
}
// Fallback Factory
@Component
@Slf4j
public class AIChatFallbackFactory implements FallbackFactory<AIChatClient> {
@Override
public AIChatClient create(Throwable cause) {
log.error("AIChatClient フォールバック作成: {}", cause.getMessage());
return (apiKey, request) -> {
throw new AIResilienceException("AI API 利用不可 - サーキットブレーカー开启中", cause);
};
}
}
// カスタム例外
public class AIResilienceException extends RuntimeException {
public AIResilienceException(String message, Throwable cause) {
super(message, cause);
}
}
Spring Boot アプリケーションでの設定
Spring Boot 3.x 环境下での完整的設定を示します。
@SpringBootApplication
@EnableFeignClients
@EnableCircuitBreaker
public class AIResilienceApplication {
public static void main(String[] args) {
SpringApplication.run(AIResilienceApplication.class, args);
}
@Bean
public Customizer<Resilience4JCircuitBreakerFactory> circuitBreakerCustomizer() {
return factory -> {
// カスタムサーキットブレーカー設定の適用
factory.configureDefault(id -> new Resilience4jConfigBuilder(id)
.circuitBreakerConfig(CircuitBreakerConfig.custom()
.slidingWindowType(CircuitBreakerConfig.SlidingWindowType.COUNT_BASED)
.slidingWindowSize(10)
.failureRateThreshold(50)
.waitDurationInOpenState(Duration.ofSeconds(60))
.build())
.retryConfig(RetryConfig.custom()
.maxAttempts(3)
.waitDuration(Duration.ofSeconds(2))
.build())
.timeLimiterConfig(TimeLimiterConfig.custom()
.timeoutDuration(Duration.ofSeconds(30))
.build())
.build());
};
}
}
// REST コントローラーでの使用例
@RestController
@RequestMapping("/api/chat")
@Slf4j
public class ChatController {
private final AIChatService aiChatService;
@Autowired
public ChatController(AIChatService aiChatService) {
this.aiChatService = aiChatService;
}
@PostMapping
public ResponseEntity<ChatResponse> chat(@RequestBody ChatRequest request) {
log.info("チャットリクエスト受信: userId={}", request.userId());
try {
String response = aiChatService.chat(request.message()).get();
return ResponseEntity.ok(new ChatResponse(true, response, null));
} catch (Exception e) {
log.error("チャット処理エラー: {}", e.getMessage(), e);
return ResponseEntity.status(503)
.body(new ChatResponse(false, null, "サービスが一時的に利用できません"));
}
}
}
public record ChatRequest(String userId, String message) {}
public record ChatResponse(boolean success, String message, String error) {}
実践的なユースケース:企業 RAG システムの構築
もう一つの実戦的例子として、企業内部的 RAG(Retrieval-Augmented Generation)システムの構築案例を紹介します。このシステムは、ドキュメント検索と AI 生成を組み合わせたナレッジベース検索を提供しました。
/**
* RAG システムにおける AI API サーキットブレーカー管理
* ドキュメント検索 + AI 生成のハイブリッド处理
*/
@Service
@Slf4j
public class RAGChatService {
private final AIChatClient aiChatClient;
private final DocumentSearchService documentSearchService;
// サーキットブレーカー状态の監視用
private final CircuitBreakerRegistry circuitBreakerRegistry;
@Autowired
public RAGChatService(
AIChatClient aiChatClient,
DocumentSearchService documentSearchService,
CircuitBreakerRegistry circuitBreakerRegistry) {
this.aiChatClient = aiChatClient;
this.documentSearchService = documentSearchService;
this.circuitBreakerRegistry = circuitBreakerRegistry;
}
/**
* RAG チャット:ドキュメント检索 + AI 生成
* サーキットブレーカー状態に応じて処理を変更
*/
public RAGResponse ragChat(String query, String userId) {
CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("aiChat");
CircuitBreakerState state = circuitBreaker.getState();
log.info("RAG チャット開始 - 状態: {}, userId: {}", state, userId);
// ステップ1:関連ドキュメントの检索(常に実行)
List<SearchResult> documents = documentSearchService.search(query, 5);
if (documents.isEmpty()) {
return new RAGResponse(
"関連ドキュメントが見つかりませんでした。",
Collections.emptyList(),
"NO_DOCUMENTS"
);
}
// ステップ2:サーキットブレーカー状态に応じた処理分岐
switch (state) {
case CLOSED -> {
// 正常状態:AI API を呼び出して回答生成
return generateAIResponse(query, documents, userId);
}
case HALF_OPEN -> {
// 半开启状態:试验的に AI API を呼び出し
log.info("サーキットブレーカー半开启状態 - 試験的 API 调用");
try {
return generateAIResponse(query, documents, userId);
} catch (Exception e) {
log.warn("試験的 API 调用失敗 - フォールバック処理に切换");
return generateFallbackResponse(query, documents);
}
}
case OPEN, FORCED_OPEN, DISABLED, METRICS_ONLY -> {
// 开启状態:フォールバック応答のみ返回
log.info("サーキットブレーカー开启状態 - フォールバック応答を返します");
return generateFallbackResponse(query, documents);
}
default -> {
return generateFallbackResponse(query, documents);
}
}
}
/**
* AI API を使用した回答生成
*/
private RAGResponse generateAIResponse(
String query,
List<SearchResult> documents,
String userId) {
// ドキュメント内容をコンテキストとして整形
String context = formatDocuments(documents);
String systemPrompt = """
あなたは企業の知識ベースを検索するアシスタントです。
提供されたドキュメントに基づいて、准确な回答を生成してください。
ドキュメントに情報がない場合は、「不确定」と明示的に回答してください。
""";
String userPrompt = String.format("""
質問: %s
参考ドキュメント:
%s
""", query, context);
try {
ChatCompletionRequest request = new ChatCompletionRequest(
"gpt-4.1",
List.of(
new ChatMessage("system", systemPrompt),
new ChatMessage("user", userPrompt)
),
0.3, // クリエイティブな応答より正確性を重視
2000
);
ChatCompletionResponse response = aiChatClient.chatCompletions(
"YOUR_HOLYSHEEP_API_KEY",
request
);
return new RAGResponse(
response.choices().get(0).message().content(),
documents,
"AI_GENERATED"
);
} catch (Exception e) {
log.error("RAG AI 生成エラー: {}", e.getMessage());
throw new RuntimeException("AI 回答生成に失敗しました", e);
}
}
/**
* フォールバック応答:ドキュメントのみを返回
*/
private RAGResponse generateFallbackResponse(
String query,
List<SearchResult> documents) {
StringBuilder response = new StringBuilder();
response.append("只今、AIサービスが不安定なため、関連ドキュメントのみを представします。\n\n");
documents.forEach(doc -> {
response.append("【").append(doc.title()).append("】\n");
response.append(doc.snippet()).append("\n\n");
});
response.append("\n※ AI による要約は 서비스를 안정화 후에利用できるようになります。");
return new RAGResponse(response.toString(), documents, "FALLBACK_DOCS_ONLY");
}
private String formatDocuments(List<SearchResult> documents) {
return documents.stream()
.map(d -> "- " + d.title() + ": " + d.content())
.collect(Collectors.joining("\n"));
}
// 監視用のMetrics Endpoint
@GetMapping("/api/rag/health")
public Map<String, Object> getCircuitBreakerHealth() {
CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("aiChat");
CircuitBreakerMetrics metrics = circuitBreaker.getMetrics();
return Map.of(
"state", circuitBreaker.getState().toString(),
"failureRate", metrics.getFailureRate(),
"numberOfSuccessfulCalls", metrics.getNumberOfSuccessfulCalls(),
"numberOfFailedCalls", metrics.getNumberOfFailedCalls(),
"numberOfNotPermittedCalls", metrics.getNumberOfNotPermittedCalls()
);
}
}
public record SearchResult(String id, String title, String snippet, String content) {}
public record RAGResponse(String answer, List<SearchResult> documents, String status) {}
モニタリングとヘルスチェックの実装
本番環境では、サーキットブレーカーの状態を可視化し、問題発生時に迅速に対応できるようにすることが重要です。
/**
* AI API サーキットブレーカー ヘルス監視サービス
*/
@Service
@Slf4j
public class AIHealthMonitorService {
private final CircuitBreakerRegistry circuitBreakerRegistry;
private final MeterRegistry meterRegistry;
@Autowired
public AIHealthMonitorService(
CircuitBreakerRegistry circuitBreakerRegistry,
MeterRegistry meterRegistry) {
this.circuitBreakerRegistry = circuitBreakerRegistry;
this.meterRegistry = meterRegistry;
// カスタムMetrics の登録
registerCustomMetrics();
}
private void registerCustomMetrics() {
Tags tags = Tags.of("service", "ai-api", "provider", "holySheep");
Gauge.builder("ai.circuitbreaker.state", this, AIHealthMonitorService::getCircuitBreakerStateValue)
.tags(tags)
.description("Circuit Breaker State (0=CLOSED, 1=HALF_OPEN, 2=OPEN)")
.register(meterRegistry);
Gauge.builder("ai.circuitbreaker.failureRate", this, AIHealthMonitorService::getFailureRate)
.tags(tags)
.description("Circuit Breaker Failure Rate")
.register(meterRegistry);
}
/**
* サーキットブレーカー状態の詳細取得
*/
public CircuitBreakerHealthReport getHealthReport() {
CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("aiChat");
CircuitBreakerMetrics metrics = circuitBreaker.getMetrics();
return new CircuitBreakerHealthReport(
circuitBreaker.getState(),
metrics.getFailureRate(),
metrics.getNumberOfSuccessfulCalls(),
metrics.getNumberOfFailedCalls(),
metrics.getNumberOfNotPermittedCalls(),
Instant.now()
);
}