Closed tagged-tuple union emitted by a streaming speech-synthesis call. Layer A data.
Every event is {tag, payload}, one of:
{:speech_started, started}: the provider accepted the request. The payload carries correlation (:request_id,:model,:provider) and what the audio will be (:format,:mime_type,:sample_rate).:mime_typebeginsaudio/, and:sample_rateis the rate the bytes are encoded at, which is what a:pcmstream is played back at.{:audio_delta, bytes}: the next slice of audio, never empty.{:speech_completed, completed}: the stream finished. The payload carries:request_id,:id,:usage(anALLM.Usage.t/0) and:metadata.{:error, %ALLM.Error.SpeechAdapterError{}}: the stream failed.
Stream grammar
A successful stream is one :speech_started, then one or more
:audio_delta events, then one :speech_completed. A failed stream is an
optional :speech_started, zero or more :audio_delta events, then one
:error. Nothing follows a terminal event (:speech_completed or
:error).
A stream that reaches its end with zero audio bytes, including one whose
text input produced nothing to speak, ends with
{:error, %SpeechAdapterError{reason: :invalid_request, metadata: %{cause: :empty_input}}},
so a successful stream always carries at least one delta.
Concatenating every :audio_delta payload in order yields a valid file
of the started :format, or raw PCM at :sample_rate for :pcm.
Serializability
Events round-trip through :erlang.term_to_binary/1. They are not
JSON-encoded: :audio_delta carries raw audio bytes, which are not UTF-8.
The same holds for ALLM.TranscriptionEvent.
Closed union
Adding a variant is a breaking change for any reducer of this union, the
same rule as ALLM.Event. Adding a key to an existing payload map is not.
The union is separate from ALLM.Event: event?/1 is false on every
chat event. The reverse does not hold: ALLM.Event.event?/1 treats :error
payloads as opaque, so it also returns true on this union's :error
event. Route by this module's event?/1, or by the error struct.
Build events with the variant constructors (speech_started/1,
audio_delta/1, speech_completed/1), which check the payload's required
keys. :error has no constructor; its payload is the error struct.
Summary
Functions
Build an :audio_delta event. Raises ArgumentError on "": a delta is
never empty.
Return true when value is a well-shaped ALLM.SpeechEvent.
Build a :speech_completed event.
Build a :speech_started event.
Types
@type completed() :: %{ request_id: String.t() | nil, id: String.t() | nil, usage: ALLM.Usage.t(), metadata: map() }
Payload of :speech_completed.
@type started() :: %{ request_id: String.t() | nil, model: String.t() | nil, provider: atom() | nil, format: ALLM.SpeechRequest.format() | nil, mime_type: String.t(), sample_rate: pos_integer() | nil }
Payload of :speech_started.
@type t() :: {:speech_started, started()} | {:audio_delta, binary()} | {:speech_completed, completed()} | {:error, ALLM.Error.SpeechAdapterError.t()}
Functions
Build an :audio_delta event. Raises ArgumentError on "": a delta is
never empty.
Examples
iex> ALLM.SpeechEvent.audio_delta(<<1, 2, 3>>)
{:audio_delta, <<1, 2, 3>>}
Return true when value is a well-shaped ALLM.SpeechEvent.
The tag must be one of the four variants. :speech_started and
:speech_completed need a map payload, :audio_delta a non-empty
binary, and :error an %ALLM.Error.SpeechAdapterError{}. Payload keys
are not checked here; the constructors check them.
Examples
iex> ALLM.SpeechEvent.event?({:audio_delta, "ab"})
true
iex> ALLM.SpeechEvent.event?({:audio_delta, 1})
false
iex> ALLM.SpeechEvent.event?({:text_delta, %{id: nil, delta: "hi"}})
false
Build a :speech_completed event.
Raises ArgumentError when payload lacks any of :request_id, :id,
:usage or :metadata.
Examples
iex> ALLM.SpeechEvent.speech_completed(%{request_id: "r", id: nil, usage: %ALLM.Usage{}, metadata: %{}})
{:speech_completed, %{request_id: "r", id: nil, usage: %ALLM.Usage{}, metadata: %{}}}
Build a :speech_started event.
Raises ArgumentError when payload lacks any of :request_id,
:model, :provider, :format, :mime_type or :sample_rate (a key
may be present with a nil value).
Examples
iex> {:speech_started, p} =
...> ALLM.SpeechEvent.speech_started(%{
...> request_id: nil, model: "tts-1", provider: :openai,
...> format: :pcm, mime_type: "audio/pcm", sample_rate: 24_000
...> })
iex> p.sample_rate
24000