Tasuke Hubのロゴ

ITを中心に困っている人を助けるメディア

分かりやすく解決策を提供することで、あなたの困ったをサポート。 全ての人々がスムーズに生活できる世界を目指します。

【2025年最新】LLM開発ベストプラクティス:アーキテクチャ設計からデプロイまでの完全ガイド

記事のサムネイル
TH

Tasuke Hub管理人

東証プライム市場上場企業エンジニア

情報系修士卒業後、大手IT企業にてフルスタックエンジニアとして活躍。 Webアプリケーション開発からクラウドインフラ構築まで幅広い技術に精通し、 複数のプロジェクトでリードエンジニアを担当。 技術ブログやオープンソースへの貢献を通じて、日本のIT技術コミュニティに積極的に関わっている。

🎓情報系修士🏢東証プライム上場企業💻フルスタックエンジニア📝技術ブログ執筆者

LLM開発の現状と課題:2025年の開発環境概観

大規模言語モデル(LLM)を活用したアプリケーション開発は、2025年現在、企業のデジタル変革において中核的な技術となっています。GPT-4o、Claude 3.5 Sonnet、Gemini Ultra等の高性能モデルの登場により、従来では不可能だった複雑なタスクの自動化や、人間レベルの推論能力を持つアプリケーションの開発が現実的になりました。

LLM開発における主要な課題

現代のLLM開発者が直面する主要な課題は以下の通りです:

  1. コスト管理: API使用料とインフラコストの最適化
  2. レイテンシ: リアルタイム応答が求められるアプリケーションでの性能確保
  3. 品質保証: 非決定的な出力の品質管理と一貫性の確保
  4. セキュリティ: プロンプトインジェクションや機密情報漏洩の防止
  5. スケーラビリティ: 大量のユーザーリクエストに対する安定した処理能力
// LLM開発における基本的なアーキテクチャパターン
interface LLMApplicationArchitecture {
  // クライアント層
  client: {
    frontend: 'React' | 'Vue' | 'Angular';
    mobile?: 'React Native' | 'Flutter';
    sdk?: 'TypeScript' | 'Python';
  };
  
  // API Gateway層
  gateway: {
    authentication: 'JWT' | 'OAuth2' | 'API Key';
    rateLimiting: RateLimitConfig;
    logging: LoggingConfig;
    caching: CacheConfig;
  };
  
  // アプリケーション層
  application: {
    promptEngine: PromptEngineConfig;
    llmOrchestrator: LLMOrchestratorConfig;
    responseProcessor: ResponseProcessorConfig;
    fallbackStrategy: FallbackConfig;
  };
  
  // LLM統合層
  llmIntegration: {
    providers: LLMProvider[];
    loadBalancer: LoadBalancerConfig;
    circuitBreaker: CircuitBreakerConfig;
    retryPolicy: RetryPolicyConfig;
  };
  
  // データ層
  data: {
    vectorDatabase: 'Pinecone' | 'Weaviate' | 'Chroma';
    conversationStore: 'Redis' | 'DynamoDB' | 'PostgreSQL';
    analyticsStore: 'ClickHouse' | 'BigQuery';
  };
  
  // インフラ層
  infrastructure: {
    deployment: 'Kubernetes' | 'AWS ECS' | 'Vercel';
    monitoring: 'Prometheus' | 'DataDog' | 'New Relic';
    alerting: 'PagerDuty' | 'OpsGenie';
  };
}

// 実装例:基本的なLLMクライアントクラス
class LLMClient {
  private providers: Map<string, LLMProvider>;
  private circuitBreaker: CircuitBreaker;
  private cache: Cache;
  private metrics: MetricsCollector;

  constructor(config: LLMClientConfig) {
    this.providers = this.initializeProviders(config.providers);
    this.circuitBreaker = new CircuitBreaker(config.circuitBreaker);
    this.cache = new Cache(config.cache);
    this.metrics = new MetricsCollector(config.metrics);
  }

  async generateResponse(
    request: LLMRequest,
    options: LLMOptions = {}
  ): Promise<LLMResponse> {
    const startTime = Date.now();
    
    try {
      // 1. リクエストの検証
      this.validateRequest(request);
      
      // 2. キャッシュチェック
      const cacheKey = this.generateCacheKey(request);
      const cachedResponse = await this.cache.get(cacheKey);
      if (cachedResponse && !options.skipCache) {
        this.metrics.recordCacheHit();
        return cachedResponse;
      }
      
      // 3. プロバイダー選択
      const provider = this.selectProvider(request, options);
      
      // 4. Circuit Breakerチェック
      if (!this.circuitBreaker.canExecute(provider.name)) {
        throw new Error(`Circuit breaker open for provider: ${provider.name}`);
      }
      
      // 5. LLM API呼び出し
      const response = await this.circuitBreaker.execute(
        provider.name,
        () => this.callLLM(provider, request, options)
      );
      
      // 6. レスポンス後処理
      const processedResponse = await this.processResponse(response, request);
      
      // 7. キャッシュ保存
      if (this.shouldCache(request, processedResponse)) {
        await this.cache.set(cacheKey, processedResponse, options.cacheTTL);
      }
      
      // 8. メトリクス記録
      this.metrics.recordSuccess(provider.name, Date.now() - startTime);
      
      return processedResponse;
      
    } catch (error) {
      this.metrics.recordError(error);
      
      // フォールバック戦略の実行
      return await this.handleFailure(request, error, options);
    }
  }
  
  private async callLLM(
    provider: LLMProvider, 
    request: LLMRequest, 
    options: LLMOptions
  ): Promise<LLMResponse> {
    // プロバイダー固有の実装
    switch (provider.type) {
      case 'openai':
        return await this.callOpenAI(provider, request, options);
      case 'anthropic':
        return await this.callAnthropic(provider, request, options);
      case 'google':
        return await this.callGoogle(provider, request, options);
      default:
        throw new Error(`Unsupported provider type: ${provider.type}`);
    }
  }
}

2025年のLLM開発トレンド

現在の開発トレンドには以下のようなものがあります:

  • マルチモーダル対応: テキスト、画像、音声を統合した処理能力
  • エージェント指向アーキテクチャ: 自律的なタスク実行が可能なAIエージェント
  • RAG(Retrieval-Augmented Generation): 外部知識ベースと連携した高精度回答生成
  • Function Calling: 外部APIやツールとの動的連携
  • Fine-tuning as a Service: 企業固有のニーズに特化したモデルカスタマイズ

これらのトレンドを踏まえた開発戦略の立案が、競争優位性の確立において重要になっています。

このトピックはこちらの書籍で勉強するのがおすすめ!

この記事の内容をさらに深く理解したい方におすすめの一冊です。実践的な知識を身につけたい方は、ぜひチェックしてみてください!

アーキテクチャ設計:スケーラブルなLLMアプリケーションの基盤

効果的なLLMアプリケーションの開発には、将来の拡張性とパフォーマンスを考慮したアーキテクチャ設計が不可欠です。ここでは、企業レベルで運用可能なスケーラブルなアーキテクチャパターンを詳しく解説します。

マイクロサービスアーキテクチャによるLLMシステム設計

// マイクロサービス構成の例
interface LLMMicroservicesArchitecture {
  services: {
    // プロンプト管理サービス
    promptService: {
      responsibilities: [
        'プロンプトテンプレート管理',
        'バージョン管理',
        'A/Bテスト機能',
        'プロンプト最適化'
      ];
      technologies: ['Node.js', 'TypeScript', 'PostgreSQL'];
      apis: [
        'GET /prompts/{id}',
        'POST /prompts',
        'PUT /prompts/{id}',
        'POST /prompts/{id}/test'
      ];
    };
    
    // LLMオーケストレーションサービス
    orchestrationService: {
      responsibilities: [
        'プロバイダー管理',
        'ロードバランシング',
        'フェイルオーバー',
        'レスポンス集約'
      ];
      technologies: ['Go', 'Redis', 'Kubernetes'];
      apis: [
        'POST /llm/generate',
        'POST /llm/batch',
        'GET /llm/status',
        'POST /llm/providers/{id}/health-check'
      ];
    };
    
    // コンテキスト管理サービス
    contextService: {
      responsibilities: [
        '会話履歴管理',
        'セッション管理',
        'コンテキスト圧縮',
        'メモリ最適化'
      ];
      technologies: ['Python', 'FastAPI', 'Redis Cluster'];
      apis: [
        'GET /context/{sessionId}',
        'POST /context/{sessionId}/messages',
        'DELETE /context/{sessionId}',
        'POST /context/{sessionId}/compress'
      ];
    };
    
    // RAGサービス
    ragService: {
      responsibilities: [
        'ドキュメント検索',
        'エンベディング生成',
        'セマンティック検索',
        'チャンク管理'
      ];
      technologies: ['Python', 'FastAPI', 'Pinecone', 'LangChain'];
      apis: [
        'POST /rag/search',
        'POST /rag/documents',
        'DELETE /rag/documents/{id}',
        'POST /rag/embeddings'
      ];
    };
    
    // 監視・分析サービス
    analyticsService: {
      responsibilities: [
        'ユーザー行動分析',
        'コスト追跡',
        '品質メトリクス',
        'パフォーマンス監視'
      ];
      technologies: ['Python', 'ClickHouse', 'Grafana'];
      apis: [
        'POST /analytics/events',
        'GET /analytics/dashboards/{id}',
        'GET /analytics/costs',
        'GET /analytics/quality-metrics'
      ];
    };
  };
}

// プロンプト管理サービスの実装例
class PromptManagementService {
  private promptRepository: PromptRepository;
  private versionManager: VersionManager;
  private abTestManager: ABTestManager;
  private metrics: MetricsCollector;

  constructor(dependencies: ServiceDependencies) {
    this.promptRepository = dependencies.promptRepository;
    this.versionManager = dependencies.versionManager;
    this.abTestManager = dependencies.abTestManager;
    this.metrics = dependencies.metrics;
  }

  async getPrompt(
    promptId: string, 
    context: PromptContext
  ): Promise<ResolvedPrompt> {
    try {
      // 1. A/Bテストグループの決定
      const testGroup = await this.abTestManager.getTestGroup(
        context.userId, 
        promptId
      );
      
      // 2. プロンプトテンプレートの取得
      const template = await this.promptRepository.getTemplate(
        promptId, 
        testGroup.version
      );
      
      if (!template) {
        throw new Error(`Prompt template not found: ${promptId}`);
      }
      
      // 3. コンテキスト変数の解決
      const resolvedPrompt = await this.resolveVariables(template, context);
      
      // 4. プロンプト使用統計の記録
      await this.metrics.recordPromptUsage(promptId, testGroup.version);
      
      return {
        id: promptId,
        version: testGroup.version,
        content: resolvedPrompt,
        metadata: {
          testGroup: testGroup.name,
          resolvedAt: new Date(),
          variables: context.variables
        }
      };
      
    } catch (error) {
      this.metrics.recordError('prompt_resolution_failed', error);
      throw new PromptResolutionError(`Failed to resolve prompt ${promptId}: ${error.message}`);
    }
  }

  async createPrompt(promptData: CreatePromptRequest): Promise<Prompt> {
    // プロンプトの作成とバージョン管理
    const validation = await this.validatePromptSyntax(promptData.template);
    if (!validation.isValid) {
      throw new ValidationError(`Invalid prompt syntax: ${validation.errors.join(', ')}`);
    }

    const prompt = await this.promptRepository.create({
      ...promptData,
      version: '1.0.0',
      status: 'draft',
      createdAt: new Date()
    });

    await this.versionManager.createVersion(prompt.id, prompt.version, prompt.template);
    
    return prompt;
  }

  async testPrompt(
    promptId: string, 
    testConfig: PromptTestConfig
  ): Promise<PromptTestResult> {
    // プロンプトのA/Bテスト実行
    const testResults = await Promise.all(
      testConfig.testCases.map(testCase => 
        this.executePromptTest(promptId, testCase)
      )
    );

    const aggregatedResults = this.aggregateTestResults(testResults);
    
    await this.abTestManager.recordTestResults(promptId, aggregatedResults);
    
    return aggregatedResults;
  }

  private async resolveVariables(
    template: string, 
    context: PromptContext
  ): Promise<string> {
    // テンプレート変数の解決ロジック
    let resolved = template;
    
    // 基本変数の置換
    for (const [key, value] of Object.entries(context.variables)) {
      const placeholder = `{{${key}}}`;
      resolved = resolved.replace(new RegExp(placeholder, 'g'), String(value));
    }
    
    // 動的関数の実行
    const functionMatches = resolved.match(/\{\{fn:(\w+)\((.*?)\)\}\}/g);
    if (functionMatches) {
      for (const match of functionMatches) {
        const [, functionName, args] = match.match(/\{\{fn:(\w+)\((.*?)\)\}\}/) || [];
        const result = await this.executeDynamicFunction(functionName, args, context);
        resolved = resolved.replace(match, result);
      }
    }
    
    return resolved;
  }

  private async executeDynamicFunction(
    functionName: string, 
    args: string, 
    context: PromptContext
  ): Promise<string> {
    // 動的関数の実行(例:現在時刻、ユーザー情報取得など)
    switch (functionName) {
      case 'currentTime':
        return new Date().toISOString();
      case 'userName':
        return context.user?.name || 'Unknown User';
      case 'randomExample':
        const examples = args.split(',').map(s => s.trim());
        return examples[Math.floor(Math.random() * examples.length)];
      default:
        throw new Error(`Unknown dynamic function: ${functionName}`);
    }
  }
}

RAG(Retrieval-Augmented Generation)アーキテクチャ

RAGは外部知識ベースと連携してより正確で最新の情報を提供するアーキテクチャパターンです。

# RAGサービスの実装例
from typing import List, Dict, Optional
from dataclasses import dataclass
import asyncio
from datetime import datetime

@dataclass
class Document:
    id: str
    content: str
    metadata: Dict
    embedding: Optional[List[float]] = None
    created_at: datetime = datetime.now()

@dataclass
class SearchResult:
    document: Document
    score: float
    relevance_explanation: str

class RAGService:
    def __init__(self, vector_db, embedding_model, llm_client):
        self.vector_db = vector_db
        self.embedding_model = embedding_model
        self.llm_client = llm_client
        self.chunk_size = 1000
        self.chunk_overlap = 200
        
    async def add_documents(self, documents: List[Document]) -> None:
        """ドキュメントをベクターデータベースに追加"""
        try:
            # 1. ドキュメントのチャンク分割
            chunks = []
            for doc in documents:
                doc_chunks = self._split_document(doc)
                chunks.extend(doc_chunks)
            
            # 2. エンベディングの生成
            embeddings = await self._generate_embeddings([chunk.content for chunk in chunks])
            
            # 3. ベクターデータベースに保存
            for chunk, embedding in zip(chunks, embeddings):
                chunk.embedding = embedding
                await self.vector_db.upsert(chunk)
                
            print(f"Successfully added {len(chunks)} chunks from {len(documents)} documents")
            
        except Exception as e:
            print(f"Error adding documents: {e}")
            raise

    async def search_documents(
        self, 
        query: str, 
        top_k: int = 5,
        filter_metadata: Optional[Dict] = None
    ) -> List[SearchResult]:
        """セマンティック検索の実行"""
        try:
            # 1. クエリのエンベディング生成
            query_embedding = await self._generate_embeddings([query])
            
            # 2. ベクター検索
            search_results = await self.vector_db.search(
                query_embedding[0],
                top_k=top_k,
                filter=filter_metadata
            )
            
            # 3. 関連性の説明生成
            enhanced_results = []
            for result in search_results:
                explanation = await self._generate_relevance_explanation(query, result.document.content)
                enhanced_results.append(SearchResult(
                    document=result.document,
                    score=result.score,
                    relevance_explanation=explanation
                ))
            
            return enhanced_results
            
        except Exception as e:
            print(f"Error searching documents: {e}")
            raise

    async def generate_answer(
        self, 
        question: str, 
        context_documents: Optional[List[Document]] = None,
        max_context_length: int = 4000
    ) -> Dict:
        """RAGを使った回答生成"""
        try:
            # 1. 関連ドキュメントの検索(contextが提供されていない場合)
            if context_documents is None:
                search_results = await self.search_documents(question, top_k=3)
                context_documents = [result.document for result in search_results]
            
            # 2. コンテキストの構築
            context = self._build_context(context_documents, max_context_length)
            
            # 3. プロンプトの構築
            prompt = self._build_rag_prompt(question, context)
            
            # 4. LLMによる回答生成
            response = await self.llm_client.generate_response(prompt)
            
            # 5. 引用情報の追加
            answer_with_citations = self._add_citations(response, context_documents)
            
            return {
                'answer': answer_with_citations,
                'sources': [doc.metadata for doc in context_documents],
                'context_used': context,
                'confidence_score': self._calculate_confidence(response, context_documents)
            }
            
        except Exception as e:
            print(f"Error generating RAG answer: {e}")
            raise

    def _split_document(self, document: Document) -> List[Document]:
        """ドキュメントを最適なサイズのチャンクに分割"""
        chunks = []
        content = document.content
        
        # シンプルな重複あり分割
        for i in range(0, len(content), self.chunk_size - self.chunk_overlap):
            chunk_content = content[i:i + self.chunk_size]
            
            if len(chunk_content.strip()) < 50:  # 短すぎるチャンクはスキップ
                continue
                
            chunk = Document(
                id=f"{document.id}_chunk_{i}",
                content=chunk_content,
                metadata={
                    **document.metadata,
                    'parent_document_id': document.id,
                    'chunk_index': i,
                    'chunk_size': len(chunk_content)
                }
            )
            chunks.append(chunk)
        
        return chunks

    async def _generate_embeddings(self, texts: List[str]) -> List[List[float]]:
        """テキストのエンベディング生成"""
        # バッチ処理で効率化
        batch_size = 32
        embeddings = []
        
        for i in range(0, len(texts), batch_size):
            batch = texts[i:i + batch_size]
            batch_embeddings = await self.embedding_model.encode(batch)
            embeddings.extend(batch_embeddings)
        
        return embeddings

    def _build_context(self, documents: List[Document], max_length: int) -> str:
        """検索されたドキュメントからコンテキストを構築"""
        context_parts = []
        current_length = 0
        
        for i, doc in enumerate(documents):
            doc_text = f"文書{i+1}: {doc.content}"
            
            if current_length + len(doc_text) > max_length:
                break
                
            context_parts.append(doc_text)
            current_length += len(doc_text)
        
        return '\n\n'.join(context_parts)

    def _build_rag_prompt(self, question: str, context: str) -> str:
        """RAG用のプロンプトを構築"""
        return f"""
以下の文書を参考に、質問に正確に答えてください。
答えは文書の内容に基づいて作成し、推測や想像は避けてください。
文書に情報がない場合は、「提供された文書にはその情報がありません」と回答してください。

参考文書:
{context}

質問: {question}

回答:"""

    def _add_citations(self, answer: str, documents: List[Document]) -> str:
        """回答に引用情報を追加"""
        # シンプルな引用追加(実際の実装では、より高度な引用マッチングが必要)
        citation_info = "\n\n**参考文献:**\n"
        for i, doc in enumerate(documents):
            title = doc.metadata.get('title', f'文書{i+1}')
            source = doc.metadata.get('source', 'Unknown')
            citation_info += f"- {title} ({source})\n"
        
        return answer + citation_info

    def _calculate_confidence(self, answer: str, documents: List[Document]) -> float:
        """回答の信頼度を計算"""
        # 簡単な信頼度計算(実際はより複雑なロジックが必要)
        if "提供された文書にはその情報がありません" in answer:
            return 0.1
        elif len(documents) >= 3:
            return 0.9
        elif len(documents) >= 2:
            return 0.7
        else:
            return 0.5

# RAGサービスの使用例
async def main():
    # 初期化
    vector_db = VectorDatabase()
    embedding_model = EmbeddingModel()
    llm_client = LLMClient()
    
    rag_service = RAGService(vector_db, embedding_model, llm_client)
    
    # ドキュメントの追加
    documents = [
        Document(
            id="doc1",
            content="人工知能(AI)は、コンピュータが人間のような知的能力を示す技術です...",
            metadata={"title": "AI入門", "source": "tech_book.pdf", "category": "technology"}
        ),
        Document(
            id="doc2", 
            content="機械学習は人工知能の一分野で、データから自動的に学習する能力を指します...",
            metadata={"title": "機械学習基礎", "source": "ml_guide.pdf", "category": "technology"}
        )
    ]
    
    await rag_service.add_documents(documents)
    
    # 質問への回答生成
    result = await rag_service.generate_answer("人工知能と機械学習の違いは何ですか?")
    
    print("回答:", result['answer'])
    print("信頼度:", result['confidence_score'])
    print("参考文献:", result['sources'])

if __name__ == "__main__":
    asyncio.run(main())

このようなアーキテクチャにより、スケーラブルで信頼性の高いLLMアプリケーションを構築することができます。

このトピックはこちらの書籍で勉強するのがおすすめ!

この記事の内容をさらに深く理解したい方におすすめの一冊です。実践的な知識を身につけたい方は、ぜひチェックしてみてください!

プロンプトエンジニアリング:システマティックなアプローチ

プロンプトエンジニアリングは、LLMアプリケーションの性能を決定する最も重要な要素の一つです。体系的なアプローチにより、一貫して高品質な出力を得ることができます。

プロンプトテンプレートシステムの構築

// プロンプトテンプレートシステムの実装
interface PromptTemplate {
  id: string;
  name: string;
  version: string;
  template: string;
  variables: VariableDefinition[];
  examples: PromptExample[];
  metadata: PromptMetadata;
}

interface VariableDefinition {
  name: string;
  type: 'string' | 'number' | 'boolean' | 'array' | 'object';
  required: boolean;
  description: string;
  validation?: ValidationRule;
  defaultValue?: any;
}

interface PromptExample {
  input: Record<string, any>;
  expectedOutput: string;
  explanation: string;
}

interface PromptMetadata {
  purpose: string;
  domain: string;
  complexity: 'low' | 'medium' | 'high';
  estimatedTokens: number;
  tags: string[];
  created: Date;
  updated: Date;
}

class PromptTemplateEngine {
  private templates: Map<string, PromptTemplate> = new Map();
  private validator: PromptValidator;
  private optimizer: PromptOptimizer;

  constructor() {
    this.validator = new PromptValidator();
    this.optimizer = new PromptOptimizer();
  }

  // プロンプトテンプレートの登録
  registerTemplate(template: PromptTemplate): void {
    // バリデーション
    const validation = this.validator.validate(template);
    if (!validation.isValid) {
      throw new Error(`Invalid template: ${validation.errors.join(', ')}`);
    }

    this.templates.set(template.id, template);
  }

  // プロンプトの生成
  async generatePrompt(
    templateId: string,
    variables: Record<string, any>,
    options: GenerationOptions = {}
  ): Promise<GeneratedPrompt> {
    const template = this.templates.get(templateId);
    if (!template) {
      throw new Error(`Template not found: ${templateId}`);
    }

    // 変数の検証
    this.validateVariables(template, variables);

    // テンプレートの処理
    let prompt = template.template;

    // 1. 基本変数の置換
    prompt = this.replaceBasicVariables(prompt, variables);

    // 2. 条件分岐の処理
    prompt = this.processConditionals(prompt, variables);

    // 3. ループ処理
    prompt = this.processLoops(prompt, variables);

    // 4. 関数呼び出し
    prompt = await this.processFunctions(prompt, variables);

    // 5. 最適化(オプション)
    if (options.optimize) {
      prompt = await this.optimizer.optimize(prompt, options.optimizationGoals);
    }

    return {
      prompt,
      templateId,
      variables,
      metadata: {
        estimatedTokens: this.estimateTokens(prompt),
        generatedAt: new Date(),
        version: template.version
      }
    };
  }

  private replaceBasicVariables(template: string, variables: Record<string, any>): string {
    return template.replace(/\{\{(\w+)\}\}/g, (match, varName) => {
      const value = variables[varName];
      return value !== undefined ? String(value) : match;
    });
  }

  private processConditionals(template: string, variables: Record<string, any>): string {
    // {{#if condition}}content{{/if}} 形式の条件分岐処理
    return template.replace(
      /\{\{#if\s+(\w+)\}\}(.*?)\{\{\/if\}\}/gs,
      (match, condition, content) => {
        return variables[condition] ? content : '';
      }
    );
  }

  private processLoops(template: string, variables: Record<string, any>): string {
    // {{#each items}}{{name}}{{/each}} 形式のループ処理
    return template.replace(
      /\{\{#each\s+(\w+)\}\}(.*?)\{\{\/each\}\}/gs,
      (match, arrayName, itemTemplate) => {
        const items = variables[arrayName];
        if (!Array.isArray(items)) return '';

        return items.map(item => {
          return itemTemplate.replace(/\{\{(\w+)\}\}/g, (_, prop) => {
            return item[prop] || '';
          });
        }).join('');
      }
    );
  }

  private async processFunctions(template: string, variables: Record<string, any>): Promise<string> {
    // {{fn:functionName(arg1, arg2)}} 形式の関数処理
    const functionRegex = /\{\{fn:(\w+)\((.*?)\)\}\}/g;
    let result = template;
    const matches = [...template.matchAll(functionRegex)];

    for (const match of matches) {
      const [fullMatch, functionName, argsString] = match;
      const args = this.parseArguments(argsString, variables);
      const functionResult = await this.executeFunction(functionName, args, variables);
      result = result.replace(fullMatch, functionResult);
    }

    return result;
  }

  private parseArguments(argsString: string, variables: Record<string, any>): any[] {
    if (!argsString.trim()) return [];

    return argsString.split(',').map(arg => {
      const trimmed = arg.trim();
      
      // 変数参照
      if (trimmed.startsWith('$')) {
        const varName = trimmed.slice(1);
        return variables[varName];
      }
      
      // 文字列リテラル
      if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
        return trimmed.slice(1, -1);
      }
      
      // 数値
      if (!isNaN(Number(trimmed))) {
        return Number(trimmed);
      }
      
      // ブール値
      if (trimmed === 'true') return true;
      if (trimmed === 'false') return false;
      
      return trimmed;
    });
  }

  private async executeFunction(
    functionName: string, 
    args: any[], 
    context: Record<string, any>
  ): Promise<string> {
    switch (functionName) {
      case 'currentTime':
        return new Date().toISOString();
      
      case 'randomChoice':
        const choices = args[0];
        return Array.isArray(choices) 
          ? choices[Math.floor(Math.random() * choices.length)]
          : '';
      
      case 'formatList':
        const items = args[0];
        const format = args[1] || 'bullet';
        return this.formatList(items, format);
      
      case 'summarize':
        const text = args[0];
        const maxLength = args[1] || 100;
        return this.summarizeText(text, maxLength);
      
      default:
        throw new Error(`Unknown function: ${functionName}`);
    }
  }

  private formatList(items: any[], format: string): string {
    if (!Array.isArray(items)) return '';

    switch (format) {
      case 'bullet':
        return items.map(item => `• ${item}`).join('\n');
      case 'numbered':
        return items.map((item, index) => `${index + 1}. ${item}`).join('\n');
      case 'comma':
        return items.join(', ');
      default:
        return items.join('\n');
    }
  }

  private summarizeText(text: string, maxLength: number): string {
    if (text.length <= maxLength) return text;
    return text.substring(0, maxLength - 3) + '...';
  }

  private estimateTokens(text: string): number {
    // 簡単なトークン数推定(実際の実装では専用ライブラリを使用)
    return Math.ceil(text.length / 4);
  }
}

// プロンプトテンプレートの例
const customerSupportTemplate: PromptTemplate = {
  id: 'customer-support-v1',
  name: 'Customer Support Response',
  version: '1.0.0',
  template: `
あなたは{{companyName}}のカスタマーサポート担当者です。

顧客情報:
- 名前: {{customerName}}
- 会員レベル: {{membershipLevel}}
- 過去の問い合わせ回数: {{previousInquiries}}

{{#if membershipLevel === 'premium'}}
この顧客はプレミアム会員のため、特別な配慮をお願いします。
{{/if}}

顧客からの問い合わせ:
{{inquiry}}

{{#if previousSolutions}}
過去の解決策:
{{#each previousSolutions}}
- {{date}}: {{solution}}
{{/each}}
{{/if}}

以下の方針で回答してください:
1. 親切で丁寧な対応
2. 具体的で実行可能な解決策の提示
3. 必要に応じて追加の支援を提案
4. {{companyName}}のブランド価値を体現した対応

現在時刻: {{fn:currentTime()}}
`,
  variables: [
    {
      name: 'companyName',
      type: 'string',
      required: true,
      description: '企業名'
    },
    {
      name: 'customerName',
      type: 'string',
      required: true,
      description: '顧客名'
    },
    {
      name: 'membershipLevel',
      type: 'string',
      required: true,
      description: '会員レベル (basic, premium, enterprise)',
      validation: {
        enum: ['basic', 'premium', 'enterprise']
      }
    },
    {
      name: 'inquiry',
      type: 'string',
      required: true,
      description: '顧客からの問い合わせ内容'
    },
    {
      name: 'previousInquiries',
      type: 'number',
      required: false,
      description: '過去の問い合わせ回数',
      defaultValue: 0
    },
    {
      name: 'previousSolutions',
      type: 'array',
      required: false,
      description: '過去の解決策のリスト'
    }
  ],
  examples: [
    {
      input: {
        companyName: 'TechCorp',
        customerName: '田中太郎',
        membershipLevel: 'premium',
        inquiry: '商品の返品をしたいのですが、手続きがわかりません。',
        previousInquiries: 2
      },
      expectedOutput: '丁寧で具体的な返品手続きの説明',
      explanation: 'プレミアム会員に対する特別配慮を含む応答'
    }
  ],
  metadata: {
    purpose: 'カスタマーサポート応答の生成',
    domain: 'customer-service',
    complexity: 'medium',
    estimatedTokens: 200,
    tags: ['customer-support', 'service', 'template'],
    created: new Date(),
    updated: new Date()
  }
};

// 使用例
const promptEngine = new PromptTemplateEngine();
promptEngine.registerTemplate(customerSupportTemplate);

const generatedPrompt = await promptEngine.generatePrompt(
  'customer-support-v1',
  {
    companyName: 'TechCorp',
    customerName: '田中太郎',
    membershipLevel: 'premium',
    inquiry: '注文した商品がまだ届きません。',
    previousInquiries: 1,
    previousSolutions: [
      { date: '2024-01-15', solution: '配送状況の確認と追跡番号の提供' }
    ]
  },
  { optimize: true }
);

console.log(generatedPrompt.prompt);

A/Bテストによるプロンプト最適化

// プロンプトA/Bテストシステム
class PromptABTestManager {
  private experiments: Map<string, ABTestExperiment> = new Map();
  private trafficSplitter: TrafficSplitter;
  private metricsCollector: MetricsCollector;

  constructor() {
    this.trafficSplitter = new TrafficSplitter();
    this.metricsCollector = new MetricsCollector();
  }

  async createExperiment(config: ABTestConfig): Promise<string> {
    const experiment: ABTestExperiment = {
      id: this.generateExperimentId(),
      name: config.name,
      description: config.description,
      variants: config.variants,
      trafficAllocation: config.trafficAllocation,
      successMetrics: config.successMetrics,
      startDate: config.startDate,
      endDate: config.endDate,
      status: 'active',
      results: new Map()
    };

    this.experiments.set(experiment.id, experiment);
    
    await this.metricsCollector.initializeExperiment(experiment);
    
    return experiment.id;
  }

  async getVariant(
    experimentId: string, 
    userId: string, 
    context: any = {}
  ): Promise<PromptVariant> {
    const experiment = this.experiments.get(experimentId);
    if (!experiment || experiment.status !== 'active') {
      throw new Error(`Experiment not found or inactive: ${experimentId}`);
    }

    // ユーザーをバリアントに割り当て
    const variantKey = this.trafficSplitter.getVariant(
      userId, 
      experiment.trafficAllocation
    );

    const variant = experiment.variants.find(v => v.key === variantKey);
    if (!variant) {
      throw new Error(`Variant not found: ${variantKey}`);
    }

    // 実験参加の記録
    await this.metricsCollector.recordParticipation(
      experimentId, 
      variantKey, 
      userId, 
      context
    );

    return variant;
  }

  async recordSuccess(
    experimentId: string,
    userId: string,
    metricName: string,
    value: number,
    metadata: any = {}
  ): Promise<void> {
    await this.metricsCollector.recordSuccess(
      experimentId,
      userId,
      metricName,
      value,
      metadata
    );
  }

  async getExperimentResults(experimentId: string): Promise<ABTestResults> {
    const experiment = this.experiments.get(experimentId);
    if (!experiment) {
      throw new Error(`Experiment not found: ${experimentId}`);
    }

    const results = await this.metricsCollector.getExperimentResults(experimentId);
    
    // 統計的有意性の計算
    const statisticalSignificance = this.calculateStatisticalSignificance(results);
    
    return {
      experimentId,
      variantResults: results,
      winner: this.determineWinner(results, statisticalSignificance),
      statisticalSignificance,
      confidence: this.calculateConfidence(results),
      recommendations: this.generateRecommendations(results, experiment)
    };
  }

  private calculateStatisticalSignificance(results: VariantResults[]): StatisticalSignificance {
    // Z-testまたはChi-square testによる有意性計算
    const controlVariant = results.find(r => r.isControl);
    const testVariants = results.filter(r => !r.isControl);
    
    const significance: StatisticalSignificance = {
      pValue: 0,
      isSignificant: false,
      confidenceInterval: { lower: 0, upper: 0 },
      method: 'z-test'
    };

    if (!controlVariant || testVariants.length === 0) {
      return significance;
    }

    // 簡単なZ-test実装(実際の実装では統計ライブラリを使用)
    for (const testVariant of testVariants) {
      const pooledSuccessRate = (controlVariant.successes + testVariant.successes) / 
                               (controlVariant.totalParticipants + testVariant.totalParticipants);
      
      const standardError = Math.sqrt(
        pooledSuccessRate * (1 - pooledSuccessRate) * 
        (1/controlVariant.totalParticipants + 1/testVariant.totalParticipants)
      );
      
      const zScore = (testVariant.successRate - controlVariant.successRate) / standardError;
      const pValue = 2 * (1 - this.normalCDF(Math.abs(zScore)));
      
      if (pValue < 0.05) {
        significance.isSignificant = true;
        significance.pValue = Math.min(significance.pValue || 1, pValue);
      }
    }

    return significance;
  }

  private normalCDF(x: number): number {
    // 標準正規分布の累積分布関数の近似
    return (1 + Math.sign(x) * Math.sqrt(1 - Math.exp(-2 * x * x / Math.PI))) / 2;
  }

  private determineWinner(
    results: VariantResults[], 
    significance: StatisticalSignificance
  ): string | null {
    if (!significance.isSignificant) {
      return null;
    }

    return results.reduce((winner, current) => 
      current.successRate > winner.successRate ? current : winner
    ).variantKey;
  }

  private generateRecommendations(
    results: VariantResults[],
    experiment: ABTestExperiment
  ): string[] {
    const recommendations: string[] = [];
    
    const winner = results.reduce((w, c) => c.successRate > w.successRate ? c : w);
    const improvement = ((winner.successRate - results[0].successRate) / results[0].successRate) * 100;
    
    if (improvement > 10) {
      recommendations.push(`Winner variant shows ${improvement.toFixed(1)}% improvement - recommend full rollout`);
    } else if (improvement > 5) {
      recommendations.push(`Moderate improvement detected - consider extended testing`);
    } else {
      recommendations.push(`Minimal difference - consider testing alternative approaches`);
    }
    
    return recommendations;
  }
}

// A/Bテスト設定例
const abTestConfig: ABTestConfig = {
  name: 'Product Description Generation',
  description: 'Testing different approaches for product description generation',
  variants: [
    {
      key: 'control',
      name: 'Current Template',
      promptTemplate: 'standard-product-description',
      isControl: true
    },
    {
      key: 'emotional',
      name: 'Emotional Appeal',
      promptTemplate: 'emotional-product-description',
      isControl: false
    },
    {
      key: 'technical',
      name: 'Technical Focus',
      promptTemplate: 'technical-product-description',
      isControl: false
    }
  ],
  trafficAllocation: {
    'control': 0.4,
    'emotional': 0.3,
    'technical': 0.3
  },
  successMetrics: ['click_through_rate', 'conversion_rate', 'user_engagement'],
  startDate: new Date(),
  endDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000) // 30日後
};

// 使用例
const abTestManager = new PromptABTestManager();
const experimentId = await abTestManager.createExperiment(abTestConfig);

// ユーザーリクエスト時
const variant = await abTestManager.getVariant(experimentId, 'user123');
const prompt = await promptEngine.generatePrompt(variant.promptTemplate, productData);
const response = await llmClient.generate(prompt);

// 成功指標の記録
await abTestManager.recordSuccess(experimentId, 'user123', 'click_through_rate', 1);

このようなシステマティックなアプローチにより、プロンプトの品質を継続的に改善し、ビジネス目標に最適化されたLLMアプリケーションを構築できます。

このトピックはこちらの書籍で勉強するのがおすすめ!

この記事の内容をさらに深く理解したい方におすすめの一冊です。実践的な知識を身につけたい方は、ぜひチェックしてみてください!

パフォーマンス最適化:レスポンス時間とコスト効率の改善

LLMアプリケーションにおけるパフォーマンス最適化は、ユーザー体験とコスト効率の両方において重要な課題です。効果的な最適化戦略により、応答速度を向上させながら運用コストを削減できます。

マルチレベルキャッシュ戦略

// 階層化キャッシュシステムの実装
interface CacheConfig {
  levels: CacheLevel[];
  defaultTTL: number;
  maxMemoryUsage: number;
  evictionPolicy: 'LRU' | 'LFU' | 'FIFO';
}

interface CacheLevel {
  name: string;
  type: 'memory' | 'redis' | 'disk';
  capacity: number;
  ttl: number;
  priority: number;
}

class HierarchicalCache {
  private cacheLevels: Map<string, CacheProvider> = new Map();
  private config: CacheConfig;
  private metrics: CacheMetrics;

  constructor(config: CacheConfig) {
    this.config = config;
    this.metrics = new CacheMetrics();
    this.initializeCacheLevels();
  }

  private initializeCacheLevels(): void {
    for (const level of this.config.levels) {
      let provider: CacheProvider;
      
      switch (level.type) {
        case 'memory':
          provider = new MemoryCache(level);
          break;
        case 'redis':
          provider = new RedisCache(level);
          break;
        case 'disk':
          provider = new DiskCache(level);
          break;
        default:
          throw new Error(`Unsupported cache type: ${level.type}`);
      }
      
      this.cacheLevels.set(level.name, provider);
    }
  }

  async get(key: string): Promise<any> {
    const startTime = Date.now();
    
    // 優先度順にキャッシュレベルを確認
    const sortedLevels = this.config.levels.sort((a, b) => a.priority - b.priority);
    
    for (const level of sortedLevels) {
      const provider = this.cacheLevels.get(level.name);
      if (!provider) continue;
      
      try {
        const value = await provider.get(key);
        if (value !== null) {
          this.metrics.recordHit(level.name, Date.now() - startTime);
          
          // 上位レベルにプロモート
          await this.promoteToUpperLevels(key, value, level);
          
          return value;
        }
      } catch (error) {
        console.warn(`Cache level ${level.name} error:`, error);
      }
    }
    
    this.metrics.recordMiss(Date.now() - startTime);
    return null;
  }

  async set(key: string, value: any, ttl?: number): Promise<void> {
    const effectiveTTL = ttl || this.config.defaultTTL;
    
    // 全レベルに保存
    const promises = this.config.levels.map(async level => {
      const provider = this.cacheLevels.get(level.name);
      if (provider) {
        try {
          await provider.set(key, value, Math.min(effectiveTTL, level.ttl));
        } catch (error) {
          console.warn(`Failed to set cache in ${level.name}:`, error);
        }
      }
    });
    
    await Promise.allSettled(promises);
  }

  private async promoteToUpperLevels(
    key: string, 
    value: any, 
    currentLevel: CacheLevel
  ): Promise<void> {
    const upperLevels = this.config.levels.filter(
      level => level.priority < currentLevel.priority
    );
    
    for (const level of upperLevels) {
      const provider = this.cacheLevels.get(level.name);
      if (provider) {
        try {
          await provider.set(key, value, level.ttl);
        } catch (error) {
          console.warn(`Failed to promote to ${level.name}:`, error);
        }
      }
    }
  }

  async invalidate(pattern: string): Promise<void> {
    const promises = Array.from(this.cacheLevels.values()).map(
      provider => provider.invalidate(pattern)
    );
    await Promise.allSettled(promises);
  }

  getMetrics(): CacheMetricsReport {
    return this.metrics.getReport();
  }
}

// スマートキャッシュキー生成
class CacheKeyGenerator {
  static generateLLMCacheKey(
    prompt: string,
    model: string,
    parameters: any,
    context?: any
  ): string {
    // プロンプトの正規化
    const normalizedPrompt = this.normalizePrompt(prompt);
    
    // パラメータのハッシュ化
    const paramHash = this.hashObject(parameters);
    
    // コンテキストの重要部分のみ考慮
    const contextHash = context ? this.hashObject(this.extractRelevantContext(context)) : '';
    
    return `llm:${model}:${this.hashString(normalizedPrompt)}:${paramHash}:${contextHash}`;
  }

  private static normalizePrompt(prompt: string): string {
    return prompt
      .trim()
      .replace(/\s+/g, ' ')  // 複数の空白を単一の空白に
      .toLowerCase();
  }

  private static extractRelevantContext(context: any): any {
    // コンテキストから影響の大きい要素のみ抽出
    return {
      userId: context.userId,
      sessionType: context.sessionType,
      timestamp: Math.floor(Date.now() / (1000 * 60 * 60)) // 時間単位で丸める
    };
  }

  private static hashString(str: string): string {
    // 簡単なハッシュ関数(実際の実装ではcrypto.createHashを使用)
    let hash = 0;
    for (let i = 0; i < str.length; i++) {
      const char = str.charCodeAt(i);
      hash = ((hash << 5) - hash) + char;
      hash = hash & hash; // 32bit整数に変換
    }
    return hash.toString(36);
  }

  private static hashObject(obj: any): string {
    return this.hashString(JSON.stringify(obj, Object.keys(obj).sort()));
  }
}

// LLMレスポンスの条件付きキャッシュ
class ConditionalCacheStrategy {
  static shouldCache(
    request: LLMRequest,
    response: LLMResponse,
    metadata: any
  ): boolean {
    // キャッシュすべきでない条件
    if (this.containsSensitiveInformation(request, response)) {
      return false;
    }
    
    if (this.isUserSpecificContent(request)) {
      return false;
    }
    
    if (this.isTimeDependent(request)) {
      return false;
    }
    
    // キャッシュすべき条件
    if (this.isExpensiveOperation(metadata)) {
      return true;
    }
    
    if (this.isCommonQuery(request)) {
      return true;
    }
    
    return false;
  }

  static getCacheTTL(
    request: LLMRequest,
    response: LLMResponse
  ): number {
    // コンテンツタイプに基づくTTL決定
    if (this.isFactualContent(request)) {
      return 24 * 60 * 60; // 24時間
    }
    
    if (this.isCreativeContent(request)) {
      return 60 * 60; // 1時間
    }
    
    if (this.isAnalysisContent(request)) {
      return 12 * 60 * 60; // 12時間
    }
    
    return 60 * 60; // デフォルト1時間
  }

  private static containsSensitiveInformation(
    request: LLMRequest,
    response: LLMResponse
  ): boolean {
    const sensitivePatterns = [
      /password/i,
      /secret/i,
      /api[_\s]?key/i,
      /credit[_\s]?card/i,
      /ssn/i,
      /social[_\s]?security/i
    ];
    
    const combinedText = request.prompt + response.content;
    return sensitivePatterns.some(pattern => pattern.test(combinedText));
  }

  private static isUserSpecificContent(request: LLMRequest): boolean {
    return request.prompt.includes('my ') || 
           request.prompt.includes('I ') ||
           request.context?.userId !== undefined;
  }

  private static isTimeDependent(request: LLMRequest): boolean {
    const timePatterns = [
      /today/i,
      /now/i,
      /current/i,
      /latest/i,
      /recent/i
    ];
    
    return timePatterns.some(pattern => pattern.test(request.prompt));
  }

  private static isExpensiveOperation(metadata: any): boolean {
    return metadata.tokenCount > 2000 || 
           metadata.processingTime > 5000;
  }

  private static isCommonQuery(request: LLMRequest): boolean {
    // よくある質問のパターンマッチング
    const commonPatterns = [
      /what is/i,
      /how to/i,
      /explain/i,
      /define/i
    ];
    
    return commonPatterns.some(pattern => pattern.test(request.prompt));
  }

  private static isFactualContent(request: LLMRequest): boolean {
    return request.prompt.includes('fact') || 
           request.prompt.includes('definition') ||
           request.prompt.includes('explain');
  }

  private static isCreativeContent(request: LLMRequest): boolean {
    return request.prompt.includes('creative') || 
           request.prompt.includes('story') ||
           request.prompt.includes('poem');
  }

  private static isAnalysisContent(request: LLMRequest): boolean {
    return request.prompt.includes('analyze') || 
           request.prompt.includes('summarize') ||
           request.prompt.includes('compare');
  }
}

ストリーミングとバッチ処理の最適化

# ストリーミングレスポンス処理の実装
import asyncio
from typing import AsyncGenerator, List, Dict, Any
from dataclasses import dataclass
import time

@dataclass
class StreamingConfig:
    chunk_size: int = 50
    max_buffer_size: int = 1000
    flush_interval: float = 0.1
    enable_compression: bool = True
    enable_delta_encoding: bool = True

class StreamingLLMProcessor:
    def __init__(self, config: StreamingConfig):
        self.config = config
        self.active_streams: Dict[str, StreamContext] = {}
        
    async def stream_response(
        self, 
        request_id: str,
        prompt: str,
        model: str,
        **kwargs
    ) -> AsyncGenerator[str, None]:
        """ストリーミングレスポンスの生成"""
        
        # ストリームコンテキストの初期化
        context = StreamContext(
            request_id=request_id,
            start_time=time.time(),
            buffer=[],
            last_sent=0
        )
        self.active_streams[request_id] = context
        
        try:
            # LLMストリームの開始
            stream = await self.llm_client.stream(prompt, model, **kwargs)
            
            async for chunk in stream:
                # チャンクの処理と最適化
                processed_chunk = await self.process_chunk(chunk, context)
                
                if processed_chunk:
                    yield processed_chunk
                    context.last_sent = time.time()
                    
                # バッファサイズのチェック
                if len(context.buffer) > self.config.max_buffer_size:
                    await self.flush_buffer(context)
            
            # 最終フラッシュ
            final_chunk = await self.finalize_stream(context)
            if final_chunk:
                yield final_chunk
                
        except Exception as e:
            # エラーストリームの送信
            error_chunk = await self.create_error_chunk(str(e), context)
            yield error_chunk
            
        finally:
            # クリーンアップ
            self.active_streams.pop(request_id, None)
    
    async def process_chunk(self, chunk: str, context: StreamContext) -> str:
        """チャンクの処理と最適化"""
        
        # バッファに追加
        context.buffer.append(chunk)
        
        # チャンクサイズに達したかチェック
        if len(''.join(context.buffer)) >= self.config.chunk_size:
            return await self.create_optimized_chunk(context)
        
        # 時間ベースのフラッシュ
        if time.time() - context.last_sent > self.config.flush_interval:
            return await self.create_optimized_chunk(context)
        
        return None
    
    async def create_optimized_chunk(self, context: StreamContext) -> str:
        """最適化されたチャンクの作成"""
        if not context.buffer:
            return None
        
        content = ''.join(context.buffer)
        context.buffer.clear()
        
        # デルタエンコーディング(差分のみ送信)
        if self.config.enable_delta_encoding:
            content = self.apply_delta_encoding(content, context)
        
        # 圧縮
        if self.config.enable_compression:
            content = await self.compress_content(content)
        
        return self.format_chunk(content, context)
    
    def apply_delta_encoding(self, content: str, context: StreamContext) -> str:
        """差分エンコーディングの適用"""
        # 前回送信分との差分のみ計算
        if hasattr(context, 'last_content'):
            # 簡単な差分計算(実際の実装ではより効率的なアルゴリズムを使用)
            return content[len(context.last_content):]
        
        context.last_content = content
        return content
    
    async def compress_content(self, content: str) -> str:
        """コンテンツの圧縮"""
        # 簡単な圧縮実装(実際はgzipなどを使用)
        return content.strip()
    
    def format_chunk(self, content: str, context: StreamContext) -> str:
        """チャンクのフォーマット"""
        return f"data: {content}\n\n"

# バッチ処理の最適化
class BatchLLMProcessor:
    def __init__(self, max_batch_size: int = 10, batch_timeout: float = 1.0):
        self.max_batch_size = max_batch_size
        self.batch_timeout = batch_timeout
        self.pending_requests: List[BatchRequest] = []
        self.batch_lock = asyncio.Lock()
        
    async def process_request(self, request: LLMRequest) -> LLMResponse:
        """バッチ処理でリクエストを処理"""
        
        # バッチリクエストの作成
        batch_request = BatchRequest(
            id=self.generate_request_id(),
            request=request,
            future=asyncio.Future(),
            timestamp=time.time()
        )
        
        async with self.batch_lock:
            self.pending_requests.append(batch_request)
            
            # バッチ実行の条件チェック
            should_execute = (
                len(self.pending_requests) >= self.max_batch_size or
                self.should_timeout_batch()
            )
            
            if should_execute:
                # バッチの実行
                await self.execute_batch()
        
        # 結果の待機
        return await batch_request.future
    
    async def execute_batch(self):
        """バッチの実行"""
        if not self.pending_requests:
            return
        
        batch = self.pending_requests.copy()
        self.pending_requests.clear()
        
        try:
            # 並列LLM呼び出し
            responses = await self.call_llm_batch([req.request for req in batch])
            
            # 結果の配布
            for batch_request, response in zip(batch, responses):
                batch_request.future.set_result(response)
                
        except Exception as e:
            # エラーの配布
            for batch_request in batch:
                batch_request.future.set_exception(e)
    
    async def call_llm_batch(self, requests: List[LLMRequest]) -> List[LLMResponse]:
        """LLMバッチ呼び出し"""
        # 並列処理でLLM API呼び出し
        tasks = [
            self.call_single_llm(request) 
            for request in requests
        ]
        
        return await asyncio.gather(*tasks, return_exceptions=True)
    
    async def call_single_llm(self, request: LLMRequest) -> LLMResponse:
        """単一LLM呼び出し"""
        # 実際のLLM API呼び出し実装
        pass
    
    def should_timeout_batch(self) -> bool:
        """バッチタイムアウトの判定"""
        if not self.pending_requests:
            return False
        
        oldest_request = min(self.pending_requests, key=lambda r: r.timestamp)
        return time.time() - oldest_request.timestamp > self.batch_timeout

# 使用例
streaming_processor = StreamingLLMProcessor(StreamingConfig(
    chunk_size=100,
    flush_interval=0.05,
    enable_compression=True
))

batch_processor = BatchLLMProcessor(
    max_batch_size=5,
    batch_timeout=0.5
)

# ストリーミング処理
async def handle_streaming_request(request_id: str, prompt: str):
    async for chunk in streaming_processor.stream_response(request_id, prompt, "gpt-4"):
        # WebSocketまたはServer-Sent Eventsでクライアントに送信
        await send_to_client(chunk)

# バッチ処理
async def handle_batch_request(request: LLMRequest):
    response = await batch_processor.process_request(request)
    return response

自動スケーリングとロードバランシング

// 自動スケーリング機能の実装
interface ScalingConfig {
  minInstances: number;
  maxInstances: number;
  targetCPUUtilization: number;
  targetMemoryUtilization: number;
  scaleUpThreshold: number;
  scaleDownThreshold: number;
  cooldownPeriod: number;
}

class AutoScaler {
  private config: ScalingConfig;
  private currentInstances: number;
  private lastScaleTime: number = 0;
  private metricsHistory: MetricPoint[] = [];

  constructor(config: ScalingConfig) {
    this.config = config;
    this.currentInstances = config.minInstances;
  }

  async checkScaling(): Promise<ScalingDecision> {
    const currentTime = Date.now();
    
    // クールダウン期間のチェック
    if (currentTime - this.lastScaleTime < this.config.cooldownPeriod) {
      return { action: 'none', reason: 'cooldown period' };
    }

    const currentMetrics = await this.collectMetrics();
    this.metricsHistory.push(currentMetrics);
    
    // 過去5分間のメトリクスの平均を計算
    const avgMetrics = this.calculateAverageMetrics();
    
    // スケーリング判定
    const decision = this.makeScalingDecision(avgMetrics);
    
    if (decision.action !== 'none') {
      await this.executeScaling(decision);
      this.lastScaleTime = currentTime;
    }
    
    return decision;
  }

  private async collectMetrics(): Promise<MetricPoint> {
    // 現在のシステムメトリクスを収集
    return {
      timestamp: Date.now(),
      cpuUtilization: await this.getCPUUtilization(),
      memoryUtilization: await this.getMemoryUtilization(),
      requestRate: await this.getRequestRate(),
      responseTime: await this.getAverageResponseTime(),
      errorRate: await this.getErrorRate()
    };
  }

  private calculateAverageMetrics(): MetricPoint {
    const recentMetrics = this.metricsHistory.slice(-5); // 直近5データポイント
    
    return {
      timestamp: Date.now(),
      cpuUtilization: this.average(recentMetrics.map(m => m.cpuUtilization)),
      memoryUtilization: this.average(recentMetrics.map(m => m.memoryUtilization)),
      requestRate: this.average(recentMetrics.map(m => m.requestRate)),
      responseTime: this.average(recentMetrics.map(m => m.responseTime)),
      errorRate: this.average(recentMetrics.map(m => m.errorRate))
    };
  }

  private makeScalingDecision(metrics: MetricPoint): ScalingDecision {
    // スケールアップの判定
    if (this.shouldScaleUp(metrics)) {
      const targetInstances = Math.min(
        this.currentInstances + 1,
        this.config.maxInstances
      );
      
      return {
        action: 'scale-up',
        targetInstances,
        reason: `High utilization: CPU=${metrics.cpuUtilization}%, Memory=${metrics.memoryUtilization}%`
      };
    }
    
    // スケールダウンの判定
    if (this.shouldScaleDown(metrics)) {
      const targetInstances = Math.max(
        this.currentInstances - 1,
        this.config.minInstances
      );
      
      return {
        action: 'scale-down',
        targetInstances,
        reason: `Low utilization: CPU=${metrics.cpuUtilization}%, Memory=${metrics.memoryUtilization}%`
      };
    }
    
    return { action: 'none', reason: 'metrics within target range' };
  }

  private shouldScaleUp(metrics: MetricPoint): boolean {
    return (
      metrics.cpuUtilization > this.config.scaleUpThreshold ||
      metrics.memoryUtilization > this.config.scaleUpThreshold ||
      metrics.responseTime > 5000 // 5秒以上の応答時間
    ) && this.currentInstances < this.config.maxInstances;
  }

  private shouldScaleDown(metrics: MetricPoint): boolean {
    return (
      metrics.cpuUtilization < this.config.scaleDownThreshold &&
      metrics.memoryUtilization < this.config.scaleDownThreshold &&
      metrics.responseTime < 2000 // 2秒以下の応答時間
    ) && this.currentInstances > this.config.minInstances;
  }

  private async executeScaling(decision: ScalingDecision): Promise<void> {
    console.log(`Executing scaling: ${decision.action} to ${decision.targetInstances} instances`);
    
    if (decision.action === 'scale-up') {
      await this.addInstances(decision.targetInstances - this.currentInstances);
    } else if (decision.action === 'scale-down') {
      await this.removeInstances(this.currentInstances - decision.targetInstances);
    }
    
    this.currentInstances = decision.targetInstances;
  }

  private async addInstances(count: number): Promise<void> {
    // 新しいインスタンスの起動
    for (let i = 0; i < count; i++) {
      await this.launchInstance();
    }
  }

  private async removeInstances(count: number): Promise<void> {
    // インスタンスの graceful shutdown
    for (let i = 0; i < count; i++) {
      await this.terminateInstance();
    }
  }

  private average(numbers: number[]): number {
    return numbers.reduce((sum, num) => sum + num, 0) / numbers.length;
  }
}

// ロードバランサーの実装
class LLMLoadBalancer {
  private providers: LLMProvider[] = [];
  private healthChecker: HealthChecker;
  private metrics: LoadBalancerMetrics;

  constructor() {
    this.healthChecker = new HealthChecker();
    this.metrics = new LoadBalancerMetrics();
  }

  addProvider(provider: LLMProvider): void {
    this.providers.push(provider);
    this.healthChecker.addTarget(provider);
  }

  async routeRequest(request: LLMRequest): Promise<LLMResponse> {
    // 健全なプロバイダーのフィルタリング
    const healthyProviders = this.providers.filter(
      provider => this.healthChecker.isHealthy(provider.id)
    );

    if (healthyProviders.length === 0) {
      throw new Error('No healthy providers available');
    }

    // 最適なプロバイダーの選択
    const selectedProvider = this.selectProvider(healthyProviders, request);
    
    try {
      const response = await this.executeRequest(selectedProvider, request);
      this.metrics.recordSuccess(selectedProvider.id);
      return response;
      
    } catch (error) {
      this.metrics.recordError(selectedProvider.id);
      
      // フェイルオーバー
      return await this.handleFailover(healthyProviders, selectedProvider, request);
    }
  }

  private selectProvider(providers: LLMProvider[], request: LLMRequest): LLMProvider {
    // 加重ラウンドロビン + レスポンス時間考慮
    const weightedProviders = providers.map(provider => ({
      provider,
      weight: this.calculateWeight(provider),
      score: this.calculateScore(provider, request)
    }));

    // スコアの高い順にソート
    weightedProviders.sort((a, b) => b.score - a.score);
    
    return weightedProviders[0].provider;
  }

  private calculateWeight(provider: LLMProvider): number {
    const metrics = this.metrics.getProviderMetrics(provider.id);
    
    // 成功率、レスポンス時間、現在の負荷を考慮
    const successRate = metrics.successRate;
    const avgResponseTime = metrics.averageResponseTime;
    const currentLoad = metrics.currentRequests / provider.maxConcurrentRequests;
    
    return (successRate * 0.4) + 
           ((1 / avgResponseTime) * 0.3) + 
           ((1 - currentLoad) * 0.3);
  }

  private calculateScore(provider: LLMProvider, request: LLMRequest): number {
    let score = this.calculateWeight(provider);
    
    // リクエストタイプに基づく調整
    if (request.type === 'creative' && provider.capabilities.creative) {
      score *= 1.2;
    }
    
    if (request.type === 'analytical' && provider.capabilities.analytical) {
      score *= 1.2;
    }
    
    // コスト考慮
    if (request.priority === 'low' && provider.costTier === 'low') {
      score *= 1.1;
    }
    
    return score;
  }

  private async handleFailover(
    providers: LLMProvider[],
    failedProvider: LLMProvider,
    request: LLMRequest
  ): Promise<LLMResponse> {
    // 失敗したプロバイダーを除外
    const remainingProviders = providers.filter(p => p.id !== failedProvider.id);
    
    if (remainingProviders.length === 0) {
      throw new Error('All providers failed');
    }
    
    // 次のプロバイダーで再試行
    const nextProvider = this.selectProvider(remainingProviders, request);
    return await this.executeRequest(nextProvider, request);
  }
}

これらの最適化手法により、LLMアプリケーションの性能を大幅に向上させ、コスト効率を改善することができます。

このトピックはこちらの書籍で勉強するのがおすすめ!

この記事の内容をさらに深く理解したい方におすすめの一冊です。実践的な知識を身につけたい方は、ぜひチェックしてみてください!

まとめ:持続可能なLLM開発体制の構築

LLM開発における成功は、技術的な実装だけでなく、持続可能な開発体制と運用プロセスの構築にかかっています。本記事で紹介したベストプラクティスを総合し、長期的に価値を提供し続けるLLMアプリケーション開発のフレームワークを構築しましょう。

開発ライフサイクルの確立

効果的なLLM開発には、以下の段階的なアプローチが重要です:

  1. 要件定義: ビジネス価値と技術的実現可能性の両立
  2. プロトタイピング: 迅速な検証とフィードバック収集
  3. アーキテクチャ設計: スケーラビリティと保守性の確保
  4. 実装: ベストプラクティスに基づく品質の高い開発
  5. テスト: 自動化されたテストと品質保証
  6. デプロイ: 段階的なリリースとモニタリング
  7. 運用: 継続的な改善と最適化

品質保証とガバナンス

LLMアプリケーションの特殊性を考慮した品質保証体制:

  • プロンプト品質管理: A/Bテストと継続的な最適化
  • 出力品質監視: 自動評価と人間による品質チェック
  • コスト管理: 使用量追跡と予算制御
  • セキュリティ: 定期的な脆弱性評価と対策
  • コンプライアンス: 業界規制と企業ポリシーの遵守

将来への備え

LLM技術の急速な進化に対応するための戦略:

  • モデル非依存アーキテクチャ: プロバイダーやモデルの変更に柔軟に対応
  • 継続学習: 新しい技術トレンドと手法の習得
  • コミュニティ参加: オープンソースへの貢献と知識共有
  • 実験文化: 新しいアプローチの積極的な試行と評価

適切なベストプラクティスの適用により、LLMアプリケーションは企業の競争優位性を高める強力なツールとなります。技術的な実装だけでなく、チーム体制、プロセス、文化の構築にも注力し、持続可能な開発環境を構築することが成功の鍵となるでしょう。

このトピックはこちらの書籍で勉強するのがおすすめ!

この記事の内容をさらに深く理解したい方におすすめの一冊です。実践的な知識を身につけたい方は、ぜひチェックしてみてください!

おすすめコンテンツ