Оценка разделения больше не роняет задачу, журнал доступен через API

Полный трейс показал настоящую причину сбоя: падало не распознавание,
а метрика separation_quality из 0.5.0 - она скармливала модели отпечатков
реплики целиком, и на 190-секундной та не выдержала. Теперь берётся кусок
из середины реплики, а сбой оценки лишь обнуляет её, не трогая расшифровку.

Добавлен /v1/logs: последние записи журнала с фильтром по уровню, чтобы
разбирать сбои без копирования консоли вручную.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Vladimir Bryzgalov
2026-08-15 23:44:09 +05:00
co-authored by Claude Opus 5
parent f2f266ba1c
commit 710fc8b90b
6 changed files with 174 additions and 7 deletions
+22 -6
View File
@@ -26,6 +26,9 @@ SAMPLE_RATE = 16000
MAX_CHUNK_SEC = 60.0
# Ниже этого делить бессмысленно: явно не длина виновата.
MIN_SPLIT_SEC = 2.0
# Модель отпечатков рассчитана на короткий фрагмент и на длинном падает.
# Для оценки голоса секунд более чем достаточно.
EMBED_SEC = 8.0
# Ниже этого значения голоса практически неразличимы и разметка по говорящим
# случайна. На записях с одним микрофоном в комнате так бывает часто.
RELIABLE_SEPARATION = 0.35
@@ -169,15 +172,23 @@ class Pipeline:
self._embedder = sherpa_onnx.SpeakerEmbeddingExtractor(
sherpa_onnx.SpeakerEmbeddingExtractorConfig(
model=str(self.models_dir / EMB_MODEL_REL), num_threads=self.threads))
vectors = []
vectors, labels = [], []
for seg in usable:
# Берём кусок из середины реплики: там речь устойчивее, чем на краях,
# а длинный фрагмент модель отпечатков просто не переваривает.
middle = (seg.start + seg.end) / 2
half = min(EMBED_SEC, seg.end - seg.start) / 2
piece = samples[int((middle - half) * SAMPLE_RATE):int((middle + half) * SAMPLE_RATE)]
if len(piece) < SAMPLE_RATE // 2:
continue
stream = self._embedder.create_stream()
stream.accept_waveform(SAMPLE_RATE,
samples[int(seg.start * SAMPLE_RATE):int(seg.end * SAMPLE_RATE)])
stream.accept_waveform(SAMPLE_RATE, piece)
stream.input_finished()
vectors.append(np.array(self._embedder.compute(stream)))
return separation_quality(np.array(vectors),
np.array([s.speaker for s in usable]))
labels.append(seg.speaker)
if len(vectors) < 4:
return 0.0
return separation_quality(np.array(vectors), np.array(labels))
def _recognize_safely(self, audio: np.ndarray, depth: int = 0) -> str:
"""Распознаёт кусок, при ошибке деля его пополам.
@@ -214,7 +225,12 @@ class Pipeline:
t0 = time.time()
raw = self._diarizer(num_speakers).process(samples).sort_by_start_time()
segments = [Segment(start=s.start, end=s.end, speaker=s.speaker) for s in raw]
quality = self._separation_quality(samples, segments)
try:
quality = self._separation_quality(samples, segments)
except Exception as exc: # noqa: BLE001 - оценка вспомогательная
# Метрика не должна ронять задачу: без неё расшифровка всё равно нужна.
log.warning("не удалось оценить разделение говорящих: %s", exc)
quality = 0.0
t_diar = time.time() - t0
t0 = time.time()