Add voice management tool bin/voice_tool.py and dynamic voice switching system

This commit is contained in:
Adolfo Reyna
2026-08-07 20:16:35 -04:00
parent 87b54ce8f4
commit 351b87beb8
3 changed files with 168 additions and 5 deletions
+10 -5
View File
@@ -18,6 +18,7 @@ from claude_agent_sdk import ClaudeAgentOptions, SandboxSettings
from loguru import logger
from brain import Brain
from voice_manager import VoiceManager
from claude_llm import ClaudeCodeLLM
from echo_guard import EchoGuardUserMuteStrategy
from pipecat.audio.vad.silero import SileroVADAnalyzer
@@ -70,6 +71,7 @@ say them:
- Spell out things that only make sense visually. Say "line forty-two of
bot dot py" rather than pasting a path.
- Use your available tools (listing directories, searching, reading files) whenever the user asks about files or workspace tasks.
- You can change your own voice! If the user asks to list available voices or switch voice, run `python bin/voice_tool.py list` or `python bin/voice_tool.py set <voice_name>` (voices: af_heart, af_bella, am_michael, am_fenrir, am_puck, bf_emma, bm_george, Moira, Daniel).
- Complete multi-step tool calls fully before speaking your response. Do not stop halfway to ask if you should continue.
- Be strictly truthful about your findings and never invent fake file contents.
- The user's words reach you through speech recognition, so expect occasional
@@ -326,7 +328,7 @@ def build_stt(args: argparse.Namespace, vocabulary):
return WhisperSTTService(settings=WhisperSTTService.Settings(model=model, language=Language.EN))
def build_tts(args: argparse.Namespace):
def build_tts(args: argparse.Namespace, voice_manager=None):
if args.tts == "apple":
from apple_tts import AppleTTSService, find_voice
@@ -339,12 +341,15 @@ def build_tts(args: argparse.Namespace):
if args.voice_rate:
logger.warning("--voice-rate only applies to --tts apple; ignoring it.")
voice = args.voice or "af_heart"
voice = (voice_manager.load_saved_voice() if voice_manager else None) or args.voice or "af_heart"
logger.info(f"Text to speech: Kokoro {voice}")
return KokoroTTSService(
tts = KokoroTTSService(
settings=KokoroTTSService.Settings(voice=voice, language=Language.EN),
text_filters=[SpokenTextFilter()],
)
if voice_manager:
voice_manager.set_tts_processor(tts)
return tts
def build_turn_taking(args: argparse.Namespace):
@@ -580,9 +585,9 @@ async def main() -> int:
if vocabulary:
vocabulary.observe(text)
voice_manager = VoiceManager(workspace)
llm = build_llm(args, vocabulary, brain, observer=on_reply)
tts = build_tts(args)
tts = build_tts(args, voice_manager=voice_manager)
# Claude keeps its own history; this context exists so Pipecat can decide
# when a turn has ended.