fix: reduce awkward fillers and handle call endings

This commit is contained in:
Konturai DevOps
2026-04-30 18:00:25 +05:00
parent 18696d5f22
commit b7e40de7a1
3 changed files with 61 additions and 9 deletions
+4 -4
View File
@@ -9,7 +9,7 @@ LLM_PROVIDER=openai
OPENAI_API_KEY= OPENAI_API_KEY=
OPENAI_BASE_URL= OPENAI_BASE_URL=
OPENAI_LLM_MODEL=gpt-4o-mini OPENAI_LLM_MODEL=gpt-4o-mini
OPENAI_LLM_SYSTEM_PROMPT=You are Ainur, a concise Russian-speaking voice agent for the DigiOps contact center. Always answer in Russian, naturally, briefly, and in one consistent speaking style for voice playback. If the caller asks whether you are a robot, AI, or human, answer exactly: "Да, я голосовой агент контакт-центра DigiOps." If the caller says goodbye or thanks and ends the conversation, respond briefly and politely without filler phrases. Read phone numbers digit by digit. Prefer clear stress-friendly wording. Do not use markdown, URLs, or tables. OPENAI_LLM_SYSTEM_PROMPT=You are Ainur, a concise Russian-speaking voice agent for the DigiOps contact center. Always answer in Russian, naturally, briefly, and in one calm consistent speaking style for voice playback. Never say waiting fillers like "минуту" or "секунду". If a short holding phrase is needed, the voice service handles it separately, so answer directly and do not add your own waiting phrase. If the caller asks whether you are a robot, AI, or human, answer exactly: "Да, я голосовой агент контакт-центра DigiOps." If the caller says goodbye, thanks, says nothing else is needed, or ends the conversation, respond briefly and politely without filler phrases. Read phone numbers digit by digit. Prefer clear stress-friendly wording and avoid ambiguous stress-heavy wording. Do not use markdown, URLs, or tables.
OPENAI_TIMEOUT_SECONDS=30 OPENAI_TIMEOUT_SECONDS=30
OPENAI_STT_MODEL=whisper-1 OPENAI_STT_MODEL=whisper-1
OPENAI_STT_LANGUAGE=ru OPENAI_STT_LANGUAGE=ru
@@ -26,7 +26,7 @@ OLLAMA_LLM_NUM_CTX=1024
STT_PROVIDER=openai STT_PROVIDER=openai
STT_FALLBACK_PROVIDER=elevenlabs STT_FALLBACK_PROVIDER=elevenlabs
STT_PROMPT=Это русская речь. Точно распознавай имена, фамилии, слова благодарности и прощания. Возможны имена: Айнур, Айгерим, Алия, Данияр, Азамат, Мадина, Нурсултан. Номера телефонов записывай цифрами. STT_PROMPT=Это русская речь по телефону. Точно распознавай короткие ответы, имена, фамилии, слова благодарности и прощания. Особое внимание именам: Айнур, Айгерим, Алия, Данияр, Азамат, Арман, Мадина, Нурсултан, Руслан, Асель. Если человек представился одним словом, сохраняй его как имя. Номера телефонов записывай цифрами.
TTS_PROVIDER=elevenlabs TTS_PROVIDER=elevenlabs
ENABLE_AUDIO_DUMP=false ENABLE_AUDIO_DUMP=false
@@ -55,11 +55,11 @@ YANDEX_STT_TOPIC=general
VAD_THRESHOLD=0.5 VAD_THRESHOLD=0.5
VAD_NEGATIVE_THRESHOLD= VAD_NEGATIVE_THRESHOLD=
VAD_SILENCE_TIMEOUT_MS=420 VAD_SILENCE_TIMEOUT_MS=420
VAD_SPEECH_PAD_MS=120 VAD_SPEECH_PAD_MS=180
VAD_MIN_SPEECH_DURATION_MS=0 VAD_MIN_SPEECH_DURATION_MS=0
VAD_USE_ONNX=false VAD_USE_ONNX=false
REALTIME_VOICE_INITIAL_GREETING_TEXT=Здравствуйте! Меня зовут Айнур, я эй-ай-ассистент компании ДиджиОпс. Как я могу к вам обращаться? REALTIME_VOICE_INITIAL_GREETING_TEXT=Здравствуйте! Меня зовут Айнур, я эй-ай-ассистент компании ДиджиОпс. Как я могу к вам обращаться?
SEMANTIC_ENDPOINTING_HOLD_MS=600 SEMANTIC_ENDPOINTING_HOLD_MS=600
REALTIME_VOICE_FILLER_DELAY_MS=200 REALTIME_VOICE_FILLER_DELAY_MS=300
REALTIME_VOICE_TTS_CHUNK_SOFT_MIN_CHARS=10 REALTIME_VOICE_TTS_CHUNK_SOFT_MIN_CHARS=10
REALTIME_VOICE_TTS_CHUNK_SOFT_MIN_WORDS=2 REALTIME_VOICE_TTS_CHUNK_SOFT_MIN_WORDS=2
+4 -2
View File
@@ -14,18 +14,20 @@ from realtime_voice_service.providers.base import BaseTTS
LOGGER = logging.getLogger("uvicorn.error") LOGGER = logging.getLogger("uvicorn.error")
DEFAULT_FILLER_TEXTS: dict[str, tuple[str, ...]] = { DEFAULT_FILLER_TEXTS: dict[str, tuple[str, ...]] = {
"serper": ( "serper": (
"Сейчас уточню.",
"Проверяю информацию.", "Проверяю информацию.",
"Сейчас подскажу.",
), ),
"generic": ( "generic": (
"Сейчас отвечу.", "Сейчас отвечу.",
"Уточняю.", "Уже отвечаю.",
), ),
} }
class FillerAudioLibrary: class FillerAudioLibrary:
def __init__( def __init__(
self, self,
+53 -3
View File
@@ -52,6 +52,7 @@ SEMANTIC_ENDPOINTING_HOLD_MS = _semantic_hold_ms()
FILLER_AUDIO_DELAY_MS = _filler_delay_ms() FILLER_AUDIO_DELAY_MS = _filler_delay_ms()
TTS_CHUNK_SOFT_MIN_CHARS = _tts_chunk_soft_min_chars() TTS_CHUNK_SOFT_MIN_CHARS = _tts_chunk_soft_min_chars()
TTS_CHUNK_SOFT_MIN_WORDS = _tts_chunk_soft_min_words() TTS_CHUNK_SOFT_MIN_WORDS = _tts_chunk_soft_min_words()
_FAREWELL_MARKERS: frozenset[str] = frozenset({ _FAREWELL_MARKERS: frozenset[str] = frozenset({
"спасибо", "спасибо",
"благодарю", "благодарю",
@@ -60,9 +61,22 @@ _FAREWELL_MARKERS: frozenset[str] = frozenset({
"пока", "пока",
"до встречи", "до встречи",
"хорошего дня", "хорошего дня",
"ничего не нужно",
"ничего не надо",
"мне ничего не нужно",
"мне ничего не надо",
"больше ничего",
"это все",
"это всё",
"все понятно",
"всё понятно",
"вопросов нет",
"не надо",
"не нужно",
"goodbye", "goodbye",
"bye", "bye",
}) })
_FAREWELL_QUERY_MARKERS: frozenset[str] = frozenset({ _FAREWELL_QUERY_MARKERS: frozenset[str] = frozenset({
"вопрос", "вопрос",
"подскаж", "подскаж",
@@ -73,9 +87,25 @@ _FAREWELL_QUERY_MARKERS: frozenset[str] = frozenset({
"где", "где",
"когда", "когда",
"можно", "можно",
"нужно", "нужно ли",
"нужно еще",
"нужно ещё",
"хочу", "хочу",
"мне", "еще",
"ещё",
"теперь",
"тогда",
"адрес",
"тариф",
"оператор",
"статус",
"проблем",
"помощ",
"помочь",
"узнать",
"нужен",
"нужна",
"нужны",
}) })
FAREWELL_RESPONSE_TEXT = "Спасибо за обращение в DigiOps. Всего доброго, до свидания!" FAREWELL_RESPONSE_TEXT = "Спасибо за обращение в DigiOps. Всего доброго, до свидания!"
@@ -1082,6 +1112,26 @@ class CallSession:
self.session_id, self.session_id,
len(self._conversation), len(self._conversation),
) )
if _is_terminal_farewell(transcript):
LOGGER.info(
"realtime session %s farewell detected before assistant response: epoch=%s transcript=%r",
self.session_id,
epoch,
_preview_text(transcript),
)
await self._play_prepared_response(
epoch=epoch,
response_text=FAREWELL_RESPONSE_TEXT,
actually_spoken_chunks=actually_spoken_chunks,
)
self._ensure_generation(epoch)
self._commit_assistant_context(
llm_generated_text=FAREWELL_RESPONSE_TEXT,
actually_spoken_chunks=actually_spoken_chunks,
interrupted=False,
)
await self._close_after_farewell()
return
name_collection_response = self._build_name_collection_response(transcript) name_collection_response = self._build_name_collection_response(transcript)
if name_collection_response is not None: if name_collection_response is not None:
LOGGER.info( LOGGER.info(
@@ -1143,7 +1193,7 @@ class CallSession:
epoch=epoch, epoch=epoch,
tool_name=None, tool_name=None,
started_monotonic=llm_started_monotonic, started_monotonic=llm_started_monotonic,
suppress=_is_farewell_transcript(transcript), suppress=_is_terminal_farewell(transcript) or _is_farewell_transcript(transcript),
), ),
name=f"{self.session_id}-filler-delay-{epoch}", name=f"{self.session_id}-filler-delay-{epoch}",
) )