Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/server/bot/core/component_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from ..components.llm_tools.end_conversation_handler import EndConversationHandler
from ..processors.speech.lipsync_processor import LipsyncProcessor
from ..transport.custom_services.kokoro_service import KokoroTTSService
from ..transport.custom_services.emoji_text_filter import EmojiTextFilter
from ..utils.device_utils import get_best_device
from ..transport.custom_services.ollama_service import CustomOLLamaLLMService
from ..components.memory import MemoryHandler
Expand Down Expand Up @@ -178,6 +179,10 @@ def build_instruction(self) -> str:
"discourse markers (you know, I mean), and brief pauses (uh, um). "
"Use sparingly; never in numbers or names.\n"
)
instruction += (
"Important: Do not use emojis or any special unicode symbols. "
"Your responses are converted directly to audio — emojis will be read aloud as their names.\n"
)

return instruction.strip()

Expand Down Expand Up @@ -377,20 +382,23 @@ def _build_tts_service(self) -> Optional[object]:
if not self.tts_type:
return None

emoji_filter = EmojiTextFilter()
if self.tts_type == "openai":
return OpenAITTSService(
voice=self._get_voice_for_openai(),
model=(self.tts_params or {}).get("model", "gpt-4o-mini-tts"),
text_filters=[emoji_filter],
)
elif self.tts_type == "elevenlabs":
return ElevenLabsTTSService(
api_key=os.getenv("ELEVENLABS_API_KEY"),
voice_id=self._get_voice_id_for_elevenlabs(),
model="eleven_flash_v2_5",
text_filters=[emoji_filter],
)
elif self.tts_type == "kokoro":
device = get_best_device()
return KokoroTTSService(
voice=self._get_voice_id_for_kokoro(), device=device
voice=self._get_voice_id_for_kokoro(), device=device, text_filters=[emoji_filter]
)
return None
39 changes: 39 additions & 0 deletions src/server/bot/transport/custom_services/emoji_text_filter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import re
from typing import Any, Mapping

from pipecat.utils.text.base_text_filter import BaseTextFilter

_EMOJI_RE = re.compile(
"["
"\U0001F600-\U0001F64F"
"\U0001F300-\U0001F5FF"
"\U0001F680-\U0001F6FF"
"\U0001F1E0-\U0001F1FF"
"\U00002600-\U000026FF"
"\U00002700-\U000027BF"
"\U0000FE00-\U0000FE0F"
"\U0001F900-\U0001F9FF"
"\U0001FA70-\U0001FAFF"
"\U00002300-\U000023FF"
"\U00002B50-\U00002B55"
"\U0001F004"
"\U0001F0CF"
"]+",
flags=re.UNICODE,
)
Comment on lines +6 to +23

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current regex for identifying emojis is missing several important ranges and special characters. Specifically, it does not include the Zero Width Joiner (\u200D), which is frequently used in complex emoji sequences (like family or profession emojis), nor does it cover combining characters like the keycap symbol (\u20E3). Additionally, many symbols in the \U0001F000-\U0001F2FF range (Mahjong, Dominoes, Playing Cards, Enclosed Alphanumeric/Ideographic Supplements) are omitted. Consider using a more comprehensive range or adding these specific characters to prevent them from being read aloud by the TTS engine.



class EmojiTextFilter(BaseTextFilter):
"""Strips emoji characters from text before TTS synthesis."""

async def update_settings(self, settings: Mapping[str, Any]):
pass

async def filter(self, text: str) -> str:
return _EMOJI_RE.sub("", text).strip()
Comment on lines +32 to +33

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Replacing emojis with an empty string can lead to double spaces in the middle of sentences (e.g., "Hello 😃 world" becomes "Hello world"), which might cause unnatural pauses in some TTS engines. It is better to replace emojis with a single space and then normalize the whitespace to ensure a clean string is passed to the synthesis service.

Suggested change
async def filter(self, text: str) -> str:
return _EMOJI_RE.sub("", text).strip()
async def filter(self, text: str) -> str:
# Replace emojis with a space to avoid merging words
text = _EMOJI_RE.sub(" ", text)
# Collapse multiple spaces and strip leading/trailing whitespace
return " ".join(text.split())


async def handle_interruption(self):
pass

async def reset_interruption(self):
pass