[播报文本净化]:完成TTS表情包过滤,包含emoji清理、上下文净化和回归测试
This commit is contained in:
@@ -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})
|
||||
|
||||
Reference in New Issue
Block a user