Контур замкнут без LLM: звонящий отвечает заготовками через слот-автомат. Живой диалог через WebSocket прошёл 5 раз подряд без сбоев: первая реплика после снятия трубки — 87–154 мс, ход — 1.2–1.4 с, перебивание — 88–90 мс. На LLM до цели 1.5 с остаётся 100–300 мс. Задача ответа живёт, пока у курсанта доигрывает звук: перебивание — это отмена одной задачи, и tts.end приходит, когда звонящий действительно замолчал. Что нашлось при сборке: - Silero молча выбрасывает цифры: «улица Ленина, дом… квартира…» без номеров. Числа и сокращения разворачиваются в слова до синтеза. - onnxruntime брал все 16 ядер и вытеснял синтез, первый вызов был холодным: распознавание 576 мс вместо ~210. Прогрев на старте и по 4 потока на модель. - Весь адрес одним предложением — ~455 мс синтеза до первого звука. Длинное предложение режется по запятым, номер от улицы не отрывается: ~250 мс. - Первая реплика «Алло! Помогите!» запускалась мимо задачи ответа и не перебивалась вовсе. Отдельно: сломал make dev — num2words попал в pyproject, а Dockerfile ставил зависимости ручным списком, и бэкенд в контейнере упал на импорте. Образ теперь ставит зависимости из pyproject.toml. Голоса в контейнере нет: под WSL модели работают нативно, make back.
54 lines
2.1 KiB
Python
54 lines
2.1 KiB
Python
"""Настройки. Нормативы ГОСТ — в миллисекундах и из конфига, не из кода."""
|
||
|
||
from functools import lru_cache
|
||
|
||
from pydantic import AliasChoices, Field
|
||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||
|
||
from app.domain.timers import NORMATIVES, TimerCode
|
||
|
||
|
||
class Settings(BaseSettings):
|
||
model_config = SettingsConfigDict(env_file=".env", extra="ignore")
|
||
|
||
# Данные
|
||
database_url: str = "postgresql+asyncpg://lct:lct@localhost:5432/lct"
|
||
|
||
# Голосовой контур
|
||
models_dir: str = "models"
|
||
stt_model: str = "gigaam-v3-rnnt"
|
||
endpointing_ms: int = 600
|
||
mic_sample_rate: int = 16_000
|
||
tts_sample_rate: int = 24_000
|
||
offline: bool = False
|
||
# Голосовой контур: грузит ~5 с моделей при старте. В тестах выключен.
|
||
voice_enabled: bool = True
|
||
|
||
# LLM. Провайдер меняется значением, не кодом (docs/arch/STACK.md).
|
||
# Имена COMPAT_MODEL_* принимаются тоже — так их выставляет командный сниппет.
|
||
llm_provider: str = "openai_compatible"
|
||
llm_base_url: str = Field(
|
||
default="", validation_alias=AliasChoices("llm_base_url", "compat_model_url")
|
||
)
|
||
llm_api_key: str = Field(
|
||
default="", validation_alias=AliasChoices("llm_api_key", "compat_model_api_key"),
|
||
repr=False,
|
||
)
|
||
llm_model_caller: str = Field(
|
||
default="", validation_alias=AliasChoices("llm_model_caller", "compat_model_name")
|
||
)
|
||
llm_model_judge: str = Field(
|
||
default="", validation_alias=AliasChoices("llm_model_judge", "compat_model_name")
|
||
)
|
||
judge_temperature: float = 0.0
|
||
|
||
# Нормативы: переопределяют значения по умолчанию из domain/timers.py
|
||
timer_limits_ms: dict[TimerCode, int] = {}
|
||
|
||
def limit_ms(self, code: TimerCode) -> int:
|
||
return self.timer_limits_ms.get(code, NORMATIVES[code].limit_ms)
|
||
|
||
|
||
@lru_cache
|
||
def get_settings() -> Settings:
|
||
return Settings()
|