エージェント応答における音声合成
エージェントパイプラインでOpenAI TTS、ElevenLabs、Google TTS APIを使用します。
「エージェント応答における音声合成」はCoddyKit上の無料AI Agentsレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはAI Agents学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 AI Agentsコースには全4レッスンが含まれています。
エージェントにTTSが必要な理由
聞くだけでテキストで応答する音声エージェントは、本当の意味での音声エージェントではありません。音声合成(TTS)は、エージェントのテキスト応答を音声に変換し、音声による一連のやり取りを完成させます。
主なAPIは2つあります。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, clearOpenAIによるストリーミング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_pathElevenLabsによるストリーミング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の長い応答の場合は、テキストを文に分割し、1文ずつ合成して再生します。残りのテキストが生成されている間も、ユーザーは約500ms以内に最初の文を聞き始められます。
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時間対応のAIチューター)、AI Agentsコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 AI Agentsコースには全4レッスンが含まれています。
「エージェント応答における音声合成」で何を学びますか?
エージェントパイプラインでOpenAI TTS、ElevenLabs、Google TTS APIを使用します。 ブラウザで直接実行するハンズオンコードでAI Agentsを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
AI Agentsを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのAI Agentsは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。
「エージェント応答における音声合成」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このAI Agentsレッスンでコードを書いて実行できますか?
はい。すべてのAI Agentsレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- WhisperとDeepgramによる音声認識
- エージェント応答における音声合成
- 音声会話ループの構築
- 音声エージェントのレイテンシ最適化