deepgram-migration-deep-dive

v2026.09.24

Deep dive into migrating to Deepgram from other transcription providers. Use when migrating from AWS Transcribe, Google Cloud STT, Azure Speech, OpenAI Whisper, AssemblyAI, or Rev.ai to Deepgram. Trigger: "deepgram migration", "switch to deepgram", "migrate transcription", "deepgram from AWS", "deepgram from Google", "replace whisper with deepgram".

GitHub
安装命令
npx skhub add jeremylongshore/deepgram-migration-deep-dive
Markdown
SKILL.md

Deepgram Migration Deep Dive

Prerequisites

  • An inventory of existing audio/transcript flows, data retention/consent obligations, evaluation fixtures, and rollback owner.
  • A staging environment with separate credentials and a compatibility/quality acceptance threshold.

Examples

Run the old and new transcription flow against approved non-sensitive fixtures, compare aggregate accuracy/latency/error metrics, and route a small reversible staging canary only after acceptance passes. Roll back immediately on quality, consent, or operational regression; do not bulk-migrate or reprocess customer recordings before the observation window closes.

Current State

!npm list @deepgram/sdk 2>/dev/null | grep deepgram || echo 'Not installed' !npm list @aws-sdk/client-transcribe 2>/dev/null | grep transcribe || echo 'AWS Transcribe SDK not found' !pip show google-cloud-speech 2>/dev/null | grep Version || echo 'Google STT not found'

Overview

Migrate to Deepgram from AWS Transcribe, Google Cloud Speech-to-Text, Azure Cognitive Services, or OpenAI Whisper. Uses an adapter pattern with a unified interface, parallel running for quality validation, percentage-based traffic shifting, and automated rollback.

Feature Mapping

AWS Transcribe -> Deepgram

AWS TranscribeDeepgramNotes
LanguageCode: 'en-US'language: 'en'ISO 639-1 (2-letter)
ShowSpeakerLabels: truediarize: trueSame feature, different param
VocabularyName: 'custom'keywords: ['term:1.5']Inline boosting, no pre-upload
ContentRedactionType: 'PII'redact: ['pci', 'ssn']Granular PII categories
OutputBucketNamecallback: 'https://...'Callback URL, not S3
Job polling modelSync response or callbackNo polling needed

Google Cloud STT -> Deepgram

Google STTDeepgramNotes
RecognitionConfig.encodingAuto-detectedDeepgram auto-detects format
RecognitionConfig.sampleRateHertzsample_rate (live only)REST auto-detects
RecognitionConfig.model: 'latest_long'model: 'nova-3'Direct mapping
SpeakerDiarizationConfigdiarize: trueSimpler configuration
StreamingRecognizelisten.live()WebSocket vs gRPC

OpenAI Whisper -> Deepgram

WhisperDeepgramNotes
Local GPU processingAPI callNo GPU needed
whisper.transcribe(audio)listen.prerecorded.transcribeFile()Similar interface
model='large-v3'model: 'nova-3'10-100x faster
language='en'language: 'en'Same format
No diarizationdiarize: trueDeepgram advantage
No streaminglisten.live()Deepgram advantage

Instructions

Step 1: Adapter Pattern

interface TranscriptionResult {
  transcript: string;
  confidence: number;
  words: Array<{ word: string; start: number; end: number; speaker?: number }>;
  duration: number;
  provider: string;
}

interface TranscriptionAdapter {
  transcribeUrl(url: string, options: any): Promise<TranscriptionResult>;
  transcribeFile(path: string, options: any): Promise<TranscriptionResult>;
  name: string;
}

Step 2: Deepgram Adapter

import { createClient } from '@deepgram/sdk';
import { readFileSync } from 'fs';

class DeepgramAdapter implements TranscriptionAdapter {
  name = 'deepgram';
  private client: ReturnType<typeof createClient>;

  constructor(apiKey: string) {
    this.client = createClient(apiKey);
  }

  async transcribeUrl(url: string, options: any = {}): Promise<TranscriptionResult> {
    const { result, error } = await this.client.listen.prerecorded.transcribeUrl(
      { url },
      {
        model: options.model ?? 'nova-3',
        smart_format: true,
        diarize: options.diarize ?? false,
        language: options.language ?? 'en',
        keywords: options.keywords,
        redact: options.redact,
      }
    );
    if (error) throw new Error(`Deepgram: ${error.message}`);
    return this.normalize(result);
  }

  async transcribeFile(path: string, options: any = {}): Promise<TranscriptionResult> {
    const audio = readFileSync(path);
    const { result, error } = await this.client.listen.prerecorded.transcribeFile(
      audio,
      {
        model: options.model ?? 'nova-3',
        smart_format: true,
        diarize: options.diarize ?? false,
      }
    );
    if (error) throw new Error(`Deepgram: ${error.message}`);
    return this.normalize(result);
  }

  private normalize(result: any): TranscriptionResult {
    const alt = result.results.channels[0].alternatives[0];
    return {
      transcript: alt.transcript,
      confidence: alt.confidence,
      words: (alt.words ?? []).map((w: any) => ({
        word: w.punctuated_word ?? w.word,
        start: w.start,
        end: w.end,
        speaker: w.speaker,
      })),
      duration: result.metadata.duration,
      provider: 'deepgram',
    };
  }
}

Step 3: AWS Transcribe Adapter (Legacy)

// Legacy adapter — wraps existing AWS Transcribe code for parallel running
import { TranscribeClient, StartTranscriptionJobCommand, GetTranscriptionJobCommand }
  from '@aws-sdk/client-transcribe';

class AWSTranscribeAdapter implements TranscriptionAdapter {
  name = 'aws-transcribe';
  private client: TranscribeClient;

  constructor() {
    this.client = new TranscribeClient({});
  }

  async transcribeUrl(url: string, options: any = {}): Promise<TranscriptionResult> {
    const jobName = `migration-${Date.now()}`;

    await this.client.send(new StartTranscriptionJobCommand({
      TranscriptionJobName: jobName,
      LanguageCode: options.language ?? 'en-US',
      Media: { MediaFileUri: url },
      Settings: {
        ShowSpeakerLabels: options.diarize ?? false,
        MaxSpeakerLabels: options.diarize ? 10 : undefined,
      },
    }));

    // Poll for completion (AWS is async-only)
    let job;
    do {
      await new Promise(r => setTimeout(r, 5000));
      const result = await this.client.send(new GetTranscriptionJobCommand({
        TranscriptionJobName: jobName,
      }));
      job = result.TranscriptionJob;
    } while (job?.TranscriptionJobStatus === 'IN_PROGRESS');

    if (job?.TranscriptionJobStatus !== 'COMPLETED') {
      throw new Error(`AWS Transcribe failed: ${job?.FailureReason}`);
    }

    // Fetch and normalize result
    const response = await fetch(job.Transcript!.TranscriptFileUri!);
    const data = await response.json();

    return {
      transcript: data.results.transcripts[0].transcript,
      confidence: 0, // AWS doesn't provide overall confidence
      words: data.results.items
        .filter((i: any) => i.type === 'pronunciation')
        .map((i: any) => ({
          word: i.alternatives[0].content,
          start: parseFloat(i.start_time),
          end: parseFloat(i.end_time),
          speaker: i.speaker_label ? parseInt(i.speaker_label.replace('spk_', '')) : undefined,
        })),
      duration: 0,
      provider: 'aws-transcribe',
    };
  }

  async transcribeFile(path: string): Promise<TranscriptionResult> {
    throw new Error('Upload to S3 first, then use transcribeUrl');
  }
}

Step 4: Migration Router with Traffic Shifting

class MigrationRouter {
  private adapters: Map<string, TranscriptionAdapter> = new Map();
  private deepgramPercent: number;

  constructor(deepgramPercent = 0) {
    this.deepgramPercent = deepgramPercent;
  }

  register(adapter: TranscriptionAdapter) {
    this.adapters.set(adapter.name, adapter);
  }

  setDeepgramPercent(percent: number) {
    this.deepgramPercent = Math.max(0, Math.min(100, percent));
    console.log(`Traffic split: ${this.deepgramPercent}% Deepgram, ${100 - this.deepgramPercent}% legacy`);
  }

  async transcribe(url: string, options: any = {}): Promise<TranscriptionResult> {
    const useDeepgram = Math.random() * 100 < this.deepgramPercent;
    const primary = useDeepgram ? 'deepgram' : this.getLegacyName();
    const adapter = this.adapters.get(primary);

    if (!adapter) throw new Error(`Adapter not found: ${primary}`);

    const start = Date.now();
    const result = await adapter.transcribeUrl(url, options);
    const elapsed = Date.now() - start;

    console.log(`[${primary}] ${elapsed}ms, confidence: ${result.confidence.toFixed(3)}`);
    return result;
  }

  private getLegacyName(): string {
    for (const [name] of this.adapters) {
      if (name !== 'deepgram') return name;
    }
    throw new Error('No legacy adapter registered');
  }
}

// Migration rollout:
const router = new MigrationRouter(0);
router.register(new AWSTranscribeAdapter());
router.register(new DeepgramAdapter(process.env.DEEPGRAM_API_KEY!));

// Week 1: 5% to Deepgram
router.setDeepgramPercent(5);
// Week 2: 25%
router.setDeepgramPercent(25);
// Week 3: 50%
router.setDeepgramPercent(50);
// Week 4: 100% — migration complete
router.setDeepgramPercent(100);

Step 5: Parallel Running and Quality Validation

async function validateMigration(
  testAudioUrls: string[],
  legacyAdapter: TranscriptionAdapter,
  deepgramAdapter: TranscriptionAdapter,
  minSimilarity = 0.85
) {
  console.log(`Validating ${testAudioUrls.length} files (min similarity: ${minSimilarity})`);

  const results: Array<{
    url: string;
    similarity: number;
    legacyConfidence: number;
    deepgramConfidence: number;
    legacyTime: number;
    deepgramTime: number;
    pass: boolean;
  }> = [];

  for (const url of testAudioUrls) {
    const legacyStart = Date.now();
    const legacy = await legacyAdapter.transcribeUrl(url);
    const legacyTime = Date.now() - legacyStart;

    const dgStart = Date.now();
    const dg = await deepgramAdapter.transcribeUrl(url);
    const dgTime = Date.now() - dgStart;

    // Jaccard similarity
    const words1 = new Set(legacy.transcript.toLowerCase().split(/\s+/));
    const words2 = new Set(dg.transcript.toLowerCase().split(/\s+/));
    const intersection = new Set([...words1].filter(w => words2.has(w)));
    const union = new Set([...words1, ...words2]);
    const similarity = intersection.size / union.size;

    results.push({
      url: url.substring(url.lastIndexOf('/') + 1),
      similarity,
      legacyConfidence: legacy.confidence,
      deepgramConfidence: dg.confidence,
      legacyTime,
      deepgramTime,
      pass: similarity >= minSimilarity,
    });
  }

  // Report
  const passCount = results.filter(r => r.pass).length;
  console.log(`\n=== Validation Results ===`);
  for (const r of results) {
    console.log(`${r.pass ? 'PASS' : 'FAIL'} ${r.url}: similarity=${(r.similarity * 100).toFixed(1)}% ` +
      `(legacy: ${r.legacyTime}ms, deepgram: ${r.deepgramTime}ms)`);
  }
  console.log(`\n${passCount}/${results.length} passed (${(passCount / results.length * 100).toFixed(0)}%)`);

  return { results, allPassed: passCount === results.length };
}

Step 6: Migration Checklist

PhaseActionsDuration
AssessmentAudit current usage, map features, estimate costs1 week
SetupInstall SDK, implement adapter pattern, create test suite1 week
ValidationParallel run on test corpus, measure similarity1 week
Rollout 5%Enable for 5% of traffic, monitor closely1 week
Rollout 25%Increase if no issues, monitor error rate1 week
Rollout 50%Continue monitoring, compare costs1 week
Rollout 100%Full cutover, decommission legacy1 week
CleanupRemove legacy adapter, update docs1 week

Output

  • Unified TranscriptionAdapter interface
  • Deepgram and legacy (AWS/Google) adapter implementations
  • Migration router with percentage-based traffic shifting
  • Parallel running with Jaccard similarity validation
  • Migration timeline and checklist

Error Handling

IssueCauseSolution
Low similarityFeature mapping incompleteCheck options mapping (language, diarize)
Deepgram slower than expectedFirst request cold startPre-warm with test request
Missing featuresNo direct equivalentUse keywords for custom vocab
Rollback neededQuality regressionrouter.setDeepgramPercent(0) immediately

Resources

发现
标签

此技能尚未发布标签。

版本
最新版本元数据

版本

v2026.09.24

发布时间

2026年9月24日

分类

未分类

许可证

MIT

源路径

skills/.curated/deepgram-migration-deep-dive

默认分支

main

最新提交

e5a6c3b

Tree SHA

c2dc8e8