Skip to main content

pytest-audioeval

Pytest plugin for STT/TTS integration testing. Built on the httpx ecosystem (httpx, httpx-ws, httpx-sse) with built-in metrics, embedded ground-truth audio samples, and chainable assertions.

Features

  • STT via WebSocket — audioeval.stt.ws() streams audio, collects transcription
  • TTS via HTTP — audioeval.tts.post() batch, .stream() chunked, .sse() Server-Sent Events
  • Text metrics — WER, CER, substitutions, insertions, deletions (via jiwer)
  • Audio metrics — PESQ MOS 1–5 scale (via pesq)
  • Embedded samples — ground-truth audio + reference text pairs, multi-language ready
  • Chainable assertions — result.compute_metrics(ref).assert_quality(max_wer=0.2)
  • CLI thresholds — --audioeval-wer, --audioeval-cer, --audioeval-mos

Install

uv add pytest-audioeval

Quick Start

STT — WebSocket

import asyncio
import uuid
import orjson as json
from pytest_audioeval.client import AudioEval


async def test_user_stt_ws(audioeval: AudioEval) -> None:
    sample = audioeval.samples.en_hello_world

    async with audioeval.stt.ws(sample=sample) as session:
        config = json.dumps(
            {"uid": str(uuid.uuid4()), "language": "en", "task": "transcribe",
             "model": "large-v3-turbo", "use_vad": True}
        ).decode()
        await session.send_text(config)

        ready = await session.receive_text()
        assert "SERVER_READY" in ready

        await session.send_sample(sample, chunk_ms=200)
        await asyncio.sleep(2)
        await session.send_text("END_OF_AUDIO")

        # Collect transcription segments...

TTS — Batch POST

import io
import soundfile as sf
from pytest_audioeval.client import AudioEval


async def test_user_tts_batch(audioeval: AudioEval) -> None:
    response = await audioeval.tts.post(
        json={"input": "Hello world.", "model": "kokoro",
              "voice": "af_heart", "response_format": "wav", "stream": False},
    )
    data, rate = sf.read(io.BytesIO(response.content), dtype="float32")
    assert rate == 24_000
    assert len(data) > 0

TTS — Chunked Streaming

async def test_user_tts_streaming(audioeval: AudioEval) -> None:
    chunks = []
    async with audioeval.tts.stream(json={"input": "Hello.", ...}) as response:
        async for chunk in response.aiter_bytes():
            chunks.append(chunk)
    assert len(chunks) > 0

TTS — Server-Sent Events

async def test_user_tts_sse(audioeval: AudioEval) -> None:
    async with audioeval.tts.sse(json={"input": "Hello.", ...}) as event_source:
        async for sse in event_source.aiter_sse():
            print(sse.data)

Text Metrics

from pytest_audioeval.metrics.text import TextMetrics


async def test_user_metrics_text() -> None:
    metrics = TextMetrics.compute(
        reference="the quick brown fox jumps over the lazy dog",
        hypothesis="the quick brown fox jumps over the lazy dock",
    )
    assert metrics.wer < 0.15
    assert metrics.substitutions == 1

STT Result — Chainable Assertions

from pytest_audioeval.stt import STTResult


async def test_user_stt_result() -> None:
    result = STTResult(hypothesis_text="Hello world.")
    result.compute_metrics("Hello world.")
    result.assert_quality(max_wer=0.2, max_cer=0.15)

Sample Registry

from pytest_audioeval.samples.registry import SampleLang


async def test_user_samples_browse(audioeval: AudioEval) -> None:
    # All samples
    assert len(audioeval.samples) >= 3

    # Filter by language
    en_samples = audioeval.samples.by_lang(SampleLang.EN)

    # Attribute access: {lang}_{name}
    sample = audioeval.samples.en_hello_world
    assert sample.reference_text == "Hello world."

    # Audio access
    audio_f32 = sample.audio_numpy()        # numpy float32 array
    audio_raw = sample.audio_bytes()         # raw bytes
    chunks = sample.chunks(chunk_ms=200)     # chunked for streaming

CLI Thresholds

async def test_user_thresholds(audioeval_thresholds: dict[str, float]) -> None:
    assert audioeval_thresholds["max_wer"] == 0.2
    assert audioeval_thresholds["max_cer"] == 0.15
    assert audioeval_thresholds["min_mos"] == 3.0

CLI Options

pytest --stt-url=ws://localhost:45120 --tts-url=http://localhost:45130/v1/audio/speech
pytest --audioeval-wer=0.15 --audioeval-cer=0.10 --audioeval-mos=3.5
Option Default Description
--stt-url None STT service WebSocket URL
--tts-url None TTS service HTTP URL
--audioeval-wer 0.2 Max WER threshold
--audioeval-cer 0.15 Max CER threshold
--audioeval-mos 3.0 Min PESQ MOS threshold

Fixtures

Fixture Scope Type Description
audioeval session AudioEval Main facade — audioeval.stt, audioeval.tts, audioeval.samples
audioeval_thresholds function dict[str, float] CLI-driven threshold dict

Architecture

src/pytest_audioeval/
├── plugin.py              # pytest entry point (fixtures, CLI options)
├── client.py              # AudioEval facade
├── stt.py                 # STTClient (httpx-ws), STTSession, STTResult
├── tts.py                 # TTSClient (httpx + httpx-sse)
├── metrics/
│   ├── text.py            # TextMetrics — WER, CER via jiwer
│   └── audio.py           # AudioMetrics — PESQ MOS via pesq
└── samples/
    ├── registry.py        # SampleRegistry + AudioSample + SampleLang
    └── audio/en/          # Embedded ground-truth WAV + TXT pairs

Clients

Client Transport Methods
STTClient httpx-ws .ws() — WebSocket context manager yielding STTSession
TTSClient httpx + httpx-sse .post() batch, .stream() chunked, .sse() SSE

Metrics

Metric Class Source Range
Word Error Rate (WER) TextMetrics jiwer 0.0 – 1.0+
Character Error Rate (CER) TextMetrics jiwer 0.0 – 1.0+
Substitutions / Insertions / Deletions TextMetrics jiwer 0 – N
PESQ MOS AudioMetrics pesq 1.0 – 5.0

Samples

Embedded ground-truth audio with reference transcriptions:

samples/audio/
└── en/                    # English (16kHz, float32)
    ├── hello_world.wav    # "Hello world."
    ├── quick_brown_fox.wav
    └── counting.wav       # "One, two, three, four, five."

Access: audioeval.samples.en_hello_world, audioeval.samples.en_counting, etc.

Infrastructure

Integration tests require GPU-accelerated TTS/STT services:

make infra-up       # Start TTS (Kokoro) + STT (WhisperLive)
make infra-status   # Check health
make infra-logs     # View logs
make infra-down     # Stop services
Service Image Port Protocol
TTS (Kokoro) ghcr.io/remsky/kokoro-fastapi-gpu 45130 HTTP
STT (WhisperLive) ghcr.io/collabora/whisperlive-gpu 45120 WebSocket

Development

make install            # uv sync --dev
make lint               # ruff check + format
make test-unit          # unit tests (no services)
make test-integration   # integration tests (requires services)
make coverage           # coverage report (>90%)

Requirements

  • Python >= 3.13
  • NVIDIA GPU + Docker with nvidia-container-toolkit (for integration tests)

License

MIT

Metadata

Release files for pytest-audioeval 0.1.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pytest-audioeval 0.1.3
File Size Uploaded
pytest_audioeval-0.1.3.tar.gz 4.1 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for pytest-audioeval 0.1.3
File Interpreter ABI Platform
pytest_audioeval-0.1.3-py3-none-any.whl Python 3 none any Details

Total release size: 8.0 MB

Release files / pytest_audioeval-0.1.3.tar.gz

Download URL pytest_audioeval-0.1.3.tar.gz
Size 4.1 MB
Tags Source
SHA-256 checksum
How to use checksums
e37654bc869b8fbc092f17bf826ece8421601f3f3de9f8fe9b23c6dfd2989011
BLAKE2b-256 checksum
How to use checksums
493594206d6312efb1049c9993f354e261e33540b55303d2c6bc4bc879845341
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.8 {"installer":{"name":"uv","version":"0.10.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / pytest_audioeval-0.1.3-py3-none-any.whl

Download URL pytest_audioeval-0.1.3-py3-none-any.whl
Size 4.0 MB
Tags Python 3
SHA-256 checksum
How to use checksums
8b97369347232d43180dd9860189c547ccc8dedb42b7c21d511ddc5a05ef7ecc
BLAKE2b-256 checksum
How to use checksums
6d3fbbe9ff3c5aa09abfebdda827cf7c98ec7c8ae030043ba3dc79a03415fea1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.8 {"installer":{"name":"uv","version":"0.10.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

0.1.3 This release

2 release files

0.1.2

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page