음성 전사
음성 전사 (Transcription)
오디오를 텍스트로 바꾸는 STT(음성 인식)가 필요한 경우가 있어요. AI SDK는 전사(transcription) 모델로 오디오를 텍스트로 만드는 transcribe 함수를 제공합니다.
출처: 공식문서
본문
import { transcribe } from 'ai';
import { openai } from '@ai-sdk/openai';
import { readFile } from 'fs/promises';
const transcript = await transcribe({
model: openai.transcription('whisper-1'),
audio: await readFile('audio.mp3'),
});
audio 속성은 Uint8Array·ArrayBuffer·Buffer·string(base64로 인코딩된 오디오 데이터)·URL 중 하나일 수 있어요.
생성된 전사 텍스트에 접근하는 방법입니다.
const text = transcript.text; // transcript text e.g. "Hello, world!"
const segments = transcript.segments; // array of segments with start and end times, if available
const language = transcript.language; // language of the transcript e.g. "en", if available
const durationInSeconds = transcript.durationInSeconds; // duration of the transcript in seconds, if available
스트리밍 전사
스트리밍 전사는 실험 기능이에요.
실시간 원본 오디오가 있고, 전체 오디오 스트림이 끝나기 전에 전사 결과를 갱신받고 싶다면 experimental_streamTranscribe를 사용하세요. 이 함수는 스트리밍을 지원하는 전사 모델을 사용하며, 제공자별 옵션으로 동작을 구성하되 스트리밍 작업 자체는 함수가 선택합니다.
import { openai } from '@ai-sdk/openai';
import { experimental_streamTranscribe as streamTranscribe } from 'ai';
const result = streamTranscribe({
model: openai.transcription('gpt-realtime-whisper'),
audio: audioStream, // ReadableStream<Uint8Array | string>
inputAudioFormat: { type: 'audio/pcm', rate: 24000 },
providerOptions: {
openai: {
language: 'en',
streaming: {
delay: 'low',
},
},
},
});
for await (const part of result.fullStream) {
if (part.type === 'transcript-delta') {
process.stdout.write(part.delta);
}
if (part.type === 'transcript-partial') {
console.log('partial:', part.text);
}
if (part.type === 'transcript-final') {
console.log('final:', part.text);
}
}
console.log(await result.text);
fullStream은 한 명의 소비자만 가질 수 있는 라이브 스트림이라 한 번만 접근할 수 있어요. 스트림 파트와 최종 결과가 모두 필요하면 fullStream을 먼저 접근하고, 그걸 소비하는 동안이나 이후에 결과 프로미스를 기다리면 됩니다. 결과 프로미스를 먼저 접근하면 스트림이 내부적으로 소비되어 fullStream을 더는 쓸 수 없어요. 이렇게 해서 라이브 오디오의 무제한 재생 버퍼를 붙잡지 않게 됩니다.
최종 전사 메타데이터에 접근하는 방법입니다.
const text = await result.text; // final transcript text
const segments = await result.segments; // final segments with timing, if available
const language = await result.language; // language of the transcript, if available
const durationInSeconds = await result.durationInSeconds; // duration in seconds, if available
audio 스트림에는 원본 오디오 청크가 들어 있어야 합니다. Uint8Array 청크는 원시 바이트이고, string 청크는 base64로 인코딩된 원시 바이트예요. 보내는 청크와 일치하도록 항상 inputAudioFormat을 설정하세요.
문자열 모델 ID는 전역 제공자(기본값은 AI Gateway)를 통해 해석됩니다. AI Gateway는 지원 모델(예: openai/gpt-realtime-whisper, elevenlabs/eleven-scribe-2-realtime, xai/grok-stt)에 대해 스트리밍 전사를 지원하므로 문자열 ID로도 동작해요: experimental_streamTranscribe({ model: 'openai/gpt-realtime-whisper', ... }). 제공자 모델 인스턴스(예: openai.transcription('gpt-realtime-whisper'))를 넘기면 제공자에 직접 스트리밍할 수도 있습니다.
OpenAI 스트리밍 전사는 openai.transcription('gpt-realtime-whisper')를 사용합니다. Cartesia는 스트리밍 전용 Ink 2 전사에 cartesia.transcription('ink-2')를, ElevenLabs는 Scribe v2 Realtime에 elevenLabs.transcription('scribe_v2_realtime')를 써요. xAI는 요청/응답과 스트리밍 전사에 같은 xai.transcription() 모델을 쓰며, experimental_streamTranscribe가 제공자의 WebSocket STT 전송을 선택합니다.
import { xai } from '@ai-sdk/xai';
import { experimental_streamTranscribe as streamTranscribe } from 'ai';
const result = streamTranscribe({
model: xai.transcription(),
audio: audioStream,
inputAudioFormat: { type: 'audio/pcm', rate: 16000 },
providerOptions: {
xai: {
language: 'en',
keyterm: ['AI SDK', 'Grok'],
streaming: {
interimResults: true,
endpointing: 500,
},
},
},
});
일부 제공자는 직접 스트리밍 STT에 WebSocket 헤더를 요구해요. 그런 런타임에서는 제공자를 만들 때 제공자별 webSocket 구현을 넘기세요.
설정
제공자별 설정
전사 모델은 종종 제공자·모델별 설정을 가지며, providerOptions 파라미터로 지정합니다.
import { transcribe } from 'ai';
import { openai } from '@ai-sdk/openai';
import { readFile } from 'fs/promises';
const transcript = await transcribe({
model: openai.transcription('whisper-1'),
audio: await readFile('audio.mp3'),
providerOptions: {
openai: {
timestampGranularities: ['word'],
},
},
});
다운로드 크기 제한
audio가 URL이면 SDK는 기본 2 GiB 크기 제한으로 파일을 내려받아요. 이 값은 createDownload로 조정할 수 있습니다.
import { transcribe, createDownload } from 'ai';
import { openai } from '@ai-sdk/openai';
const transcript = await transcribe({
model: openai.transcription('whisper-1'),
audio: new URL('https://example.com/audio.mp3'),
download: createDownload({ maxBytes: 50 * 1024 * 1024 }), // 50 MB limit
});
완전한 커스텀 다운로드 함수를 제공할 수도 있어요.
import { transcribe } from 'ai';
import { openai } from '@ai-sdk/openai';
const transcript = await transcribe({
model: openai.transcription('whisper-1'),
audio: new URL('https://example.com/audio.mp3'),
download: async ({ url }) => {
const res = await myAuthenticatedFetch(url);
return {
data: new Uint8Array(await res.arrayBuffer()),
mediaType: res.headers.get('content-type') ?? undefined,
};
},
});
다운로드가 크기 제한을 넘으면 DownloadError가 던져집니다.
import { transcribe, DownloadError } from 'ai';
import { openai } from '@ai-sdk/openai';
try {
await transcribe({
model: openai.transcription('whisper-1'),
audio: new URL('https://example.com/audio.mp3'),
});
} catch (error) {
if (DownloadError.isInstance(error)) {
console.log('Download failed:', error.message);
}
}
중단 신호와 타임아웃
transcribe는 선택적으로 AbortSignal 타입의 abortSignal 파라미터를 받아요. 전사 과정을 중단하거나 타임아웃을 걸 때 씁니다.
URL 다운로드와 함께 쓰면 오래 지속되는 요청을 막는 데 특히 유용합니다.
import { openai } from '@ai-sdk/openai';
import { transcribe } from 'ai';
const transcript = await transcribe({
model: openai.transcription('whisper-1'),
audio: new URL('https://example.com/audio.mp3'),
abortSignal: AbortSignal.timeout(5000), // Abort after 5 seconds
});
커스텀 헤더
transcribe는 선택적으로 Record<string, string> 타입의 headers 파라미터를 받아 전사 요청에 커스텀 헤더를 더할 수 있어요.
import { openai } from '@ai-sdk/openai';
import { transcribe } from 'ai';
import { readFile } from 'fs/promises';
const transcript = await transcribe({
model: openai.transcription('whisper-1'),
audio: await readFile('audio.mp3'),
headers: { 'X-Custom-Header': 'custom-value' },
});
경고(Warnings)
경고(예: 지원하지 않는 파라미터)는 warnings 속성에서 확인할 수 있어요.
import { openai } from '@ai-sdk/openai';
import { transcribe } from 'ai';
import { readFile } from 'fs/promises';
const transcript = await transcribe({
model: openai.transcription('whisper-1'),
audio: await readFile('audio.mp3'),
});
const warnings = transcript.warnings;
오류 처리
transcribe가 유효한 전사 텍스트를 만들지 못하면 AI_NoTranscriptGeneratedError를 던져요.
이 오류는 다음 이유 중 하나로 발생할 수 있습니다.
- 모델이 응답을 생성하지 못했거나
- 모델이 생성했지만 파싱할 수 없는 응답일 때
오류는 로깅에 도움이 되도록 다음 정보를 보존합니다.
responses: 전사 모델 응답에 대한 메타데이터(타임스탬프·모델·헤더 포함)cause: 오류의 원인. 더 상세한 오류 처리를 할 때 씁니다.
import { transcribe, NoTranscriptGeneratedError } from 'ai';
import { openai } from '@ai-sdk/openai';
import { readFile } from 'fs/promises';
try {
await transcribe({
model: openai.transcription('whisper-1'),
audio: await readFile('audio.mp3'),
});
} catch (error) {
if (NoTranscriptGeneratedError.isInstance(error)) {
console.log('NoTranscriptGeneratedError');
console.log('Cause:', error.cause);
console.log('Responses:', error.responses);
}
}
전사 모델
OpenAI(whisper-1, gpt-4o-transcribe 외), ElevenLabs(scribe_v1 외), Groq(whisper-large-v3-turbo 외), Mistral(voxtral-mini-latest), Azure OpenAI, Rev.ai, Deepgram(nova-3 외), Gladia, AssemblyAI, Fal, Google Vertex(chirp_3 외), xAI(default), Cartesia(ink-whisper), Fish Audio(transcribe-1) 등 다양한 제공자에서 전사 모델을 지원해요. 위 목록은 AI SDK 제공자들이 지원하는 전사 모델 중 일부에 불과하니, 더 자세한 내용은 각 제공자 문서를 참고하세요.
더 알아보기
- 음성 생성(Speech)으로 텍스트를 음성으로
- 전사 모델 API 레퍼런스(
transcribe,experimental_streamTranscribe) - 파일 업로드(File Uploads)로 제공자에 자산 올리기