Models a "delta" message from a chat LLM. A delta is a small chunk, or piece of a much larger complete message. A series of deltas are used to construct the complete message.
Delta messages must be applied in order for them to be valid. Delta messages
can be combined and transformed into a LangChain.Message once the final
piece is received.
Roles
:unknown- The role data is missing for the delta.:assistant- Responses coming back from the LLM.
Tool Usage
Tools can be used or called by the assistant (LLM). A tool call is also split across many message deltas and must be fully assembled before it can be executed.
Metadata
The metadata field is a map that can contain any additional information
about the message delta. It is used to store token usage, model, and other
LLM-specific information.
Content Fields
The content field may contain:
- A string (for backward compatibility)
- A
LangChain.Message.ContentPartstruct - An empty list
[]that is received from some services like Anthropic, which is a signal that the content will be a list of content parts
The module uses two content-related fields:
content- The raw content received from the LLM. This can be either a string (for backward compatibility), aLangChain.Message.ContentPartstruct, or a[]indicating it will be a list of content parts. This field is cleared (set tonil) after merging intomerged_content.merged_content- The accumulated list ofContentParts after merging deltas. This is the source of truth for the message content and is used when converting to aLangChain.Message. When merging deltas:- For string content, it's converted to a
ContentPartof type:text - For
ContentPartcontent, it's merged based on theindexfield - Multiple content parts can be maintained in the list to support multi-modal responses (text, images, audio) or separate thinking content from final text
- For string content, it's converted to a
Summary
Functions
Accumulates token usage from delta messages. Uses LangChain.TokenUsage.add/2 to combine
the usage data from both deltas.
Check whether every ToolCall in the delta has reached a terminal
execution status. Returns true only when there is at least one tool
call AND every call has metadata["execution_status"] set to
"completed" or "failed".
Convert the MessageDelta's merged content to a string. Specify the type of
content to convert so it can return just the text parts or thinking parts,
etc. Defaults to :text.
Merge two MessageDelta structs. The first MessageDelta is the primary
one that smaller deltas are merged into.
Merges a list of MessageDeltas into a single MessageDelta. The deltas
are merged in order, with each delta being merged into the result of the
previous merge.
Merges a list of MessageDeltas into an accumulated MessageDelta. This is
useful for UI applications that accumulate deltas as they are received from
a streaming LLM response.
Migrates a MessageDelta's string content to use LangChain.Message.ContentPart.
This is for backward compatibility with models that don't yet support ContentPart streaming.
Create a new MessageDelta that represents a message chunk.
Create a new MessageDelta that represents a message chunk and return it or
raise an error if invalid.
Set the display_text on the ToolCall with the matching call_id.
Find the ToolCall with the matching call_id and update its
"execution_status" metadata. Delegates to
LangChain.Message.ToolCall.set_execution_status/2.
Convert the MessageDelta to a Message. Can only convert a fully complete MessageDelta.
Insert a ToolCall into the delta's tool_calls list, or merge its
non-nil fields onto an existing ToolCall with the same call_id.
Types
Functions
Accumulates token usage from delta messages. Uses LangChain.TokenUsage.add/2 to combine
the usage data from both deltas.
Example
iex> alias LangChain.TokenUsage
iex> alias LangChain.MessageDelta
iex> delta1 = %MessageDelta{
...> metadata: %{
...> usage: %TokenUsage{input: 10, output: 5}
...> }
...> }
iex> delta2 = %MessageDelta{
...> metadata: %{
...> usage: %TokenUsage{input: 5, output: 15}
...> }
...> }
iex> result = MessageDelta.accumulate_token_usage(delta1, delta2)
iex> result.metadata.usage.input
15
iex> result.metadata.usage.output
20
Check whether every ToolCall in the delta has reached a terminal
execution status. Returns true only when there is at least one tool
call AND every call has metadata["execution_status"] set to
"completed" or "failed".
Returns false for nil or empty tool_calls — there is nothing to
gate on yet.
Intended for UI flows that keep a streaming delta visible while any tool is still mid-execution and only clear it once all tools have terminated, so sibling tool calls don't lose their UI state when one finishes before another.
Examples
iex> alias LangChain.Message.ToolCall
iex> done = %ToolCall{call_id: "a", metadata: %{"execution_status" => "completed"}}
iex> running = %ToolCall{call_id: "b", metadata: %{"execution_status" => "executing"}}
iex> LangChain.MessageDelta.all_tools_terminal?(%LangChain.MessageDelta{tool_calls: [done]})
true
iex> LangChain.MessageDelta.all_tools_terminal?(%LangChain.MessageDelta{tool_calls: [done, running]})
false
Convert the MessageDelta's merged content to a string. Specify the type of
content to convert so it can return just the text parts or thinking parts,
etc. Defaults to :text.
Merge two MessageDelta structs. The first MessageDelta is the primary
one that smaller deltas are merged into.
The merging process:
- Migrates any string content to
ContentParts for backward compatibility - Merges the content into
merged_contentbased on theindexfield - Clears the
contentfield (sets tonil) after merging - Updates other fields (tool_calls, status, etc.)
Examples
iex> delta_1 =
...> %LangChain.MessageDelta{
...> content: nil,
...> index: 0,
...> tool_calls: [],
...> role: :assistant,
...> status: :incomplete
...> }
iex> delta_2 =
...> %LangChain.MessageDelta{
...> content: "Hello",
...> index: 0,
...> tool_calls: [],
...> role: :unknown,
...> status: :incomplete
...> }
iex> LangChain.MessageDelta.merge_delta(delta_1, delta_2)
%LangChain.MessageDelta{
content: nil,
merged_content: [%LangChain.Message.ContentPart{type: :text, content: "Hello"}],
status: :incomplete,
index: 0,
role: :assistant,
tool_calls: []
}A set of deltas can be easily merged like this:
MessageDelta.merge_deltas(list_of_delta_messages)
Merges a list of MessageDeltas into a single MessageDelta. The deltas
are merged in order, with each delta being merged into the result of the
previous merge.
Examples
iex> deltas = [
...> %LangChain.MessageDelta{content: "Hello", role: :assistant},
...> %LangChain.MessageDelta{content: " world", role: :assistant},
...> %LangChain.MessageDelta{content: "!", role: :assistant, status: :complete}
...> ]
iex> LangChain.MessageDelta.merge_deltas(deltas)
%LangChain.MessageDelta{
content: nil,
merged_content: [%LangChain.Message.ContentPart{type: :text, content: "Hello world!"}],
status: :complete,
role: :assistant
}
Merges a list of MessageDeltas into an accumulated MessageDelta. This is
useful for UI applications that accumulate deltas as they are received from
a streaming LLM response.
The first argument is the accumulated delta (or nil if no deltas have been
processed yet). The second argument is a list of new deltas to merge in.
Examples
iex> accumulated = nil
iex> batch_1 = [
...> %LangChain.MessageDelta{content: "Hello", role: :assistant},
...> %LangChain.MessageDelta{content: " world", role: :assistant}
...> ]
iex> accumulated = LangChain.MessageDelta.merge_deltas(accumulated, batch_1)
iex> batch_2 = [
...> %LangChain.MessageDelta{content: "!", role: :assistant, status: :complete}
...> ]
iex> result = LangChain.MessageDelta.merge_deltas(accumulated, batch_2)
iex> result
%LangChain.MessageDelta{
content: nil,
merged_content: [%LangChain.Message.ContentPart{type: :text, content: "Hello world!"}],
status: :complete,
role: :assistant
}
Migrates a MessageDelta's string content to use LangChain.Message.ContentPart.
This is for backward compatibility with models that don't yet support ContentPart streaming.
Examples
iex> delta = %LangChain.MessageDelta{content: "Hello world"}
iex> upgraded = migrate_to_content_parts(delta)
iex> upgraded.content
%LangChain.Message.ContentPart{type: :text, content: "Hello world"}
@spec new(attrs :: map()) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
Create a new MessageDelta that represents a message chunk.
Create a new MessageDelta that represents a message chunk and return it or
raise an error if invalid.
Set the display_text on the ToolCall with the matching call_id.
A non-nil incoming value overwrites the existing one. As a tool executes
and the caller learns more, it is reasonable to refine the display from
"Reading file" to "Reading "outline.md" (lines 60–100)", and this
helper supports that flow.
A nil incoming value is a no-op and leaves the existing display_text
alone, so the UI never goes from showing something to showing nothing.
Returns the delta unchanged if no tool call matches the given call_id.
Examples
iex> alias LangChain.Message.ToolCall
iex> delta = %LangChain.MessageDelta{
...> role: :assistant,
...> tool_calls: [%ToolCall{call_id: "abc", name: "search"}]
...> }
iex> updated = LangChain.MessageDelta.set_tool_display_text(delta, "abc", "Searching")
iex> hd(updated.tool_calls).display_text
"Searching"
Find the ToolCall with the matching call_id and update its
"execution_status" metadata. Delegates to
LangChain.Message.ToolCall.set_execution_status/2.
Intended for callers like Phoenix LiveView and Sagents that track the lifecycle of a streaming tool call (identified → executing → completed/failed) and need to drive the UI as the agent progresses.
Returns the delta unchanged if no tool call matches the given call_id.
Examples
iex> alias LangChain.Message.ToolCall
iex> delta = %LangChain.MessageDelta{
...> role: :assistant,
...> tool_calls: [%ToolCall{call_id: "abc", name: "search"}]
...> }
iex> updated = LangChain.MessageDelta.set_tool_execution_status(delta, "abc", "executing")
iex> hd(updated.tool_calls).metadata
%{"execution_status" => "executing"}
@spec to_message(t()) :: {:ok, LangChain.Message.t()} | {:error, String.t()}
Convert the MessageDelta to a Message. Can only convert a fully complete MessageDelta.
This is assumed to be the result of merging all the received MessageDeltas.
An error is returned if the status is :incomplete.
If the MessageDelta fails to convert to a LangChain.Message, an error is
returned with the reason.
@spec upsert_tool_call(t(), LangChain.Message.ToolCall.t()) :: t()
Insert a ToolCall into the delta's tool_calls list, or merge its
non-nil fields onto an existing ToolCall with the same call_id.
Intended for streaming pipelines that learn about a tool call across
several events: the first event creates the call, later events fill in
fields like display_text or update metadata.
Merge rules when a ToolCall with the same call_id already exists:
- Non-nil scalar fields on the incoming call overwrite the existing value
(
name,arguments,display_text). statusis only promoted forward — an incoming:completeoverwrites an existing:incomplete, but never the other way around.metadatais shallow-merged, with incoming keys winning per-key.
A call with call_id: nil is ignored and the delta is returned unchanged
(there is no key to upsert against).
Examples
iex> alias LangChain.Message.ToolCall
iex> delta = %LangChain.MessageDelta{role: :assistant, tool_calls: []}
iex> tc = %ToolCall{call_id: "abc", name: "search"}
iex> updated = LangChain.MessageDelta.upsert_tool_call(delta, tc)
iex> updated.tool_calls
[%ToolCall{call_id: "abc", name: "search"}]