받아쓰기(STT)를 붙이고 나니 반대 방향도 필요해졌다. 글로 저장된 메모를 다시 소리로 들려주는 기능이다. 운전 중이거나 화면을 못 볼 때 읽어주는 용도인데, 이것도 OpenAI TTS를 직접 붙일까 하다가 이미 받아쓰기를 Vercel AI SDK로 처리하고 있어서 같은 SDK에 대응 함수가 있는지 찾아봤다.
ai 패키지에 experimental_generateSpeech가 있다. 텍스트를 넣으면 오디오가 나온다. 음성을 텍스트로 바꾸던 transcribe랑 정확히 짝을 이루는 함수라, 호출부 감각이 그대로였다.
기본 사용법
provider는 받아쓰기 때 깔아둔 @ai-sdk/openai를 그대로 쓴다.
npm install ai @ai-sdk/openai
라우트 핸들러에서 텍스트를 받아 음성 바이트를 돌려준다.
// app/api/speech/route.ts
import { experimental_generateSpeech as generateSpeech } from 'ai';
import { openai } from '@ai-sdk/openai';
export async function POST(req: Request) {
const { text } = await req.json();
const { audio } = await generateSpeech({
model: openai.speech('gpt-4o-mini-tts'),
text,
voice: 'alloy',
});
return new Response(audio.uint8Array, {
headers: { 'Content-Type': audio.mediaType ?? 'audio/mpeg' },
});
}
포인트는 openai.speech(...)다. 텍스트는 openai(...), 받아쓰기는 .transcription(...), 음성 합성은 .speech(...)으로 모델을 고른다. voice에 alloy, echo, nova 같은 프리셋을 넣으면 목소리가 바뀐다.
반환값에서 챙길 것
audio 객체 하나에 필요한 게 다 들어있다. uint8Array로 바이트를 바로 응답에 실을 수 있고, base64가 필요하면 audio.base64도 있다. mediaType은 audio/mpeg처럼 실제 포맷을 알려주니까 Content-Type에 그대로 꽂으면 된다.
포맷을 바꾸고 싶으면 providerOptions로 내려준다.
const { audio } = await generateSpeech({
model: openai.speech('gpt-4o-mini-tts'),
text,
voice: 'nova',
providerOptions: { openai: { response_format: 'wav', speed: 1.1 } },
});
클라이언트에서 재생하기
응답을 Blob으로 받아 URL.createObjectURL로 오디오 소스만 만들어주면 끝이다.
'use client';
async function play(text: string) {
const res = await fetch('/api/speech', {
method: 'POST',
body: JSON.stringify({ text }),
});
const blob = await res.blob();
const audio = new Audio(URL.createObjectURL(blob));
await audio.play();
}
<audio> 태그에 물려도 되고, 이렇게 코드로 바로 재생해도 된다.
TTS API를 직접 붙였으면 요청 바디랑 바이너리 응답 처리를 새로 읽어야 했을 텐데, 받아쓰기 뽑던 감각 그대로 음성 합성까지 됐다. provider만 바꾸면 다른 TTS 모델로 갈아타도 호출부는 그대로다. 아직 experimental_ 접두사가 붙어 있으니 시그니처가 바뀔 수 있다는 것만 염두에 두면 된다.