0Pricing
AI Agents · 강의

에이전트 응답의 텍스트-음성 변환

에이전트 파이프라인에서 OpenAI TTS, ElevenLabs, Google TTS API를 사용합니다.

에이전트 응답의 텍스트-음성 변환은(는) CoddyKit의 무료 AI Agents 강의입니다. 이것은 4개 중 2번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 AI Agents 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. AI Agents 강의에는 총 4개의 강의가 포함되어 있습니다.

에이전트에 TTS가 필요한 이유

듣기만 하고 텍스트로 응답하는 음성 에이전트는 진정한 음성 에이전트라고 할 수 없습니다. 텍스트 음성 변환(TTS)은 에이전트의 텍스트 응답을 음성 오디오로 변환하여 완전한 음성 대화 흐름을 완성합니다.

대표적인 두 API는 OpenAI TTS(빠르고 저렴하며 6가지 음성 지원)와 ElevenLabs(초실감형 음성, 스트리밍, 음성 복제 지원)입니다.

OpenAI TTS 기초

OpenAI의 TTS API는 텍스트를 몇 초 만에 음성으로 변환합니다. 기본 제공 음성은 alloy, echo, fable, onyx, nova, shimmer의 6가지입니다. MP3, opus, AAC, FLAC 출력 형식을 지원합니다.

import openai
import os

client = openai.OpenAI(api_key=os.getenv('OPENAI_API_KEY'))

def text_to_speech(text, voice='alloy', output_path='response.mp3'):
    response = client.audio.speech.create(
        model='tts-1',          # tts-1 (fast) or tts-1-hd (higher quality)
        voice=voice,            # alloy, echo, fable, onyx, nova, shimmer
        input=text,
        response_format='mp3'   # mp3, opus, aac, flac
    )
    with open(output_path, 'wb') as f:
        f.write(response.content)
    print(f'Audio saved to: {output_path}')
    return output_path

# Available voices:
# alloy    - neutral, balanced
# echo     - warm, conversational
# fable    - expressive
# onyx     - deep, authoritative
# nova     - friendly, upbeat
# shimmer  - soft, clear

OpenAI를 사용한 스트리밍 TTS

사용자가 느끼는 지연 시간을 줄이려면 전체 파일이 완성될 때까지 기다리지 말고 생성되는 즉시 오디오를 스트리밍합니다. 나머지 오디오가 계속 합성되는 동안에도 통화 상대는 오디오 재생을 시작할 수 있습니다.

import openai
import os

client = openai.OpenAI(api_key=os.getenv('OPENAI_API_KEY'))

def stream_tts_to_file(text, output_path, voice='nova'):
    with client.audio.speech.with_streaming_response.create(
        model='tts-1',
        voice=voice,
        input=text
    ) as response:
        response.stream_to_file(output_path)
    return output_path

def stream_tts_to_bytes(text, voice='nova'):
    audio_chunks = []
    with client.audio.speech.with_streaming_response.create(
        model='tts-1',
        voice=voice,
        input=text
    ) as response:
        for chunk in response.iter_bytes(chunk_size=4096):
            audio_chunks.append(chunk)
    return b''.join(audio_chunks)

ElevenLabs SDK: 고품질 TTS

ElevenLabs는 현재 이용 가능한 음성 중 가장 현실적인 음성을 제공합니다. 음성 복제, 감정적 어조 조절, 스트리밍을 지원합니다.

pip install elevenlabs로 설치합니다.

from elevenlabs import ElevenLabs, VoiceSettings
import os

client = ElevenLabs(api_key=os.getenv('ELEVENLABS_API_KEY'))

def elevenlabs_tts(text, voice_id='pNInz6obpgDQGcFmaJgB', output_path='response.mp3'):
    # Common voice IDs:
    # Rachel:  21m00Tcm4TlvDq8ikWAM
    # Adam:    pNInz6obpgDQGcFmaJgB
    # Bella:   EXAVITQu4vr4xnSDxMaL

    audio = client.generate(
        text=text,
        voice=voice_id,
        model='eleven_multilingual_v2',  # supports 29 languages
        voice_settings=VoiceSettings(
            stability=0.5,        # 0.0-1.0: lower = more expressive
            similarity_boost=0.8, # 0.0-1.0: higher = closer to original voice
            style=0.3             # 0.0-1.0: style exaggeration
        )
    )

    with open(output_path, 'wb') as f:
        for chunk in audio:
            f.write(chunk)

    return output_path

ElevenLabs 스트리밍 TTS

ElevenLabs는 스트리밍을 지원하므로 전체 텍스트가 합성되기 전에 오디오 청크를 받을 수 있습니다. 지연 시간이 중요한 음성 에이전트에서는 이 기능이 필수적입니다.

from elevenlabs import ElevenLabs
import os

client = ElevenLabs(api_key=os.getenv('ELEVENLABS_API_KEY'))

def streaming_tts(text, voice_id='pNInz6obpgDQGcFmaJgB'):
    audio_stream = client.generate(
        text=text,
        voice=voice_id,
        model='eleven_turbo_v2',  # fastest model, optimized for streaming
        stream=True
    )
    return audio_stream  # yields audio chunks as they are generated

def stream_to_file(text, output_path, voice_id='pNInz6obpgDQGcFmaJgB'):
    stream = streaming_tts(text, voice_id)
    with open(output_path, 'wb') as f:
        for chunk in stream:
            if chunk:
                f.write(chunk)
    print(f'Streaming TTS complete: {output_path}')

sounddevice를 사용한 오디오 재생

TTS 오디오를 생성한 후 로컬 장치에서 재생합니다. 로컬 컴퓨터에서 실행되는 에이전트에는 sounddevice와 soundfile을 함께 사용하는 방법이 가장 간단한 크로스 플랫폼 옵션입니다.

pip install sounddevice soundfile로 설치합니다.

import sounddevice as sd
import soundfile as sf
import numpy as np
import tempfile
import os

def play_audio_file(audio_path):
    data, sample_rate = sf.read(audio_path)
    sd.play(data, sample_rate)
    sd.wait()  # block until playback finishes

def play_mp3_bytes(audio_bytes):
    # Write to temp file then play (soundfile needs a file)
    with tempfile.NamedTemporaryFile(suffix='.mp3', delete=False) as f:
        f.write(audio_bytes)
        tmp_path = f.name
    try:
        play_audio_file(tmp_path)
    finally:
        os.unlink(tmp_path)

def say(text, voice='nova'):
    audio_bytes = stream_tts_to_bytes(text, voice=voice)
    play_mp3_bytes(audio_bytes)

# Usage
say('Hello! How can I help you today?')

pygame을 사용한 오디오 재생

pygame은 더 세밀한 제어 기능을 제공합니다. 오디오가 아직 재생 중인지 확인하고, 재생을 일찍 중지하며, 여러 사운드를 재생할 수 있습니다. 음성 에이전트의 인터럽트 처리에 유용합니다.

pip install pygame으로 설치합니다.

import pygame
import io
import tempfile
import os

pygame.mixer.init(frequency=44100, size=-16, channels=1, buffer=512)

def play_audio_pygame(audio_bytes):
    with tempfile.NamedTemporaryFile(suffix='.mp3', delete=False) as f:
        f.write(audio_bytes)
        tmp_path = f.name

    try:
        pygame.mixer.music.load(tmp_path)
        pygame.mixer.music.play()
        while pygame.mixer.music.get_busy():
            pygame.time.wait(50)  # check every 50ms
    finally:
        pygame.mixer.music.stop()
        os.unlink(tmp_path)

def stop_audio():
    if pygame.mixer.music.get_busy():
        pygame.mixer.music.stop()
        print('Audio stopped (interrupt)')

TTS 응답 캐싱

인사말, 오류 메시지, 메뉴 안내와 같은 일반적인 문구는 반복해서 합성됩니다. 이러한 오디오 파일을 캐시하면 문구마다 한 번만 합성 비용을 지불할 수 있습니다.

import hashlib
import os

TTS_CACHE_DIR = '/tmp/tts_cache'
os.makedirs(TTS_CACHE_DIR, exist_ok=True)

def cached_tts(text, voice='nova'):
    cache_key = hashlib.md5(f'{voice}:{text}'.encode()).hexdigest()
    cache_path = os.path.join(TTS_CACHE_DIR, f'{cache_key}.mp3')

    if os.path.exists(cache_path):
        print('TTS cache hit')
        return cache_path

    # Generate and save
    audio_bytes = stream_tts_to_bytes(text, voice=voice)
    with open(cache_path, 'wb') as f:
        f.write(audio_bytes)

    print(f'TTS cached: {cache_path}')
    return cache_path

# Pre-warm cache for common phrases on startup
COMMON_PHRASES = [
    'Hello! How can I help you today?',
    'I did not catch that. Could you repeat?',
    'Let me look that up for you.',
    'Thank you, goodbye!'
]

def prewarm_tts_cache():
    for phrase in COMMON_PHRASES:
        cached_tts(phrase)
    print(f'Pre-warmed {len(COMMON_PHRASES)} phrases')

스트리밍을 위한 문장별 TTS

긴 LLM 응답은 텍스트를 문장으로 나누고, 한 문장씩 합성하여 재생합니다. 나머지 응답이 계속 생성되는 동안에도 사용자는 약 500밀리초 안에 첫 문장을 들을 수 있습니다.

import re

def split_into_sentences(text):
    sentences = re.split(r'(?<=[.!?])\s+', text.strip())
    return [s.strip() for s in sentences if s.strip()]

def stream_response_as_voice(llm_response_text, voice='nova'):
    sentences = split_into_sentences(llm_response_text)
    print(f'Speaking {len(sentences)} sentences')

    for i, sentence in enumerate(sentences):
        if not sentence:
            continue
        print(f'Speaking ({i+1}/{len(sentences)}): {sentence[:60]}')
        cache_path = cached_tts(sentence, voice=voice)
        play_audio_file(cache_path)

# Example
response = 'The weather today is sunny and 22 degrees. Perfect for outdoor activities. Bring sunscreen if you plan to be outside for more than an hour.'
stream_response_as_voice(response)

음성과 모델 선택

음성 선택을 에이전트의 페르소나에 맞추세요. 격식 있거나 전문적인 에이전트에는 더 낮은 음성인 onyx와 echo를 사용하고, 친근한 어시스턴트에는 더 따뜻한 음성인 nova와 shimmer를 사용합니다.

VOICE_PROFILES = {
    'assistant':    {'service': 'openai',     'voice': 'nova',   'model': 'tts-1'},
    'professional': {'service': 'openai',     'voice': 'onyx',   'model': 'tts-1-hd'},
    'narrator':     {'service': 'openai',     'voice': 'fable',  'model': 'tts-1-hd'},
    'premium':      {'service': 'elevenlabs', 'voice_id': '21m00Tcm4TlvDq8ikWAM', 'model': 'eleven_multilingual_v2'}
}

def speak(text, persona='assistant'):
    profile = VOICE_PROFILES.get(persona, VOICE_PROFILES['assistant'])

    if profile['service'] == 'openai':
        audio_bytes = stream_tts_to_bytes(text, voice=profile['voice'])
        play_mp3_bytes(audio_bytes)
    elif profile['service'] == 'elevenlabs':
        output_path = elevenlabs_tts(text, voice_id=profile['voice_id'])
        play_audio_file(output_path)

speak('Your meeting has been rescheduled.', persona='professional')

말하기 속도와 음높이 조정

상황에 따라 적절한 말하기 특성이 달라집니다. 알림은 조금 더 빠르게, 이야기는 더 느리고 표현력 있게 전달해야 합니다. 음높이를 바꾸지 않고 속도를 조정하려면 TTS 오디오를 후처리하세요.

from pydub import AudioSegment
from pydub.effects import speedup
import os

def adjust_speech_speed(audio_path, speed_factor=1.0, output_path=None):
    if speed_factor == 1.0:
        return audio_path

    audio = AudioSegment.from_file(audio_path)

    if speed_factor > 1.0:
        # Speed up without changing pitch
        audio = speedup(audio, playback_speed=speed_factor)
    else:
        # Slow down: overlay with silence (simple method)
        # For production: use ffmpeg with atempo filter
        slow_factor = 1.0 / speed_factor
        audio = audio._spawn(
            audio.raw_data,
            overrides={'frame_rate': int(audio.frame_rate / slow_factor)}
        ).set_frame_rate(audio.frame_rate)

    if output_path is None:
        base = os.path.splitext(audio_path)[0]
        output_path = f'{base}_speed{speed_factor}.mp3'

    audio.export(output_path, format='mp3')
    return output_path

# Usage
path = text_to_speech('Your meeting starts in 5 minutes.')
fast_path = adjust_speech_speed(path, speed_factor=1.2)  # 20% faster for alerts

지식 확인

음성 에이전트에서 문장별 TTS 합성을 사용하는 가장 큰 이점은 무엇인가요?

복습: 에이전트 응답의 텍스트 음성 변환

OpenAI TTS(tts-1, tts-1-hd)는 빠른 합성과 스트리밍을 지원하는 6가지 음성을 제공하므로 대부분의 음성 에이전트에 적합합니다. ElevenLabs는 eleven_turbo_v2를 사용하여 지연 시간이 짧은 스트리밍과 더 높은 품질의 실감 나는 음성을 제공합니다.

오디오는 sounddevice(간단한 방식) 또는 pygame(인터럽트 가능)을 사용해 재생합니다. 일반적인 문구를 캐시하면 반복되는 API 비용을 없앨 수 있습니다. 긴 응답에는 문장별 합성을 사용하여 사용자가 느끼는 지연 시간을 최소화하세요. MD5 해시를 사용한 TTS 캐싱을 적용하면 동일한 텍스트를 항상 캐시에서 제공할 수 있습니다.

자주 묻는 질문

“에이전트 응답의 텍스트-음성 변환” 강의는 무료인가요?

네 — “에이전트 응답의 텍스트-음성 변환” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 AI Agents 강의 전체를 잠금 해제할 수 있습니다. AI Agents 강의에는 총 4개의 강의가 포함되어 있습니다.

“에이전트 응답의 텍스트-음성 변환”에서 뭘 배우나요?

에이전트 파이프라인에서 OpenAI TTS, ElevenLabs, Google TTS API를 사용합니다. 브라우저에서 직접 실행하는 실습 코드로 AI Agents을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

AI Agents을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 AI Agents은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 2번째 강의입니다.

“에이전트 응답의 텍스트-음성 변환” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 AI Agents 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 AI Agents 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. Whisper 및 Deepgram을 사용한 음성-텍스트 변환
  2. 에이전트 응답의 텍스트-음성 변환
  3. 음성 대화 반복 과정 만들기
  4. 음성 에이전트 지연 시간 최적화
← AI Agents(으)로 돌아가기