Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
64901ec
docs: добавить план пошагового рефакторинга проекта
Sanich137 Apr 22, 2026
2e7e821
refactor: перевести config.py на pydantic-settings
Sanich137 Apr 22, 2026
c4351ca
fix: добавить валидацию GPU-флагов VAD и пунктуации
Sanich137 Apr 22, 2026
f927a1c
fix: correct audio config permissions
Sanich137 Apr 22, 2026
c5f85fb
docs: перенести комментарии к переменным в config.py
Sanich137 Apr 22, 2026
0a47d69
Правка APP и централизация в main.py. Переезд роутеров на APIrouter
Sanich137 Apr 22, 2026
5ae1410
Переработка pydantic моделей.
Sanich137 Apr 22, 2026
e4d3165
Косметические правки.
Sanich137 Apr 23, 2026
ae10ec5
Приведение конфига к BaseSettings
Sanich137 Apr 23, 2026
25d5d9f
Добавление v1 API для сохранения функционала и приведения ответов к е…
Sanich137 Apr 28, 2026
70efef2
Добавление v1 API для сохранения функционала и приведения ответов к е…
Sanich137 Apr 28, 2026
81d7866
Добавление legacy API для сохранения функционала и приведения ответов…
Sanich137 Apr 28, 2026
d0c79c9
Добавления описания "/ws"
Sanich137 Apr 29, 2026
431565e
Добавление ALLOWED_HOSTS, CORS_ORIGINS, TRUSTED_PROXIES.
Sanich137 Apr 29, 2026
a3b62f2
переезд логгера и recognizer в lifespan
Sanich137 Apr 30, 2026
a8eee88
использование глобального логгера.
Sanich137 Apr 30, 2026
c675255
исправление ошибки неправильной обработки "последнего неполного чанка…
Sanich137 Apr 30, 2026
746e336
Перенос инициализации diarizer в main.py
Sanich137 Apr 30, 2026
203cadb
Подготовка к авторизации.
Sanich137 May 4, 2026
45b3c91
актуализация тестов
Sanich137 May 4, 2026
31e7725
Введение моделей пользователей и платежей.
Sanich137 May 4, 2026
26517ed
Введение моделей пользователей и платежей.
Sanich137 May 4, 2026
7239ff0
Введение моделей пользователей и платежей.
Sanich137 May 4, 2026
0a7dd1f
Изменение файловой структуры API
Sanich137 May 5, 2026
9f5c78e
Добавлено "Deprecated" в legacy роуты.
Sanich137 May 5, 2026
cd48468
Готовим к выезду бизнес-логики WS из роута.
Sanich137 May 6, 2026
7b6d2a2
Выводим glоbals из url и file.
Sanich137 May 7, 2026
03a922f
Выводим glоbals из url и file.
Sanich137 May 7, 2026
54c579e
добавление роутов метрик для фронта.
Sanich137 May 7, 2026
bf84292
Работа над отображением метрик работы сервиса.
Sanich137 May 8, 2026
488817f
Чистка.
Sanich137 May 8, 2026
416d152
Добавлена web-панель.
Sanich137 May 9, 2026
4b76e5b
Добавлена админ-панель.
Sanich137 May 10, 2026
c4b8507
Добавлена страница с тарифами в админ панель
Sanich137 May 10, 2026
8320b15
Доработана страница с сессиями пользователей в админ панели
Sanich137 May 10, 2026
6814751
режим эксперта при выборе параметров распознавания
Sanich137 May 10, 2026
00e2c78
Устранены баги
Sanich137 May 11, 2026
11ffede
Устранены баги
Sanich137 May 11, 2026
a9c3ece
Устранены баги
Sanich137 May 11, 2026
6594926
Устранены баги
Sanich137 May 11, 2026
3b28135
Оформление окна ответов
Sanich137 May 11, 2026
882f0d5
Распознавание с микрофона.
Sanich137 May 11, 2026
ee9254c
Детали Админ страниц.
Sanich137 May 11, 2026
816bc1b
Важный фикс. Исправлена диаризация.
Sanich137 May 12, 2026
9d69f76
Ридми.
Sanich137 May 12, 2026
1397762
T-one потоковый риалтайм /api/v1/asr/ws-stream + автодетект протокола
Jun 3, 2026
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
16 changes: 16 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
venv/
.venv/
models/
__pycache__/
*.py[cod]
.git/
logs/
trash/
bench/
*.log
*.db
asr_local.db
.idea/
.dockerignore
local_asr/to_asr/*
local_asr/after_asr/*
15 changes: 14 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*.ogg
*.onnx
*.m4a
*.md
*.history.md
/.venv/
/venv/
/models/vosk-model-ru/
Expand All @@ -17,3 +17,16 @@
/models/sherpa-onnx-whisper-small/
/trash/test_data.py
/.idea/
.aider*
/.continue/
.env

# runtime / cache / dev
__pycache__/
*.py[cod]
*.db
asr_local.db
/logs/
/bench/
/models/hub/
/models/sbert_punc_case_ru_onnx/
1 change: 1 addition & 0 deletions .idea/Vosk5_FastAPI_streaming.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

53 changes: 21 additions & 32 deletions Diarisation/__init__.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
import config
from config import settings
from utils.pre_start_init import paths
from utils.do_logging import logger
from VoiceActivityDetector import vad
import requests
import logging
from starlette.requests import HTTPConnection
logger = logging.getLogger(__name__)


if config.CAN_DIAR:
def ensure_diar_model() -> bool:
"""Проверяет наличие модели диаризации и при необходимости скачивает её."""
if not settings.CAN_DIAR:
return False
if not paths.get("diar_speaker_model_path").exists():
logger.error(f"Модель для диаризации не найдена. Предпринимаются попытки скачать {config.DIAR_MODEL_NAME}")
logger.error(f"Модель для диаризации не найдена. Предпринимаются попытки скачать {settings.DIAR_MODEL_NAME}")
output_path = paths.get("diar_speaker_model_path")
api_url = "https://modelscope.cn/api/v1/datasets/wenet/wespeaker_pretrained_models/oss/tree"

try: # Предпринимаем попытки скачать и положить файл в нужное место.
# 1. Получаем JSON с данными файлов
try:
response = requests.get(api_url, headers={"User-Agent": "Mozilla/5.0"})
response.raise_for_status()
# 2. Ищем нужный файл
target_file = config.DIAR_MODEL_NAME
target_file = settings.DIAR_MODEL_NAME
file_data = next((item for item in response.json()["Data"] if item["Key"] == target_file), None)

if not file_data:
logger.error(f"Модели с именем {config.DIAR_MODEL_NAME} в списке возможных для загрузки нет.")
# Получаем список всех ONNX-моделей
logger.error(f"Модели с именем {settings.DIAR_MODEL_NAME} в списке возможных для загрузки нет.")
onnx_models_with_size = [
(item['Key'].split(".")[0], item['Size'] // (1024 * 1024))
for item in response.json()["Data"]
Expand All @@ -30,42 +32,29 @@
logger.info("Доступны для скачивания следующие модели:")
for name, size in sorted(onnx_models_with_size):
logger.info(f"{name} - {size} MB")

raise Exception("Файл не найден в API")

# 3. Скачиваем по прямой ссылке
download_url = file_data["Url"]
with requests.get(download_url, stream=True) as r:
r.raise_for_status()
with open(output_path, "wb") as f:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)
except Exception as e:
logger.error(f"Файл модели не сохранён.Ошибка - {e})")

logger.error(f"Файл модели не сохранён. Ошибка - {e}")
return False
else:
logger.info(f"Модель успешно загружена : {output_path}")

else:
logger.debug(f"Будет использован имеющийся файл {config.DIAR_MODEL_NAME}")

logger.debug(f"Будет использован имеющийся файл {settings.DIAR_MODEL_NAME}")

if not paths.get("diar_speaker_model_path").exists():
logger.error(f"Модель для Диаризации отсутствует. Диризация выключена и будет не доступна.")
logger.error(f"Модель для Диаризации отсутствует. Диаризация выключена.")
logger.error(f"Скачайте модель со страницы 'https://github.com/wenet-e2e/wespeaker/blob/master/docs/pretrained.md' "
f"и поместите в по адресу: {str(paths.get('diar_speaker_model_path'))}")
config.CAN_DIAR = False
else:
from .do_diarize import Diarizer
f"и поместите по адресу: {str(paths.get('diar_speaker_model_path'))}")
return False
return True

diarizer = Diarizer(embedding_model_path=paths.get("diar_speaker_model_path"),
vad=vad,
max_phrase_gap=1,
batch_size=config.DIAR_GPU_BATCH_SIZE,
cpu_workers=config.CPU_WORKERS,
use_gpu=config.DIAR_WITH_GPU
)

logger.info(f"Успешно загружена модель Диаризации")
else:
logger.info(f"Диаризация не включена и будет недоступна.")
def get_diarizer(conn: HTTPConnection):
return conn.app.state.diarizer
44 changes: 37 additions & 7 deletions Diarisation/diarazer.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import datetime

import config
from pydub import AudioSegment
from Diarisation.do_diarize import load_and_preprocess_audio
from utils.pre_start_init import posted_and_downloaded_audio
from utils.do_logging import logger

from collections import defaultdict
import logging
logger = logging.getLogger(__name__)

if config.CAN_DIAR:
from Diarisation import diarizer

async def do_diarizing(
file_id:str,
asr_raw_data,
diarizer,
num_speakers:int = -1,
filter_cutoff:int = 50,
filter_order:int = 10,
Expand Down Expand Up @@ -193,6 +191,38 @@ def match_asr_with_diarization(asr_data, diarization_data, min_overlap_ratio=0.5

return dict(result)

async def do_diarizing_v1(
audio_segment: AudioSegment,
asr_raw_data,
diarizer,
num_speakers:int = -1,
filter_cutoff:int = 50,
filter_order:int = 10,
diar_vad_sensity: int = 3
):
# Предобработка аудио.
# ВАЖНЫЙ момент. Мы диаризируем только последний канал.
audio_frames = await load_and_preprocess_audio(audio_segment.split_to_mono()[-1])

logger.debug("Старт процесса диаризации.")
st=datetime.datetime.now()
# Непосредственно получение временных меток речи
diar_result = await diarizer.diarize_and_merge(
audio_frames,
num_speakers=num_speakers,
filter_cutoff=filter_cutoff,
filter_order=filter_order,
vad_sensity=diar_vad_sensity
)

for r in diar_result:
logger.debug(f"Спикер {r['speaker']}: {r['start']:.2f} - {r['end']:.2f} сек")
logger.debug(f"Диаризация завершена за {(datetime.datetime.now()-st).total_seconds()} секунд")

# Построение структуры аналогично raw_data для дальнейшего построения диалога и вывод результата
return match_asr_with_diarization(asr_raw_data, diar_result)


def group_words(words):
if not words:
return []
Expand All @@ -219,4 +249,4 @@ def group_words(words):
# min_overlap_ratio=0.5,
# max_pause=1.5,
# group_unmatched_words=True # Включить группировку несопоставленных слов
# ) )
# ) )
4 changes: 2 additions & 2 deletions Diarisation/do_diarize.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
import time
from umap import UMAP
from hdbscan import HDBSCAN
from utils.do_logging import logger
from utils.pre_start_init import paths
from utils.resamppling import async_resample_audiosegment
import logging
logger = logging.getLogger(__name__)


class Diarizer:
Expand Down
47 changes: 47 additions & 0 deletions Docker/Dockerfile.local
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Локальный ТЕСТОВЫЙ образ новой архитектуры (api/v1 + services): собирается из рабочего
# дерева (COPY), модели монтируются с хоста. Прод-образ - Docker/Dockerfile.
#
# Сборка из КОРНЯ репозитория:
# CPU: docker build -f Docker/Dockerfile.local -t asr-local .
# GPU: docker build -f Docker/Dockerfile.local --build-arg PROVIDER=CUDA -t asr-local .
FROM python:3.12-slim-bullseye

# build-essential/cmake/libboost/libeigen нужны для сборки kenlm (зависимость T-one)
RUN apt-get update && apt-get install -y \
git git-lfs ffmpeg curl ca-certificates jq \
build-essential cmake libboost-all-dev libeigen3-dev \
&& rm -rf /var/lib/apt/lists/*
RUN git lfs install

# Провайдер: CPU (по умолч.) | CUDA. Для CUDA меняем onnxruntime -> onnxruntime-gpu + cupy.
ARG PROVIDER=CPU
COPY requirements.txt /tmp/req.txt
RUN if [ "$PROVIDER" = "CUDA" ] || [ "$PROVIDER" = "TENSORRT" ]; then \
sed -i 's#^onnxruntime\s*$#onnxruntime-gpu[cuda,cudnn]==1.23.2#' /tmp/req.txt && \
echo "cupy-cuda12x==13.5.1" >> /tmp/req.txt ; \
fi
RUN pip install --no-cache-dir -r /tmp/req.txt

# T-one (потоковый путь /api/v1/asr/ws-stream): --no-deps, затем зависимости (kenlm соберётся)
RUN pip install --no-cache-dir --no-deps "git+https://github.com/voicekit-team/T-one.git" && \
pip install --no-cache-dir pyctcdecode kenlm miniaudio

# Для CUDA фиксируем cuDNN как в проде
RUN if [ "$PROVIDER" = "CUDA" ] || [ "$PROVIDER" = "TENSORRT" ]; then \
pip install --no-cache-dir --force-reinstall nvidia-cudnn-cu12==9.6.0.74 ; \
fi

# Код приложения - из локального рабочего дерева (.dockerignore исключает venv/models/.git)
COPY . /ASR_FastAPI_WS_RU/

# Каталоги, обязательные на старте (логи; иначе TimedRotatingFileHandler падает)
RUN mkdir -p /ASR_FastAPI_WS_RU/logs /ASR_FastAPI_WS_RU/local_asr/to_asr /ASR_FastAPI_WS_RU/local_asr/after_asr

ENV PYTHONPATH=/ASR_FastAPI_WS_RU \
HF_HOME=/ASR_FastAPI_WS_RU/models \
IS_PROD=1 \
LOGGING_LEVEL=INFO

WORKDIR /ASR_FastAPI_WS_RU
EXPOSE 49153
CMD ["python3", "main.py"]
Loading