ALLM.SpeechEvent (allm v0.6.1)

Copy Markdown View Source

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_type begins audio/, and :sample_rate is the rate the bytes are encoded at, which is what a :pcm stream 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 (an ALLM.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

Types

Payload of :speech_completed.

Payload of :speech_started.

t()

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

completed()

@type completed() :: %{
  request_id: String.t() | nil,
  id: String.t() | nil,
  usage: ALLM.Usage.t(),
  metadata: map()
}

Payload of :speech_completed.

started()

@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.

t()

@type t() ::
  {:speech_started, started()}
  | {:audio_delta, binary()}
  | {:speech_completed, completed()}
  | {:error, ALLM.Error.SpeechAdapterError.t()}

Functions

audio_delta(bytes)

@spec audio_delta(binary()) :: t()

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>>}

event?(arg1)

@spec event?(term()) :: boolean()

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

speech_completed(payload)

@spec speech_completed(completed()) :: t()

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: %{}}}

speech_started(payload)

@spec speech_started(started()) :: t()

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