Merge pull request #1600 from pipecat-ai/mb/11labs-previous-text
Add previous_text context to ElevenLabsHttpTTSService
This commit is contained in:
@@ -16,6 +16,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
you to control aggregator settings. You can now pass these arguments when
|
you to control aggregator settings. You can now pass these arguments when
|
||||||
creating aggregator pairs with `create_context_aggregator()`.
|
creating aggregator pairs with `create_context_aggregator()`.
|
||||||
|
|
||||||
|
- Added `previous_text` context support to ElevenLabsHttpTTSService, improving
|
||||||
|
speech consistency across sentences within an LLM response.
|
||||||
|
|
||||||
- Added word/timestamp pairs to `ElevenLabsHttpTTSService`.
|
- Added word/timestamp pairs to `ElevenLabsHttpTTSService`.
|
||||||
|
|
||||||
- It is now possible to disable `SoundfileMixer` when created. You can then use
|
- It is now possible to disable `SoundfileMixer` when created. You can then use
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from pipecat.frames.frames import (
|
|||||||
EndFrame,
|
EndFrame,
|
||||||
ErrorFrame,
|
ErrorFrame,
|
||||||
Frame,
|
Frame,
|
||||||
|
LLMFullResponseEndFrame,
|
||||||
StartFrame,
|
StartFrame,
|
||||||
StartInterruptionFrame,
|
StartInterruptionFrame,
|
||||||
TTSAudioRawFrame,
|
TTSAudioRawFrame,
|
||||||
@@ -508,6 +509,9 @@ class ElevenLabsHttpTTSService(WordTTSService):
|
|||||||
self._cumulative_time = 0
|
self._cumulative_time = 0
|
||||||
self._started = False
|
self._started = False
|
||||||
|
|
||||||
|
# Store previous text for context within a turn
|
||||||
|
self._previous_text = ""
|
||||||
|
|
||||||
def language_to_service_language(self, language: Language) -> Optional[str]:
|
def language_to_service_language(self, language: Language) -> Optional[str]:
|
||||||
"""Convert pipecat Language to ElevenLabs language code."""
|
"""Convert pipecat Language to ElevenLabs language code."""
|
||||||
return language_to_elevenlabs_language(language)
|
return language_to_elevenlabs_language(language)
|
||||||
@@ -519,22 +523,32 @@ class ElevenLabsHttpTTSService(WordTTSService):
|
|||||||
def _set_voice_settings(self):
|
def _set_voice_settings(self):
|
||||||
return build_elevenlabs_voice_settings(self._settings)
|
return build_elevenlabs_voice_settings(self._settings)
|
||||||
|
|
||||||
|
def _reset_state(self):
|
||||||
|
"""Reset internal state variables."""
|
||||||
|
self._cumulative_time = 0
|
||||||
|
self._started = False
|
||||||
|
self._previous_text = ""
|
||||||
|
logger.debug(f"{self}: Reset internal state")
|
||||||
|
|
||||||
async def start(self, frame: StartFrame):
|
async def start(self, frame: StartFrame):
|
||||||
"""Initialize the service upon receiving a StartFrame."""
|
"""Initialize the service upon receiving a StartFrame."""
|
||||||
await super().start(frame)
|
await super().start(frame)
|
||||||
self._output_format = output_format_from_sample_rate(self.sample_rate)
|
self._output_format = output_format_from_sample_rate(self.sample_rate)
|
||||||
self._cumulative_time = 0
|
self._reset_state()
|
||||||
self._started = False
|
|
||||||
|
|
||||||
async def push_frame(self, frame: Frame, direction: FrameDirection = FrameDirection.DOWNSTREAM):
|
async def push_frame(self, frame: Frame, direction: FrameDirection = FrameDirection.DOWNSTREAM):
|
||||||
await super().push_frame(frame, direction)
|
await super().push_frame(frame, direction)
|
||||||
if isinstance(frame, (StartInterruptionFrame, TTSStoppedFrame)):
|
if isinstance(frame, (StartInterruptionFrame, TTSStoppedFrame)):
|
||||||
# Reset timing on interruption or stop
|
# Reset timing on interruption or stop
|
||||||
self._started = False
|
self._reset_state()
|
||||||
self._cumulative_time = 0
|
|
||||||
if isinstance(frame, TTSStoppedFrame):
|
if isinstance(frame, TTSStoppedFrame):
|
||||||
await self.add_word_timestamps([("LLMFullResponseEndFrame", 0), ("Reset", 0)])
|
await self.add_word_timestamps([("LLMFullResponseEndFrame", 0), ("Reset", 0)])
|
||||||
|
|
||||||
|
elif isinstance(frame, LLMFullResponseEndFrame):
|
||||||
|
# End of turn - reset previous text
|
||||||
|
self._previous_text = ""
|
||||||
|
|
||||||
def calculate_word_times(self, alignment_info: Mapping[str, Any]) -> List[Tuple[str, float]]:
|
def calculate_word_times(self, alignment_info: Mapping[str, Any]) -> List[Tuple[str, float]]:
|
||||||
"""Calculate word timing from character alignment data.
|
"""Calculate word timing from character alignment data.
|
||||||
|
|
||||||
@@ -597,6 +611,10 @@ class ElevenLabsHttpTTSService(WordTTSService):
|
|||||||
async def run_tts(self, text: str) -> AsyncGenerator[Frame, None]:
|
async def run_tts(self, text: str) -> AsyncGenerator[Frame, None]:
|
||||||
"""Generate speech from text using ElevenLabs streaming API with timestamps.
|
"""Generate speech from text using ElevenLabs streaming API with timestamps.
|
||||||
|
|
||||||
|
Makes a request to the ElevenLabs API to generate audio and timing data.
|
||||||
|
Tracks the duration of each utterance to ensure correct sequencing.
|
||||||
|
Includes previous text as context for better prosody continuity.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
text: Text to convert to speech
|
text: Text to convert to speech
|
||||||
|
|
||||||
@@ -613,6 +631,10 @@ class ElevenLabsHttpTTSService(WordTTSService):
|
|||||||
"model_id": self._model_name,
|
"model_id": self._model_name,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Include previous text as context if available
|
||||||
|
if self._previous_text:
|
||||||
|
payload["previous_text"] = self._previous_text
|
||||||
|
|
||||||
if self._voice_settings:
|
if self._voice_settings:
|
||||||
payload["voice_settings"] = self._voice_settings
|
payload["voice_settings"] = self._voice_settings
|
||||||
|
|
||||||
@@ -701,6 +723,13 @@ class ElevenLabsHttpTTSService(WordTTSService):
|
|||||||
if utterance_duration > 0:
|
if utterance_duration > 0:
|
||||||
self._cumulative_time += utterance_duration
|
self._cumulative_time += utterance_duration
|
||||||
|
|
||||||
|
# Append the current text to previous_text for context continuity
|
||||||
|
# Only add a space if there's already text
|
||||||
|
if self._previous_text:
|
||||||
|
self._previous_text += " " + text
|
||||||
|
else:
|
||||||
|
self._previous_text = text
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in run_tts: {e}")
|
logger.error(f"Error in run_tts: {e}")
|
||||||
yield ErrorFrame(error=str(e))
|
yield ErrorFrame(error=str(e))
|
||||||
|
|||||||
Reference in New Issue
Block a user