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_BASE_URL=
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_STT_MODEL=whisper-1
OPENAI_STT_LANGUAGE=ru
@@ -26,7 +26,7 @@ OLLAMA_LLM_NUM_CTX=1024
STT_PROVIDER=openai
STT_FALLBACK_PROVIDER=elevenlabs
STT_PROMPT=Это русская речь. Точно распознавай имена, фамилии, слова благодарности и прощания. Возможны имена: Айнур, Айгерим, Алия, Данияр, Азамат, Мадина, Нурсултан. Номера телефонов записывай цифрами.
STT_PROMPT=Это русская речь по телефону. Точно распознавай короткие ответы, имена, фамилии, слова благодарности и прощания. Особое внимание именам: Айнур, Айгерим, Алия, Данияр, Азамат, Арман, Мадина, Нурсултан, Руслан, Асель. Если человек представился одним словом, сохраняй его как имя. Номера телефонов записывай цифрами.
TTS_PROVIDER=elevenlabs
ENABLE_AUDIO_DUMP=false
@@ -55,11 +55,11 @@ YANDEX_STT_TOPIC=general
VAD_THRESHOLD=0.5
VAD_NEGATIVE_THRESHOLD=
VAD_SILENCE_TIMEOUT_MS=420
VAD_SPEECH_PAD_MS=120
VAD_SPEECH_PAD_MS=180
VAD_MIN_SPEECH_DURATION_MS=0
VAD_USE_ONNX=false
REALTIME_VOICE_INITIAL_GREETING_TEXT=Здравствуйте! Меня зовут Айнур, я эй-ай-ассистент компании ДиджиОпс. Как я могу к вам обращаться?
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_WORDS=2
+4 -2
View File
@@ -14,18 +14,20 @@ from realtime_voice_service.providers.base import BaseTTS
LOGGER = logging.getLogger("uvicorn.error")
DEFAULT_FILLER_TEXTS: dict[str, tuple[str, ...]] = {
"serper": (
"Сейчас уточню.",
"Проверяю информацию.",
"Сейчас подскажу.",
),
"generic": (
"Сейчас отвечу.",
"Уточняю.",
"Уже отвечаю.",
),
}
class FillerAudioLibrary:
def __init__(
self,
+53 -3
View File
@@ -52,6 +52,7 @@ SEMANTIC_ENDPOINTING_HOLD_MS = _semantic_hold_ms()
FILLER_AUDIO_DELAY_MS = _filler_delay_ms()
TTS_CHUNK_SOFT_MIN_CHARS = _tts_chunk_soft_min_chars()
TTS_CHUNK_SOFT_MIN_WORDS = _tts_chunk_soft_min_words()
_FAREWELL_MARKERS: frozenset[str] = frozenset({
"спасибо",
"благодарю",
@@ -60,9 +61,22 @@ _FAREWELL_MARKERS: frozenset[str] = frozenset({
"пока",
"до встречи",
"хорошего дня",
"ничего не нужно",
"ничего не надо",
"мне ничего не нужно",
"мне ничего не надо",
"больше ничего",
"это все",
"это всё",
"все понятно",
"всё понятно",
"вопросов нет",
"не надо",
"не нужно",
"goodbye",
"bye",
})
_FAREWELL_QUERY_MARKERS: frozenset[str] = frozenset({
"вопрос",
"подскаж",
@@ -73,9 +87,25 @@ _FAREWELL_QUERY_MARKERS: frozenset[str] = frozenset({
"где",
"когда",
"можно",
"нужно",
"нужно ли",
"нужно еще",
"нужно ещё",
"хочу",
"мне",
"еще",
"ещё",
"теперь",
"тогда",
"адрес",
"тариф",
"оператор",
"статус",
"проблем",
"помощ",
"помочь",
"узнать",
"нужен",
"нужна",
"нужны",
})
FAREWELL_RESPONSE_TEXT = "Спасибо за обращение в DigiOps. Всего доброго, до свидания!"
@@ -1082,6 +1112,26 @@ class CallSession:
self.session_id,
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)
if name_collection_response is not None:
LOGGER.info(
@@ -1143,7 +1193,7 @@ class CallSession:
epoch=epoch,
tool_name=None,
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}",
)