Skip to main content
Glama

SRT Translation MCP Server

by omd0
context-optimization.ts28 kB
/** * AI Context Optimization for SRT Chunking Functions * Advanced context management for different AI models with intelligent chunking */ import { SRTSubtitle, SRTChunk } from '../types/srt.js'; import { detectConversations, detectConversationsAdvanced } from '../chunking/conversation-detector.js'; /** * Context Optimization Strategy */ export type ContextOptimizationStrategy = | 'aggressive' // Maximum chunking for minimal context usage | 'conservative' // Minimal chunking for maximum context preservation | 'balanced' // Balanced approach | 'adaptive' // Dynamic based on content complexity | 'model-specific'; // Optimized for specific AI model /** * Context Optimization Configuration */ export interface ContextOptimizationConfig { strategy: ContextOptimizationStrategy; maxContextSize: number; chunkSizeLimit: number; contextManagement: 'aggressive' | 'conservative' | 'balanced'; enableSemanticChunking: boolean; enableSpeakerAwareChunking: boolean; enableComplexityBasedChunking: boolean; enableTimingBasedChunking: boolean; modelSpecific: { claude: ClaudeContextConfig; gpt: GPTContextConfig; gemini: GeminiContextConfig; generic: GenericContextConfig; }; } /** * Model-specific context configurations */ export interface ClaudeContextConfig { maxContextSize: 200000; optimalChunkSize: 8; contextOverhead: 1000; reasoningTokens: 500; responseTokens: 1000; safetyMargin: 0.8; } export interface GPTContextConfig { maxContextSize: 128000; optimalChunkSize: 12; contextOverhead: 800; reasoningTokens: 0; responseTokens: 2000; safetyMargin: 0.85; } export interface GeminiContextConfig { maxContextSize: 1000000; optimalChunkSize: 20; contextOverhead: 500; reasoningTokens: 1000; responseTokens: 3000; safetyMargin: 0.9; } export interface GenericContextConfig { maxContextSize: 50000; optimalChunkSize: 5; contextOverhead: 2000; reasoningTokens: 0; responseTokens: 500; safetyMargin: 0.7; } /** * Context Analysis Result */ export interface ContextAnalysisResult { totalContextSize: number; availableContext: number; contextEfficiency: number; chunkCount: number; averageChunkSize: number; largestChunk: number; smallestChunk: number; contextDistribution: ContextDistribution; optimizationSuggestions: OptimizationSuggestion[]; complexityScore: number; speakerConsistency: number; topicCoherence: number; } /** * Context Distribution */ export interface ContextDistribution { underLimit: number; // Chunks under context limit atLimit: number; // Chunks at context limit overLimit: number; // Chunks over context limit severelyOverLimit: number; // Chunks severely over limit } /** * Optimization Suggestion */ export interface OptimizationSuggestion { type: 'split' | 'merge' | 'reorder' | 'compress'; priority: 'low' | 'medium' | 'high' | 'critical'; description: string; expectedImprovement: number; affectedChunks: string[]; implementation: string[]; } /** * Optimized Chunk Result */ export interface OptimizedChunkResult { originalChunks: SRTChunk[]; optimizedChunks: SRTChunk[]; contextAnalysis: ContextAnalysisResult; optimizationApplied: OptimizationApplied[]; performanceMetrics: PerformanceMetrics; } /** * Optimization Applied */ export interface OptimizationApplied { type: string; description: string; chunksAffected: number; contextSaved: number; qualityImpact: 'none' | 'minimal' | 'moderate' | 'significant'; } /** * Performance Metrics */ export interface PerformanceMetrics { totalProcessingTime: number; contextEfficiency: number; chunkingQuality: number; speakerConsistency: number; topicCoherence: number; overallScore: number; } /** * AI Context Optimizer */ export class AIContextOptimizer { private config: ContextOptimizationConfig; private modelType: string; constructor(config: ContextOptimizationConfig, modelType: string) { this.config = config; this.modelType = modelType; } /** * Optimize chunks for AI context */ async optimizeChunksForAI( chunks: SRTChunk[], processingType: 'translation' | 'analysis' | 'conversation-detection' ): Promise<OptimizedChunkResult> { const startTime = Date.now(); // Step 1: Analyze current context usage const contextAnalysis = await this.analyzeContextUsage(chunks); // Step 2: Generate optimization suggestions const suggestions = await this.generateOptimizationSuggestions(chunks, contextAnalysis); // Step 3: Apply optimizations const optimizedChunks = await this.applyOptimizations(chunks, suggestions); // Step 4: Validate optimizations const validationResult = await this.validateOptimizations(chunks, optimizedChunks); // Step 5: Calculate performance metrics const performanceMetrics = await this.calculatePerformanceMetrics( chunks, optimizedChunks, Date.now() - startTime ); return { originalChunks: chunks, optimizedChunks, contextAnalysis, optimizationApplied: validationResult.applied, performanceMetrics }; } /** * Analyze context usage across chunks */ private async analyzeContextUsage(chunks: SRTChunk[]): Promise<ContextAnalysisResult> { const modelConfig = this.getModelConfig(); const totalContextSize = chunks.reduce((sum, chunk) => sum + this.calculateChunkContextSize(chunk), 0); const availableContext = modelConfig.maxContextSize * modelConfig.safetyMargin; const contextEfficiency = totalContextSize / (chunks.length * modelConfig.maxContextSize); const chunkSizes = chunks.map(chunk => this.calculateChunkContextSize(chunk)); const averageChunkSize = chunkSizes.reduce((sum, size) => sum + size, 0) / chunkSizes.length; const largestChunk = Math.max(...chunkSizes); const smallestChunk = Math.min(...chunkSizes); const contextDistribution = this.calculateContextDistribution(chunks, modelConfig); const optimizationSuggestions = await this.generateOptimizationSuggestions(chunks, { totalContextSize, availableContext, contextEfficiency, chunkCount: chunks.length, averageChunkSize, largestChunk, smallestChunk, contextDistribution, optimizationSuggestions: [], complexityScore: this.calculateComplexityScore(chunks), speakerConsistency: this.calculateSpeakerConsistency(chunks), topicCoherence: this.calculateTopicCoherence(chunks) }); return { totalContextSize, availableContext, contextEfficiency, chunkCount: chunks.length, averageChunkSize, largestChunk, smallestChunk, contextDistribution, optimizationSuggestions, complexityScore: this.calculateComplexityScore(chunks), speakerConsistency: this.calculateSpeakerConsistency(chunks), topicCoherence: this.calculateTopicCoherence(chunks) }; } /** * Calculate chunk context size */ private calculateChunkContextSize(chunk: SRTChunk): number { let size = 0; // Base metadata size size += JSON.stringify(chunk).length; // Add subtitle text size for (const subtitle of chunk.subtitles) { size += (subtitle.text || '').length; } // Add context overhead based on model const modelConfig = this.getModelConfig(); size += modelConfig.contextOverhead; return size; } /** * Calculate context distribution */ private calculateContextDistribution( chunks: SRTChunk[], modelConfig: any ): ContextDistribution { const limit = modelConfig.maxContextSize * modelConfig.safetyMargin; const severeLimit = modelConfig.maxContextSize; let underLimit = 0; let atLimit = 0; let overLimit = 0; let severelyOverLimit = 0; for (const chunk of chunks) { const size = this.calculateChunkContextSize(chunk); if (size < limit * 0.8) { underLimit++; } else if (size <= limit) { atLimit++; } else if (size <= severeLimit) { overLimit++; } else { severelyOverLimit++; } } return { underLimit, atLimit, overLimit, severelyOverLimit }; } /** * Generate optimization suggestions */ private async generateOptimizationSuggestions( chunks: SRTChunk[], analysis: ContextAnalysisResult ): Promise<OptimizationSuggestion[]> { const suggestions: OptimizationSuggestion[] = []; const modelConfig = this.getModelConfig(); // Check for chunks that exceed context limits for (const chunk of chunks) { const chunkSize = this.calculateChunkContextSize(chunk); if (chunkSize > modelConfig.maxContextSize * modelConfig.safetyMargin) { suggestions.push({ type: 'split', priority: chunkSize > modelConfig.maxContextSize ? 'critical' : 'high', description: `Split chunk ${chunk.id} (${chunkSize} chars) to fit context limits`, expectedImprovement: chunkSize - modelConfig.maxContextSize * modelConfig.safetyMargin, affectedChunks: [chunk.id], implementation: [ 'Split chunk into smaller parts', 'Maintain conversation context', 'Preserve speaker consistency' ] }); } } // Check for opportunities to merge small chunks const smallChunks = chunks.filter(chunk => this.calculateChunkContextSize(chunk) < modelConfig.optimalChunkSize * 0.5 ); if (smallChunks.length > 1) { suggestions.push({ type: 'merge', priority: 'medium', description: `Merge ${smallChunks.length} small chunks to improve efficiency`, expectedImprovement: smallChunks.length * 1000, // Estimated improvement affectedChunks: smallChunks.map(chunk => chunk.id), implementation: [ 'Merge consecutive small chunks', 'Maintain conversation boundaries', 'Preserve timing information' ] }); } // Check for complexity-based optimizations if (analysis.complexityScore > 0.7) { suggestions.push({ type: 'compress', priority: 'medium', description: 'Apply complexity-based compression to reduce context usage', expectedImprovement: analysis.complexityScore * 1000, affectedChunks: chunks.map(chunk => chunk.id), implementation: [ 'Simplify complex sentences', 'Remove redundant information', 'Maintain semantic meaning' ] }); } return suggestions; } /** * Apply optimizations to chunks */ private async applyOptimizations( chunks: SRTChunk[], suggestions: OptimizationSuggestion[] ): Promise<SRTChunk[]> { let optimizedChunks = [...chunks]; for (const suggestion of suggestions) { switch (suggestion.type) { case 'split': optimizedChunks = await this.applySplitOptimization(optimizedChunks, suggestion); break; case 'merge': optimizedChunks = await this.applyMergeOptimization(optimizedChunks, suggestion); break; case 'compress': optimizedChunks = await this.applyCompressionOptimization(optimizedChunks, suggestion); break; case 'reorder': optimizedChunks = await this.applyReorderOptimization(optimizedChunks, suggestion); break; } } return optimizedChunks; } /** * Apply split optimization */ private async applySplitOptimization( chunks: SRTChunk[], suggestion: OptimizationSuggestion ): Promise<SRTChunk[]> { const optimizedChunks: SRTChunk[] = []; const modelConfig = this.getModelConfig(); for (const chunk of chunks) { if (suggestion.affectedChunks.includes(chunk.id)) { const chunkSize = this.calculateChunkContextSize(chunk); if (chunkSize > modelConfig.maxContextSize * modelConfig.safetyMargin) { const splitChunks = this.splitChunkForContext(chunk, modelConfig); optimizedChunks.push(...splitChunks); } else { optimizedChunks.push(chunk); } } else { optimizedChunks.push(chunk); } } return optimizedChunks; } /** * Apply merge optimization */ private async applyMergeOptimization( chunks: SRTChunk[], suggestion: OptimizationSuggestion ): Promise<SRTChunk[]> { const optimizedChunks: SRTChunk[] = []; const modelConfig = this.getModelConfig(); const affectedChunks = chunks.filter(chunk => suggestion.affectedChunks.includes(chunk.id) ); let i = 0; while (i < chunks.length) { const chunk = chunks[i]; if (suggestion.affectedChunks.includes(chunk.id)) { // Try to merge with next chunk if both are small const nextChunk = chunks[i + 1]; if (nextChunk && suggestion.affectedChunks.includes(nextChunk.id)) { const mergedChunk = this.mergeChunksForContext(chunk, nextChunk, modelConfig); optimizedChunks.push(mergedChunk); i += 2; // Skip next chunk as it's merged } else { optimizedChunks.push(chunk); i++; } } else { optimizedChunks.push(chunk); i++; } } return optimizedChunks; } /** * Apply compression optimization */ private async applyCompressionOptimization( chunks: SRTChunk[], suggestion: OptimizationSuggestion ): Promise<SRTChunk[]> { return chunks.map(chunk => { if (suggestion.affectedChunks.includes(chunk.id)) { return this.compressChunkForContext(chunk); } return chunk; }); } /** * Apply reorder optimization */ private async applyReorderOptimization( chunks: SRTChunk[], suggestion: OptimizationSuggestion ): Promise<SRTChunk[]> { // Reorder chunks by complexity and context size for optimal processing return chunks.sort((a, b) => { const aSize = this.calculateChunkContextSize(a); const bSize = this.calculateChunkContextSize(b); const aComplexity = this.calculateChunkComplexity(a); const bComplexity = this.calculateChunkComplexity(b); // Process smaller, less complex chunks first if (aSize !== bSize) return aSize - bSize; return aComplexity - bComplexity; }); } /** * Split chunk for context limits */ private splitChunkForContext(chunk: SRTChunk, modelConfig: any): SRTChunk[] { const subtitles = chunk.subtitles; const maxSubtitlesPerChunk = Math.floor( (modelConfig.maxContextSize * modelConfig.safetyMargin) / 1000 ); const newChunks: SRTChunk[] = []; for (let i = 0; i < subtitles.length; i += maxSubtitlesPerChunk) { const chunkSubtitles = subtitles.slice(i, i + maxSubtitlesPerChunk); const newChunk: SRTChunk = { ...chunk, id: `${chunk.id}-part-${Math.floor(i / maxSubtitlesPerChunk) + 1}`, startIndex: chunkSubtitles[0].index, endIndex: chunkSubtitles[chunkSubtitles.length - 1].index, subtitles: chunkSubtitles, context: { ...chunk.context, conversationId: chunk.context?.conversationId || '', isSplitChunk: true, originalChunkId: chunk.id, partNumber: Math.floor(i / maxSubtitlesPerChunk) + 1, totalParts: Math.ceil(subtitles.length / maxSubtitlesPerChunk) } }; newChunks.push(newChunk); } return newChunks; } /** * Merge chunks for context optimization */ private mergeChunksForContext( chunk1: SRTChunk, chunk2: SRTChunk, modelConfig: any ): SRTChunk { const mergedSize = this.calculateChunkContextSize(chunk1) + this.calculateChunkContextSize(chunk2); if (mergedSize > modelConfig.maxContextSize * modelConfig.safetyMargin) { // Can't merge, return original chunks return chunk1; } return { id: `${chunk1.id}-${chunk2.id}`, startIndex: chunk1.startIndex, endIndex: chunk2.endIndex, subtitles: [...chunk1.subtitles, ...chunk2.subtitles], context: { speaker: chunk1.context?.speaker || chunk2.context?.speaker, conversationId: chunk1.context?.conversationId || chunk2.context?.conversationId || '', previousContext: chunk1.context?.previousContext, nextContext: chunk2.context?.nextContext, isMergedChunk: true, originalChunkIds: [chunk1.id, chunk2.id] } }; } /** * Compress chunk for context */ private compressChunkForContext(chunk: SRTChunk): SRTChunk { // Simple compression by removing extra whitespace and optimizing text const compressedSubtitles = chunk.subtitles.map(subtitle => ({ ...subtitle, text: subtitle.text.replace(/\s+/g, ' ').trim(), rawText: subtitle.rawText?.replace(/\s+/g, ' ').trim() })); return { ...chunk, subtitles: compressedSubtitles, context: { ...chunk.context, conversationId: chunk.context?.conversationId || '', isCompressed: true, compressionApplied: true } }; } /** * Validate optimizations */ private async validateOptimizations( originalChunks: SRTChunk[], optimizedChunks: SRTChunk[] ): Promise<{ applied: OptimizationApplied[]; valid: boolean }> { const applied: OptimizationApplied[] = []; const modelConfig = this.getModelConfig(); // Check if all chunks are within context limits let allWithinLimits = true; for (const chunk of optimizedChunks) { const chunkSize = this.calculateChunkContextSize(chunk); if (chunkSize > modelConfig.maxContextSize * modelConfig.safetyMargin) { allWithinLimits = false; break; } } // Record applied optimizations if (optimizedChunks.length > originalChunks.length) { applied.push({ type: 'split', description: 'Split large chunks to fit context limits', chunksAffected: optimizedChunks.length - originalChunks.length, contextSaved: 0, // Would need to calculate actual savings qualityImpact: 'minimal' }); } if (optimizedChunks.length < originalChunks.length) { applied.push({ type: 'merge', description: 'Merged small chunks for efficiency', chunksAffected: originalChunks.length - optimizedChunks.length, contextSaved: 0, // Would need to calculate actual savings qualityImpact: 'minimal' }); } return { applied, valid: allWithinLimits }; } /** * Calculate performance metrics */ private async calculatePerformanceMetrics( originalChunks: SRTChunk[], optimizedChunks: SRTChunk[], processingTime: number ): Promise<PerformanceMetrics> { const originalContextSize = originalChunks.reduce((sum, chunk) => sum + this.calculateChunkContextSize(chunk), 0); const optimizedContextSize = optimizedChunks.reduce((sum, chunk) => sum + this.calculateChunkContextSize(chunk), 0); const contextEfficiency = optimizedContextSize / originalContextSize; const chunkingQuality = this.calculateChunkingQuality(optimizedChunks); const speakerConsistency = this.calculateSpeakerConsistency(optimizedChunks); const topicCoherence = this.calculateTopicCoherence(optimizedChunks); const overallScore = ( contextEfficiency * 0.3 + chunkingQuality * 0.3 + speakerConsistency * 0.2 + topicCoherence * 0.2 ); return { totalProcessingTime: processingTime, contextEfficiency, chunkingQuality, speakerConsistency, topicCoherence, overallScore }; } /** * Calculate chunking quality */ private calculateChunkingQuality(chunks: SRTChunk[]): number { if (chunks.length === 0) return 0; let quality = 0; for (const chunk of chunks) { // Check for conversation boundaries if (chunk.context?.speaker) quality += 0.3; // Check for timing consistency if (chunk.subtitles.length > 1) { const timingConsistent = this.checkTimingConsistency(chunk); if (timingConsistent) quality += 0.3; } // Check for semantic coherence const semanticCoherence = this.checkSemanticCoherence(chunk); quality += semanticCoherence * 0.4; } return Math.min(1, quality / chunks.length); } /** * Calculate complexity score */ private calculateComplexityScore(chunks: SRTChunk[]): number { if (chunks.length === 0) return 0; let totalComplexity = 0; for (const chunk of chunks) { const chunkComplexity = this.calculateChunkComplexity(chunk); totalComplexity += chunkComplexity; } return totalComplexity / chunks.length; } /** * Calculate chunk complexity */ private calculateChunkComplexity(chunk: SRTChunk): number { let complexity = 0; // Text length complexity const totalTextLength = chunk.subtitles.reduce((sum, s) => sum + s.text.length, 0); complexity += Math.min(1, totalTextLength / 1000); // Word count complexity const totalWords = chunk.subtitles.reduce((sum, s) => sum + s.text.split(/\s+/).length, 0); complexity += Math.min(1, totalWords / 100); // Formatting complexity const hasFormatting = chunk.subtitles.some(s => s.text.includes('<i>') || s.text.includes('<b>') || s.text.includes('<u>')); if (hasFormatting) complexity += 0.2; // Speaker complexity if (chunk.context?.speaker) complexity += 0.1; return Math.min(1, complexity); } /** * Calculate speaker consistency */ private calculateSpeakerConsistency(chunks: SRTChunk[]): number { if (chunks.length === 0) return 0; const speakers = chunks.map(chunk => chunk.context?.speaker).filter(Boolean); const uniqueSpeakers = new Set(speakers); if (uniqueSpeakers.size === 0) return 0; if (uniqueSpeakers.size === 1) return 1; // Calculate consistency based on speaker distribution const speakerCounts = new Map<string, number>(); for (const speaker of speakers) { speakerCounts.set(speaker!, (speakerCounts.get(speaker!) || 0) + 1); } const maxCount = Math.max(...speakerCounts.values()); const totalCount = speakers.length; return maxCount / totalCount; } /** * Calculate topic coherence */ private calculateTopicCoherence(chunks: SRTChunk[]): number { if (chunks.length < 2) return 1; let coherence = 0; for (let i = 1; i < chunks.length; i++) { const prevChunk = chunks[i - 1]; const currentChunk = chunks[i]; const prevKeywords = this.extractKeywords(prevChunk); const currentKeywords = this.extractKeywords(currentChunk); const overlap = this.calculateKeywordOverlap(prevKeywords, currentKeywords); coherence += overlap; } return coherence / (chunks.length - 1); } /** * Extract keywords from chunk */ private extractKeywords(chunk: SRTChunk): string[] { const allText = chunk.subtitles.map(s => s.text).join(' '); const words = allText.toLowerCase().split(/\s+/) .filter(word => word.length > 3) .filter(word => !/^[a-z]+$/.test(word) || word.length > 4); return [...new Set(words)].slice(0, 10); // Top 10 unique keywords } /** * Calculate keyword overlap */ private calculateKeywordOverlap(keywords1: string[], keywords2: string[]): number { const set1 = new Set(keywords1); const set2 = new Set(keywords2); const intersection = new Set([...set1].filter(x => set2.has(x))); const union = new Set([...set1, ...set2]); return intersection.size / union.size; } /** * Check timing consistency */ private checkTimingConsistency(chunk: SRTChunk): boolean { if (chunk.subtitles.length < 2) return true; for (let i = 1; i < chunk.subtitles.length; i++) { const prev = chunk.subtitles[i - 1]; const current = chunk.subtitles[i]; const prevEnd = prev.endTime; const currentStart = current.startTime; // Check if timing is logical (current starts after previous ends) const prevEndMs = (prevEnd.hours * 3600 + prevEnd.minutes * 60 + prevEnd.seconds) * 1000 + prevEnd.milliseconds; const currentStartMs = (currentStart.hours * 3600 + currentStart.minutes * 60 + currentStart.seconds) * 1000 + currentStart.milliseconds; if (currentStartMs < prevEndMs) { return false; } } return true; } /** * Check semantic coherence */ private checkSemanticCoherence(chunk: SRTChunk): number { if (chunk.subtitles.length < 2) return 1; let coherence = 0; for (let i = 1; i < chunk.subtitles.length; i++) { const prev = chunk.subtitles[i - 1].text; const current = chunk.subtitles[i].text; const similarity = this.calculateTextSimilarity(prev, current); coherence += similarity; } return coherence / (chunk.subtitles.length - 1); } /** * Calculate text similarity */ private calculateTextSimilarity(text1: string, text2: string): number { const words1 = text1.toLowerCase().split(/\s+/); const words2 = text2.toLowerCase().split(/\s+/); const set1 = new Set(words1); const set2 = new Set(words2); const intersection = new Set([...set1].filter(x => set2.has(x))); const union = new Set([...set1, ...set2]); return intersection.size / union.size; } /** * Get model-specific configuration */ private getModelConfig(): any { switch (this.modelType) { case 'claude': return this.config.modelSpecific.claude; case 'gpt': return this.config.modelSpecific.gpt; case 'gemini': return this.config.modelSpecific.gemini; default: return this.config.modelSpecific.generic; } } } /** * Context Optimization Factory */ export class ContextOptimizationFactory { /** * Create context optimizer for specific model */ static createOptimizer(modelType: string): AIContextOptimizer { const config = this.getDefaultConfig(); return new AIContextOptimizer(config, modelType); } /** * Get default configuration */ private static getDefaultConfig(): ContextOptimizationConfig { return { strategy: 'adaptive', maxContextSize: 200000, chunkSizeLimit: 15, contextManagement: 'balanced', enableSemanticChunking: true, enableSpeakerAwareChunking: true, enableComplexityBasedChunking: true, enableTimingBasedChunking: true, modelSpecific: { claude: { maxContextSize: 200000, optimalChunkSize: 8, contextOverhead: 1000, reasoningTokens: 500, responseTokens: 1000, safetyMargin: 0.8 }, gpt: { maxContextSize: 128000, optimalChunkSize: 12, contextOverhead: 800, reasoningTokens: 0, responseTokens: 2000, safetyMargin: 0.85 }, gemini: { maxContextSize: 1000000, optimalChunkSize: 20, contextOverhead: 500, reasoningTokens: 1000, responseTokens: 3000, safetyMargin: 0.9 }, generic: { maxContextSize: 50000, optimalChunkSize: 5, contextOverhead: 2000, reasoningTokens: 0, responseTokens: 500, safetyMargin: 0.7 } } }; } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/omd0/srt-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server