From 62d1a889ffa47d18595be6d819eb836fa77c2512 Mon Sep 17 00:00:00 2001 From: Andy Hong Date: Tue, 25 Aug 2026 22:25:44 +0900 Subject: [PATCH] feat: apply AI voice preferences --- agent/agent.py | 5 +++-- agent/logic.py | 16 ++++++++++++++++ agent/tests/test_logic.py | 15 ++++++++++++++- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/agent/agent.py b/agent/agent.py index 42869e2..32b5fb2 100644 --- a/agent/agent.py +++ b/agent/agent.py @@ -13,7 +13,7 @@ from livekit.agents import llm, stt from livekit.plugins import openai -from logic import Mode, admitted_mode, caption_attributes, is_hearing_microphone_audio, spoken_text +from logic import Mode, admitted_mode, caption_attributes, is_hearing_microphone_audio, speech_prompt, spoken_text log = logging.getLogger(__name__) server = agents.AgentServer() @@ -201,12 +201,13 @@ async def speak(reader: rtc.TextStreamReader, identity: str) -> None: text = spoken_text(await reader.read_all()) if state.mode_for(identity) is not Mode.DEAF or text is None: return + attributes = reader.info.attributes or {} async with state.tts_lock: started = time.monotonic() first_frame = True try: chat = llm.ChatContext() - chat.add_message(role="user", content=text) + chat.add_message(role="user", content=speech_prompt(text, attributes)) await state.realtime_session.update_chat_ctx(chat) generation = await state.realtime_session.generate_reply() async for message in generation.message_stream: diff --git a/agent/logic.py b/agent/logic.py index 46d665f..ef92863 100644 --- a/agent/logic.py +++ b/agent/logic.py @@ -40,6 +40,22 @@ def spoken_text(text: str) -> str | None: return value if 0 < len(value) <= 500 else None +def speech_prompt(text: str, attributes: dict[str, str]) -> str: + voices = { + "CALM": "차분하고 편안한 목소리로", + "BRIGHT": "밝고 친근한 목소리로", + "CLEAR": "또박또박 명료한 목소리로", + } + speeds = { + "SLOW": "평소보다 천천히", + "NORMAL": "자연스러운 속도로", + "FAST": "평소보다 빠르게", + } + voice = voices.get(attributes.get("openwindow.voice", ""), voices["CALM"]) + speed = speeds.get(attributes.get("openwindow.voice_speed", ""), speeds["NORMAL"]) + return f"다음 문장만 그대로, {voice} {speed} 읽어 주세요: {text}" + + def caption_attributes(segment_id: str, final: bool, track_id: str) -> dict[str, str]: return { "lk.segment_id": segment_id, diff --git a/agent/tests/test_logic.py b/agent/tests/test_logic.py index 5f3bf49..785a2cb 100644 --- a/agent/tests/test_logic.py +++ b/agent/tests/test_logic.py @@ -1,7 +1,7 @@ import asyncio from agent import RoomState -from logic import Mode, admitted_mode, caption_attributes, is_hearing_microphone_audio, mode_from_metadata, spoken_text +from logic import Mode, admitted_mode, caption_attributes, is_hearing_microphone_audio, mode_from_metadata, speech_prompt, spoken_text def test_admission_reserves_one_slot_and_one_mode_per_identity(): @@ -31,6 +31,19 @@ def test_text_bound_and_caption_attributes(): } +def test_speech_prompt_uses_preferences_and_safe_defaults(): + configured = speech_prompt( + "안녕하세요", {"openwindow.voice": "CLEAR", "openwindow.voice_speed": "SLOW"} + ) + assert "또박또박 명료한" in configured + assert "평소보다 천천히" in configured + assert configured.endswith("안녕하세요") + + defaulted = speech_prompt("반갑습니다", {"openwindow.voice": "UNKNOWN"}) + assert "차분하고 편안한" in defaulted + assert "자연스러운 속도" in defaulted + + def test_track_cleanup_cancels_the_transcription_task(): async def check() -> None: state = RoomState.__new__(RoomState)