[播报文本净化]:完成TTS表情包过滤,包含emoji清理、上下文净化和回归测试

This commit is contained in:
mkbk
2026-06-18 12:45:31 +08:00
parent 25255f178e
commit 3408a30e25
8 changed files with 285 additions and 22 deletions
+28 -7
View File
@@ -33,7 +33,7 @@ from .models import AudioSegment, ErrorCode, PipelineState, ProviderError
from .protocols import AudioTransport, LlmProvider, RealtimeSttProvider, SttProvider, TtsProvider, WakeWordProvider
from .stt import CloudAsrSttProvider, SherpaOnnxSttProvider, is_valid_transcript_text
from .transport import SoundDeviceAudioTransport
from .tts import CloudTtsProvider, MacSayTtsProvider, SentenceBuffer
from .tts import CloudTtsProvider, MacSayTtsProvider, SentenceBuffer, sanitize_tts_text
from .vad import EnergyVadProvider, HybridVadProvider, PrimarySpeakerVadRecorder, SherpaOnnxVadProvider, VadRecorder
from .wakeword import SherpaOnnxKeywordWakeWordProvider
@@ -267,13 +267,18 @@ class LiveVoiceRuntime:
self.context.append_user(user_text)
self._event(LLM_STARTED, PipelineState.THINKING, "思考中:正在生成回复", turn_id=turn_id)
assistant_text = ""
spoken_parts: list[str] = []
try:
for delta in self.llm.stream_reply(self.context.build_llm_messages()):
assistant_text += delta.text_delta
for sentence in self.sentence_buffer.feed(delta.text_delta, bool(delta.finish_reason)):
self._speak(sentence, turn_id)
spoken = self._speak(sentence, turn_id)
if spoken:
spoken_parts.append(spoken)
for sentence in self.sentence_buffer.flush():
self._speak(sentence, turn_id)
spoken = self._speak(sentence, turn_id)
if spoken:
spoken_parts.append(spoken)
except ProviderError as exc:
return self._recover(exc, turn_id)
if not assistant_text.strip():
@@ -287,18 +292,34 @@ class LiveVoiceRuntime:
),
turn_id,
)
self.context.append_assistant(assistant_text)
spoken_text = "".join(spoken_parts)
if not spoken_text.strip():
return self._recover(
ProviderError(
ErrorCode.TTS_EMPTY_AUDIO,
"LLM reply contained no speakable text after TTS sanitization",
True,
"live-runtime",
"tts",
),
turn_id,
)
self.context.append_assistant(spoken_text)
self._event(STANDBY_RESUMED, PipelineState.WAKE_LISTENING, "恢复待机:可继续唤醒", turn_id=turn_id)
return TurnResult(True, user_text, assistant_text, states=list(self._states))
return TurnResult(True, user_text, spoken_text, states=list(self._states))
def _speak(self, sentence: str, turn_id: int) -> None:
def _speak(self, sentence: str, turn_id: int) -> str:
spoken_sentence = sanitize_tts_text(sentence)
if not spoken_sentence:
return ""
self._event(TTS_STARTED, PipelineState.SPEAKING, "播放中:正在播报回复", turn_id=turn_id)
segment = self.tts.synthesize(sentence)
segment = self.tts.synthesize(spoken_sentence)
playback = self.transport.play_pcm(segment)
if playback.error:
raise playback.error
self._event(PLAYBACK_FINISHED, PipelineState.SPEAKING, "播放完成", turn_id=turn_id)
self._drain_input_after_playback()
return spoken_sentence
def _recover(self, error: ProviderError, turn_id: int) -> TurnResult:
self._event(STAGE_ERROR, PipelineState.ERROR_RECOVERING, error.message, turn_id=turn_id, payload={"error": error})