AI API を活用したシステムにおいて、外部サービスへの依存は避けて通れません。しかし、急激なトラフィック増加やサービス障害发生时、システム全体への波及效应を防ぐ必要があります。本稿では、Java/Spring Boot 環境で AI API 调用のilience を確保するために、resilience4j を活用したサーキットブレーカーとグレードダウン策略について、実戦経験を交えながら解説いたします。

なぜ AI API にサーキットブレーカーが必要か

私は以前、電子商取引プラットフォームの AI カスタマーサービスシステムを構築していたとき、週末に予想外のトラフィック急増が発生し、AI API への过多要求而导致応答遅延が频発しました。この経験が、サーキットブレーカー実装の重要性を実感させたきっかけです。

AI API の特徴として、通常の REST API と比较して以下の点が異なります:

このような环境下で、サーキットブレーカーとグレードダウン机制を実装することで、以下の效果が得られます:

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()
        );
    }