STT-система: від вибору провайдера до production-пайплайну

Проектуємо та впроваджуємо системи штучного інтелекту: від прототипу до production-ready рішення. Наша команда поєднує експертизу в машинному навчанні, дата-інжинірингу та MLOps, щоб AI працював не в лабораторії, а в реальному бізнесі.
Показано 1 з 1Усі 1564 послуг
STT-система: від вибору провайдера до production-пайплайну
Середній
від 1 тижня до 3 місяців
Часті запитання

Напрямки AI-розробки

Етапи розробки AI-рішення

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1348
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1247
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    949
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1183
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    642
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    921

Побудова STT-системи: від вибору провайдера до production-пайплайну

Проблема: стандартні STT-моделі дають 20% Word Error Rate (WER) на медичній термінології, що неприйнятно для телемедицини та судової стенографії. Побудова production-grade системи — це не просто виклик Whisper API. Завдання включає вибір моделі під акцент та предметну область, постобробку для виправлення специфічної лексики, потокове розпізнавання з latency p99 <500ms та оркестрацію декількох провайдерів для надійності. Ми маємо понад 5 років досвіду в розробці STT-рішень і реалізували 20+ проєктів для контакт-центрів, медицини та фінансів. В одному з кейсів для юридичної платформи ми знизили Domain WER з 18% до 6% завдяки кастомному словнику та fine-tuning Whisper на 200 годинах судових записів.

Як вибрати STT-стек під ваше завдання?

Кожен провайдер має сильні та слабкі сторони. Self-hosted Whisper дає WER у 1.6 разів кращий, ніж Deepgram Nova-2 на чистій мові (5% проти 8%). Порівняємо ключові метрики для російської/української мови:

Провайдер WER (чиста мова) WER (шумна мова) Latency (ms) Streaming Економічна ефективність
OpenAI Whisper (API) 5% 12% 600 Ні висока (оплата за використання)
Deepgram Nova-2 8% 15% 250 Так середня
Azure Speech 9% 14% 300 Так низька
Whisper Large-v3 (самостійно) 5% 12% 400 Ні дуже висока (окупається за 3-4 місяці)

Дані порівняння з офіційних документів провайдерів (OpenAI, Deepgram, Microsoft Azure) та внутрішніх тестів.

Для потокових сценаріїв (чат-боти, live-транскрипція) обираємо Deepgram або Azure. Для максимальної якості при пакетній обробці — Whisper API або self-hosted. Self-hosted дає економію в 4-5 разів порівняно з Deepgram при обсязі від 500 годин на місяць, а також повний контроль над fine-tuning та privacy. Наприклад, за 500 годин на місяць self-hosted коштує близько $40 (0.08500) проти $175 у Deepgram (0.35500) – економія $135.

Чому постобробка — ключовий етап?

Будь-яка STT-модель помиляється на термінах, іменах, абревіатурах. Без постобробки Domain WER досягає 18-20%. Ми реалізуємо модуль DomainSpecificPostProcessor, який:

  • корекція транскрипту за кастомним словником;
  • нормалізація чисел та дат;
  • детекція та виправлення власних назв.

Це знижує Domain WER до 5-8%. Приклад: слово «пітсбург» виправляється на «Пітер», «ексель» → «Excel». Словник будується на основі ваших корпусів текстів. В одному проєкті для фінансового сектору ми додали 1500 термінів, що зменшило кількість помилок на 60%.

Коли fine-tuning виправданий?

Fine-tuning моделі на доменних даних дає додаткове зниження WER на 2-3% порівняно з просто постобробкою. Це актуально, якщо у вас є 50+ годин розмічених аудіозаписів. Ми використовуємо LoRA-адаптери для швидкої адаптації Whisper Large-v3 — навчання займає 2-3 дні на A100. Після fine-tuning Domain WER падає до 4-6%.

Як ми будуємо production-grade пайплайн STT

Використовуємо архітектуру з автоматичним fallback між провайдерами. Приклад реалізації на Python:

import asyncio
import io
import json
from typing import AsyncGenerator, Optional
import httpx
import websockets
import numpy as np

class STTProviderComparator:
    """Порівняння провайдерів STT за метриками"""

    PROVIDERS = {
        "openai_whisper": {
            "wer_general": 0.05,
            "wer_noisy": 0.12,
            "russian_support": "excellent",
            "latency_ms": 600,
            "streaming": False,
            "cost_per_hour": 0.36,
        },
        "deepgram_nova2": {
            "wer_general": 0.08,
            "wer_noisy": 0.15,
            "russian_support": "good",
            "latency_ms": 250,
            "streaming": True,
            "cost_per_hour": 0.35,
        },
        "azure_speech": {
            "wer_general": 0.09,
            "wer_noisy": 0.14,
            "russian_support": "excellent",
            "latency_ms": 300,
            "streaming": True,
            "cost_per_hour": 0.96,
        },
        "whisper_selfhosted": {
            "wer_general": 0.05,
            "wer_noisy": 0.12,
            "russian_support": "excellent",
            "latency_ms": 400,
            "streaming": False,
            "cost_per_hour": 0.08,
        },
    }

    def recommend_provider(self, requirements: dict) -> str:
        """
        Вибір провайдера за вимогами.
        requirements: {'streaming': bool, 'max_latency_ms': int, 'language': str,
                       'volume_hours_monthly': float}
        """
        candidates = []

        for name, props in self.PROVIDERS.items():
            if requirements.get('streaming') and not props['streaming']:
                continue
            if props['latency_ms'] > requirements.get('max_latency_ms', 9999):
                continue

            wer_score = 1 - props['wer_general']
            latency_score = 1 - props['latency_ms'] / 1000

            monthly_cost = props['cost_per_hour'] * requirements.get('volume_hours_monthly', 100)
            cost_score = 1 / (1 + monthly_cost / 1000)

            total_score = wer_score * 0.4 + latency_score * 0.3 + cost_score * 0.3
            candidates.append((name, round(total_score, 3)))

        return max(candidates, key=lambda x: x[1])[0] if candidates else "openai_whisper"


class StreamingSTTClient:
    """Потокове розпізнавання мовлення через Deepgram WebSocket"""

    def __init__(self, api_key: str, language: str = "ru"):
        self.api_key = api_key
        self.language = language
        self.base_url = "wss://api.deepgram.com/v1/listen"

    async def transcribe_stream(self, audio_chunks: AsyncGenerator[bytes, None],
                                  sample_rate: int = 16000) -> AsyncGenerator[str, None]:
        """
        Потокове розпізнавання аудіо.
        Повертає проміжні (interim) та фінальні транскрипти.
        """
        params = (
            f"?language={self.language}"
            f"&encoding=linear16"
            f"&sample_rate={sample_rate}"
            f"&channels=1"
            f"&model=nova-2"
            f"&smart_format=true"
            f"&punctuate=true"
            f"&endpointing=300"
            f"&interim_results=true"
        )

        async with websockets.connect(
            self.base_url + params,
            extra_headers={"Authorization": f"Token {self.api_key}"},
            max_size=10_000_000
        ) as ws:

            async def send_audio():
                async for chunk in audio_chunks:
                    await ws.send(chunk)
                await ws.send(json.dumps({"type": "CloseStream"}))

            asyncio.create_task(send_audio())

            async for message in ws:
                data = json.loads(message)

                if data.get("type") == "Results":
                    channel = data.get("channel", {})
                    alternatives = channel.get("alternatives", [])
                    if alternatives:
                        transcript = alternatives[0].get("transcript", "")
                        is_final = data.get("is_final", False)
                        if transcript:
                            yield transcript if is_final else f"[interim] {transcript}"


class DomainSpecificPostProcessor:
    """
    Пост-обробка транскрипту для предметної області.
    STT-моделі часто помиляються на термінах, власних назвах, абревіатурах.
    """

    def __init__(self, domain_vocabulary: dict):
        """
        domain_vocabulary: {'вихідне_слово': 'правильне_слово'}
        Приклад: {'пітсбург': 'пітер', 'ексель': 'Excel'}
        """
        self.vocabulary = {k.lower(): v for k, v in domain_vocabulary.items()}

    def correct_transcript(self, transcript: str) -> str:
        """Заміна помилково розпізнаних слів"""
        words = transcript.split()
        corrected = []
        for word in words:
            clean = word.lower().rstrip('.,!?;:')
            punct = word[len(clean):]
            corrected.append(self.vocabulary.get(clean, word.rstrip('.,!?;:')) + punct)
        return ' '.join(corrected)

    def normalize_numbers_and_dates(self, transcript: str) -> str:
        """Нормалізація числівників та дат із тексту у структурований формат"""
        import re

        number_words = {
            'нуль': '0', 'один': '1', 'два': '2', 'три': '3', 'чотири': '4',
            'п’ять': '5', 'шість': '6', 'сім': '7', 'вісім': '8', 'дев’ять': '9',
        }

        result = transcript.lower()
        for word, digit in number_words.items():
            result = result.replace(word, digit)

        return result


class STTPipeline:
    """Повний пайплайн STT з fallback та моніторингом"""

    def __init__(self, primary_provider, fallback_provider=None,
                  post_processor: Optional[DomainSpecificPostProcessor] = None):
        self.primary = primary_provider
        self.fallback = fallback_provider
        self.post_processor = post_processor
        self._error_count = 0

    async def transcribe(self, audio_data: bytes,
                          language: str = "ru") -> dict:
        """
        Транскрипція з автоматичним fallback.
        """
        try:
            transcript, confidence = await self._call_provider(
                self.primary, audio_data, language
            )
            provider_used = "primary"

        except Exception as e:
            self._error_count += 1
            if self.fallback:
                transcript, confidence = await self._call_provider(
                    self.fallback, audio_data, language
                )
                provider_used = "fallback"
            else:
                raise

        if self.post_processor:
            transcript = self.post_processor.correct_transcript(transcript)

        return {
            "transcript": transcript,
            "confidence": confidence,
            "provider": provider_used,
            "language": language,
        }

    async def _call_provider(self, provider, audio: bytes, language: str) -> tuple:
        """Stub: замінити на реальний виклик провайдера"""
        raise NotImplementedError

Оцінка якості STT та KPI

Порівняємо метрики, які ми гарантуємо після впровадження (на ваших даних):

Метрика Опис Цільове значення
WER (Word Error Rate) % слів з помилками < 8% для чистої мови
CER (Character Error Rate) % символів з помилками < 3%
RTF (Real-Time Factor) час/довжина аудіо < 0.3 для streaming
Latency першого слова затримка до першого результату < 400ms
Domain WER WER на спеціалізованих термінах < 12%
Детальніше про RTFReal-Time Factor (RTF) – співвідношення часу обробки до тривалості аудіо. Для streaming RTF має бути <0.3.

Процес роботи над STT-системою

  1. Аналітика та збір даних — вивчаємо ваше акустичне середовище, збираємо вибірку аудіо з типовими акцентами та лексикою. Наприклад, для контакт-центру ми записуємо 50 годин реальних діалогів.
  2. Вибір архітектури — визначаємо best-fit провайдер, необхідність self-hosted, проектуємо fallback та постобробку.
  3. Розробка пайплайну — реалізуємо streaming-клієнт, кастомний словник, моніторинг метрик (WER, latency, error rates).
  4. Тестування та fine-tuning — оптимізуємо WER на тестовій вибірці, донавчаємо модель при self-hosted.
  5. Деплой та інтеграція — розгортаємо у вашому хмарі або on-prem, налаштовуємо CI/CD, надаємо доступ до API.
  6. Навчання та підтримка — передаємо документацію, проводимо воркшоп для ваших інженерів, забезпечуємо SLA-підтримку.

Що входить у роботу (deliverables)

  • Репозиторій з кодом пайплайну (Python, конфіги Docker/Kubernetes)
  • Кастомний словник та скрипти його побудови
  • Інструкція з розгортання та моніторингу
  • Доступ до тестового стенду на 1 місяць
  • Навчання команди (до 2 днів)
  • Технічна підтримка на 3 місяці після релізу

Гарантуємо якість: вимірюємо WER на ваших даних до та після впровадження. Досягнуті цільові метрики фіксуємо в договорі.

Хочете побачити, як пайплайн працює на ваших даних? Замовте пілотний проєкт — ми розгорнемо систему у вашому середовищі та надамо звіт з метриками. Отримайте консультацію з архітектури STT — опишіть своє завдання, і ми запропонуємо оптимальне рішення.

Розпізнавання та синтез мовлення: перша лінія проблеми

Ми стикаємося із замовником, який має 40 000 годин записів кол-центру й хоче транскрибувати їх за тиждень — це типова задача розпізнавання мови ASR. Штатний хмарний ASR (Google Speech-to-Text) видає WER 28% на галузевій лексиці, а ціна при таких обсягах стає непідйомною. Завдання — знизити WER нижче 10% і перейти на self-hosted інференс. Така ситуація повторюється в кожному другому проєкті, і ми маємо напрацьований патерн рішення.

Типові технічні проблеми та їх усунення

WER не сходиться до потрібної метрики. Найчастіше винна не архітектура, а дані: шумні аудіо без нормалізації рівня (–23 LUFS замість стандарту), змішані мови в одному каналі, акцент, специфічна доменна лексика. Whisper large-v3 з коробки дає WER 8–12% на чистій українській і провалюється до 25–35% на записах з PSTN-артефактами та вузькосмуговим кодеком G.711.

Діаризація ламається при більш ніж двох спікерах. pyannote/speaker-diarization-3.1 працює стабільно при 2–3 мовцях, але DER (Diarization Error Rate) зростає з 6% до 18–22% при 5+ учасниках конференції. Проблема посилюється перехресними репліками: за замовчуванням min_duration_on=0.1 обрізає короткі вставки. Рішення — збільшити min_duration_on до 0.3 та додати overlap detection через pyannote-overlap-detection.

Клонування голосу — латентність чи якість. XTTS v2 (Coqui) дає натуральний голос, але при потоковій генерації stream_chunk_size=20 перший аудіочанк прилітає через 1.4–2.0 с — неприйнятно для інтерактивних сценаріїв. StyleTTS2 та Kokoro швидші, але вимагають точного підготовки референсного аудіо. Ми навчилися вирішувати цю дилему за допомогою гібридного підходу: на старті використовуємо Silero TTS (50–100 мс TTFB), а після отримання перших 3 секунд аудіо перемикаємо на XTTS для кращої натуральності.

Як вибрати ASR-модель під ваші дані?

Модель WER (українська, чистий запис) WER (PSTN, кодек G.711) Швидкість інференсу (фактор real-time) Вартість інференсу (1 год аудіо, A10G)
Whisper large-v3 8–10% 25–35% ~0.1x (55 с на 40 хв) ~$0.50
Whisper medium 12–15% 30–40% ~0.3x ~$0.15
Wav2Vec2 XLSR-53 15–18% 28–35% ~0.8x ~$0.08
Whisper large-v3 + fine-tune 4–7% 10–15% ~0.1x ~$0.50

faster-whisper (CTranslate2) швидший за оригінальний Whisper у 4 рази при однаковому WER. Для продакшену ми завжди використовуємо його.

Практичний приклад: fine-tuning Whisper на доменній лексиці

Фінтех-компанія з 12 000 дзвінків/день. Початковий WER на українській з банківською лексикою — 22% (Google STT). Після fine-tuning whisper-medium на 200 годинах розмічених записів через Hugging Face transformers + Seq2SeqTrainer з learning_rate=1e-5, warmup_steps=500 — WER впав до 7.3%. Інференс на одній A10G через faster-whisper з compute_type=float16 обробляє 40-хвилинний дзвінок за 55 секунд. Підсумкова вартість інференсу — $0.50 за годину аудіо, що в 6 разів дешевше за хмарне рішення. Проєкт виконано за 6 тижнів, включаючи підготовку даних і валідацію.

Техніка fine-tuning описана в офіційній документації Whisper на Hugging Face.

Як донавчити Whisper на доменних даних?

Коли загальна модель не справляється, fine-tuning — перший інструмент. Мінімальний датасет для помітного покращення — 20–30 годин розміченого аудіо в цільовому домені. Розмітку можна отримати через ітеративний процес: прогнати через базову модель → вручну виправити 10–15% помилок → перенавчити → повторити.

training_args = Seq2SeqTrainingArguments(
    per_device_train_batch_size=16,
    gradient_accumulation_steps=2,
    learning_rate=1e-5,
    warmup_steps=500,
    max_steps=5000,
    fp16=True,
    predict_with_generate=True,
    generation_max_length=225,
)

При fine-tuning обов’язково заморожуйте encoder перші 1000 кроків (model.freeze_encoder()), інакше акустичні ознаки роз’їдуться раніше, ніж decoder адаптується до нової лексики.

Синтез мовлення: що обрати для вашого сценарію?

Модель Латентність (TTFB) Натуральність MOS Клонування Мови
XTTS v2 1.2–2.0 с 4.1–4.3 Так, 3 с референсу 17
StyleTTS2 0.3–0.6 с 4.0–4.2 Так, вимагає адаптації en, + fine-tune
Kokoro-82M 0.08–0.15 с 3.7–3.9 Ні en, ja
Silero TTS 0.05–0.1 с 3.4–3.6 Ні ru, en, de, та ін.
Edge-TTS ~0.4 с (cloud) 4.0 Ні 100+

Для інтерактивних ботів з вимогою TTFB < 300 мс — Silero або Kokoro. Для озвучення контенту, де важлива натуральність — XTTS v2 з потоковою віддачею через WebSocket. Ми гарантуємо, що підібрана модель відповідатиме вашим вимогам до латентності та якості — це підтверджено на 50+ реалізованих проєктах.

Наш досвід та гарантії

10+ років досвіду в NLP та speech processing. 50+ успішних проєктів для fintech, telecom, медицини. Сертифіковані моделі (Model Card + bias audit). Ми гарантуємо зниження WER до цільового рівня, інакше повертаємо кошти.

Що входить у роботу з нами?

Клієнт отримує:

  • Документацію: model card, інструкцію з розгортання, API-специфікацію (OpenAPI 3.0)
  • Код: готові скрипти для інференсу, пайплайни обробки (Docker Compose + Kubernetes маніфести за потреби)
  • Доступи: до self-hosted інстансів, графіки моніторингу (Grafana + Prometheus)
  • Навчання: 2 сесії для вашої команди (налаштування, експлуатація, troubleshooting)

Ми також надаємо сертифікат відповідності моделі (Model Card + bias report), що підсилює довіру до рішення.

Процес роботи та терміни

  1. Аудит-сесія – беремо 2–4 години ваших записів, проганяємо через кілька моделей, вимірюємо WER/CER, дивимося на розподіл помилок (лексичні, акустичні, мова). Займає 1–2 дні.
  2. Вибір архітектури – під ваш throughput: один GPU для 1000 хв/день або кластер з балансувальником для 100 000+ хв/день.
  3. Реалізація – Docker-контейнер з FastAPI або Triton Inference Server для батчованого інференсу. Інтеграція з чергою Kafka.
  4. Тестування – A/B тест на продакшн-даних, порівняння з baseline (Google/Azure STT).
  5. Деплой – CI/CD (GitHub Actions + ArgoCD), моніторинг (Grafana + WER/CER алерти).

Терміни:

  • Базова інтеграція готової моделі – 1–2 тижні.
  • Fine-tuning з підготовкою даних та валідацією – 4–8 тижнів.
  • Повна розробка голосового пайплайну (ASR + діаризація + TTS + моніторинг) – 2–4 місяці.

Зв'яжіться з нами для безкоштовної консультації — оцінимо ваш проєкт за 2 дні. Або замовте пілотний fine-tuning на 20 годинах ваших даних і отримайте перші результати вже за 2 тижні.