Streaming provider adapter contract. See spec §7.2.
stream/2 returns an Enumerable.t() of ALLM.Event values. The
enumerable is lazy — no HTTP call fires until the caller starts
reducing over it — and must be resource-safe: if the consumer halts
early (Stream.take/2), the underlying HTTP request must be cancelled.
HTTP transport guidance
Use Finch directly with HTTP/1. Req's SSE path does not cover every
provider's chunking quirks, and HTTP/2 flow control breaks for request
bodies larger than 64 KB (the same issue documented in req_llm).
Engines may inject a custom Finch name via
adapter_opts: [finch_name: MyApp.Finch].
Implementations should use Stream.resource/3 (not Stream.unfold/2) —
resource/3 has an explicit after_fun which is the canonical place
to cancel the Finch ref.
Invariants
- The synchronous
{:error, _}branch returns%AdapterError{}for pre-flight failures (missing key, invalid request shape, immediate HTTP error like 401 before the first event). - The stream itself may terminate with either
{:error, %AdapterError{}}(HTTP-shaped failure mid-response — the provider returned a 4xx/5xx after streaming started) or{:error, %ALLM.Error.StreamError{}}(transport-shaped failure — stream cancelled, timed out, malformed event). Both variants are emitted via theALLM.Event{:error, _}tag per spec §8. - The stream must be halt-safe: a consumer halt within 500 ms must
cancel the
Finchref. opts[:stream_timeout](time between consecutive events) is honored by the adapter; exceeding it emits a terminating{:error, %AdapterError{reason: :timeout}}event.- Adapters emitting
{:raw_chunk, {:usage, _}}events must pre-map provider-wire usage keys to%ALLM.Usage{}field names before emitting; seeALLM.StreamCollector's usage-fold contract.
Summary
Callbacks
Open a streaming request against the provider.
Callbacks
@callback stream( ALLM.Request.t(), keyword() ) :: {:ok, Enumerable.t()} | {:error, ALLM.Error.AdapterError.t()}
Open a streaming request against the provider.
Returns {:ok, enumerable} on success (the enumerable is lazy — no HTTP
call has fired yet) or {:error, %ALLM.Error.AdapterError{}} on
pre-flight failure.
Synchronous error reasons (same as ALLM.Adapter.generate/2)
| Reason | HTTP status | Fires when |
|---|---|---|
:authentication_failed | 401 | API key missing or invalid. |
:rate_limited | 429 | Provider quota exceeded; :retry_after_ms populated when Retry-After header is present. |
:invalid_request | 400 | Request shape rejected by provider. |
:content_filter | 400 (provider-specific) | Provider's content filter rejected the prompt. |
:context_length_exceeded | 400 | Request exceeded the model's context window. |
:provider_unavailable | 500, 502, 503, 504, 529 | Provider server-side failure, retryable. |
:timeout | — | Pre-flight request exceeded opts[:request_timeout]. |
:network_error | — | TCP/TLS/DNS failure before the first event. |
:malformed_response | — | Provider returned a non-SSE response body to the streaming endpoint. |
:unsupported_feature | — | Request combined features the adapter cannot express. |
:unknown | any | Catch-all for shapes the adapter cannot classify. |
Mid-stream {:error, _} event reasons
The enumerable may emit a terminating {:error, _} event carrying
either an %AdapterError{} (HTTP-shaped) or a %StreamError{}
(transport-shaped):
| Struct type | Reason | Fires when |
|---|---|---|
AdapterError | :rate_limited | Provider returned 429 after SSE began. |
AdapterError | :provider_unavailable | Provider returned 5xx after SSE began. |
AdapterError | :content_filter | Provider interrupted the stream with a content-filter signal. |
AdapterError | :timeout | opts[:stream_timeout] elapsed between events. |
StreamError | :cancelled | Consumer halted the stream early. |
StreamError | :timeout | Transport-level timeout between chunks (distinct from adapter-level request timeout). |
StreamError | :malformed_event | An SSE line could not be parsed. |
StreamError | :adapter_error | Wraps an underlying %AdapterError{} (see :cause field). |
StreamError | :unknown | Catch-all for transport failures the adapter cannot classify. |