One event in a streamed response.
ExAgent.chat_stream/3 and ExAgent.Provider.stream/3 yield these instead of
bare strings, so usage, finish reasons, reasoning traces, and tool-call deltas
are all available rather than discarded.
agent
|> ExAgent.chat_stream("Explain OTP")
|> Enum.each(fn
%ExAgent.Chunk{type: :text_delta, text: text} -> IO.write(text)
%ExAgent.Chunk{type: :done, finish_reason: reason} -> IO.puts("\n[#{reason}]")
_chunk -> :ok
end)Use ExAgent.collect/1 to fold a chunk stream back into the same
ExAgent.Response that ExAgent.chat/3 returns.
Types
| Type | Carries |
|---|---|
:text_delta | :text - a piece of the assistant's answer |
:thinking_delta | :text - a piece of the model's reasoning trace |
:tool_call_delta | :index, :id, :name, :arguments |
:usage | :usage - token counts |
:done | :finish_reason, and :error when the stream failed |
Every stream ends with exactly one :done chunk, including when it fails.
That invariant is what lets ExAgent.collect/1 always return a result.
Tool-call arguments arrive in pieces
:arguments is a raw JSON fragment, not a decoded map. Providers emit
function arguments split across many chunks, to be concatenated by :index
before decoding. :name arrives once, on the first fragment of a call.
ExAgent.collect/1 does this reassembly for you.
Summary
Functions
Builds the terminal chunk.
Builds a terminal chunk carrying an error.
Reassembles :tool_call_delta chunks into completed tool calls.
Normalizes a provider's finish-reason string.
Builds a text delta chunk.
Builds a reasoning-trace delta chunk.
Builds a usage chunk.
Types
@type finish_reason() ::
:stop | :length | :tool_calls | :content_filter | :error | nil
@type t() :: %ExAgent.Chunk{ arguments: String.t() | nil, error: ExAgent.Error.t() | nil, finish_reason: finish_reason(), id: String.t() | nil, index: non_neg_integer() | nil, name: String.t() | nil, text: String.t() | nil, type: type(), usage: map() | nil }
@type type() :: :text_delta | :thinking_delta | :tool_call_delta | :usage | :done
Functions
@spec done(finish_reason(), ExAgent.Error.t() | nil) :: t()
Builds the terminal chunk.
@spec error(ExAgent.Error.t()) :: t()
Builds a terminal chunk carrying an error.
Streams never raise mid-flight: whatever was already emitted stays valid, and the failure arrives as the final chunk.
Reassembles :tool_call_delta chunks into completed tool calls.
Fragments are grouped by :index, concatenated in arrival order, and decoded.
A fragment set that does not parse as JSON is kept as %{"raw" => fragment}
rather than discarded, matching how non-streaming responses handle the same
case.
Returns nil when there were no tool calls, so it can be handed straight to
ExAgent.Message.new/1.
Examples
iex> chunks = [
...> %ExAgent.Chunk{type: :tool_call_delta, index: 0, name: "sum", arguments: ~s({"a":)},
...> %ExAgent.Chunk{type: :tool_call_delta, index: 0, arguments: ~s(1})}
...> ]
iex> ExAgent.Chunk.finalize_tool_calls(chunks)
[%{"name" => "sum", "args" => %{"a" => 1}}]
@spec finish_reason(String.t() | nil) :: finish_reason()
Normalizes a provider's finish-reason string.
Unrecognized values become :stop rather than being dropped, since the stream
did in fact end.
Examples
iex> ExAgent.Chunk.finish_reason("tool_calls")
:tool_calls
iex> ExAgent.Chunk.finish_reason("MAX_TOKENS")
:length
Builds a text delta chunk.
Builds a reasoning-trace delta chunk.
Builds a usage chunk.