# ElevenLabs Migration Guide

This guide helps you migrate from other Text-to-Speech (TTS) providers to
ElevenLabs, or upgrade between ElevenLabs API versions and models.

## Table of Contents

- [Migration Overview](#migration-overview)
- [Migrating from Other TTS Providers](#migrating-from-other-tts-providers)
  - [From Amazon Polly](#from-amazon-polly)
  - [From Google Cloud TTS](#from-google-cloud-tts)
  - [From Azure Cognitive Services](#from-azure-cognitive-services)
  - [From OpenAI TTS](#from-openai-tts)
- [ElevenLabs Model Migrations](#elevenlabs-model-migrations)
  - [Upgrading to Multilingual v2](#upgrading-to-multilingual-v2)
  - [Upgrading to Turbo v2.5](#upgrading-to-turbo-v25)
- [Voice Migration Strategies](#voice-migration-strategies)
- [API Version Migration](#api-version-migration)
- [Data Migration](#data-migration)
- [Testing and Validation](#testing-and-validation)
- [Rollback Procedures](#rollback-procedures)

---

## Migration Overview

### Pre-Migration Checklist

Before starting any migration:

- [ ] Audit current TTS usage (volume, features, voices used)
- [ ] Identify all integration points in your codebase
- [ ] Document current voice configurations and mappings
- [ ] Set up ElevenLabs account with appropriate subscription tier
- [ ] Create test environment for validation
- [ ] Plan rollback strategy
- [ ] Estimate quota requirements
- [ ] Communicate migration timeline to stakeholders

### Migration Phases

```
┌─────────────────────────────────────────────────────────────────────────────┐
│                        Migration Process Overview                            │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  Phase 1: Assessment       Phase 2: Setup          Phase 3: Implementation  │
│  ┌──────────────────┐     ┌──────────────────┐    ┌──────────────────┐     │
│  │ • Audit usage    │     │ • Create account │    │ • Update code    │     │
│  │ • Map features   │────▶│ • Select voices  │───▶│ • Voice mapping  │     │
│  │ • Document APIs  │     │ • Configure keys │    │ • Error handling │     │
│  └──────────────────┘     └──────────────────┘    └──────────────────┘     │
│                                                            │                │
│                                                            ▼                │
│  Phase 6: Cleanup         Phase 5: Cutover        Phase 4: Testing         │
│  ┌──────────────────┐     ┌──────────────────┐    ┌──────────────────┐     │
│  │ • Remove old     │     │ • Switch traffic │    │ • A/B testing    │     │
│  │ • Update docs    │◀────│ • Monitor closely│◀───│ • Quality check  │     │
│  │ • Archive config │     │ • Verify metrics │    │ • Load testing   │     │
│  └──────────────────┘     └──────────────────┘    └──────────────────┘     │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘
```

---

## Migrating from Other TTS Providers

### From Amazon Polly

#### Feature Mapping

| Amazon Polly Feature | ElevenLabs Equivalent      | Notes                           |
| -------------------- | -------------------------- | ------------------------------- |
| Standard voices      | Multilingual v2            | Higher quality in ElevenLabs    |
| Neural voices        | Turbo v2.5                 | Similar latency, better quality |
| SSML support         | Native text + audio tags   | Different syntax                |
| Lexicons             | Pronunciation dictionaries | Via API                         |
| Speech marks         | Timestamps API             | Different format                |
| Long Audio Synthesis | Projects API               | For content > 5000 chars        |

#### Voice Mapping

```typescript
// Amazon Polly to ElevenLabs voice mapping
const POLLY_TO_ELEVENLABS_VOICES: Record<string, string> = {
  // US English Female
  Joanna: 'Rachel', // Professional, clear
  Kendra: 'Domi', // Warm, friendly
  Kimberly: 'Bella', // Conversational
  Salli: 'Elli', // Young adult
  Ivy: 'Charlotte', // Child-like → Young professional

  // US English Male
  Matthew: 'Adam', // Deep, authoritative
  Joey: 'Josh', // Casual, friendly
  Justin: 'Sam', // Young adult

  // British English
  Amy: 'Dorothy', // British female
  Emma: 'Emily', // British female
  Brian: 'Clyde', // British male

  // Other languages - use ElevenLabs auto-detect
  Celine: 'auto', // French → Multilingual v2
  Hans: 'auto', // German → Multilingual v2
  Mizuki: 'auto', // Japanese → Multilingual v2
};

// Migration adapter
class PollyToElevenLabsAdapter {
  private elevenLabs: ElevenLabsClient;

  constructor(apiKey: string) {
    this.elevenLabs = new ElevenLabsClient({ apiKey });
  }

  async synthesizeSpeech(params: PollyParams): Promise<Buffer> {
    const voiceId = this.mapVoice(params.VoiceId);
    const text = this.convertSSML(params.Text, params.TextType);

    return this.elevenLabs.textToSpeech({
      voiceId,
      text,
      modelId: this.selectModel(params),
      voiceSettings: this.mapVoiceSettings(params),
    });
  }

  private mapVoice(pollyVoice: string): string {
    const mapped = POLLY_TO_ELEVENLABS_VOICES[pollyVoice];
    if (!mapped || mapped === 'auto') {
      // Use a default multilingual voice
      return 'pNInz6obpgDQGcFmaJgB'; // Adam
    }
    return this.getVoiceIdByName(mapped);
  }

  private convertSSML(text: string, textType: string): string {
    if (textType !== 'ssml') return text;

    // Convert Polly SSML to ElevenLabs format
    let converted = text
      // Remove SSML wrapper
      .replace(/<speak>/g, '')
      .replace(/<\/speak>/g, '')
      // Convert breaks
      .replace(/<break time="(\d+)ms"\/>/g, '<break time="$1ms" />')
      .replace(/<break strength="(\w+)"\/>/g, (_, strength) => {
        const msMap: Record<string, string> = {
          none: '0ms',
          'x-weak': '100ms',
          weak: '200ms',
          medium: '400ms',
          strong: '600ms',
          'x-strong': '1000ms',
        };
        return `<break time="${msMap[strength] || '400ms'}" />`;
      })
      // Convert prosody
      .replace(/<prosody rate="(\w+)">/g, '')
      .replace(/<\/prosody>/g, '')
      // Convert emphasis
      .replace(/<emphasis level="(\w+)">/g, '')
      .replace(/<\/emphasis>/g, '')
      // Remove unsupported tags
      .replace(/<amazon:effect[^>]*>/g, '')
      .replace(/<\/amazon:effect>/g, '')
      .replace(/<phoneme[^>]*>[^<]*<\/phoneme>/g, (match) => {
        // Extract the word from phoneme tag
        const word = match.match(/>([^<]*)</)?.[1] || '';
        return word;
      });

    return converted.trim();
  }

  private selectModel(params: PollyParams): string {
    // Neural voices → Turbo for low latency
    if (params.Engine === 'neural') {
      return 'eleven_turbo_v2_5';
    }
    // Standard voices → Multilingual for quality
    return 'eleven_multilingual_v2';
  }

  private mapVoiceSettings(params: PollyParams): VoiceSettings {
    return {
      stability: 0.5,
      similarity_boost: 0.75,
      style: 0,
      use_speaker_boost: true,
    };
  }
}
```

#### SSML Conversion Reference

```typescript
// Polly SSML to ElevenLabs conversion
const SSML_CONVERSIONS = {
  // Pauses
  polly: '<break time="500ms"/>',
  elevenlabs: '<break time="500ms" />',

  // Emphasis (not directly supported, use stability)
  polly: '<emphasis level="strong">important</emphasis>',
  elevenlabs: 'important', // Adjust via voice settings

  // Prosody (not directly supported)
  polly: '<prosody rate="slow">slowly</prosody>',
  elevenlabs: 'slowly...', // Use ellipsis for pacing

  // Whisper (Polly-specific, no equivalent)
  polly: '<amazon:effect name="whispered">secret</amazon:effect>',
  elevenlabs: 'secret', // No direct equivalent

  // Phonemes
  polly: '<phoneme alphabet="ipa" ph="pɪˈkɑːn">pecan</phoneme>',
  elevenlabs: 'pecan', // Use pronunciation dictionary instead
};
```

### From Google Cloud TTS

#### Feature Mapping

| Google Cloud TTS Feature | ElevenLabs Equivalent  | Notes                    |
| ------------------------ | ---------------------- | ------------------------ |
| Standard voices          | Multilingual v2        | Better quality           |
| WaveNet voices           | Multilingual v2        | Comparable quality       |
| Neural2 voices           | Turbo v2.5             | Similar performance      |
| Studio voices            | Voice Design           | Create custom voices     |
| SSML support             | Audio tags             | Different syntax         |
| Audio profiles           | Output format settings | Similar control          |
| Custom Voice             | Voice Cloning          | ElevenLabs more flexible |
| Long Audio               | Projects API           | For extended content     |

#### Voice Mapping

```typescript
const GOOGLE_TO_ELEVENLABS_VOICES: Record<string, string> = {
  // US English Female
  'en-US-Standard-C': 'Rachel',
  'en-US-Standard-E': 'Domi',
  'en-US-Standard-F': 'Bella',
  'en-US-Standard-G': 'Elli',
  'en-US-Standard-H': 'Charlotte',
  'en-US-Wavenet-C': 'Rachel',
  'en-US-Wavenet-E': 'Domi',
  'en-US-Wavenet-F': 'Bella',
  'en-US-Neural2-C': 'Rachel',
  'en-US-Neural2-E': 'Domi',
  'en-US-Neural2-F': 'Bella',

  // US English Male
  'en-US-Standard-A': 'Adam',
  'en-US-Standard-B': 'Josh',
  'en-US-Standard-D': 'Arnold',
  'en-US-Standard-I': 'Sam',
  'en-US-Standard-J': 'Antoni',
  'en-US-Wavenet-A': 'Adam',
  'en-US-Wavenet-B': 'Josh',
  'en-US-Wavenet-D': 'Arnold',
  'en-US-Neural2-A': 'Adam',
  'en-US-Neural2-D': 'Arnold',

  // British English
  'en-GB-Standard-A': 'Dorothy',
  'en-GB-Standard-B': 'Clyde',
  'en-GB-Wavenet-A': 'Dorothy',
  'en-GB-Wavenet-B': 'Clyde',
  'en-GB-Neural2-A': 'Dorothy',
  'en-GB-Neural2-B': 'Clyde',
};

class GoogleTTSToElevenLabsAdapter {
  private elevenLabs: ElevenLabsClient;

  constructor(apiKey: string) {
    this.elevenLabs = new ElevenLabsClient({ apiKey });
  }

  async synthesizeSpeech(request: GoogleTTSRequest): Promise<Buffer> {
    const voiceId = this.mapVoice(request.voice);
    const text = this.convertSSML(request.input);
    const outputFormat = this.mapAudioConfig(request.audioConfig);

    return this.elevenLabs.textToSpeech({
      voiceId,
      text,
      modelId: this.selectModel(request.voice),
      outputFormat,
      voiceSettings: this.mapVoiceSettings(request),
    });
  }

  private mapVoice(voice: GoogleVoice): string {
    const key = `${voice.languageCode}-${voice.name}`;
    const mapped = GOOGLE_TO_ELEVENLABS_VOICES[key];

    if (mapped) {
      return this.getVoiceIdByName(mapped);
    }

    // Fallback: use language to select appropriate voice
    return this.selectVoiceByLanguage(voice.languageCode, voice.ssmlGender);
  }

  private convertSSML(input: GoogleTTSInput): string {
    if (input.text) return input.text;
    if (!input.ssml) return '';

    let text = input.ssml
      // Remove SSML wrapper
      .replace(/<speak>/g, '')
      .replace(/<\/speak>/g, '')
      // Convert breaks
      .replace(/<break time="(\d+(?:\.\d+)?)(s|ms)"\/>/g, (_, time, unit) => {
        const ms = unit === 's' ? parseFloat(time) * 1000 : parseFloat(time);
        return `<break time="${Math.round(ms)}ms" />`;
      })
      // Convert say-as
      .replace(/<say-as interpret-as="([^"]+)"[^>]*>([^<]*)<\/say-as>/g, '$2')
      // Convert sub (substitution)
      .replace(/<sub alias="([^"]+)">([^<]*)<\/sub>/g, '$1')
      // Remove audio tags (not supported)
      .replace(/<audio[^>]*>.*?<\/audio>/gs, '')
      // Remove par/seq (timing)
      .replace(/<par>/g, '')
      .replace(/<\/par>/g, '')
      .replace(/<seq>/g, '')
      .replace(/<\/seq>/g, '')
      // Remove mark tags
      .replace(/<mark name="[^"]*"\/>/g, '');

    return text.trim();
  }

  private mapAudioConfig(config: GoogleAudioConfig): string {
    const encodingMap: Record<string, string> = {
      MP3: 'mp3_44100_128',
      MP3_64_KBPS: 'mp3_44100_64',
      OGG_OPUS: 'pcm_44100', // Convert later
      LINEAR16: 'pcm_16000',
      MULAW: 'ulaw_8000',
    };

    return encodingMap[config.audioEncoding] || 'mp3_44100_128';
  }

  private selectModel(voice: GoogleVoice): string {
    // Neural2 and Studio voices → Turbo for speed
    if (voice.name?.includes('Neural2') || voice.name?.includes('Studio')) {
      return 'eleven_turbo_v2_5';
    }
    // WaveNet and Standard → Multilingual for quality
    return 'eleven_multilingual_v2';
  }
}
```

### From Azure Cognitive Services

#### Feature Mapping

| Azure TTS Feature      | ElevenLabs Equivalent  | Notes                  |
| ---------------------- | ---------------------- | ---------------------- |
| Standard voices        | Multilingual v2        | Better quality         |
| Neural voices          | Turbo v2.5             | Similar latency        |
| Custom Neural Voice    | Voice Cloning          | ElevenLabs easier      |
| SSML support           | Audio tags             | Different syntax       |
| Viseme (lip sync)      | Not directly supported | Use external tools     |
| Word boundary events   | Timestamps API         | Different format       |
| Audio Content Creation | Voice Design           | Similar capability     |
| Speaking styles        | Voice settings         | Map to stability/style |

#### Voice Mapping

```typescript
const AZURE_TO_ELEVENLABS_VOICES: Record<string, string> = {
  // US English Female Neural
  'en-US-JennyNeural': 'Rachel',
  'en-US-AriaNeural': 'Domi',
  'en-US-SaraNeural': 'Bella',
  'en-US-JaneNeural': 'Charlotte',
  'en-US-NancyNeural': 'Elli',

  // US English Male Neural
  'en-US-GuyNeural': 'Adam',
  'en-US-DavisNeural': 'Josh',
  'en-US-JasonNeural': 'Arnold',
  'en-US-TonyNeural': 'Antoni',

  // British English Neural
  'en-GB-SoniaNeural': 'Dorothy',
  'en-GB-RyanNeural': 'Clyde',
  'en-GB-LibbyNeural': 'Emily',

  // Multilingual voices
  'en-US-JennyMultilingualNeural': 'Rachel', // + multilingual model
  'en-US-RyanMultilingualNeural': 'Adam', // + multilingual model
};

// Azure speaking styles to ElevenLabs settings mapping
const STYLE_TO_SETTINGS: Record<string, VoiceSettings> = {
  cheerful: { stability: 0.4, similarity_boost: 0.8, style: 0.3 },
  sad: { stability: 0.7, similarity_boost: 0.6, style: 0.5 },
  angry: { stability: 0.3, similarity_boost: 0.9, style: 0.8 },
  fearful: { stability: 0.6, similarity_boost: 0.5, style: 0.4 },
  friendly: { stability: 0.5, similarity_boost: 0.75, style: 0.2 },
  newscast: { stability: 0.8, similarity_boost: 0.7, style: 0 },
  customerservice: { stability: 0.6, similarity_boost: 0.7, style: 0.1 },
  shouting: { stability: 0.3, similarity_boost: 0.9, style: 0.9 },
  whispering: { stability: 0.9, similarity_boost: 0.5, style: 0 },
  default: { stability: 0.5, similarity_boost: 0.75, style: 0 },
};

class AzureTTSToElevenLabsAdapter {
  private elevenLabs: ElevenLabsClient;

  constructor(apiKey: string) {
    this.elevenLabs = new ElevenLabsClient({ apiKey });
  }

  async synthesizeSpeech(ssml: string): Promise<Buffer> {
    const parsed = this.parseAzureSSML(ssml);

    return this.elevenLabs.textToSpeech({
      voiceId: this.mapVoice(parsed.voice),
      text: parsed.text,
      modelId: this.selectModel(parsed),
      voiceSettings: this.mapStyle(parsed.style),
    });
  }

  private parseAzureSSML(ssml: string): ParsedAzureSSML {
    const result: ParsedAzureSSML = {
      voice: '',
      text: '',
      style: 'default',
      rate: 1.0,
      pitch: 0,
    };

    // Extract voice name
    const voiceMatch = ssml.match(/<voice name="([^"]+)">/);
    if (voiceMatch) {
      result.voice = voiceMatch[1];
    }

    // Extract style
    const styleMatch = ssml.match(/<mstts:express-as style="([^"]+)"[^>]*>/);
    if (styleMatch) {
      result.style = styleMatch[1];
    }

    // Extract text (remove all tags)
    result.text = ssml
      .replace(/<speak[^>]*>/g, '')
      .replace(/<\/speak>/g, '')
      .replace(/<voice[^>]*>/g, '')
      .replace(/<\/voice>/g, '')
      .replace(/<mstts:[^>]*>/g, '')
      .replace(/<\/mstts:[^>]*>/g, '')
      .replace(/<prosody[^>]*>/g, '')
      .replace(/<\/prosody>/g, '')
      .replace(/<break[^>]*\/>/g, (match) => {
        const timeMatch = match.match(/time="(\d+)ms"/);
        if (timeMatch) {
          return `<break time="${timeMatch[1]}ms" />`;
        }
        return '';
      })
      .replace(/<[^>]+>/g, '')
      .trim();

    return result;
  }

  private mapVoice(azureVoice: string): string {
    const mapped = AZURE_TO_ELEVENLABS_VOICES[azureVoice];
    if (mapped) {
      return this.getVoiceIdByName(mapped);
    }

    // Parse language and gender from Azure voice name
    const match = azureVoice.match(/^(\w{2}-\w{2})-(\w+)Neural$/);
    if (match) {
      const [, locale, name] = match;
      return this.selectVoiceByLocale(locale, name);
    }

    // Default fallback
    return 'pNInz6obpgDQGcFmaJgB'; // Adam
  }

  private mapStyle(style: string): VoiceSettings {
    return STYLE_TO_SETTINGS[style] || STYLE_TO_SETTINGS.default;
  }

  private selectModel(parsed: ParsedAzureSSML): string {
    // Multilingual voices → Multilingual model
    if (parsed.voice.includes('Multilingual')) {
      return 'eleven_multilingual_v2';
    }
    // Default to Turbo for speed
    return 'eleven_turbo_v2_5';
  }
}
```

### From OpenAI TTS

#### Feature Mapping

| OpenAI TTS Feature | ElevenLabs Equivalent | Notes                |
| ------------------ | --------------------- | -------------------- |
| tts-1 model        | Turbo v2.5            | Similar speed        |
| tts-1-hd model     | Multilingual v2       | Both high quality    |
| 6 voices           | 100+ voices           | Much more variety    |
| Streaming          | Streaming API         | Similar capability   |
| No voice cloning   | Voice Cloning         | ElevenLabs advantage |
| No SSML            | Audio tags            | ElevenLabs advantage |

#### Voice Mapping

```typescript
const OPENAI_TO_ELEVENLABS_VOICES: Record<string, string> = {
  alloy: 'Rachel', // Neutral, balanced
  echo: 'Adam', // Deep, resonant
  fable: 'Antoni', // Warm, engaging
  onyx: 'Arnold', // Deep, authoritative
  nova: 'Bella', // Young, energetic
  shimmer: 'Domi', // Clear, expressive
};

class OpenAITTSToElevenLabsAdapter {
  private elevenLabs: ElevenLabsClient;

  constructor(apiKey: string) {
    this.elevenLabs = new ElevenLabsClient({ apiKey });
  }

  async createSpeech(params: OpenAITTSParams): Promise<Buffer> {
    const voiceId = this.mapVoice(params.voice);

    return this.elevenLabs.textToSpeech({
      voiceId,
      text: params.input,
      modelId: this.selectModel(params.model),
      outputFormat: this.mapFormat(params.response_format),
      voiceSettings: {
        stability: params.speed ? this.speedToStability(params.speed) : 0.5,
        similarity_boost: 0.75,
        style: 0,
        use_speaker_boost: true,
      },
    });
  }

  async createSpeechStream(
    params: OpenAITTSParams
  ): AsyncGenerator<Uint8Array> {
    const voiceId = this.mapVoice(params.voice);

    return this.elevenLabs.textToSpeechStream({
      voiceId,
      text: params.input,
      modelId: this.selectModel(params.model),
      outputFormat: this.mapFormat(params.response_format),
    });
  }

  private mapVoice(openaiVoice: string): string {
    const mapped = OPENAI_TO_ELEVENLABS_VOICES[openaiVoice];
    return mapped ? this.getVoiceIdByName(mapped) : 'pNInz6obpgDQGcFmaJgB'; // Adam as default
  }

  private selectModel(openaiModel: string): string {
    return openaiModel === 'tts-1'
      ? 'eleven_turbo_v2_5'
      : 'eleven_multilingual_v2';
  }

  private mapFormat(format?: string): string {
    const formatMap: Record<string, string> = {
      mp3: 'mp3_44100_128',
      opus: 'pcm_44100', // Convert after
      aac: 'mp3_44100_128', // Use MP3
      flac: 'pcm_44100', // Convert after
      wav: 'pcm_44100',
      pcm: 'pcm_16000',
    };
    return formatMap[format || 'mp3'] || 'mp3_44100_128';
  }

  private speedToStability(speed: number): number {
    // OpenAI speed: 0.25 to 4.0, default 1.0
    // Map to stability: higher speed = lower stability
    // speed 0.25 → stability 0.8
    // speed 1.0 → stability 0.5
    // speed 4.0 → stability 0.2
    return Math.max(0.1, Math.min(1.0, 1.1 - speed * 0.2));
  }
}
```

#### OpenAI to ElevenLabs Code Migration

```typescript
// Before (OpenAI)
import OpenAI from 'openai';

const openai = new OpenAI();

const mp3 = await openai.audio.speech.create({
  model: 'tts-1',
  voice: 'alloy',
  input: 'Hello, world!',
});

const buffer = Buffer.from(await mp3.arrayBuffer());

// After (ElevenLabs)
import { ElevenLabsClient } from '@oshun/elevenlabs-client';

const elevenlabs = new ElevenLabsClient({
  apiKey: process.env.ELEVENLABS_API_KEY,
});

const buffer = await elevenlabs.textToSpeech({
  voiceId: 'Rachel', // or voice ID
  text: 'Hello, world!',
  modelId: 'eleven_turbo_v2_5',
});
```

---

## ElevenLabs Model Migrations

### Upgrading to Multilingual v2

When migrating from older models to Multilingual v2:

#### Compatibility Matrix

| Previous Model      | Migration Path     | Breaking Changes |
| ------------------- | ------------------ | ---------------- |
| eleven_monolingual  | Direct replacement | None             |
| eleven_multilingual | Direct replacement | Minor quality    |
| eleven_turbo_v2     | Quality vs. speed  | Latency increase |

#### Migration Steps

```typescript
// 1. Update model ID in configuration
const CONFIG_MIGRATION = {
  old: {
    modelId: 'eleven_monolingual_v1',
  },
  new: {
    modelId: 'eleven_multilingual_v2',
  },
};

// 2. Adjust voice settings for new model
const VOICE_SETTINGS_MIGRATION = {
  old: {
    stability: 0.5,
    similarity_boost: 0.75,
  },
  new: {
    stability: 0.5,
    similarity_boost: 0.75,
    style: 0, // New parameter
    use_speaker_boost: true, // New parameter
  },
};

// 3. Test with existing voices
async function testMigration(
  voiceId: string,
  testTexts: string[]
): Promise<MigrationTestResult> {
  const results: MigrationTestResult = {
    voiceId,
    tests: [],
  };

  for (const text of testTexts) {
    const oldAudio = await generateWithModel(
      voiceId,
      text,
      'eleven_monolingual_v1'
    );
    const newAudio = await generateWithModel(
      voiceId,
      text,
      'eleven_multilingual_v2'
    );

    results.tests.push({
      text,
      oldLatency: oldAudio.latency,
      newLatency: newAudio.latency,
      oldSize: oldAudio.size,
      newSize: newAudio.size,
    });
  }

  return results;
}
```

### Upgrading to Turbo v2.5

For latency-sensitive applications migrating to Turbo v2.5:

#### Performance Comparison

| Metric           | Turbo v2 | Turbo v2.5 | Change |
| ---------------- | -------- | ---------- | ------ |
| Average latency  | 300ms    | 250ms      | -17%   |
| First byte       | 150ms    | 100ms      | -33%   |
| Quality (MOS)    | 4.2      | 4.4        | +5%    |
| Language support | 5        | 32         | +540%  |

#### Migration Code

```typescript
// Turbo v2 to v2.5 migration
const TURBO_MIGRATION = {
  modelIdUpdate: {
    old: 'eleven_turbo_v2',
    new: 'eleven_turbo_v2_5',
  },

  // Voice settings remain compatible
  voiceSettings: {
    stability: 0.5,
    similarity_boost: 0.75,
    style: 0,
    use_speaker_boost: true,
  },
};

// Gradual rollout implementation
class TurboMigrationController {
  private rolloutPercentage: number = 0;

  setRolloutPercentage(percentage: number) {
    this.rolloutPercentage = Math.max(0, Math.min(100, percentage));
  }

  selectModel(requestId: string): string {
    // Deterministic selection based on request ID
    const hash = this.hashCode(requestId);
    const bucket = Math.abs(hash % 100);

    if (bucket < this.rolloutPercentage) {
      return 'eleven_turbo_v2_5';
    }
    return 'eleven_turbo_v2';
  }

  private hashCode(str: string): number {
    let hash = 0;
    for (let i = 0; i < str.length; i++) {
      hash = (hash << 5) - hash + str.charCodeAt(i);
      hash |= 0;
    }
    return hash;
  }
}
```

---

## Voice Migration Strategies

### Preserving Voice Consistency

When migrating, maintaining voice consistency is critical:

```typescript
// Voice consistency migration
class VoiceMigrationManager {
  private voiceMapping: Map<string, VoiceMapping>;

  async createMapping(
    oldProvider: string,
    oldVoiceId: string
  ): Promise<VoiceMapping> {
    // 1. Get characteristics of old voice
    const characteristics = await this.analyzeVoiceCharacteristics(
      oldProvider,
      oldVoiceId
    );

    // 2. Find best matching ElevenLabs voice
    const candidates = await this.findMatchingVoices(characteristics);

    // 3. Allow human review
    const mapping: VoiceMapping = {
      oldProvider,
      oldVoiceId,
      newVoiceId: candidates[0].voiceId,
      confidence: candidates[0].matchScore,
      alternatives: candidates.slice(1, 4),
      characteristics,
      needsReview: candidates[0].matchScore < 0.8,
    };

    this.voiceMapping.set(`${oldProvider}:${oldVoiceId}`, mapping);
    return mapping;
  }

  private async analyzeVoiceCharacteristics(
    provider: string,
    voiceId: string
  ): Promise<VoiceCharacteristics> {
    // Generate sample audio with known text
    const sampleText = 'The quick brown fox jumps over the lazy dog.';
    const audio = await this.generateSample(provider, voiceId, sampleText);

    // Analyze audio characteristics
    return {
      pitch: this.analyzePitch(audio),
      speed: this.analyzeSpeed(audio),
      tone: this.analyzeTone(audio),
      gender: this.detectGender(audio),
      age: this.estimateAge(audio),
      accent: this.detectAccent(audio),
    };
  }

  private async findMatchingVoices(
    characteristics: VoiceCharacteristics
  ): Promise<VoiceCandidate[]> {
    const allVoices = await this.elevenLabs.getVoices();

    const candidates = allVoices.map((voice) => ({
      voiceId: voice.voice_id,
      name: voice.name,
      matchScore: this.calculateMatchScore(voice, characteristics),
      labels: voice.labels,
    }));

    return candidates.sort((a, b) => b.matchScore - a.matchScore).slice(0, 10);
  }

  private calculateMatchScore(
    voice: Voice,
    target: VoiceCharacteristics
  ): number {
    let score = 1.0;

    // Gender match (required)
    if (voice.labels?.gender !== target.gender) {
      score *= 0.3;
    }

    // Age match
    const ageMatch = this.matchAge(voice.labels?.age, target.age);
    score *= ageMatch;

    // Accent match
    if (voice.labels?.accent) {
      const accentMatch = this.matchAccent(voice.labels.accent, target.accent);
      score *= accentMatch;
    }

    return score;
  }
}
```

### Voice Cloning for Custom Voices

If you have custom voices from another provider:

```typescript
// Migrate custom voice to ElevenLabs via cloning
class CustomVoiceMigrator {
  async migrateCustomVoice(
    sourceProvider: string,
    sourceVoiceId: string,
    voiceName: string
  ): Promise<ClonedVoice> {
    // 1. Generate diverse samples from source
    const samples = await this.generateSourceSamples(
      sourceProvider,
      sourceVoiceId
    );

    // 2. Clone voice on ElevenLabs
    const clonedVoice = await this.elevenLabs.cloneVoice({
      name: voiceName,
      description: `Migrated from ${sourceProvider}`,
      files: samples,
      labels: {
        migrated_from: sourceProvider,
        original_id: sourceVoiceId,
        migration_date: new Date().toISOString(),
      },
    });

    // 3. Validate clone quality
    const validation = await this.validateClone(
      sourceProvider,
      sourceVoiceId,
      clonedVoice.voice_id
    );

    if (!validation.acceptable) {
      // Try with different samples or Professional Voice Cloning
      return this.attemptProfessionalCloning(
        sourceProvider,
        sourceVoiceId,
        voiceName
      );
    }

    return clonedVoice;
  }

  private async generateSourceSamples(
    provider: string,
    voiceId: string
  ): Promise<Buffer[]> {
    // Generate samples with varied content
    const sampleTexts = [
      // Declarative
      'The weather today is sunny with a slight breeze from the west.',
      // Interrogative
      'What time does the meeting start tomorrow morning?',
      // Exclamatory
      'What an incredible achievement! Congratulations to the entire team!',
      // Emotional range
      'I understand your concerns, and I want to help find a solution.',
      "This is absolutely fantastic news! I couldn't be happier!",
      // Technical content
      'The system processes approximately one thousand requests per second.',
    ];

    const samples: Buffer[] = [];
    for (const text of sampleTexts) {
      const audio = await this.generateFromProvider(provider, voiceId, text);
      samples.push(audio);
    }

    return samples;
  }
}
```

---

## API Version Migration

### Handling API Changes

```typescript
// API version compatibility layer
class ElevenLabsVersionAdapter {
  private apiVersion: string;

  constructor(targetVersion: string = 'v1') {
    this.apiVersion = targetVersion;
  }

  // Normalize request format across versions
  normalizeRequest(request: any): NormalizedRequest {
    // Handle deprecated parameters
    if (request.voice_settings?.speaking_rate !== undefined) {
      // speaking_rate was deprecated, map to stability
      console.warn('voice_settings.speaking_rate is deprecated');
      request.voice_settings.stability = this.mapSpeakingRateToStability(
        request.voice_settings.speaking_rate
      );
      delete request.voice_settings.speaking_rate;
    }

    // Ensure required parameters
    if (!request.model_id) {
      request.model_id = 'eleven_multilingual_v2';
    }

    // Add new required parameters
    if (!request.voice_settings?.use_speaker_boost) {
      request.voice_settings = {
        ...request.voice_settings,
        use_speaker_boost: true,
      };
    }

    return request as NormalizedRequest;
  }

  // Normalize response format
  normalizeResponse(response: any): NormalizedResponse {
    // Handle response format changes
    return {
      audio: response.audio || response.audio_base64,
      contentType: response.content_type || 'audio/mpeg',
      characterCount: response.character_count || response.characters_used,
      historyItemId: response.history_item_id,
    };
  }
}
```

### Deprecation Handling

```typescript
// Monitor and handle deprecated features
class DeprecationMonitor {
  private deprecationWarnings: Map<string, DeprecationWarning> = new Map();

  checkForDeprecations(request: any): DeprecationWarning[] {
    const warnings: DeprecationWarning[] = [];

    // Check deprecated models
    const deprecatedModels = ['eleven_monolingual_v1', 'eleven_english_v1'];
    if (deprecatedModels.includes(request.model_id)) {
      warnings.push({
        type: 'model',
        feature: request.model_id,
        message: `Model ${request.model_id} is deprecated`,
        replacement: 'eleven_multilingual_v2',
        removalDate: '2025-06-01',
      });
    }

    // Check deprecated voice settings
    if (request.voice_settings?.speaking_rate !== undefined) {
      warnings.push({
        type: 'parameter',
        feature: 'voice_settings.speaking_rate',
        message: 'speaking_rate parameter is deprecated',
        replacement: 'Use stability parameter instead',
        removalDate: '2025-03-01',
      });
    }

    // Log warnings
    warnings.forEach((w) => this.logDeprecation(w));

    return warnings;
  }

  private logDeprecation(warning: DeprecationWarning) {
    const key = `${warning.type}:${warning.feature}`;

    if (!this.deprecationWarnings.has(key)) {
      this.deprecationWarnings.set(key, warning);
      console.warn(
        `DEPRECATION WARNING: ${warning.message}. ` +
          `Use ${warning.replacement} instead. ` +
          `Will be removed on ${warning.removalDate}.`
      );
    }
  }
}
```

---

## Data Migration

### History Migration

```typescript
// Migrate audio history from other providers
class HistoryMigrator {
  async migrateHistory(
    sourceProvider: string,
    sourceHistory: SourceHistoryItem[]
  ): Promise<MigrationResult> {
    const results: MigrationResult = {
      total: sourceHistory.length,
      migrated: 0,
      skipped: 0,
      failed: 0,
      items: [],
    };

    for (const item of sourceHistory) {
      try {
        // Re-generate audio with ElevenLabs
        const voiceId = this.mapVoice(sourceProvider, item.voiceId);

        const audio = await this.elevenLabs.textToSpeech({
          voiceId,
          text: item.text,
          modelId: 'eleven_multilingual_v2',
        });

        // Store with metadata
        const historyItem = await this.storeWithMetadata(audio, {
          originalProvider: sourceProvider,
          originalId: item.id,
          originalTimestamp: item.timestamp,
          originalVoiceId: item.voiceId,
          text: item.text,
        });

        results.migrated++;
        results.items.push({
          sourceId: item.id,
          newId: historyItem.id,
          status: 'migrated',
        });
      } catch (error) {
        results.failed++;
        results.items.push({
          sourceId: item.id,
          status: 'failed',
          error: error.message,
        });
      }
    }

    return results;
  }
}
```

### Configuration Migration

```typescript
// Migrate configuration settings
interface MigrationConfig {
  sourceProvider: string;
  targetDefaults: {
    modelId: string;
    voiceSettings: VoiceSettings;
    outputFormat: string;
  };
  voiceMapping: Record<string, string>;
}

const createMigrationConfig = (sourceProvider: string): MigrationConfig => {
  const configs: Record<string, MigrationConfig> = {
    'amazon-polly': {
      sourceProvider: 'amazon-polly',
      targetDefaults: {
        modelId: 'eleven_multilingual_v2',
        voiceSettings: {
          stability: 0.5,
          similarity_boost: 0.75,
          style: 0,
          use_speaker_boost: true,
        },
        outputFormat: 'mp3_44100_128',
      },
      voiceMapping: POLLY_TO_ELEVENLABS_VOICES,
    },
    'google-cloud': {
      sourceProvider: 'google-cloud',
      targetDefaults: {
        modelId: 'eleven_multilingual_v2',
        voiceSettings: {
          stability: 0.5,
          similarity_boost: 0.75,
          style: 0,
          use_speaker_boost: true,
        },
        outputFormat: 'mp3_44100_128',
      },
      voiceMapping: GOOGLE_TO_ELEVENLABS_VOICES,
    },
    azure: {
      sourceProvider: 'azure',
      targetDefaults: {
        modelId: 'eleven_multilingual_v2',
        voiceSettings: {
          stability: 0.5,
          similarity_boost: 0.75,
          style: 0,
          use_speaker_boost: true,
        },
        outputFormat: 'mp3_44100_128',
      },
      voiceMapping: AZURE_TO_ELEVENLABS_VOICES,
    },
    openai: {
      sourceProvider: 'openai',
      targetDefaults: {
        modelId: 'eleven_turbo_v2_5',
        voiceSettings: {
          stability: 0.5,
          similarity_boost: 0.75,
          style: 0,
          use_speaker_boost: true,
        },
        outputFormat: 'mp3_44100_128',
      },
      voiceMapping: OPENAI_TO_ELEVENLABS_VOICES,
    },
  };

  return configs[sourceProvider] || configs['openai'];
};
```

---

## Testing and Validation

### Migration Testing Framework

```typescript
// Comprehensive migration testing
class MigrationTester {
  async runMigrationTests(
    migrationConfig: MigrationConfig
  ): Promise<MigrationTestReport> {
    const report: MigrationTestReport = {
      timestamp: new Date().toISOString(),
      sourceProvider: migrationConfig.sourceProvider,
      tests: [],
    };

    // Test 1: Voice mapping coverage
    report.tests.push(await this.testVoiceMappingCoverage(migrationConfig));

    // Test 2: Audio quality comparison
    report.tests.push(await this.testAudioQuality(migrationConfig));

    // Test 3: Latency comparison
    report.tests.push(await this.testLatency(migrationConfig));

    // Test 4: Feature parity
    report.tests.push(await this.testFeatureParity(migrationConfig));

    // Test 5: Error handling
    report.tests.push(await this.testErrorHandling(migrationConfig));

    // Calculate overall score
    report.overallScore = this.calculateOverallScore(report.tests);
    report.recommendation = this.getRecommendation(report.overallScore);

    return report;
  }

  private async testAudioQuality(config: MigrationConfig): Promise<TestResult> {
    const testCases = [
      { text: 'Hello, world!', category: 'short' },
      {
        text: 'The quick brown fox jumps over the lazy dog.',
        category: 'medium',
      },
      {
        text:
          'In the beginning, there was silence. Then came the voice, ' +
          'clear and resonant, carrying with it the weight of meaning ' +
          'and the lightness of intention.',
        category: 'long',
      },
    ];

    const results: QualityTestResult[] = [];

    for (const testCase of testCases) {
      // Generate with both providers
      const sourceAudio = await this.generateFromSource(config, testCase.text);
      const targetAudio = await this.generateFromTarget(config, testCase.text);

      // Compare quality metrics
      const comparison = await this.compareAudioQuality(
        sourceAudio,
        targetAudio
      );

      results.push({
        text: testCase.text,
        category: testCase.category,
        sourceMetrics: comparison.source,
        targetMetrics: comparison.target,
        difference: comparison.difference,
        acceptable: comparison.difference.mos >= -0.5,
      });
    }

    const passed = results.every((r) => r.acceptable);

    return {
      name: 'Audio Quality Comparison',
      passed,
      score: results.filter((r) => r.acceptable).length / results.length,
      details: results,
    };
  }

  private async testLatency(config: MigrationConfig): Promise<TestResult> {
    const iterations = 10;
    const sourceLatencies: number[] = [];
    const targetLatencies: number[] = [];

    const testText = 'This is a latency test.';

    for (let i = 0; i < iterations; i++) {
      // Measure source latency
      const sourceStart = performance.now();
      await this.generateFromSource(config, testText);
      sourceLatencies.push(performance.now() - sourceStart);

      // Measure target latency
      const targetStart = performance.now();
      await this.generateFromTarget(config, testText);
      targetLatencies.push(performance.now() - targetStart);
    }

    const sourceAvg = sourceLatencies.reduce((a, b) => a + b) / iterations;
    const targetAvg = targetLatencies.reduce((a, b) => a + b) / iterations;
    const difference = ((targetAvg - sourceAvg) / sourceAvg) * 100;

    // Accept up to 20% latency increase
    const passed = difference <= 20;

    return {
      name: 'Latency Comparison',
      passed,
      score: passed ? 1 : Math.max(0, 1 - (difference - 20) / 100),
      details: {
        sourceAverageMs: sourceAvg,
        targetAverageMs: targetAvg,
        differencePercent: difference,
        sourceSamples: sourceLatencies,
        targetSamples: targetLatencies,
      },
    };
  }
}
```

### A/B Testing Framework

```typescript
// A/B testing for gradual migration
class MigrationABTest {
  private testConfig: ABTestConfig;
  private metrics: MetricsCollector;

  constructor(config: ABTestConfig) {
    this.testConfig = config;
    this.metrics = new MetricsCollector();
  }

  async processRequest(request: TTSRequest): Promise<TTSResponse> {
    // Determine test group
    const group = this.assignToGroup(request.userId || request.requestId);

    // Generate with appropriate provider
    const startTime = performance.now();
    let response: TTSResponse;

    if (group === 'control') {
      response = await this.generateWithSource(request);
    } else {
      response = await this.generateWithTarget(request);
    }

    const endTime = performance.now();

    // Collect metrics
    this.metrics.record({
      requestId: request.requestId,
      group,
      latency: endTime - startTime,
      audioSize: response.audio.length,
      characterCount: request.text.length,
      voiceId: request.voiceId,
      timestamp: new Date().toISOString(),
    });

    return response;
  }

  private assignToGroup(identifier: string): 'control' | 'treatment' {
    const hash = this.hashString(identifier);
    const bucket = Math.abs(hash % 100);

    return bucket < this.testConfig.treatmentPercentage
      ? 'treatment'
      : 'control';
  }

  async getTestResults(): Promise<ABTestResults> {
    const controlMetrics = this.metrics.getGroupMetrics('control');
    const treatmentMetrics = this.metrics.getGroupMetrics('treatment');

    return {
      control: {
        sampleSize: controlMetrics.count,
        avgLatency: controlMetrics.avgLatency,
        p95Latency: controlMetrics.p95Latency,
        errorRate: controlMetrics.errorRate,
      },
      treatment: {
        sampleSize: treatmentMetrics.count,
        avgLatency: treatmentMetrics.avgLatency,
        p95Latency: treatmentMetrics.p95Latency,
        errorRate: treatmentMetrics.errorRate,
      },
      statisticalSignificance: this.calculateSignificance(
        controlMetrics,
        treatmentMetrics
      ),
      recommendation: this.getRecommendation(controlMetrics, treatmentMetrics),
    };
  }
}
```

---

## Rollback Procedures

### Implementing Rollback

```typescript
// Rollback support for failed migrations
class MigrationRollback {
  private rollbackState: RollbackState;

  async enableRollback(migrationId: string): Promise<void> {
    this.rollbackState = {
      migrationId,
      enabled: true,
      rollbackPercentage: 0,
      startTime: new Date().toISOString(),
    };
  }

  async initiateRollback(reason: string): Promise<RollbackResult> {
    console.warn(
      `Initiating rollback for migration ${this.rollbackState.migrationId}`
    );
    console.warn(`Reason: ${reason}`);

    // Gradual rollback
    const rollbackSteps = [10, 25, 50, 75, 100];

    for (const percentage of rollbackSteps) {
      this.rollbackState.rollbackPercentage = percentage;

      // Wait and monitor
      await this.sleep(30000); // 30 seconds between steps

      const metrics = await this.getHealthMetrics();

      if (metrics.errorRate > 5) {
        // Something wrong with rollback, pause
        return {
          success: false,
          stoppedAt: percentage,
          reason: 'Error rate increased during rollback',
        };
      }
    }

    return {
      success: true,
      completedAt: new Date().toISOString(),
      reason,
    };
  }

  shouldUseOriginalProvider(requestId: string): boolean {
    if (!this.rollbackState?.enabled) {
      return false;
    }

    // Deterministic rollback based on request ID
    const hash = this.hashString(requestId);
    const bucket = Math.abs(hash % 100);

    return bucket < this.rollbackState.rollbackPercentage;
  }
}

// Usage in main service
class TTSService {
  private migrationRollback: MigrationRollback;
  private sourceProvider: TTSProvider;
  private targetProvider: ElevenLabsClient;

  async synthesize(request: TTSRequest): Promise<Buffer> {
    // Check if rollback is active
    if (this.migrationRollback.shouldUseOriginalProvider(request.requestId)) {
      return this.sourceProvider.synthesize(request);
    }

    try {
      return await this.targetProvider.textToSpeech({
        voiceId: this.mapVoice(request.voiceId),
        text: request.text,
        modelId: 'eleven_multilingual_v2',
      });
    } catch (error) {
      // On persistent errors, consider triggering rollback
      if (this.shouldTriggerRollback(error)) {
        await this.migrationRollback.initiateRollback(error.message);
      }

      // Fallback to original provider
      return this.sourceProvider.synthesize(request);
    }
  }

  private shouldTriggerRollback(error: Error): boolean {
    // Trigger rollback on critical errors
    const criticalErrors = [
      'account_suspended',
      'quota_exceeded',
      'service_unavailable',
    ];

    return criticalErrors.some((e) => error.message.includes(e));
  }
}
```

### Rollback Checklist

Before initiating rollback:

- [ ] Document the issue triggering rollback
- [ ] Notify stakeholders
- [ ] Verify original provider is operational
- [ ] Enable feature flag for rollback
- [ ] Monitor metrics during rollback
- [ ] Collect data for post-mortem

After rollback:

- [ ] Verify service is stable
- [ ] Document lessons learned
- [ ] Create action items for retry
- [ ] Update migration plan
- [ ] Schedule post-mortem meeting

---

## Related Documentation

- [ElevenLabs Troubleshooting Guide](./troubleshooting.md)
- [ElevenLabs API Reference](../../domains/lilith/extras/reference/elevenlabs-integration.md)
- [ElevenLabs Best Practices](../../../apps/lilith/svc-ai/docs/guides/elevenlabs-integration.md)
