Standard ASR

Errors & diagnostics

Standard ASR follows "explicit > implicit": when something goes wrong, you get a specific exception with machine-readable context -- never silent degradation.

Exception hierarchy

Every domain exception inherits from StandardASRError, so a single except StandardASRError catches every domain error the runtime raises. Plain caller misuse and pydantic validation sit outside the hierarchy -- see the section after the table.

StandardASRError
+-- StructuredError (adds .param / .hint / .details)
|   +-- ConfigError            invalid config (bad language, bad value, ...)
|   |   +-- ConfigurationRequiredError  required config ABSENT (for example, credential not set)
|   +-- TranscriptionError     engine failed during transcription
|   +-- UnsupportedFeatureError  unsupported feature (always) or parameter (strict mode)
|   +-- InvalidProviderParamError  wrong engine's provider_params passed
+-- AudioProcessingError       audio decode / size / sample-rate failure
|   +-- IncompatibleAudioInputError  no conversion path exists
|   |   +-- UnsafeAudioUrlError   AudioUrl failed the SSRF policy (non-HTTPS, private IP)
|   +-- FFmpegNotFoundError    FFmpeg needed but not on PATH
|   +-- FFprobeNotFoundError   FFprobe needed but not on PATH
+-- EngineContractError        engine broke the protocol contract (async transcribe, bad declaration)
+-- SubtitleRenderingError     to_srt/to_vtt: segments lack measured timing (choose a policy)
+-- StreamClosedError          audio delivered to a closed session
+-- InvalidSessionUseError     session driven incorrectly (for example, mixing feed() with send_audio())
+-- DiscoveryError             plugin discovery problem
    +-- EntrypointValidationError  bad entry-point name or metadata
    +-- FactoryLoadError          entry point failed to import / not callable

When each exception fires

ExceptionWhenTypical cause
ConfigErrorcreate(), transcribe(), or start_transcription()Invalid config value — bad pydantic validation, or a default_language that is malformed / not selectable. Fixable by whoever supplies the config.
ConfigurationRequiredErrorcreate() / from_env(), or lazily at the first transcribe() / session when the engine defers its credential checkA required field (for example, an API key) is absent from both explicit config and the environment — set it and retry; compliance treats this as a skip, not a failure.
TranscriptionErrortranscribe() / start_transcription()Engine crashed or returned an invalid result.
UnsupportedFeatureErrorstart_transcription() always for an unsupported streaming axis or wire format; transcribe() / start_transcription() in strict mode for an unsupported parameterRequested streaming on a batch-only engine, or word timestamps (strict) on an engine that does not support them.
InvalidProviderParamErrortranscribe() or start_transcription()Passed faster-whisper's provider_params to an OpenAI engine (swap-safety).
AudioProcessingErrortranscribe() / start_transcription(audio=...)Corrupt audio file, missing sample rate, unsupported format without [audio] extra.
IncompatibleAudioInputErrortranscribe() / start_transcription(audio=...)Passed a URL to an engine that only accepts arrays, and no conversion path exists.
UnsafeAudioUrlErrortranscribe() / start_transcription(audio=...)An AudioUrl failed the SSRF policy (non-HTTPS, private IP, etc.).
SubtitleRenderingErrorto_srt() / to_vtt()A segment cannot render as a visible cue — no measured start/end span, or a span that quantizes to zero milliseconds on the output grid (players silently drop T --> T cues) — and on_unrenderable is the default "error". Choose the loss explicitly: "omit" (renderable cues only) or "collapse" (one whole-text cue). Carries .unrenderable / .total counts.
EngineContractErrorany synchronous protocol member, or transcribe() / start_transcription() on a language-declaration defectThe engine returned an awaitable (an async def implementation) or a wrong-typed value from a sync member (transcribe(), start_transcription(), supports(), and so on), declared a language axis without a default_language (IC.6), or declared a malformed selectable/detectable tag. An engine/plugin bug — report it to the engine's author; nothing in your code is wrong.
StreamClosedErrorsession.send_audio(); SyncSession audio calls and iteration after the bridge tore downSending audio manually after end_audio() or after the session delivered a terminal event. The async session's feed() never raises it: a managed source's post-terminal chunks are discarded by design. The sync bridge is stricter: once it tore down (its with block exited, or a lifecycle call timed out), feed(), send_audio(), end_audio(), and iteration raise it -- there is no loop left to discard into. result() and diagnostics() still work.
InvalidSessionUseErrorsession.feed() / session.send_audio() / iterating the sessionDriving a still-live session incorrectly: mixing managed feed() with manual send_audio()/end_audio(), calling feed() twice, or iterating the event stream twice. The session is NOT closed — fix the calling code; do not rebuild the session.
EntrypointValidationErrordiscover_models() (strict mode); registry.spec() / create() on an unknown or malformed keyA plugin's entry-point name is malformed, or a lookup key does not resolve.
FactoryLoadErrorregistry.engine_class() / registry.create()Plugin's entry point cannot be imported or the factory is misconfigured.

What StandardASRError does not catch

Building a model is pydantic's job, not the runtime's. A malformed field raises pydantic.ValidationError, which is a ValueError and not a StandardASRError:

RuntimeParams(language="english")  # ValidationError: not a BCP-47 tag
RuntimeParams(candidate_languages=["auto"])

Plain caller misuse raises a built-in the same way: ValueError for a bad value (passing both audio and audio_format to start_transcription()), TypeError for a wrong type (an unsupported input type to transcribe()). Catch the domain errors and the value mistakes together:

except (StandardASRError, ValueError):   # ValidationError is a ValueError
    ...

A TypeError stays outside both families on purpose: a wrong input type is a code bug to fix, not a state to handle. The sync bridge's no-hang contract can also raise the built-in TimeoutError for a hung engine.

Structured error context

StructuredError subclasses carry machine-readable fields:

try:
    engine.transcribe("audio.wav", RuntimeParams(word_timestamps="word"))
except UnsupportedFeatureError as exc:
    print(exc.param)  # "word_timestamps" — the offending parameter
    print(exc.mode)  # "batch" — where the rejection happened
    print(exc.hint)  # actionable guidance, or None

try:
    registry.create("acme/model")
except ConfigError as exc:
    print(exc.param)  # the offending field, if a single one is implicated
    print(exc.details)  # sanitized [{"type", "loc", "msg"}, ...] entries

These fields let you build programmatic error handling (for example, fall back to another engine when a feature is unsupported) without parsing message strings. Every StructuredError also carries .details, populated where structured context exists — ConfigError, for example, puts the sanitized pydantic validation entries there (UnsupportedFeatureError leaves it None).

Diagnostics (non-fatal)

Not every problem is an exception. In best_effort mode, unsupported parameters are dropped with a structured Diagnostic instead of raising:

result = engine.transcribe("audio.wav", RuntimeParams(word_timestamps="word"))
for diag in result.diagnostics:
    print(diag.code, diag.message)
    # unsupported_parameter_ignored Ignored unsupported parameter 'word_timestamps' in batch mode (capability 'batch.word_timestamps' not supported).

Diagnostics surface:

  • Parameter-gating decisions (dropped features, truncated prompts).
  • Audio conversion steps (lossy resampling, format changes).
  • Engine-authored messages during streaming (session.diagnostics()).

The code field is a stable, machine-readable identifier; the message is human-readable. Applications should key on code for programmatic handling.

Further reading

On this page