Standard ASR

standard_asr

The top-level standard_asr namespace is the application-developer surface. Import what you need to discover engines, pass audio, read results, and stream:

from standard_asr import discover_models, RuntimeParams, TranscriptionResult

For the engine-author surface (building a plugin), see standard_asr.engine.

For inference-artifact status, acquisition, progress, and errors, see Inference artifacts.

Standard ASR -- the open interface between applications and ASR engines.

This top-level namespace is the application-developer surface: discover an engine, inspect its inference artifacts when needed, hand it audio, read a constant-shape result, and optionally stream. That is the whole 80% path:

from standard_asr import discover_models, RuntimeParams

registry = discover_models()
engine = registry.create("faster-whisper/large-v3")
result = engine.transcribe("meeting.wav", RuntimeParams(language="en"))
print(result.text)

The deeper surfaces live in dedicated, audience-signaling submodules so the names you reach for are never buried under names you don't:

  • standard_asr.engine -- everything an engine author implements and declares (EngineBase, the config/properties surface, the full capability vocabulary).
  • standard_asr.compliance -- the compliance checks an engine author runs against their plugin.
  • granular modules (standard_asr.audio.wire, standard_asr.audio.negotiation, ...) expose the framework internals for advanced use.

ARTIFACTS_NOT_APPLICABLE#attributeSource

ARTIFACTS_NOT_APPLICABLE: Final = 'not_applicable'

ARTIFACTS_READY#attributeSource

ARTIFACTS_READY: Final = 'ready'

ARTIFACTS_UNAVAILABLE#attributeSource

ARTIFACTS_UNAVAILABLE: Final = 'unavailable'

ARTIFACTS_UNKNOWN#attributeSource

ARTIFACTS_UNKNOWN: Final = 'unknown'

ARTIFACT_ACTION_ACCEPT_TERMS#attributeSource

ARTIFACT_ACTION_ACCEPT_TERMS: Final = 'accept_terms'

ARTIFACT_ACTION_AUTHENTICATE#attributeSource

ARTIFACT_ACTION_AUTHENTICATE: Final = 'authenticate'

ARTIFACT_ACTION_INSTALL_EXTERNAL#attributeSource

ARTIFACT_ACTION_INSTALL_EXTERNAL: Final = 'install_external'

ARTIFACT_ACTION_OTHER#attributeSource

ARTIFACT_ACTION_OTHER: Final = 'other'

ARTIFACT_ACTION_PROVIDE_ARTIFACTS#attributeSource

ARTIFACT_ACTION_PROVIDE_ARTIFACTS: Final = 'provide_artifacts'

ARTIFACT_ACTION_REQUEST_ACCESS#attributeSource

ARTIFACT_ACTION_REQUEST_ACCESS: Final = 'request_access'

ARTIFACT_BLOCKER_ACTION_REQUIRED#attributeSource

ARTIFACT_BLOCKER_ACTION_REQUIRED: Final = 'action_required'

ARTIFACT_BLOCKER_DOWNLOADS_DISABLED#attributeSource

ARTIFACT_BLOCKER_DOWNLOADS_DISABLED: Final = 'downloads_disabled'

ARTIFACT_BLOCKER_UNSUPPORTED#attributeSource

ARTIFACT_BLOCKER_UNSUPPORTED: Final = 'unsupported'

ARTIFACT_CORRUPT#attributeSource

ARTIFACT_CORRUPT: Final = 'corrupt'

ARTIFACT_INCOMPLETE#attributeSource

ARTIFACT_INCOMPLETE: Final = 'incomplete'

ARTIFACT_MISSING#attributeSource

ARTIFACT_MISSING: Final = 'missing'

ARTIFACT_PROGRESS_CONVERTING#attributeSource

ARTIFACT_PROGRESS_CONVERTING: Final = 'converting'

ARTIFACT_PROGRESS_EXTRACTING#attributeSource

ARTIFACT_PROGRESS_EXTRACTING: Final = 'extracting'

ARTIFACT_PROGRESS_FINALIZING#attributeSource

ARTIFACT_PROGRESS_FINALIZING: Final = 'finalizing'

ARTIFACT_PROGRESS_RESOLVING#attributeSource

ARTIFACT_PROGRESS_RESOLVING: Final = 'resolving'

ARTIFACT_PROGRESS_TRANSFERRING#attributeSource

ARTIFACT_PROGRESS_TRANSFERRING: Final = 'transferring'

ARTIFACT_PROGRESS_UNIT_BYTES#attributeSource

ARTIFACT_PROGRESS_UNIT_BYTES: Final = 'bytes'

ARTIFACT_PROGRESS_UNIT_FILES#attributeSource

ARTIFACT_PROGRESS_UNIT_FILES: Final = 'files'

ARTIFACT_PROGRESS_VERIFYING#attributeSource

ARTIFACT_PROGRESS_VERIFYING: Final = 'verifying'

ARTIFACT_READY#attributeSource

ARTIFACT_READY: Final = 'ready'

ARTIFACT_UNKNOWN#attributeSource

ARTIFACT_UNKNOWN: Final = 'unknown'

ArtifactAcquisitionBlocker#attributeSource

ArtifactAcquisitionBlocker = Annotated[str, StringConstraints(pattern=_CODE_PATTERN)]

Open vocabulary for a reason that prevents explicit acquisition.

ArtifactAcquisitionError#classSource

class ArtifactAcquisitionError(StructuredError)

ArtifactAcquisitionError(
    message: str,
    *,
    reason: Literal['downloads_disabled', 'action_required', 'unsupported', 'busy', 'failed'],
    report: ArtifactReport | None = None,
    required_actions: tuple[ArtifactAction, ...] = (),
    retriable_after: float | None = None,
    hint: str | None = None,
)

Raised when an explicit or implicit artifact acquisition fails.

Parameters
NameTypeDescription
messagestr

Human-readable description of the acquisition failure.

reasonLiteral['downloads_disabled', 'action_required', 'unsupported', 'busy', 'failed']

Machine-readable acquisition reason.

report
= None
ArtifactReport | None

Latest available artifact status report, if any.

required_actions
= ()
tuple[ArtifactAction, ...]

External actions discovered by the failed attempt.

retriable_after
= None
float | None

Suggested nonnegative delay before another attempt.

hint
= None
str | None

Actionable guidance, if any.

Raises
  • TypeError

    If reason is not a string, report is not an ArtifactReport or None, or a required action is not an ArtifactAction.

  • ValueError

    If reason is not in the closed acquisition vocabulary, retriable_after is negative or non-finite, or the report or a required action fails re-validation.

ArtifactAction#classSource

class ArtifactAction(BaseModel)

Describe an external action needed before artifact acquisition.

Raises
  • ValueError

    If url embeds user information.

Attributes
NameTypeDescription
kindArtifactActionKind

Machine-readable action classification.

messagestr

Human-readable instructions for the operator.

url
= None
HttpsUrl | None

Optional HTTPS page that explains or completes the action.

ArtifactActionKind#attributeSource

ArtifactActionKind = Annotated[str, StringConstraints(pattern=_CODE_PATTERN)]

Open vocabulary for an external action required from an operator.

ArtifactContext#classSource

class ArtifactContext(BaseModel)

Request information available before audio reaches an engine.

Attributes
NameTypeDescription
mode
= None
ModeName | None

Requested inference mode, or None for engine resolution.

params
= RuntimeParams()
RuntimeParams

Per-request parameters that can affect artifact requirements.

ArtifactProgress#classSource

class ArtifactProgress(BaseModel)

Describe one ordered artifact-acquisition progress update.

Raises
  • ValueError

    If counts and their unit disagree or completed units exceed total units.

Attributes
NameTypeDescription
phaseArtifactProgressPhase

Current operation phase.

artifact_id
= None
_ArtifactId | None

Related logical requirement, if the update is specific.

completed_units
= None
int | None
ge=0

Completed byte or file count, if known.

total_units
= None
int | None
ge=0

Total byte or file count, if known.

unit
= None
ArtifactProgressUnit | None

Unit for the supplied counts.

ArtifactProgressCallback#attributeSource

ArtifactProgressCallback = Callable[[ArtifactProgress], None]

Callback that observes validated artifact progress updates.

ArtifactProgressCallbackError#classSource

class ArtifactProgressCallbackError(StandardASRError)

ArtifactProgressCallbackError(
    message: str,
    *,
    report: ArtifactReport,
)

Raised after acquisition succeeds but its progress callback fails.

Parameters
NameTypeDescription
messagestr

Human-readable description of the callback failure.

reportArtifactReport

Final artifact report produced by the successful operation.

ArtifactProgressPhase#attributeSource

ArtifactProgressPhase = Annotated[str, StringConstraints(pattern=_CODE_PATTERN)]

Open vocabulary for an artifact operation's current phase.

ArtifactProgressUnit#attributeSource

ArtifactProgressUnit = Annotated[str, StringConstraints(pattern=_CODE_PATTERN)]

Open vocabulary for artifact progress-count units.

ArtifactReadiness#attributeSource

ArtifactReadiness = Annotated[str, StringConstraints(pattern=_CODE_PATTERN)]

Open vocabulary for aggregate inference readiness.

ArtifactReport#classSource

class ArtifactReport(BaseModel)

Report artifact requirements and aggregate inference readiness.

Raises
  • ValueError

    If applicability, identifiers, or a recognized readiness value contradicts the requirements.

Attributes
NameTypeDescription
modeModeName

Concrete inference mode represented by the report.

applicablebool

Whether artifact management applies to the resolved context.

requirements
= ()
tuple[ArtifactRequirement, ...]

Every logical requirement the resolved context depends on.

diagnostics
= ()
tuple[Diagnostic, ...]

Non-fatal decisions made while producing the report.

readinessArtifactReadiness

Stored aggregate inference-readiness verdict.

from_requirements#class methodSource

@classmethod
def ArtifactReport.from_requirements(
    *,
    mode: ModeName,
    applicable: bool,
    requirements: Iterable[ArtifactRequirement] = (),
    diagnostics: Iterable[Diagnostic] = (),
) -> ArtifactReport

Build a report with canonical aggregate readiness.

Parameters
NameTypeDescription
modeModeName

Concrete inference mode represented by the report.

applicablebool

Whether artifact management applies to the context.

requirements
= ()
Iterable[ArtifactRequirement]

Logical artifact requirements.

diagnostics
= ()
Iterable[Diagnostic]

Non-fatal status diagnostics.

Returns

ArtifactRequirement#classSource

class ArtifactRequirement(BaseModel)

Report one logical artifact requirement for a resolved context.

Raises
  • ValueError

    If acquisition fields contradict the requirement state, a size is negative, the location is relative, or the identifier is malformed.

Attributes
NameTypeDescription
artifact_id_ArtifactId

Engine-scoped identifier used to correlate reports and progress events.

labelstr

Human-readable display label.

stateArtifactState

Current requirement state.

required_for_inferencebool

Whether the selected execution path needs the requirement.

can_acquire_nowbool

Whether explicit acquisition can run immediately.

may_acquire_during_inferencebool

Whether normal inference can acquire the requirement.

source_is_mutablebool

Whether refresh can re-resolve a mutable source.

acquisition_blocker
= None
ArtifactAcquisitionBlocker | None

Reason explicit acquisition cannot run.

required_actions
= ()
tuple[ArtifactAction, ...]

External actions known to block acquisition.

location
= None
Path | None

Absolute logical file or directory root, if known.

size_bytes
= None
int | None
ge=0

Present logical size, if known.

expected_size_bytes
= None
int | None
ge=0

Complete logical size, if known.

artifact_version
= None
str | None

Opaque revision or version, if known.

ArtifactState#attributeSource

ArtifactState = Annotated[str, StringConstraints(pattern=_CODE_PATTERN)]

Open vocabulary for one logical artifact requirement's state.

ArtifactStatusError#classSource

class ArtifactStatusError(StructuredError)

Raised when artifact status inspection fails unexpectedly.

ArtifactUnavailableError#classSource

class ArtifactUnavailableError(StructuredError)

ArtifactUnavailableError(
    message: str,
    *,
    reason: Literal['missing', 'incomplete', 'corrupt', 'unknown', 'action_required', 'downloads_disabled'],
    report: ArtifactReport,
    hint: str | None = None,
)

Raised when required artifacts cannot support inference or warm-up.

Parameters
NameTypeDescription
messagestr

Human-readable description of the unavailable artifacts.

reasonLiteral['missing', 'incomplete', 'corrupt', 'unknown', 'action_required', 'downloads_disabled']

Machine-readable availability reason.

reportArtifactReport

Status report that established the unavailable state.

hint
= None
str | None

Actionable guidance, if any.

Raises
  • TypeError

    If reason is not a string or report is not an ArtifactReport.

  • ValueError

    If reason is not in the closed availability vocabulary, or report fails re-validation.

AudioArray#classSource

class AudioArray

AudioArray(
    samples: NDArray[np.floating],
    sample_rate: int | None = None,
)

An already-decoded raw waveform.

Unlike the other variants, an array does not self-describe its sample rate. When sample_rate is None the global strict / best_effort policy decides whether to raise or assume the canonical 16 kHz.

eq is disabled because NumPy arrays do not support scalar equality; instances therefore compare by identity.

Parameters
NameTypeDescription
samplesNDArray[np.floating]

Waveform samples. Canonical form is float32 mono in [-1, 1]; multi-channel is (n_samples, n_channels).

sample_rate
= None
int | None

Sample rate in Hz, or None if unknown.

__post_init__#methodSource

def AudioArray.__post_init__() -> None

Reject a non-floating dtype or a non-positive sample rate at construction.

Both downstream paths assume floating samples in [-1, 1]: array passthrough delivers them unscaled, and WAV encoding scales by full int16 range. An integer array (for example, int16 PCM) would be silently mis-scaled by either path -- a wrong-audio result, the cardinal sin -- so it is rejected at construction with an actionable message.

A sample_rate of 0 or a negative value is likewise rejected here. Otherwise an engine declaring accepted_sample_rates="any" would be handed the bogus rate verbatim (silently, the cardinal sin), while an engine declaring a concrete list would crash deep in resampling (ZeroDivisionError on the duration check, or a bare ValueError) -- the failure mode drifting with engine metadata. Failing at construction makes the application bug (a unit/shape mix-up) loud and engine-independent, matching AudioFormat.sample_rate's gt=0 and the dtype check above. None (rate unknown) stays valid: it is resolved by the strict/best_effort sample-rate policy downstream. The number of samples is not constrained here -- an empty array can be a legitimate passthrough boundary input, so emptiness is handled (where it actually matters) at resample time, not rejected at construction.

Raises
  • TypeError

    If samples does not have a floating dtype.

  • ValueError

    If sample_rate is not None and not strictly positive.

AudioBase64#classSource

class AudioBase64

AudioBase64(
    value: str,
)

Base64-encoded (or data-URI) encoded audio.

Parameters
NameTypeDescription
valuestr

Base64 string or data: URI.

AudioBytes#classSource

class AudioBytes

AudioBytes(
    data: bytes,
    container: str | None = None,
)

Encoded audio held in memory.

The sample rate is self-describing via the file header.

Parameters
NameTypeDescription
databytes

Encoded audio bytes (for example, the contents of an MP3/WAV file).

container
= None
str | None

Optional container/format hint (for example, "wav", "mp3").

AudioFormat#classSource

class AudioFormat(BaseModel)

Declared wire format for raw PCM frames fed to a streaming session.

Raises
  • ValueError

    If validation fails.

Attributes
NameTypeDescription
encodingstr

Wire encoding of the PCM frames (for example, "pcm_s16le", "mulaw"). MUST be one of the engine's wire_encodings.

sample_rateint
gt=0

Sample rate of the frames in Hz.

channels
= 1
int
gt=0

Number of interleaved channels. Defaults to 1 (mono).

AudioInput#attributeSource

AudioInput = AudioPath | AudioBytes | AudioArray | AudioUrl | AudioBase64 | AudioStorageUri

AudioInputLike#attributeSource

AudioInputLike = Union[AudioInput, str, 'os.PathLike[str]', bytes, 'NDArray[np.floating]', 'tuple[NDArray[np.floating], int]']

AudioPath#classSource

class AudioPath

AudioPath(
    value: str | os.PathLike[str],
)

A local audio file on disk.

The sample rate is self-describing via the file header.

Parameters
NameTypeDescription
valuestr | os.PathLike[str]

Path to the audio file.

AudioProcessingError#classSource

class AudioProcessingError(StandardASRError)

Raised when an error occurs during audio loading or processing.

The audio loading and conversion functions in standard_asr.audio raise this.

AudioStorageUri#classSource

class AudioStorageUri

AudioStorageUri(
    value: str,
)

A provider cloud-storage URI the engine resolves with its own credentials.

Whole engine classes are addressable only by a provider-native storage URI: AWS Transcribe batch requires an S3 URI (Media.MediaFileUri) and Google STT v2 requires a gs:// URI. These are not HTTPS-fetchable public URLs: the engine resolves them with its own cloud-SDK credentials, so -- unlike AudioUrl -- the standard MUST NOT run the HTTPS public-IP SSRF validator over them. The standard never fetches a storage URI itself; it only forwards it to an engine that declares "storage_uri" support.

Like AudioUrl / AudioBase64, this variant requires explicit construction: a bare str always coerces to AudioPath, never to a storage URI (the same SSRF safety stance against attacker-controlled strings). The scheme is validated against STORAGE_URI_SCHEMES at construction; file://, http(s)://, an empty value, or an unknown scheme is rejected with a clear error.

Parameters
NameTypeDescription
valuestr

The storage URI, for example, "s3://bucket/key.wav" or "gs://bucket/key.flac". The sample rate is self-describing at the remote/server side once the engine resolves it.

Raises
  • ValueError

    If the URI is empty, has no scheme, or uses a scheme outside STORAGE_URI_SCHEMES.

__post_init__#methodSource

def AudioStorageUri.__post_init__() -> None

Validate the URI scheme against the storage-scheme allowlist.

Raises
  • ValueError

    If the URI is empty, schemeless, or uses a scheme that is not an allowlisted provider storage scheme.

AudioUrl#classSource

class AudioUrl

AudioUrl(
    value: str,
)

A remote URL the engine or cloud service fetches server-side.

The semantics are "the server can fetch this". Security constraints (HTTPS-only, private/loopback/link-local-address rejection) are enforced before the URL is forwarded to an engine, by standard_asr.audio.negotiation.validate_fetchable_url at plan execution. The standard never fetches the URL itself.

Parameters
NameTypeDescription
valuestr

The remote URL.

ChannelResult#classSource

class ChannelResult(BaseModel)

Per-channel transcription for multi-channel audio.

Raises
  • ValueError

    If field validation fails.

Attributes
NameTypeDescription
channelint
ge=0

Channel index.

textstr

Full transcript for this channel.

segments
= None
list[Segment] | None

Optional segment-level details for this channel.

words
= None
list[Word] | None

Optional flattened word-level details for this channel.

ConfigError#classSource

class ConfigError(StructuredError, ValueError)

Raised when the CONFIGURATION -- supplied or ambient -- is invalid.

The type asserts fault ownership: the configuration's SUPPLIER can fix it (a bad init-config field, a default_language not in selectable_languages, a malformed --config / --set). Who that supplier is depends on the surface, and each surface maps the SAME error accordingly:

  • CLI: the invoking user owns the config -- the flags AND the env vars -- so every ConfigError is caller-actionable there: usage exit 2, with the sanitized message naming the field to fix.
  • Reference server: a wire client cannot supply engine config at all (construction is zero-arg; options are the portable WireRuntimeParams), so a ConfigError reaching the server -- at construction, transcription, or session establishment -- is a deployment-side fault and maps to a scrubbed 500 (WS internal_error); the client-fixable rejections have their own types (UnsupportedFeatureError -> 422, request validation -> 422). See ConfigurationRequiredError for the absent-config 503 state.

Engine-DECLARATION defects (a malformed declared language tag, an unsatisfiable prepare shape, an IC.6 violation) are NOT this type: they raise EngineContractError, because no configuration value fixes them. An engine that raises ConfigError (or lets a construction-time ValidationError, which ModelRegistry.create wraps into one, escape its factory) for a fault that is NOT about the supplied configuration mis-asserts this ownership contract -- the compliance suite's zero-arg construction check fails such engines (engine_construction_failed); consumers do not second-guess the type. The ValueError mixin serves IN-PROCESS callers, who genuinely can pass a bad config value to a constructor.

The one machine-distinguishable sub-state is ABSENT required configuration: raise (or catch) ConfigurationRequiredError for that -- consumers such as the compliance suite treat "config missing from this environment" (skip) differently from "config invalid" (fail).

ConfigurationRequiredError#classSource

class ConfigurationRequiredError(ConfigError)

Raised when required runtime configuration is ABSENT, not invalid.

The narrow, machine-distinguishable subtype of ConfigError for the one state that is a fact about the ENVIRONMENT rather than about any code or declaration: a required config field (typically a credential) was neither passed explicitly nor found in the environment. Consumers use the distinction to keep two very different verdicts apart:

  • the compliance suite SKIPS instantiation-level checks on this error (a credentialed engine on a clean CI is behaving correctly; the verdict must not depend on the runtime's credential state), while
  • any other ConfigError -- an invalid supplied value, an internally inconsistent declaration, a factory contract bug -- stays a compliance FAILURE (skipping those would let a broken plugin read as green-with-warning).

from_env raises this automatically when construction failed solely because required fields are missing, so engines following the documented explicit > env > raise pattern get the classification for free. An engine building its config another way should raise this type itself for the missing-credential state.

Transport mapping: the reference server maps this state to HTTP 503 (REST) / a service_unavailable frame (WS) with a stable generic detail -- whether it surfaces at zero-arg engine construction or lazily at transcription/session establishment (an engine deferring its credential check past __init__). An operator-side availability state, never the caller's 422, and never the absent field names (those are deployment detail, safe-logged for the operator only).

DIAG_SEGMENT_TIMESTAMPS_UNAVAILABLE#attributeSource

DIAG_SEGMENT_TIMESTAMPS_UNAVAILABLE = 'segment_timestamps_unavailable'

DIARIZE#attributeSource

DIARIZE: Final[DiarizationRequest] = DiarizationRequest()

Diagnostic#classSource

class Diagnostic(BaseModel)

A structured, non-fatal notification from the standard layer.

Diagnostics report lossy conversions, assumed parameters, best_effort degradations, and similar non-ideal paths.

Attributes
NameTypeDescription
level
= 'info'
Literal['info', 'warning']

Severity, "info" or "warning".

codestr

Stable machine-readable code (for example, "audio_conversion").

messagestr

Human-readable explanation.

param
= None
str | None

The parameter the diagnostic concerns, if any.

provided
= None
WireJsonValue

The value the application provided, if relevant.

effective
= None
WireJsonValue

The value that took effect, if relevant.

DiarizationRequest#classSource

class DiarizationRequest(BaseModel)

Request marker for speaker diarization ("who said what").

Presence enables diarization: RuntimeParams(diarization=DiarizationRequest()) (or the DIARIZE convenience constant) requests speaker labels; diarization=None (the default) means not requested. There is no []-analog -- a "requested-but-empty" state is meaningless for an on/off feature, so None vs an instance is the whole state space. On the wire the marker maps three ways: "diarization": {} -> DiarizationRequest(); "diarization": null -> None; key absent -> None.

The model is a deliberately empty frozen marker in the current generation: tuning parameters (num_speakers / min/max hints, granularity selection) are deferred because today's engine landscape cannot honor them portably -- they graduate additively onto this model once support is broad enough. extra="forbid" keeps that evolution honest: an unknown key (for example, a client guessing num_speakers) fails loudly (a 422 on the wire) instead of being silently ignored. An import-time assert in standard_asr.runtime.gating additionally forces sub-gating to be written the moment a field is added here.

Raises
  • ValueError

    If an unknown field is supplied (extra="forbid").

DiscoveryError#classSource

class DiscoveryError(StandardASRError)

Base class for discovery and plugin-related errors.

EngineContractError#classSource

class EngineContractError(StandardASRError)

Raised when a constructed engine breaks the protocol contract.

The runtime counterpart of a compliance failure, in two shapes:

  • Runtime behavior: a synchronous StandardASR member (transcribe / start_transcription / supports / recommended_wire_format / artifact_status / acquire_artifacts), or the optional prepare engine method, returned an awaitable or a value outside its pinned return type. Raised by standard_asr.runtime.protocol_boundary.require_sync_result at the consumer call sites (CLI, reference server) so the defect is loud at the boundary instead of surfacing as a confusing secondary AttributeError (or a silent misreading) deep inside another subsystem.
  • Declaration shape: the engine DECLARED something the contract forbids -- a prepare that is a coroutine function, non-callable, or parameter-requiring; a malformed selectable_languages / detectable_languages tag; a language axis without the IC.6 default_language obligation. No caller-side value can fix these, which is what separates them from ConfigError (an invalid configuration VALUE, fixable by whoever supplies the config).

An engine/plugin fault, never a caller mistake -- deliberately NOT a ValueError: transports and the CLI map the ValueError family to caller-fixable surfaces (HTTP 422 / usage exit 2), while this must land on the engine-fault surfaces (scrubbed HTTP 500 / internal_error frame / CLI exit 1). If you hit it as an application developer, report it to the engine's author. Messages carry type names only, never the offending value.

EntrypointValidationError#classSource

class EntrypointValidationError(DiscoveryError, ValueError)

Raised when an entry point name or metadata is invalid.

FFmpegNotFoundError#classSource

class FFmpegNotFoundError(AudioProcessingError, FileNotFoundError)

Raised when FFmpeg is required but not found in the system PATH.

FFprobeNotFoundError#classSource

class FFprobeNotFoundError(AudioProcessingError, FileNotFoundError)

Raised when FFprobe is required but not found in the system PATH.

FactoryLoadError#classSource

class FactoryLoadError(DiscoveryError, ImportError)

Raised when an entry point target cannot be imported or is not callable.

IncompatibleAudioInputError#classSource

class IncompatibleAudioInputError(AudioProcessingError)

IncompatibleAudioInputError(
    provided: str,
    accepted: object,
    hint: str,
)

Raised when no viable conversion path exists for the provided audio.

This happens when the shape an application provides cannot be negotiated into any shape the engine accepts (for example, a local array given to an engine that only accepts a server-fetchable URL).

Parameters
NameTypeDescription
providedstr

Human-readable description of the provided input shape.

acceptedobject

The engine's accepted input kinds.

hintstr

Actionable guidance for resolving the mismatch.

InvalidProviderParamError#classSource

class InvalidProviderParamError(StructuredError, ValueError)

Raised when provider_params are invalid for the target engine.

Unlike standard-set parameters, provider_params errors are always raised regardless of strict / best_effort -- they indicate a code-level bug (such as passing one engine's params model to another after a swap).

InvalidSessionUseError#classSource

class InvalidSessionUseError(StandardASRError, ValueError)

Raised when a streaming session is driven incorrectly while still live.

A caller-side programming error against an open session -- distinct from StreamClosedError (the stream genuinely ended). It covers the still-live-session usage breaches that are NOT lifecycle-close:

  • mixing managed feed() with manual send_audio / end_audio (only one input mode may own a session);
  • calling feed() more than once (a session owns at most one fed source);
  • iterating the event stream more than once (single-consumer contract).

The session is not closed in any of these cases -- the mistake is in how the application used it. Catching StreamClosedError here would lead an application to wrongly conclude the session terminated and rebuild it. Mixes in ValueError (like ConfigError / InvalidProviderParamError): it is a bad-call programming error. (It has no HTTP mapping: it fires only against an in-process session object, and the server drives its own sessions correctly by construction.)

HttpsUrl#attributeSource

HttpsUrl = Annotated[AnyUrl, UrlConstraints(allowed_schemes=['https'], host_required=True)]

An HTTPS display URL that Standard ASR never dereferences.

ModeName#attributeSource

ModeName = Literal['batch', 'streaming']

ProtocolCompatibilityError#classSource

class ProtocolCompatibilityError(StandardASRError)

ProtocolCompatibilityError(
    message: str,
    *,
    protocol_version: str,
    feature: str | None = None,
    required_protocol_version: str | None = None,
)

Raised when an engine's declared protocol cannot be used by this core.

The error reports a version compatibility boundary, not a missing Python attribute. It covers both directions: an engine on an older protocol line than the core supports, and an engine on a newer one. Generic consumers raise it before they inspect a member the declared protocol does not define.

Parameters
NameTypeDescription
messagestr

Human-readable description of the incompatibility.

protocol_versionstr

Protocol version declared by the engine.

feature
= None
str | None

Stable feature identifier requested by the consumer, or None when the incompatibility is at the protocol-line level rather than about one feature.

required_protocol_version
= None
str | None

Earliest protocol version that defines the feature, if known.

UnrenderablePolicy#attributeSource

UnrenderablePolicy = Literal['error', 'omit', 'collapse']

ModelRegistry#classSource

class ModelRegistry

ModelRegistry(
    specs: Mapping[str, ModelSpec],
    *,
    shadowed_engine_ids: set[str] | None = None,
)

Container for discovered ASR engine factories.

ModelRegistry holds the results of plugin discovery and provides methods to list, query, and instantiate ASR engines. It does not perform discovery itself—use discover_models() to create a populated registry.

Typical Usage:

>>> from standard_asr import discover_models
>>> registry = discover_models()
>>>
>>> # List all available models
>>> registry.names()  # ['faster-whisper/large-v3', 'whisper/base', ...]
>>>
>>> # Create an ASR instance
>>> asr = registry.create("faster-whisper/large-v3", device="cuda")
>>> result = asr.transcribe(audio)

Key Methods:

  • names(): List all discovered model keys.
  • keys_by_engine(engine_id): List models for a specific engine.
  • create(name, **kwargs): Instantiate an ASR engine.
  • spec(name): Get metadata for a model.

Note

Use discover_models() to create a ModelRegistry. Do not instantiate directly unless you're providing custom entry points for testing.

Initialize with a mapping of model specs (internal use).

Parameters
NameTypeDescription
specsMapping[str, ModelSpec]

Mapping of engine_id/model_name keys to specs.

shadowed_engine_ids
= None
set[str] | None

Engine ids contributed by more than one distribution (an engine-identity collision). Routing on these is ambiguous; consumers may surface or reject them.

Attributes
NameTypeDescription
shadowed_engine_idsset[str]

Engine ids provided by more than one distribution.

names#methodSource

def ModelRegistry.names() -> list[str]

List all discovered model keys, sorted alphabetically.

Returns
  • list[str]

    List of model keys (for example, ['faster-whisper/large-v3', 'whisper/base']).

keys_by_engine#methodSource

def ModelRegistry.keys_by_engine(engine_id: str) -> list[str]

List all model keys for a specific engine.

The argument is PEP 503-normalized to the canonical routing identity before matching, so a non-canonical form (for example, my_engine) resolves to the same engine as spec / create -- the stored engine_id is already canonical.

Parameters
NameTypeDescription
engine_idstr

Engine identifier (for example, faster-whisper). Any PEP 503 equivalent form (Faster_Whisper, faster.whisper) matches.

Returns
  • list[str]

    List of matching model keys, sorted alphabetically.

Example
>>> registry.keys_by_engine("faster-whisper")
['faster-whisper/', 'faster-whisper/large-v3', 'faster-whisper/small']

spec#methodSource

def ModelRegistry.spec(name: str) -> ModelSpec

Get metadata for a model.

Parameters
NameTypeDescription
namestr

Model key in engine_id/model_name format.

Returns
  • ModelSpec

    ModelSpec containing entry point metadata.

Raises

get_factory#methodSource

def ModelRegistry.get_factory(name: str) -> ASRFactory

Get the factory callable for a model (without instantiating).

This is a raw accessor: the returned callable and anything it constructs have not passed the engine gate or create()'s contract checks. Use create to construct an engine with those checks applied.

Parameters
NameTypeDescription
namestr

Model key in engine_id/model_name format.

Returns
  • ASRFactory

    Callable that creates a StandardASR instance.

Raises

engine_class#methodSource

def ModelRegistry.engine_class(name: str) -> type[StandardASR]

Resolve a model's engine class without instantiating it.

Use this to read class-level metadata (declared_capabilities, declared_metadata, properties, provider_params_type) for discovery, UI generation, and REST endpoints without paying the cost or authentication requirements of constructing the engine. See ModelSpec.engine_class. This is a raw accessor: resolution does not run the engine gate, so the callers that need the gate (create, the per-model server endpoints, compliance) run it on the result themselves.

Parameters
NameTypeDescription
namestr

Model key in engine_id/model_name format.

Returns
Raises

config_schema#methodSource

def ModelRegistry.config_schema(name: str) -> dict[str, Any] | None

Return the JSON Schema of a model's init config, without instantiation.

Resolves the engine class and reads its config_type ClassVar. This is the discovery path for settings UIs: an application can render a configuration form for an engine before constructing it -- construction may require the very values (credentials, default language) the form is meant to collect. Secret fields carry the format: password / writeOnly: true markers in the schema, so a schema-driven UI renders them safely.

Parameters
NameTypeDescription
namestr

Model key in engine_id/model_name format.

Returns
  • dict[str, Any] | None

    The config JSON Schema, or None when the engine does not

  • dict[str, Any] | None

    declare a class-level config_type.

Raises
  • EntrypointValidationError

    Model not found.

  • FactoryLoadError

    Entry point failed to load, the engine class cannot be determined without calling the factory, config_type is not a BaseConfig subclass, or its JSON Schema cannot be generated.

  • EngineContractError

    The engine class does not declare typed properties (AR.1 fail-closed line gate).

  • ProtocolCompatibilityError

    The engine class declares a protocol line this core does not support -- its config semantics are not interpretable, so no schema is projected.

create#methodSource

def ModelRegistry.create(name: str, /, *args: Any, **kwargs: Any) -> StandardASR

Create an ASR engine instance.

This is the primary method for instantiating ASR engines. It loads the factory and invokes it with the provided arguments.

Parameters
NameTypeDescription
namestr

Model key (for example, "faster-whisper/large-v3").

*args
= ()
Any

Positional arguments passed to the factory.

**kwargs
= {}
Any

Keyword arguments passed to the factory (for example, device="cuda").

Returns
  • StandardASR

    StandardASR instance ready for transcription.

Raises
  • EntrypointValidationError

    Model not found.

  • FactoryLoadError

    Entry point failed to load or is not callable.

  • ProtocolCompatibilityError

    The engine declares a protocol line this core does not support (AR.1). The gate runs here -- the one construction path every toolchain consumer shares -- so a mismatched installed plugin fails loudly at creation instead of transcribing with possibly drifted semantics; when the engine class is resolvable without construction, the line is checked BEFORE the factory runs, so a construction-time fault (a missing credential, say) cannot mask the mismatch behind a configuration diagnosis.

  • EngineContractError

    The engine's properties is missing or not a BaseProperties instance (its protocol line cannot be established, which is less knowable than a wrong line, so creation fails closed), the class does not declare typed properties as a ClassVar (the class declaration is the authoritative copy that show, compliance, and the per-model endpoints read without constructing the engine; when the class is resolvable without construction this is refused BEFORE the factory runs, for the same masking reason as the line preflight), the factory returned an instance of a class other than the resolved declared class (those class readers describe the declared class, so the class that runs must be exactly it), or the instance properties do not equal the class-level declaration (a divergent instance would let the class readers and the running engine disagree).

  • ConfigError

    The model needs configuration that was missing or invalid (a required credential, a bad default_language, and so on). A construction-time pydantic ValidationError -- whether from the bare constructor or an engine's own validator -- is wrapped into ConfigError with the offending input scrubbed, so a caller can except ConfigError uniformly. The wrap ASSERTS the type's ownership contract: a factory's construction-time ValidationError means the supplied configuration was rejected (the documented bare-constructor pattern). An engine whose factory lets a NON-config internal ValidationError escape construction mis-asserts that contract -- in-band the two are indistinguishable, so the compliance suite's zero-arg construction check (engine_construction_failed) polices it, not consumer-side guessing. (The reference server maps construction-time config faults by fault ownership: absent required config -> 503, anything else -> scrubbed 500 -- never a caller-blaming 422, since its construction is zero-arg; the CLI maps them to usage exit 2, since its invoker owns the config and the env.) Use config_schema to discover what configuration a model requires.

Example
>>> asr = registry.create("faster-whisper/large-v3", device="cuda")
>>> result = asr.transcribe(audio)

ModelSpec#classSource

class ModelSpec

ModelSpec(
    model_id: str,
    engine_id: str,
    model_name: str,
    entry_point: EntryPoint,
    declared_engine_id: str = '',
)

Metadata for a discovered ASR model entry point.

Note

Instances are created by discover_models(). Use load_factory() to get the callable that constructs the ASR engine.

Attributes
NameTypeDescription
model_idstr

Full routing key (engine_id/model_name), built from the canonical engine id.

engine_idstr

PEP 503 canonical engine identifier and routing identity (for example, faster-whisper). This is the unique engine discriminator.

model_namestr

Model preset name (for example, large-v3), or empty for default.

entry_pointEntryPoint

The underlying importlib.metadata.EntryPoint object.

declared_engine_id
= ''
str

The verbatim engine id as declared in the entry point (for example, faster_whisper, which canonicalizes to the faster-whisper routing engine_id). Kept for diagnostics only; never used for routing. Equals engine_id when already canonical. Note an upper-case form such as Faster_Whisper can never appear here: the declared id is validated before normalization and rejects upper case outright (plugin-entry-points.md naming table), unlike the non-canonical-but-valid _/. separators which are folded.

load_factory#methodSource

def ModelSpec.load_factory() -> ASRFactory

Load the factory callable for this entry point.

Returns
  • ASRFactory

    Callable that creates a StandardASR instance when invoked.

Raises

engine_class#methodSource

def ModelSpec.engine_class() -> type[StandardASR]

Resolve the engine class without instantiating it.

This enables reading class-level ClassVar metadata (declared_capabilities, declared_metadata, properties, provider_params_type) without calling the factory -- which the standard requires to be possible "without instantiation or authentication". Instantiating a cloud engine would force credential resolution and a heavy __init__, turning an unauthenticated metadata read into a denial-of-service vector.

The entry-point target is loaded (its module is imported) but never called. Resolution rules:

  • If the target is itself a class, it is returned directly.
  • If the target is a function (the common factory pattern), only its return annotation is resolved and, if it names a concrete class, that class is returned. The resolver deliberately does not evaluate the whole annotation namespace (for example, via typing.get_type_hints): an unrelated parameter carrying an unresolvable forward reference must not turn a static metadata read into a FactoryLoadError (metadata must stay readable without instantiation).
Returns
  • type[StandardASR]

    The engine class declaring the static metadata.

Raises
  • FactoryLoadError

    The target failed to load, or the class cannot be determined without calling the factory (for example, a factory with no concrete return annotation). Callers SHOULD fall back to instantiation only when they explicitly accept that cost.

RuntimeParams#classSource

class RuntimeParams(BaseModel)

Closed per-request parameter container.

Raises
  • ValueError

    If field validation fails.

Attributes
NameTypeDescription
language
= None
str | None

Per-request language (BCP-47 or "auto") overriding the engine default. Gated by <mode>.language.runtime_override.

candidate_languages
= None
list[str] | None

Candidate languages, meaningful only in auto mode. Gated by <mode>.language.candidate_languages.

word_timestamps
= None
WordTimestampGranularity | None

Requested word-timestamp granularity. Gated by <mode>.word_timestamps.

diarization
= None
DiarizationRequest | None

Speaker-diarization request marker; presence = enable (None = not requested). Gated by <mode>.diarization.

prompt
= None
str | None

Free-text guidance prompt. Gated by <mode>.guidance.prompt.

phrase_hints
= None
list[str] | None

Phrase-hint boost terms. Gated by <mode>.guidance.phrase_hints.

on_unsupported
= 'fail'
Literal['fail', 'degrade_to_prompt']

Guidance degradation policy. "fail" (default) keeps the fail-closed contract; "degrade_to_prompt" opts into the one-way rich->prompt fallback (a diagnostic is emitted on degrade).

provider_params
= None
ProviderParams | None

Engine-specific typed parameters, or None.

Segment#classSource

class Segment(BaseModel)

Segment-level detail, shared between batch results and streaming events.

Note

start / end follow the same time frame as Word: non-negative finite float seconds with origin at the first submitted sample (t=0), end >= start (zero-duration allowed), and NaN / Inf rejected -- OR None when the engine measured no such time. None is data, not absence-of-field: the values themselves are the single source of timing truth (there is no side-channel marker), and the legal shapes are pinned by timestamp_status. An end without a start is unrepresentable (rejected at construction): no engine measures where speech stopped without knowing it started.

Ordering: within one channel, MEASURED segments are time-ordered, and the top-level TranscriptionResult.segments with a start are sorted by (start, channel, speaker) (cross-channel spans may overlap; speaker is the final tie-break for equal-(start, channel) overlapping segments, None sorting before any real label). A start=None segment has no time position: the producer keeps the list in READING order instead (list order is the reading order, and TranscriptionResult.text joins segment texts in list order), so a single unmeasured segment never scrambles -- or forces fabricated positions into -- an otherwise real timeline.

Raises
  • ValueError

    If field validation fails (incl. NaN/Inf, a negative time, end < start, or end without start).

Attributes
NameTypeDescription
startfloat | None
ge=0.0

Segment start time in seconds (origin = first submitted sample; non-negative, finite), or None when unmeasured.

endfloat | None
ge=0.0

Segment end time in seconds (non-negative, finite, >= start), or None when unmeasured. Requires start.

textstr

Segment transcript text.

words
= None
list[Word] | None

Optional word-level details for this segment.

speaker
= None
str | None

Optional speaker label (authoritative diarization shape).

channel
= None
int | None
ge=0

Optional channel index for provenance (>= 0).

avg_logprob
= None
float | None

Optional average log-probability.

no_speech_prob
= None
float | None

Optional no-speech probability.

temperature
= None
float | None

Optional decoding temperature.

compression_ratio
= None
float | None

Optional compression-ratio metric.

extra
= dict()
WireExtra

Engine-specific extra data (engine-owned; the standard reserves no keys here).

timestamp_statusLiteral['measured', 'start_only', 'unavailable']

The segment's timing shape, derived from start/end.

Derived, not stored: the nullable values are the single source of truth, so the status can never disagree with them (the previous design stored fabricated 0.0 spans guarded by a mutable side-channel marker -- two truths that could, and did, diverge).

StandardASR#classSource

class StandardASR(Protocol)

Structural protocol for a Standard ASR engine.

Any object exposing these members is a compliant engine, regardless of how it is implemented. The protocol describes the full public surface every engine exposes -- batch (transcribe / transcribe_async) and the streaming entry point (start_transcription). start_transcription is always present; streaming support itself is optional, so a batch-only engine raises UnsupportedFeatureError from it. Because the surface is complete, callers (for example, the server) can type an engine as StandardASR and call the streaming entry point without a cast.

Gate ownership: the ProtocolCompatibilityError raises documented on the members below are kept by the standard's own checks -- they are not a duty every implementation's method body carries. ModelRegistry.create() refuses an engine on an unsupported protocol line before callers can reach these members, and EngineBase checks again at each entry as a second net; an implementation built directly on this protocol does not have to repeat those checks. A host that obtains an engine without the registry (direct construction, a custom loader, injection) MUST run require_engine_protocol on the object once before using it (AR.1).

Attributes
NameTypeDescription
propertiesBaseProperties
declared_capabilitiesDeclaredCapabilities
declared_metadataDeclaredEngineMetadata
configBaseConfig[str]

The engine's runtime configuration.

Declared as a READ-ONLY property, not a mutable attribute: a mutable protocol member is invariant under strict typing, so a real engine annotating its own subtype (config: WhisperConfig) would not be structurally assignable to StandardASR without a cast -- defeating the protocol's own no-cast promise above. Read-only makes the member covariant (any engine's narrower config satisfies it), and matches intent: config is constructor-injected; callers never reassign it through the protocol. Implementations satisfy this with a plain (even mutable) instance attribute -- no @property required.

transcribe#methodSource

def StandardASR.transcribe(
    audio: AudioInputLike,
    params: RuntimeParams | None = None,
) -> TranscriptionResult

Transcribe a complete audio input.

Parameters
NameTypeDescription
audioAudioInputLike

The audio to transcribe (any AudioInput variant or a coercible bare value).

params
= None
RuntimeParams | None

Per-request runtime parameters.

Returns
Raises
  • ProtocolCompatibilityError

    If the engine's declared protocol line is not supported by this core (checked before any work).

  • ConfigError

    On an invalid language configuration VALUE (default_language malformed or not selectable) -- fixable by whoever supplies the config.

  • EngineContractError

    On an engine-declaration defect -- a declared language axis with no default_language (IC.6), or a malformed declared selectable/detectable tag.

  • IncompatibleAudioInputError

    If no conversion path exists.

  • UnsafeAudioUrlError

    If an AudioUrl fails the SSRF policy.

  • AudioProcessingError

    On a decode / size / missing-sample-rate failure in the conversion pipeline.

  • UnsupportedFeatureError

    In strict mode, on an unsupported parameter, a non-selectable language, or a valid-but-unreachable candidate list (non-detectable candidate / over-max).

  • InvalidProviderParamError

    On wrong provider_params (swap-safety).

  • ValueError

    On a malformed or "auto" candidate-language entry (a caller code bug; raises independent of strict/best_effort).

  • ArtifactUnavailableError

    When required inference artifacts cannot support recognition under the current policy.

  • ArtifactAcquisitionError

    When an allowed implicit acquisition attempt fails before recognition.

  • TranscriptionError

    On an engine-execution failure.

transcribe_async#async methodSource

async def StandardASR.transcribe_async(
    audio: AudioInputLike,
    params: RuntimeParams | None = None,
) -> TranscriptionResult

Asynchronously transcribe a complete audio input.

Parameters
NameTypeDescription
audioAudioInputLike

The audio to transcribe (any AudioInput variant or a coercible bare value).

params
= None
RuntimeParams | None

Per-request runtime parameters.

Returns
Raises

start_transcription#methodSource

def StandardASR.start_transcription(
    *,
    audio_format: AudioFormat | None = None,
    params: RuntimeParams | None = None,
    audio: AudioInputLike | None = None,
    deadlines: StreamDeadlines | None = None,
) -> TranscriptionSession

Open a streaming transcription session.

Always present on a compliant engine, but streaming itself is optional: a batch-only engine raises UnsupportedFeatureError here. Callers that need streaming should gate on supports("streaming_input") / supports("streaming_output") (or be ready to handle the unsupported-streaming error).

Parameters
NameTypeDescription
audio_format
= None
AudioFormat | None

Wire format for incremental PCM frames.

params
= None
RuntimeParams | None

Per-request runtime parameters.

audio
= None
AudioInputLike | None

A complete audio input for whole-input streaming output.

deadlines
= None
StreamDeadlines | None

Application overrides for the session's termination deadlines; explicitly set fields win over the engine's construction-time choices.

Returns
Raises
  • ProtocolCompatibilityError

    If the engine's declared protocol line is not supported by this core (checked before any work).

  • ValueError

    If both audio_format and audio are provided, or on a malformed/auto candidate-language entry (a caller code bug; always raises, independent of strict/best_effort).

  • ConfigError

    On an invalid language configuration VALUE (default_language malformed or not selectable).

  • EngineContractError

    On an engine-declaration defect -- a declared language axis with no default_language (IC.6), or a malformed declared selectable/detectable tag.

  • UnsupportedFeatureError

    When streaming (or the requested streaming input/output axis) is unsupported, when the wire format is unreachable, or, in strict mode, on an unsupported parameter or a valid-but-unreachable candidate list (non-detectable / over-max).

  • IncompatibleAudioInputError

    If no conversion path exists for a whole-input streaming audio value.

  • UnsafeAudioUrlError

    If a whole-input AudioUrl fails the SSRF policy.

  • AudioProcessingError

    On a decode / size / missing-sample-rate failure for a whole-input audio value.

  • InvalidProviderParamError

    On wrong provider_params (swap-safety).

  • ArtifactUnavailableError

    When required inference artifacts cannot support session establishment under the current policy.

  • ArtifactAcquisitionError

    When an allowed implicit acquisition attempt fails during session establishment.

  • TranscriptionError

    When a pydantic ValidationError escapes the engine's session-construction hook (an invalid model construction is an engine fault, never a request error).

supports#methodSource

def StandardASR.supports(dot_path: str) -> bool

Return whether the capability at dot_path is supported.

Parameters
NameTypeDescription
dot_pathstr

A capability dot-path.

Returns
  • bool

    True if supported.

Raises
  • ProtocolCompatibilityError

    If the engine declares a protocol line the core does not support -- a capability answer is a semantic interpretation of the declaration and must not be given for a mismatched line.

artifact_status#methodSource

def StandardASR.artifact_status(
    context: ArtifactContext | None = None,
) -> ArtifactReport

Inspect inference-artifact readiness without acquiring anything.

The report describes the moment it was taken -- ready today does not mean ready tomorrow. A ready artifact can become missing with no action from the application (a cleared cache, an asset the OS cleaned up, a disconnected drive). Check again by calling again: while nothing external changes, repeated calls return the same report (AR.2), so a changed report means something outside the application really changed.

Parameters
NameTypeDescription
context
= None
ArtifactContext | None

Optional request context. None resolves an engine mode and uses default runtime parameters.

Returns
  • ArtifactReport

    A point-in-time artifact report for the resolved context.

Raises

acquire_artifacts#methodSource

def StandardASR.acquire_artifacts(
    context: ArtifactContext | None = None,
    *,
    refresh: bool = False,
    progress: ArtifactProgressCallback | None = None,
) -> ArtifactReport

Acquire inference artifacts explicitly and return fresh status.

Parameters
NameTypeDescription
context
= None
ArtifactContext | None

Optional request context.

refresh
= False
bool

Whether to re-resolve unblocked mutable source references.

progress
= None
ArtifactProgressCallback | None

Optional synchronous progress observer.

Returns
Raises

recommended_wire_format#methodSource

def StandardASR.recommended_wire_format() -> AudioFormat | None

Return a wire AudioFormat this engine accepts for streaming.

Part of the protocol because it is the documented first step of the streaming journey (README / quickstart / streaming guide all start with it) and the toolchain's sync-bridge runner and gating probes rely on it -- a member every caller is taught to invoke MUST be part of the contract, or a structural engine (and every StandardASR-typed variable) breaks on the standard's own 80% path. The value is purely derivable from the engine's static Properties (see EngineBase.recommended_wire_format for the derivation EngineBase provides for free) -- deliberately capability-blind: whether a bare-frame session can be OPENED is the streaming_input capability gate's job inside start_transcription, so a batch-only or output-only engine still derives a format here (callers gate on supports("streaming_input") first, per the streaming guide).

Returns
  • AudioFormat | None

    A wire format the engine's session-establishment guard accepts, or

  • AudioFormat | None

    None when no bare-frame streaming format can be recommended.

Raises
  • ProtocolCompatibilityError

    If the engine declares a protocol line the core does not support -- the recommendation is derived from the declaration and must not be interpreted for a mismatched line.

StandardASRError#classSource

class StandardASRError(Exception)

Base class for every domain error the Standard ASR runtime raises.

It does not cover data-model construction, which is pydantic's: a malformed field on RuntimeParams, on a result model, or on a config raises pydantic.ValidationError. That is a ValueError, not a StandardASRError. Plain caller misuse likewise raises a built-in: ValueError for a bad value (two mutually exclusive arguments), TypeError for a wrong type (an unsupported input type).

except (StandardASRError, ValueError) catches the domain errors and the value mistakes. A TypeError stays outside both on purpose -- a wrong input type is a code bug to fix, not a state to handle.

StreamClosedError#classSource

class StreamClosedError(StandardASRError)

Raised when audio is delivered to a streaming session that is closed.

Strictly a lifecycle-close breach: the input side is over, so the audio can no longer be consumed. Covers send_audio after end_audio() and send_audio after the session already delivered a terminal event (the audio queue has no consumer anymore). It does NOT cover usage mistakes against a still-live session (mixing feed with manual input, calling feed twice, or iterating the event stream twice) -- those raise InvalidSessionUseError, so an application can tell "the session ended" apart from "my code drove the session incorrectly".

StreamDeadlines#classSource

class StreamDeadlines(BaseModel)

Application-side overrides for a session's termination deadlines.

Pass to StandardASR.start_transcription(deadlines=...). Only fields you explicitly set are applied; unset fields keep whatever the engine (or the standard default) chose. Precedence: application explicit > engine construction choice > standard default.

The fields mirror the TranscriptionSession deadline parameters -- see there for full semantics. Each accepts None to explicitly disable that deadline.

Attributes
NameTypeDescription
done_timeout
= DEFAULT_DONE_TIMEOUT
float | None
gt=0.0

The pipeline-inactivity hang backstop, reset by events AND by audio consumption.

max_idle
= DEFAULT_MAX_IDLE
float | None
gt=0.0

The opt-in content-stall detector.

max_session_seconds
= DEFAULT_MAX_SESSION_SECONDS
float | None
gt=0.0

The opt-in absolute wall-clock cap.

StructuredError#classSource

class StructuredError(StandardASRError)

StructuredError(
    message: str = '',
    *,
    param: str | None = None,
    hint: str | None = None,
    details: list[dict[str, Any]] | None = None,
)

Base for errors that carry machine-readable context beside the message.

Gives the error half of the contract the same "don't make me parse the message" property the diagnostics have: an application can read .param (the offending field/parameter), .hint (actionable guidance), and .details (structured context -- for example, the sanitized pydantic error entries behind a wrapped config failure) without scraping str(exc). Every context field is optional and keyword-only, so Error("message") keeps working while Error("message", param="base_url", hint="use https") is now valid too -- removing the asymmetry where only some exceptions accepted structured fields (spec: explicit > implicit; structured over stringly typed).

Parameters
NameTypeDescription
message
= ''
str

Human-readable description of the error.

param
= None
str | None

The offending field / parameter name, if applicable.

hint
= None
str | None

Actionable guidance for resolving the error, if any.

details
= None
list[dict[str, Any]] | None

Optional machine-readable context (for example, the sanitized validation-error entries from a wrapped pydantic ValidationError).

SubtitleRenderingError#classSource

class SubtitleRenderingError(StandardASRError, ValueError)

SubtitleRenderingError(
    message: str = '',
    *,
    unrenderable: int | None = None,
    total: int | None = None,
)

Raised by to_srt / to_vtt when segments cannot render as visible cues.

A subtitle cue is an interval claim -- "this text occurs at this time" -- and it must survive the output's millisecond grid to be seen at all. A segment is therefore UNRENDERABLE in either of two ways: it lacks a measured span (Segment.timestamp_status is "start_only" or "unavailable"), or its measured span quantizes to zero milliseconds on the output grid (end and start format to the same timestamp -- players silently drop such cues, so emitting one silently hides the text while the render call reports success). Neither dropping the text nor fabricating timing is the renderer's to choose silently: under the default policy (on_unrenderable="error") it raises this error, and the caller picks the loss explicitly ("omit" drops the unrenderable segments' text from the timed cues; "collapse" renders one whole-text cue with no real timeline). Mixes in ValueError: the caller can fix the call -- choose a policy, or supply renderable segments.

Parameters
NameTypeDescription
message
= ''
str

Human-readable description of the rejection.

unrenderable
= None
int | None

How many segments cannot render as visible cues, if known.

total
= None
int | None

How many segments the result carries, if known.

SyncSession#classSource

class SyncSession

SyncSession(
    session: TranscriptionSession,
    *,
    submit_timeout: float | None = 30.0,
)

Synchronous bridge over an async TranscriptionSession.

Runs a single background event loop in a dedicated thread (owned by this object and torn down on close), so applications can drive an async engine synchronously and authors only ever write async code.

Lifecycle submits (__enter__ / input calls / __exit__) carry a timeout: a hanging engine _open / _close can never deadlock the calling thread, and on every cooperative path the background loop + thread are torn down even on timeout (from an external thread, no deadlock). A truly blocking, non-awaiting engine can survive the join and leave the daemon thread alive with the loop unclosed -- see _shutdown; the sync-bridge compliance check reports that state as a thread leak.

Wrap an async session.

Parameters
NameTypeDescription
sessionTranscriptionSession

The async session to drive.

submit_timeout
= 30.0
float | None

Seconds to wait for a lifecycle submit (enter / feed / send / end / exit) before raising TimeoutError and tearing the loop down. None waits forever (not recommended).

__enter__#methodSource

def SyncSession.__enter__() -> SyncSession

Enter the async session's context.

Exception-safe: a context manager whose __enter__ raises never receives __exit__, so a failed enter MUST tear down the owned loop + thread started in __init__ itself -- otherwise an engine whose _open raises (bad credentials, unreachable host) would leak the bridge's background thread (no leak). The timeout path is already torn down inside _submit; the entered guard also covers a non-timeout raise (_open raising a regular exception).

Returns
Raises
  • TimeoutError

    If the engine's _open runs past submit_timeout, or raises TimeoutError itself, which propagates unchanged. The bridge is torn down in both cases.

__exit__#methodSource

def SyncSession.__exit__(*exc: object) -> None

Exit the async session's context and tear down the owned loop.

The bridge never replaces an exception that is already propagating with an error of its own:

  • If an earlier call (a lifecycle call or the event pump) timed out, that call already tore the loop down. This method returns at once, and whatever the body raised propagates.
  • If the with body raised and the session's __aexit__ (task cancellation, then the engine's _close) runs past submit_timeout, this method logs the timeout at warning level and suppresses it, so the body's exception propagates. Raising the timeout here would replace that exception with a report that blames a hung engine for a failure the application caused.

Any error that __aexit__ raises itself, a TimeoutError included, propagates. If the body raised, its exception is that error's __context__. The owned loop and thread are torn down on every cooperative path; see _shutdown for an engine that blocks without awaiting.

Parameters
NameTypeDescription
*exc
= ()
object

The (exc_type, exc_value, traceback) triple that the with statement passes. It goes to the async session unchanged.

Raises
  • TimeoutError

    If __aexit__ runs past submit_timeout and the with body raised nothing. The timeout is then the only report that the engine hung.

  • Exception

    Any error that __aexit__ raises itself, a TimeoutError included, unchanged.

feed#methodSource

def SyncSession.feed(source: Iterable[bytes] | bytes | bytearray) -> None

Feed audio from a managed source.

Parameters
NameTypeDescription
sourceIterable[bytes] | bytes | bytearray

A sync iterable of byte chunks, or a single bytes / bytearray chunk.

Raises
  • TypeError

    If source is a str (forwarded from the async session: a str is byte-chunks-shaped only by accident -- a whole file goes to start_transcription(audio=...)).

  • InvalidSessionUseError

    If manual input or a prior feed was already used (forwarded from the async session).

  • StreamClosedError

    If the bridge was already torn down (its with block exited, or a prior lifecycle call timed out).

  • TimeoutError

    If the call does not complete within the submit timeout; the bridge is torn down first (no-hang contract).

send_audio#methodSource

def SyncSession.send_audio(chunk: bytes) -> None

Manually send one audio chunk.

Parameters
NameTypeDescription
chunkbytes

The audio chunk.

Raises
  • InvalidSessionUseError

    If feed was already used (forwarded from the async session: mixing input modes).

  • StreamClosedError

    If the input was ended or the session already delivered a terminal event (forwarded from the async session), or the bridge was already torn down (its with block exited, or a prior lifecycle call timed out).

  • TimeoutError

    If the call does not complete within the submit timeout; the bridge is torn down first (no-hang contract).

end_audio#methodSource

def SyncSession.end_audio() -> None

Mark the end of manual audio input.

Raises
  • InvalidSessionUseError

    If feed was already used (forwarded from the async session: mixing input modes).

  • StreamClosedError

    If the bridge was already torn down (its with block exited, or a prior lifecycle call timed out).

  • TimeoutError

    If the call does not complete within the submit timeout; the bridge is torn down first (no-hang contract).

__iter__#methodSource

def SyncSession.__iter__() -> Iterator[TranscriptionEvent]

Iterate events synchronously.

Event waits are unbounded by design: a live, fed session may legitimately go arbitrarily long between events (user silence), so the pump must never manufacture a timeout for it -- a stuck pipeline already surfaces as a terminal event from the async side's own deadlines. The pump waits in short slices purely to detect the two failures those in-loop deadlines cannot report: the owned event-loop thread dying, and the loop being frozen by an engine running blocking (non-async) code. Brief blocking stalls are tolerated (several consecutive unresponsive probes are required), so only a persistently frozen loop tears the bridge down.

Yields
Raises
  • StreamClosedError

    If the bridge was already torn down (its with block exited, or a prior lifecycle call timed out), or if the session was never entered (no event stream exists to pump).

  • TimeoutError

    If the owned event-loop thread died or stayed unresponsive, so no further event (or in-loop deadline) can ever be delivered.

result#methodSource

def SyncSession.result() -> TranscriptionResult

Reduce the session so far into a transcription result.

SERIALIZED WITH THE PRODUCER while the bridge is live: the reduction walks reducer state the producer task mutates (a supersede pops segments mid-walk), so it is submitted to the owned loop like every other bridge member -- asyncio's run-to-completion between awaits is the mutual exclusion, and the wrapper coroutine never awaits around the call. Running it on the caller's thread instead read that state concurrently: a rendering loop (for ev in sync: render(sync. result())) crashed with a spurious KeyError or returned a torn result mixing pre- and post-supersede segments. After teardown (__exit__, or a timed-out lifecycle call) no producer runs anymore, so the direct call is safe -- and keeps the result-after-the-with-block pattern working.

Returns
Raises
  • TimeoutError

    If the live loop cannot run the reduction within the submit timeout (frozen by blocking engine code); the bridge is torn down first (no-hang contract).

diagnostics#methodSource

def SyncSession.diagnostics() -> list[Diagnostic]

Return the session's standard-layer and lifecycle diagnostics.

Mirrors TranscriptionSession.diagnostics so a synchronously- driven session exposes the same parameter-gating / language-resolution / lifecycle-suppression diagnostics as the async one. Without this the sync bridge would silently drop a first-class, compliance-checked part of the session surface (the sync bridge is a faithful mirror of the async session). Serialized with the producer exactly like result (the snapshot copies guard state the producer task appends to); direct once torn down.

Returns
Raises
  • TimeoutError

    If the live loop cannot run the snapshot within the submit timeout (frozen by blocking engine code); the bridge is torn down first (no-hang contract).

is_loop_alive#methodSource

def SyncSession.is_loop_alive() -> bool

Whether the owned background event-loop thread is still running.

The bridge starts a dedicated event-loop thread in __init__ and MUST tear it down on close (__exit__) or on a failed __enter__ (from an external thread). After a clean lifecycle this returns False; a True here once the session is closed is a leaked loop thread. Exposed so the sync-bridge compliance check can assert on this bridge's own thread rather than diffing the whole process thread set (which would mis-flag a dependency's benign daemon thread as a leak).

Returns
  • bool

    True if the owned loop thread is alive.

TranscriptionError#classSource

class TranscriptionError(StructuredError)

TranscriptionError(
    message: str = '',
    *,
    param: str | None = None,
    hint: str | None = None,
    details: list[dict[str, Any]] | None = None,
    retriable: bool | None = None,
)

Raised when an engine fails during batch transcription (the cardinal sin guard).

This is the portable batch error contract: when an engine's model inference, network call, or SDK fails inside _transcribe, the failure MUST surface as a TranscriptionError (with the original exception preserved as __cause__ via raise ... from) so an application can catch one type across every engine instead of each engine's native exception (RuntimeError, an SDK error, requests.HTTPError, and so on). It is the batch counterpart of the streaming error event's engine_error code. It denotes an engine/runtime fault, not a caller mistake (those raise ConfigError / UnsupportedFeatureError / InvalidProviderParamError / AudioProcessingError), so the server maps it to a generic 5xx.

Carries the StructuredError fields plus .retriable: when an engine knows a failure is transient (a 503 / timeout / rate-limit) it MAY pass retriable=True so an application can decide whether to retry. None (the default) means "unknown" -- the safe reading is do not assume it is safe to retry.

Parameters
NameTypeDescription
message
= ''
str

Human-readable description of the failure.

param
= None
str | None

The offending field / parameter name, if applicable.

hint
= None
str | None

Actionable guidance, if any.

details
= None
list[dict[str, Any]] | None

Optional machine-readable context.

retriable
= None
bool | None

True / False if the engine knows whether a retry may succeed; None when unknown.

TranscriptionEvent#classSource

class TranscriptionEvent(BaseModel)

A single streaming transcription event.

Attributes
NameTypeDescription
typeEventType

The event type.

segment_id
= None
str | None

Stable id of the segment this event concerns.

text
= None
str | None

The segment's complete current text (cumulative/replace).

stable_text
= None
str | None

For partial and final, the prefix of text that does not change for as long as the segment lives. A later partial or plain final of the segment keeps and extends it, a supersede that retires the segment withdraws it together with the segment, and a closed final may reformat it once. A partial defaults to "" (nothing is stable). A final always carries the whole text, which is also its default. The engine is responsible for ending it between two user-perceived characters. Construction checks only that text starts with it; validate_stable_text checks part of the boundary rule and lists the cuts it misses. Every other event type MUST carry None, which construction does not check.

finality
= 'final'
Literal['final', 'closed']

For final events, "final" or "closed" (a closed final is the terminal restatement and may reformat stable text once).

words
= None
list[Word] | None

Optional word-level detail (shares the batch Word model).

speaker
= None
str | None

Segment-level speaker label, with the same inheritance rule as Segment.speaker (a non-None words[i].speaker overrides it at word level) and the same label-validity rule. Once a segment has stable text, its accepted speaker is locked across events by the lifecycle guard, not at construction.

start
= None
float | None
ge=0.0

Segment start time in seconds (origin = first session sample).

end
= None
float | None
ge=0.0

Segment end time in seconds.

audio_processed_until
= None
float | None
ge=0.0

Monotonic audio-time cursor in seconds.

old_ids
= list()
list[str]

For supersede, the retired segment ids.

new_ids
= list()
list[str]

For supersede, the replacement segment ids.

code
= None
str | None

For error, the error code.

recoverable
= None
bool | None

For error, whether the session may continue.

retriable_after
= None
float | None
ge=0.0

For error, suggested retry delay in seconds.

reconnect
= None
bool | None

For progress, whether this marks a reconnect.

gap_start
= None
float | None
ge=0.0

For a reconnect progress, the gap start time.

gap_end
= None
float | None
ge=0.0

For a reconnect progress, the gap end time.

detected_language
= None
str | None

The engine-detected language (BCP-47, never auto), validated like TranscriptionResult.detected_language; sticky per session (the last non-None value wins), and the reconnect-continuity carrier.

extra
= dict()
WireExtra

Engine-specific extra data.

is_contentbool

Whether this event advances transcription content.

is_terminalbool

Whether this event ends the session.

partial#class methodSource

@classmethod
def TranscriptionEvent.partial(
    segment_id: str,
    text: str,
    **kw: Any,
) -> TranscriptionEvent

Build a partial event.

Parameters
NameTypeDescription
segment_idstr

The segment id.

textstr

The segment's complete current text.

**kw
= {}
Any

Additional event fields.

Returns

final#class methodSource

@classmethod
def TranscriptionEvent.final(
    segment_id: str,
    text: str,
    **kw: Any,
) -> TranscriptionEvent

Build a final event.

Parameters
NameTypeDescription
segment_idstr

The segment id.

textstr

The segment's final text.

**kw
= {}
Any

Additional event fields. Leave stable_text out: on a final it is always the whole text.

Returns

closed#class methodSource

@classmethod
def TranscriptionEvent.closed(
    segment_id: str,
    text: str,
    **kw: Any,
) -> TranscriptionEvent

Build a closed finality event (a final with finality=closed).

Parameters
NameTypeDescription
segment_idstr

The segment id.

textstr

The segment's possibly post-processed text.

**kw
= {}
Any

Additional event fields.

Returns

supersede#class methodSource

@classmethod
def TranscriptionEvent.supersede(
    old_ids: list[str],
    new_ids: list[str],
    **kw: Any,
) -> TranscriptionEvent

Build a supersede event replacing old segments with new ones.

Lineage is set-to-set: old_ids/new_ids express the cardinality of the re-segmentation (which ids retire, which appear) but not a per-old->per-new mapping. On a merge+split (many->many) a UI cannot tell which specific old segment a given new segment descends from. This is a documented limitation of the current generation; the spec does not require a pairwise mapping. Because nothing maps old text onto new text, a supersede withdraws the retired segments as a whole, stable text included, and the replacement segments start with none.

Parameters
NameTypeDescription
old_idslist[str]

The retired segment ids, in reading (time) order.

new_idslist[str]

The replacement segment ids, in reading (time) order (must be disjoint from old_ids).

**kw
= {}
Any

Additional event fields.

Returns
Raises
  • ValueError

    If old_ids is empty (a supersede MUST retire at least one segment), if old_ids and new_ids intersect, or if either list repeats a segment id.

progress#class methodSource

@classmethod
def TranscriptionEvent.progress(**kw: Any) -> TranscriptionEvent

Build a progress event (heartbeat / cursor / reconnect notice).

Parameters
NameTypeDescription
**kw
= {}
Any

Event fields (for example, audio_processed_until, reconnect).

Returns

done#class methodSource

@classmethod
def TranscriptionEvent.done(**kw: Any) -> TranscriptionEvent

Build a terminal done event.

Parameters
NameTypeDescription
**kw
= {}
Any

Additional event fields.

Returns

make_error#class methodSource

@classmethod
def TranscriptionEvent.make_error(
    code: str,
    *,
    recoverable: bool = False,
    **kw: Any,
) -> TranscriptionEvent

Build an error event.

Parameters
NameTypeDescription
codestr

The error code.

recoverable
= False
bool

Whether the session may continue.

**kw
= {}
Any

Additional event fields.

Returns

TranscriptionResult#classSource

class TranscriptionResult(BaseModel)

The constant-shape result returned by transcribe and stream reduction.

The top-level text / segments / words are always the complete, channel- and speaker-agnostic transcription. For multi-channel audio they are the time-merge of all channels (never channel-0-only), so ignoring channels is always safe and lossless.

Raises
  • ValueError

    If field validation fails (incl. NaN/Inf, a negative duration, or a malformed detected_language).

Attributes
NameTypeDescription
textstr

Full transcript (required).

detected_language
= None
str | None

Detected language as a well-formed BCP-47 tag in auto mode; None when not applicable.

language_confidence
= None
float | None
ge=0.0, le=1.0

Detection confidence in [0, 1].

duration
= None
float | None
ge=0.0

Audio duration in seconds, if known (non-negative, finite).

segments
= None
list[Segment] | None

Segments across all channels, if available. Segments WITH a start SHOULD be sorted by (start, channel, speaker) (monotonic within a channel; speaker is the final tie-break, None sorting first); a start=None segment has no time position, so the list stays in READING order instead (list order is the reading order; text joins segment texts in list order). The ordering is an engine obligation, neither enforced at construction nor checked by the compliance suite (the streaming reducer keeps arrival order whenever any retained segment lacks a start). The SRT/VTT renderers' defensive re-sort of measured cues is the only standard-layer safety net.

words
= None
list[Word] | None

Flattened word-level details, if available.

channels
= None
list[ChannelResult] | None

Per-channel results when channel separation was performed. Each channel index MUST be unique (one entry per channel), enforced at construction.

diagnostics
= lambda: cast('list[Diagnostic]', [])()
list[Diagnostic]

Conversion / best_effort / degradation diagnostics.

extra
= dict()
WireExtra

Engine-specific / experimental data (incl. provider formats).

TranscriptionSession#classSource

class TranscriptionSession(ABC)

TranscriptionSession(
    *,
    done_timeout: float | None = DEFAULT_DONE_TIMEOUT,
    max_idle: float | None = DEFAULT_MAX_IDLE,
    max_session_seconds: float | None = DEFAULT_MAX_SESSION_SECONDS,
    event_buffer_capacity: int = DEFAULT_EVENT_BUFFER_CAPACITY,
    audio_queue_maxsize: int = DEFAULT_AUDIO_QUEUE_MAXSIZE,
    audio_history_maxlen: int = DEFAULT_AUDIO_HISTORY_MAXLEN,
    strict_lifecycle: bool = False,
    max_guard_diagnostics: int = DEFAULT_MAX_GUARD_DIAGNOSTICS,
)

Async-first, full-duplex streaming session base.

Authors implement _produce (and optionally _open / _close), reading fed audio via audio_chunks and yielding TranscriptionEvent objects. The base manages input ownership, bounded backpressure, lifecycle enforcement, a bounded rolling audio buffer

  • reconnect scaffolding, termination deadlines, and result reduction.

Reconnect contract -- base vs engine:

  • The base owns a bounded rolling audio buffer (the most recent fed chunks) and exposes replay_buffer, the source replayable classification, and note_reconnect. It cannot detect a reconnect itself (it owns no network connection).
  • The engine detects the disconnect, re-establishes the connection, replays replay_buffer audio, keeps segment_id / timestamps / detected language / speaker-label mapping continuous, then calls note_reconnect. For speaker labels the safe default is mint-fresh: the engine MUST NOT reuse a pre-reconnect label for a post-reconnect cluster without identity evidence (blind clustering restarts from zero after a reconnect) -- it MUST mint fresh labels (speaker_2, speaker_3, and so on) and emit a speaker_labels_reset diagnostic via emit_diagnostic -- the fidelity-warning counterpart of content_lost, which is an error event -- because over-counting speakers is the safe direction while silently merging two people under one label is not. The base then emits the progress(reconnect=True, gap_start, gap_end) event and, iff the engine passed content_lost=True (its own determination that the reconnect + replay could not cover the gap), a trailing error(code="content_lost", recoverable=True) fidelity warning.

Initialize the session.

Parameters
NameTypeDescription
done_timeout
= DEFAULT_DONE_TIMEOUT
float | None

Seconds of total pipeline inactivity -- no event arriving AND no fed audio consumed via audio_chunks -- before synthesizing a done_timeout error. A hang backstop, not engine-liveness detection: a silently listening engine stays alive while it keeps consuming audio. After end_audio() it bounds the engine's flush-and-done window. None disables (explicit opt-out of the backstop).

max_idle
= DEFAULT_MAX_IDLE
float | None

Seconds without a content event (partial / final / supersede) before force-terminating with a stream_stalled error. NOT reset by progress heartbeats or audio consumption, so it detects an engine that consumes (or chats) without ever producing content. None (the default) disables: silence is a normal state for a live session.

max_session_seconds
= DEFAULT_MAX_SESSION_SECONDS
float | None

Absolute wall-clock cap; None disables.

event_buffer_capacity
= DEFAULT_EVENT_BUFFER_CAPACITY
int

Pending-event budget shared by every event kind -- drop-proof final / supersede / error / done slots consume it too, and a not-yet-delivered segment can hold two slots at once (its declaration partial plus its final). A new-segment partial or progress heartbeat arriving once the budget is spent overflows, which the session reports as a terminal backpressure error.

audio_queue_maxsize
= DEFAULT_AUDIO_QUEUE_MAXSIZE
int

Max pending audio chunks; bounds feed / send_audio so a slow engine exerts real backpressure.

audio_history_maxlen
= DEFAULT_AUDIO_HISTORY_MAXLEN
int

Capacity of the bounded rolling audio buffer used to replay recent audio after a reconnect.

strict_lifecycle
= False
bool

If True, the first violation the lifecycle guard finds ends the session: an illegal transition, a stable-text or speaker rule violation, a decreasing audio cursor, stable text abandoned at done, or a stream that goes beyond the engine's effective streaming capabilities. The session then delivers a terminal error event with code engine_error in place of the offending event, and iterating the session does not raise. If False, the guard suppresses or repairs the offending event and records a diagnostic. For stable text abandoned at done, it records the diagnostic and delivers done. For a stream beyond the effective capabilities, it delivers the event unchanged and records one diagnostic per kind. Only a session given the engine's capabilities checks events against them; bind_session_capabilities says which sessions get them.

max_guard_diagnostics
= DEFAULT_MAX_GUARD_DIAGNOSTICS
int

Cap on the bounded lifecycle-suppression diagnostics channel; further diagnostics are aggregated into a single overflow summary rather than growing without bound. Exposed alongside the other bounds so a session can size it; defaults to DEFAULT_MAX_GUARD_DIAGNOSTICS.

Raises
  • ValueError

    If a deadline is not positive (or None where allowed) or a buffer/queue bound is not positive. In particular audio_queue_maxsize=0 would mean an UNBOUNDED asyncio.Queue -- silently disabling the documented feed backpressure -- so it is rejected rather than passed through. Also if max_guard_diagnostics is not positive.

Attributes
NameTypeDescription
replayablebool

Whether the audio source can be re-read after a reconnect.

done_timeoutfloat | None

The configured pipeline-inactivity backstop in seconds.

max_idlefloat | None

The configured content-stall deadline in seconds.

max_session_secondsfloat | None

The configured absolute wall-clock cap in seconds.

audio_chunks#async methodSource

async def TranscriptionSession.audio_chunks() -> AsyncIterator[bytes]

Async-iterate fed audio chunks until the input ends.

Each yielded chunk is also retained in the bounded rolling audio buffer for possible replay after a reconnect.

Consuming from this iterator is the session's liveness anchor: every dequeue resets the done_timeout backstop, so an engine that legitimately emits no events during user silence stays alive for as long as it keeps reading audio here.

Yields
  • AsyncIterator[bytes]

    Raw audio chunks in the session's declared format.

emit_diagnostic#methodSource

def TranscriptionSession.emit_diagnostic(
    *,
    code: str,
    message: str,
    level: Literal['info', 'warning'] = 'info',
    param: str | None = None,
    provided: JsonValue | BaseModel = None,
    effective: JsonValue | BaseModel = None,
) -> None

Surface a structured diagnostic from _produce (the streaming channel).

The streaming counterpart of the batch path's result.diagnostics: call this from your _produce to report a non-fatal note -- a best-effort degradation, an assumed parameter, a lossy fallback -- through the session's own diagnostics channel, so a WS client (mid-stream diagnostics frame) or a sync caller (diagnostics()) sees WHY. For a fatal condition, emit an error event instead -- this is for non-fatal notes that must not end the stream.

Recorded through the same bounded channel as the guard's own diagnostics: past the cap entries aggregate into the overflow summary rather than growing without bound, so a chatty producer cannot exhaust memory.

Security -- this is engine-authored, client-facing output, like the transcript text itself. Every field (message / param / provided / effective) is forwarded to clients VERBATIM -- to a (possibly unauthenticated) WebSocket client as a diagnostics frame, and, for the batch counterpart result.diagnostics, in the REST response -- and is NOT redacted. The standard layer scrubs only the detail IT auto-captures (an error event's extra, which holds a scrubbed exception summary -- never a raw str(exc)); content you pass here is your contract to keep. NEVER pass a credential, API key, a URL with embedded auth, or raw exception text -- route sensitive operator detail to logging instead. (That asymmetry is deliberate: an error event's extra is auto-captured so the server drops it; this note you chose, so you own its safety.)

Parameters
NameTypeDescription
codestr

Stable, machine-readable diagnostic code (for example, "vad_fallback").

messagestr

Human-readable explanation (client-facing; no secrets).

level
= 'info'
Literal['info', 'warning']

"info" or "warning" (default "info").

param
= None
str | None

The parameter the diagnostic concerns, if any.

provided
= None
JsonValue | BaseModel

The value the application provided, if relevant (client-facing). A pydantic model is projected into its JSON form via to_json_value -- the same projection the standard's own diagnostics apply -- so passing a request submodel just works instead of raising out of a live _produce.

effective
= None
JsonValue | BaseModel

The value that took effect, if relevant (client-facing; projected like provided).

Raises
  • pydantic.ValidationError

    If provided / effective has no JSON form even after projection (for example, an arbitrary in-process object) -- an engine bug surfaced loudly at the call site, naming the field, instead of failing later in the transport. Raising from _produce terminates the session, so keep such objects in your own state (see the docs' wire-visible values section).

replay_buffer#methodSource

def TranscriptionSession.replay_buffer() -> list[bytes]

Return audio for re-feeding to a freshly re-established connection.

Engines call this on reconnect to re-send audio. For a replayable (list / tuple) source the COMPLETE source is returned -- replayability promises loss-free replay, so it must not be silently truncated to the rolling ring. For a non-replayable / live source only the bounded rolling window of recent chunks is available (older audio was evicted).

Returns
  • list[bytes]

    The audio chunks to replay, oldest first: the full source if

  • list[bytes]

    replayable, otherwise the bounded rolling window.

note_reconnect#methodSource

def TranscriptionSession.note_reconnect(
    gap_start: float | None = None,
    gap_end: float | None = None,
    *,
    content_lost: bool = False,
) -> None

Record that an internal reconnect bridged a gap (engine-driven).

The base ALWAYS emits a progress(reconnect=True, gap_start, gap_end) event in order with produced events, followed -- IMMEDIATELY -- by a trailing error(code="content_lost", recoverable=True, gap_start, gap_end) IFF the engine passes content_lost=True.

The events are flushed promptly, the moment this is called, through the drop-proof path (_CoalescingBuffer.put_forced, the same sync primitive the base uses for synthesized terminals). This matters because an engine typically calls note_reconnect and then BLOCKS while it re-establishes the connection without yielding another event; deferring the flush to the next produced event would leave the consumer staring at a timeout/silence during a slow reconnect -- the opposite of "transparent but honest". note_reconnect runs in the producer-task coroutine on the session loop, so a synchronous flush is correct and ordered (the events land after already-emitted events and before any subsequent one). The _run_producer / finally drains remain as a harmless safety net -- they simply find nothing pending.

Content loss is an explicit engine determination, not something the base infers from rolling-buffer eviction: a live ring is always evicting, so eviction is the wrong signal (it would falsely claim permanent loss on every long live session). The engine -- which alone knows whether its reconnect + replay_buffer replay actually covered the gap -- sets content_lost=True only when audio the engine had not yet processed could not be replayed and is therefore truly lost. This mirrors the existing contract where segment_id / timestamps / detected language / speaker-label mapping continuity across the reconnect is likewise the engine's responsibility (the base never rewrites them). For speaker labels the engine MUST NOT reuse a pre-reconnect label without identity evidence; the safe default is to mint fresh labels and emit a speaker_labels_reset diagnostic via emit_diagnostic (the fidelity-warning counterpart of the content_lost error event).

Parameters
NameTypeDescription
gap_start
= None
float | None

Start time (seconds) of the lossy gap, if known.

gap_end
= None
float | None

End time (seconds) of the lossy gap, if known.

content_lost
= False
bool

True if the reconnect could not cover the gap and unreplayable audio was permanently lost; queues a non-terminal content_lost fidelity warning after the progress.

feed#methodSource

def TranscriptionSession.feed(
    source: Iterable[bytes] | AsyncIterable[bytes] | bytes | bytearray,
) -> None

Feed audio from a managed source (mutually exclusive with manual).

A bare bytes / bytearray is treated as a single audio chunk (not an iterable of chunks -- iterating it would yield int byte values). A non-async, re-iterable collection (list / tuple, or a wrapped bytes-like) is classified as replayable for reconnect purposes; an async source or a one-shot generator/iterator is non-replayable. Any AsyncIterable (the Pythonic __aiter__ protocol, not only the stricter AsyncIterator) is accepted and normalized via aiter.

Parameters
NameTypeDescription
sourceIterable[bytes] | AsyncIterable[bytes] | bytes | bytearray

A sync or async iterable of byte chunks (Iterable / AsyncIterable of bytes), or a single bytes / bytearray chunk.

Raises
  • TypeError

    If source is a str. A str satisfies Iterable[str] and would be silently consumed one character at a time (or fail deep inside an engine as a confusing engine_error); passing a file path here is a common slip. A whole audio file goes to start_transcription(audio=...) and incremental input is raw PCM byte chunks.

  • InvalidSessionUseError

    If manual input was already used (mixing) or feed was already called once -- a usage error against a still-live session, NOT a lifecycle close.

  • TypeError

    If a subclass rebound a reserved base attribute.

send_audio#async methodSource

async def TranscriptionSession.send_audio(chunk: bytes) -> None

Manually send one audio chunk (mutually exclusive with feed).

Manual sources are always treated as non-replayable (live input).

Parameters
NameTypeDescription
chunkbytes

The audio chunk.

Raises
  • InvalidSessionUseError

    If feed was already used (mixing input modes is a usage error against a still-live session).

  • StreamClosedError

    If the input was ended (end_audio()) or the session already delivered a terminal event -- a genuine lifecycle close: the audio queue has no consumer anymore, so raising beats blocking forever on a dead queue.

  • TypeError

    If a subclass rebound a reserved base attribute.

end_audio#async methodSource

async def TranscriptionSession.end_audio() -> None

Mark the end of manual audio input (idempotent in manual mode).

Claims manual ownership if this is the first input call, so a later feed is correctly rejected as mixing.

Raises
  • InvalidSessionUseError

    If feed was used (mixing input modes is a usage error against a still-live session).

  • TypeError

    If a subclass rebound a reserved base attribute.

__aenter__#async methodSource

async def TranscriptionSession.__aenter__() -> TranscriptionSession

Open resources and start the producer.

Anchors the max_session_seconds wall-clock origin here, at session establishment, so the absolute cap measures from when the session opened -- not from the first __anext__ -- and a re-entered iterator cannot reset it.

Returns
Raises
  • TypeError

    If a subclass rebound a reserved base attribute.

__aexit__#async methodSource

async def TranscriptionSession.__aexit__(*exc: object) -> None

Tear down the producer, feed task, and engine resources.

Cancels the producer and feed tasks and AWAITS them (so no coroutine is still touching engine state when _close runs), then closes.

__aiter__#methodSource

def TranscriptionSession.__aiter__() -> AsyncIterator[TranscriptionEvent]

Return the event async iterator (single-consumer).

A session has exactly ONE event stream and exactly ONE consumer: the events live in a shared bounded buffer, and a second concurrent iterator would race the first for _CoalescingBuffer.get, silently splitting the stream between them (each iterator would see an arbitrary subset, so neither's result would match the stream -- breaking the stream == result invariant). Re-iterating would also reset the per-iteration deadline anchors. Both are programming errors, so the second call fails loudly instead of returning a competing iterator.

Returns
Raises
  • InvalidSessionUseError

    If the session is already being iterated -- a usage error against a still-live session, not a lifecycle close.

  • TypeError

    If a subclass rebound a reserved base attribute.

diagnostics#methodSource

def TranscriptionSession.diagnostics() -> list[Diagnostic]

Return the diagnostics accumulated for this session so far.

The lifecycle-suppression channel is bounded: a misbehaving engine that trips a clamp on (nearly) every event cannot grow this list without limit over a long session. Once DEFAULT_MAX_GUARD_DIAGNOSTICS entries accumulate, further suppressions are aggregated into a single trailing diagnostics_truncated summary (per-code counts) instead of being retained individually -- so the overflow is reported honestly, never silently dropped.

Returns
  • list[Diagnostic]

    The standard-layer parameter-gating / language diagnostics attached

  • list[Diagnostic]

    at session establishment, followed by the lifecycle guard's

  • list[Diagnostic]

    suppressed illegal transitions, clamped

  • list[Diagnostic]

    stable_text values, stable text abandoned at done, and

  • list[Diagnostic]

    events that go beyond the engine's effective streaming

  • list[Diagnostic]

    capabilities (one per kind; such an event is delivered

  • list[Diagnostic]

    unchanged; see bind_session_capabilities).

  • list[Diagnostic]

    The list is capped as described above.

result#methodSource

def TranscriptionSession.result() -> TranscriptionResult

Reduce the session so far into a transcription result.

Returns

UnsafeAudioUrlError#classSource

class UnsafeAudioUrlError(IncompatibleAudioInputError)

UnsafeAudioUrlError(
    url: str,
    reason: str,
)

An AudioUrl failed the SSRF security policy and MUST NOT be forwarded.

Raised before a URL is handed to an engine when the URL fails the SSRF policy: a malformed URL or port, a non-HTTPS scheme, or a missing host. Unless allow_private_addresses opts out, it also covers a host that does not resolve, and any resolved address (in whole or in part) that is not globally routable -- private / loopback / link-local and their relatives. Under that opt-in the validator returns after the structural checks and never resolves the host, so an unresolvable name reaches the engine and fails there instead. Subclasses IncompatibleAudioInputError so existing audio-input error handling catches it, while remaining distinguishable.

Parameters
NameTypeDescription
urlstr

The offending URL.

reasonstr

A human-readable explanation of why it was rejected.

UnsupportedFeatureError#classSource

class UnsupportedFeatureError(StructuredError)

UnsupportedFeatureError(
    message: str,
    *,
    param: str | None = None,
    mode: str | None = None,
    hint: str | None = None,
)

Raised in strict mode when a requested standard feature is unsupported.

In best_effort mode the unsupported parameter is ignored and a structured diagnostic is returned instead of raising. The strict path carries the same structured context as that diagnostic so callers can inspect which feature was rejected without parsing the message.

Parameters
NameTypeDescription
messagestr

Human-readable description of the rejection.

param
= None
str | None

The offending standard parameter name, if applicable.

mode
= None
str | None

The mode ("batch" / "streaming") the rejection occurred in, if applicable.

hint
= None
str | None

Actionable guidance for resolving the rejection, if any.

Word#classSource

class Word(BaseModel)

Word-level detail, shared between batch results and streaming events.

Note

Time is measured in float seconds with the origin at the first submitted sample (audio time t=0), the same origin as the streaming cursor. start / end are therefore non-negative finite floats and end >= start (a zero-duration span is allowed). NaN / Inf are rejected (allow_inf_nan=False). Engines convert ms / protobuf-duration / ticks into this frame; a negative or inverted span is an engine bug, so the model refuses to represent one rather than let it surface as a silent wrong timestamp downstream.

Raises
  • ValueError

    If field validation fails (incl. NaN/Inf, a negative time, or end < start).

Attributes
NameTypeDescription
startfloat
ge=0.0

Word start time in seconds (origin = first submitted sample; non-negative, finite).

endfloat
ge=0.0

Word end time in seconds (non-negative, finite, >= start).

textstr

Word text.

probability
= None
float | None
ge=0.0, le=1.0

Optional confidence in [0, 1].

logprob
= None
float | None

Optional log-probability (kept separate from probability).

speaker
= None
str | None

Optional speaker label.

channel
= None
int | None
ge=0

Optional channel index for provenance (>= 0).

extra
= dict()
WireExtra

Engine-specific extra data.

WordTimestampGranularity#classSource

class WordTimestampGranularity(str, Enum)

Granularity for requested word timestamps.

The member values are the single source of truth shared with the declaration-side capability vocabulary WordTimestampGranularityName (a Literal). A module-level assertion (below) and a drift test bind the two sets so an additive change to one cannot silently desync the other.

Attributes
NameTypeDescription
WORD
= 'word'

Word-level timestamps.

SEGMENT
= 'segment'

Segment-level timestamps.

CHAR
= 'char'

Character-level timestamps (reserved, additive).

bind_session_capabilities#functionSource

def bind_session_capabilities(
    session: TranscriptionSession,
    engine: StandardASR,
) -> None

Give a session the streaming capabilities of the engine that opened it.

The session then checks each event it forwards against them. It looks for seven kinds of mismatch, each with its own diagnostic code:

  • a partial while emits_partials is unsupported (stream_exceeds_emits_partials);
  • a partial with non-empty stable_text while partial_stability is unsupported (stream_exceeds_partial_stability);
  • a supersede while re_segments is unsupported (stream_exceeds_re_segments);
  • an audio_processed_until cursor while timestamps mode is none (stream_exceeds_timestamps);
  • a non-empty words list while word_timestamps is unsupported (stream_exceeds_word_timestamps);
  • a speaker, on the event or on a word, while diarization is unsupported (stream_exceeds_diarization);
  • at done, a segment that reached final but not closed, and that no supersede retired, while finality_level mode is closed (finality_level_not_reached).

The session judges each event as the engine sent it, before any repair: a partial whose stable text the boundary repair empties still counts as carrying stable text. An event the session suppresses for another reason is not checked. The session delivers an event that goes beyond the capabilities unchanged. Removing the field would hide the engine's fault, and dropping a supersede would leave the retired segments in place and duplicate the replacement text. The session records one warning per code, the first time that code applies, in TranscriptionSession.diagnostics. With strict_lifecycle=True, the first mismatch ends the session instead: a terminal error event with code engine_error takes the place of the event. check_event_sequence runs the same check on a recorded stream.

EngineBase.start_transcription binds the engine's effective capabilities to every session it opens, so for an EngineBase this function does nothing. An engine that implements the protocol without deriving from EngineBase binds nothing. Call this function after you open a session through such an engine to get the same check; the reference server calls it for every session it opens. A session that nothing binds, such as one constructed directly in a test, is not checked.

The function reads the engine's effective_capabilities the ordinary way, so an attribute the engine serves through __getattr__ counts. An exception raised while reading it propagates, and the session stays unbound. That includes an AttributeError raised inside an effective_capabilities property. The function uses the engine's declared_capabilities only when the engine has no effective_capabilities attribute, or the attribute does not hold a DeclaredCapabilities. A tree with no streaming domain binds the default StreamingCapabilities(), in which emits_partials, partial_stability, re_segments, timestamps, word_timestamps, and diarization are unsupported and finality_level mode is final.

Parameters
NameTypeDescription
sessionTranscriptionSession

The session the engine returned.

engineStandardASR

The engine that opened it.

discover_models#functionSource

def discover_models(
    eps: Iterable[EntryPoint] | None = None,
    *,
    strict: bool = False,
    on_conflict: str = 'warn_keep_first',
) -> ModelRegistry

Discover all installed ASR plugins and return a registry.

This is the main entry point for the plugin discovery system. It scans the standard_asr.models entry point group and returns a ModelRegistry containing all discovered ASR engine factories.

Parameters
NameTypeDescription
eps
= None
Iterable[EntryPoint] | None

Custom entry points for testing. Leave None for normal discovery.

strict
= False
bool

If True, raise on invalid entry points. Default: False (warn only).

on_conflict
= 'warn_keep_first'
str

How to handle duplicate model keys:

  • "warn_keep_first": Keep first, warn about duplicates (default).
  • "replace": Use latest, warn about replacement.
Returns
Raises
Example
>>> from standard_asr import discover_models
>>> registry = discover_models()
>>> print(registry.names())
['faster-whisper/large-v3', 'whisper/base', ...]
>>> asr = registry.create("faster-whisper/large-v3")

require_engine_protocol#functionSource

def require_engine_protocol(engine: object) -> BaseProperties

Require an engine or engine class to be on the supported protocol line.

The one fail-closed gate every selected-engine surface shares: registry creation, the EngineBase inference and artifact entries, the CLI's show and transcribe preflight, and the per-model metadata endpoints all call it before interpreting anything the engine declares. Consumers that look up artifact_status or acquire_artifacts call it FIRST, so an engine on an unsupported line gets a typed compatibility error instead of a missing-attribute error -- and an unsupported line MUST NOT be read as NO_ARTIFACT_LIFECYCLE. Missing or untyped properties fails closed too: an engine whose protocol line cannot be established is less knowable than one on a wrong line, never more trustworthy. The check is the general line gate (require_supported_protocol), not a feature-table lookup: every supported line carries the artifact lifecycle, so no per-feature minimum survives to gate it -- which also keeps the first stable release's feature-table rewrite from breaking this guard.

Parameters
NameTypeDescription
engineobject

Engine instance or engine class whose declared protocol version is inspected.

Returns
Raises

to_srt#functionSource

def to_srt(
    result: TranscriptionResult,
    *,
    include_speakers: bool = False,
    on_unrenderable: UnrenderablePolicy = 'error',
) -> str

Render a transcription result as SRT.

Cue text is sanitized so it cannot forge cue structure (line terminators normalized, interior blank-line runs collapsed). Unlike to_vtt, SRT has no character-reference mechanism, so & and angle brackets in transcript text are emitted verbatim: an engine-leaked <unk> token or <i> is passed through as-is. Most SRT players render angle-bracket text literally, but some interpret a subset of HTML-like tags; if a downstream consumer must neutralize tags, do so on the transcript text before rendering. (WebVTT, which mandates escaping, is handled by to_vtt.)

Speaker rendering: SRT has no speaker syntax, so opting in mutates the cue text itself -- each labeled cue is prefixed with [<label>]: . The default is False on text-purity grounds (the renderer is a projection of the transcript; injecting labels uninvited would surprise every consumer that treats the cue text as speech), not for backward compatibility. Cues whose speaker is None are rendered unchanged; empty-text segments are skipped even when labeled (a label with no payload is not a cue).

Unrenderable segments: a segment without a measured span, or whose measured span quantizes to zero milliseconds on the output grid (its T --> T cue would be silently dropped by players), cannot render as a visible cue. By default that raises SubtitleRenderingError -- the render never silently drops, hides, or fabricates -- and the caller opts into a loss explicitly: on_unrenderable="omit" renders only the renderable cues (the other segments' text is absent from the file), "collapse" renders one synthetic whole-text cue [0, duration] (or [0, 3 s] when duration is unknown or quantizes to zero milliseconds; the fallback exists for the same player behavior). result.segments is None (segmentation not requested/applicable) uses the same whole-text fallback under every policy; segments == [] (requested but empty, for example, silence) ALWAYS yields no cues. The synthetic cue carries no speaker label -- a whole-text cue has no single attributable speaker -- so include_speakers has no effect on it. Pass a fully renderable result for time-accurate (and speaker-attributed) subtitles.

Parameters
NameTypeDescription
resultTranscriptionResult

The transcription result to render.

include_speakers
= False
bool

Render Segment.speaker labels as [<label>]: cue-text prefixes. Default False (pure transcript text).

on_unrenderable
= 'error'
UnrenderablePolicy

Policy for segments that cannot render as visible cues (unmeasured span, or a span that quantizes to zero milliseconds): "error" (default) raises; "omit" keeps only renderable cues; "collapse" renders one whole-text cue.

Returns
  • str

    The SRT document as a string.

Raises
  • SubtitleRenderingError

    Under the default "error" policy, when any segment cannot render as a visible cue.

  • ValueError

    If on_unrenderable is not one of the three policies (see _cues).

to_vtt#functionSource

def to_vtt(
    result: TranscriptionResult,
    *,
    include_speakers: bool = False,
    on_unrenderable: UnrenderablePolicy = 'error',
) -> str

Render a transcription result as WebVTT.

Cue text is escaped per the W3C WebVTT cue-text grammar (& -> &amp;, < -> &lt;, > -> &gt;) so payload text -- including engine-leaked <unk> / <|...|> tokens or "AT&T" -- is shown verbatim instead of being silently dropped by the browser's cue-span tokenizer. Cue structure is also protected (line terminators normalized, blank-line runs collapsed, --> neutralized by the > escape).

Speaker rendering: opting in wraps the whole cue body of each labeled cue in WebVTT's native voice span, <v <label>>. The default is False on text-purity grounds (the renderer is a projection of the transcript), not for backward compatibility. Cues whose speaker is None are rendered unchanged; empty-text segments are skipped even when labeled.

Unrenderable segments: identical policy contract to to_srt -- a segment without a measured span, or whose span quantizes to zero milliseconds on the output grid, raises by default (SubtitleRenderingError), and the caller opts into "omit" (renderable cues only) or "collapse" (one synthetic whole-text cue) explicitly. segments is None uses the whole-text fallback under every policy; segments == [] always yields zero cues; the synthetic cue carries no speaker label.

Parameters
NameTypeDescription
resultTranscriptionResult

The transcription result to render.

include_speakers
= False
bool

Render Segment.speaker labels as <v <label>> voice spans wrapping the cue body. Default False (pure transcript text).

on_unrenderable
= 'error'
UnrenderablePolicy

Policy for segments that cannot render as visible cues (unmeasured span, or a span that quantizes to zero milliseconds): "error" (default) raises; "omit" keeps only renderable cues; "collapse" renders one whole-text cue.

Returns
  • str

    The WebVTT document as a string.

Raises
  • SubtitleRenderingError

    Under the default "error" policy, when any segment cannot render as a visible cue.

  • ValueError

    If on_unrenderable is not one of the three policies (see _cues).

On this page

standard_asrARTIFACTS_NOT_APPLICABLEARTIFACTS_READYARTIFACTS_UNAVAILABLEARTIFACTS_UNKNOWNARTIFACT_ACTION_ACCEPT_TERMSARTIFACT_ACTION_AUTHENTICATEARTIFACT_ACTION_INSTALL_EXTERNALARTIFACT_ACTION_OTHERARTIFACT_ACTION_PROVIDE_ARTIFACTSARTIFACT_ACTION_REQUEST_ACCESSARTIFACT_BLOCKER_ACTION_REQUIREDARTIFACT_BLOCKER_DOWNLOADS_DISABLEDARTIFACT_BLOCKER_UNSUPPORTEDARTIFACT_CORRUPTARTIFACT_INCOMPLETEARTIFACT_MISSINGARTIFACT_PROGRESS_CONVERTINGARTIFACT_PROGRESS_EXTRACTINGARTIFACT_PROGRESS_FINALIZINGARTIFACT_PROGRESS_RESOLVINGARTIFACT_PROGRESS_TRANSFERRINGARTIFACT_PROGRESS_UNIT_BYTESARTIFACT_PROGRESS_UNIT_FILESARTIFACT_PROGRESS_VERIFYINGARTIFACT_READYARTIFACT_UNKNOWNArtifactAcquisitionBlockerArtifactAcquisitionErrorArtifactActionArtifactActionKindArtifactContextArtifactProgressArtifactProgressCallbackArtifactProgressCallbackErrorArtifactProgressPhaseArtifactProgressUnitArtifactReadinessArtifactReportfrom_requirementsArtifactRequirementArtifactStateArtifactStatusErrorArtifactUnavailableErrorAudioArray__post_init__AudioBase64AudioBytesAudioFormatAudioInputAudioInputLikeAudioPathAudioProcessingErrorAudioStorageUri__post_init__AudioUrlChannelResultConfigErrorConfigurationRequiredErrorDIAG_SEGMENT_TIMESTAMPS_UNAVAILABLEDIARIZEDiagnosticDiarizationRequestDiscoveryErrorEngineContractErrorEntrypointValidationErrorFFmpegNotFoundErrorFFprobeNotFoundErrorFactoryLoadErrorIncompatibleAudioInputErrorInvalidProviderParamErrorInvalidSessionUseErrorHttpsUrlModeNameProtocolCompatibilityErrorUnrenderablePolicyModelRegistrynameskeys_by_enginespecget_factoryengine_classconfig_schemacreateModelSpecload_factoryengine_classRuntimeParamsSegmentStandardASRtranscribetranscribe_asyncstart_transcriptionsupportsartifact_statusacquire_artifactsrecommended_wire_formatStandardASRErrorStreamClosedErrorStreamDeadlinesStructuredErrorSubtitleRenderingErrorSyncSession__enter____exit__feedsend_audioend_audio__iter__resultdiagnosticsis_loop_aliveTranscriptionErrorTranscriptionEventpartialfinalclosedsupersedeprogressdonemake_errorTranscriptionResultTranscriptionSessionaudio_chunksemit_diagnosticreplay_buffernote_reconnectfeedsend_audioend_audio__aenter____aexit____aiter__diagnosticsresultUnsafeAudioUrlErrorUnsupportedFeatureErrorWordWordTimestampGranularitybind_session_capabilitiesdiscover_modelsrequire_engine_protocolto_srtto_vtt