BFFとAI Gatewayアーキテクチャ:2026年統一LLMアクセス層の設計

技术架构

AI時代、なぜAI Gatewayが必要なのか?

システムがOpenAI、Claude、Gemini、Qwen、DeepSeek…を同時に統合する場合、各LLMは異なるAPIフォーマット、課金方式、レート制限戦略を持つ。統一アクセス層がなければ、ビジネスコードはLLMベンダーに完全に結合される。

実際のケース:ある企業がOpenAIからClaudeに移行する際、APIフォーマットの違いで47ファイルを変更、2週間かかった。AI Gatewayがあれば、移行は設定1行の変更で済む。

BFFパターンの3つの進化

従来BFF(2018)
  異なるフロントエンド向けにバックエンドAPIを集約
  オーバーフェッチ問題を解決

AI-Enhanced BFF(2024)
  BFF層にAI機能を追加:要約、翻訳、コンテンツ生成
  AIはBFFの下流サービスの1つに過ぎない

AI Gateway(2026)
  AIがコアとなり、BFFはAIを中心に再構築
  複数LLMへの統一アクセス、ルーティング、課金、セキュリティを管理
  ビジネスコードはAI Gatewayのみと対話、LLMを直接呼び出さない

AI Gatewayのコア機能

┌──────────────────────────────────────────────────────┐
│                  ビジネスサービス層                    │
│   OrderService │ UserService │ ContentService         │
├──────────────────────────────────────────────────────┤
│                    AI Gateway                         │
│  ┌──────────┬──────────┬──────────┬───────────────┐  │
│  │ ルーティ │ レート   │ キャッシュ│ フォールバック│  │
│  │ ング戦略 │ リミット │ 管理     │ 戦略          │  │
│  ├──────────┼──────────┼──────────┼───────────────┤  │
│  │ Prompt   │ Token    │ 監査     │ セキュリティ  │  │
│  │ 管理     │ 課金     │ ログ     │ 保護          │  │
│  ├──────────┴──────────┴──────────┴───────────────┤  │
│  │         ストリーミングレスポンスプロキシ(SSE/WS) │  │
│  ├─────────────────────────────────────────────────┤  │
│  │         マルチモデルアダプタ層                    │  │
│  └──┬─────────┬─────────┬─────────┬───────────────┘  │
├─────┼─────────┼─────────┼─────────┼──────────────────┤
│  OpenAI │ Claude  │ Gemini  │ Qwen   │ DeepSeek       │
└──────────────────────────────────────────────────────┘

マルチモデルルーティング戦略

コスト/レイテンシ/品質によるスマートLLM選択

@Configuration
public class AiGatewayConfig {

    @Bean
    public ModelRouter modelRouter() {
        return ModelRouter.builder()
            .addStrategy(new CostOptimizedStrategy())
            .addStrategy(new LatencyOptimizedStrategy())
            .addStrategy(new QualityOptimizedStrategy())
            .addStrategy(new FallbackStrategy())
            .build();
    }
}

public class CostOptimizedStrategy implements RoutingStrategy {

    private static final Map<String, ModelPricing> PRICING = Map.of(
        "gpt-4o",           new ModelPricing(0.005, 0.015),
        "gpt-4o-mini",      new ModelPricing(0.00015, 0.0006),
        "claude-3.5-sonnet", new ModelPricing(0.003, 0.015),
        "deepseek-v3",      new ModelPricing(0.00027, 0.0011)
    );

    @Override
    public ModelSelection select(RoutingContext context) {
        String taskType = context.getTaskType();
        int estimatedTokens = context.getEstimatedTokens();

        return switch (taskType) {
            case "simple_qa"     -> selectModel("gpt-4o-mini", estimatedTokens);
            case "code_review"   -> selectModel("claude-3.5-sonnet", estimatedTokens);
            case "creative"      -> selectModel("gpt-4o", estimatedTokens);
            case "chinese_nlp"   -> selectModel("deepseek-v3", estimatedTokens);
            default              -> selectModel("gpt-4o", estimatedTokens);
        };
    }
}

Promptテンプレート管理とバージョン管理

@Service
public class PromptTemplateService {

    private final PromptTemplateRepository templateRepo;

    public PromptRenderResult render(String templateId, Map<String, String> variables) {
        PromptTemplate template = templateRepo.findLatestVersion(templateId);

        String renderedPrompt = template.getContent();
        for (Map.Entry<String, String> entry : variables.entrySet()) {
            renderedPrompt = renderedPrompt.replace("{{" + entry.getKey() + "}}", entry.getValue());
        }

        return PromptRenderResult.builder()
            .templateId(templateId)
            .version(template.getVersion())
            .renderedPrompt(renderedPrompt)
            .estimatedTokens(estimateTokens(renderedPrompt))
            .build();
    }
}

Token課金と使用量追跡

@Service
public class TokenBillingService {

    private final UsageRepository usageRepo;

    public UsageRecord recordUsage(UsageRequest request) {
        BigDecimal cost = calculateCost(
            request.getModel(),
            request.getInputTokens(),
            request.getOutputTokens()
        );

        UsageRecord record = UsageRecord.builder()
            .tenantId(request.getTenantId())
            .model(request.getModel())
            .inputTokens(request.getInputTokens())
            .outputTokens(request.getOutputTokens())
            .cost(cost)
            .promptTemplateId(request.getPromptTemplateId())
            .latencyMs(request.getLatencyMs())
            .build();

        return usageRepo.save(record);
    }
}

ストリーミングレスポンスプロキシ:SSEパススルー

@RestController
@RequestMapping("/api/ai")
public class StreamingAiController {

    private final AiGatewayService gatewayService;

    @PostMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<ServerSentEvent<String>> streamChat(@RequestBody ChatRequest request) {
        return gatewayService.streamChat(request)
            .map(chunk -> ServerSentEvent.<String>builder()
                .id(chunk.getId())
                .event("delta")
                .data(chunk.getContent())
                .build())
            .concatWith(Flux.just(
                ServerSentEvent.<String>builder()
                    .event("done")
                    .data("[DONE]")
                    .build()
            ));
    }
}

Spring Cloud Gateway + AI拡張実践

spring:
  cloud:
    gateway:
      routes:
        - id: openai-route
          uri: https://api.openai.com
          predicates:
            - Path=/api/ai/openai/**
          filters:
            - name: AiGateway
              args:
                provider: openai
                model: gpt-4o
                rateLimit: 100/s
                timeout: 30s

        - id: claude-route
          uri: https://api.anthropic.com
          predicates:
            - Path=/api/ai/claude/**
          filters:
            - name: AiGateway
              args:
                provider: anthropic
                model: claude-3.5-sonnet
                rateLimit: 50/s
                timeout: 60s

セキュリティ:Promptインジェクション防護と出力フィルタリング

@Service
public class AiSecurityService {

    private static final List<Pattern> INJECTION_PATTERNS = List.of(
        Pattern.compile("(?i)ignore\\s+(all\\s+)?previous\\s+instructions"),
        Pattern.compile("(?i)system\\s*:\\s*you\\s+are"),
        Pattern.compile("(?i)forget\\s+everything"),
        Pattern.compile("(?i)pretend\\s+you\\s+are")
    );

    private static final List<Pattern> SENSITIVE_PATTERNS = List.of(
        Pattern.compile("\\b\\d{16}\\b"),
        Pattern.compile("\\b\\d{17}[\\dXx]\\b"),
        Pattern.compile("[\\w.-]+@[\\w.-]+\\.\\w+")
    );

    public SecurityCheckResult checkInput(String prompt) {
        for (Pattern pattern : INJECTION_PATTERNS) {
            if (pattern.matcher(prompt).find()) {
                return SecurityCheckResult.blocked("Promptインジェクション攻撃の疑い");
            }
        }
        return SecurityCheckResult.passed();
    }

    public String sanitizeOutput(String output) {
        String sanitized = output;
        for (Pattern pattern : SENSITIVE_PATTERNS) {
            sanitized = pattern.matcher(sanitized).replaceAll("[REDACTED]");
        }
        return sanitized;
    }
}

まとめ

  1. AI GatewayはAI時代のインフラ — 複数LLMへの統一アクセス、ビジネスコードはゼロ結合
  2. マルチモデルルーティングでコスト40%削減 — タスクタイプ別に最適モデルをスマート選択
  3. セキュリティはベースライン — Promptインジェクション防護、出力フィルタリング、機密情報マスキングは不可欠
  4. Spring Cloud Gateway + AI拡張がベストプラクティス — ゲートウェイ層で統一管理、ビジネス層は透過的

AI Gatewayはオプションのアーキテクチャではなく、AI時代の必修科目である。早く構築するほど、早くLLMベンダーロックインから脱却できる。

ブラウザローカルツールを無料で試す →

#BFF#AI Gateway#Spring Cloud#LLM代理#多模型路由