Helper to create graph nodes that call an LLM.
Produces a node function that reads messages from state, sends them to the configured LLM provider, and appends the response to the messages list.
Summary
Functions
One-shot text completion outside a graph node.
Reducer that accumulates token usage maps by summing numeric fields.
Returns a node function that calls an LLM provider.
One-shot structured extraction outside a graph node.
Returns a node function that asks the LLM for a structured result.
Validate a decoded structured result against a JSON-schema's top-level
required keys. Keys are compared as strings.
Functions
@spec complete( [LangEx.Message.t()], keyword() ) :: {:ok, LangEx.Message.AI.t(), LangEx.LLM.usage()} | {:error, term()}
One-shot text completion outside a graph node.
Resolves the provider, optionally routes through LangEx.LLM.Resilient
(:resilient), and returns the raw assistant message with token usage —
a primitive for auxiliary LLM calls (summarisation, critique, tool
selection) that need usage accounting but no graph state.
Returns {:ok, %Message.AI{}, usage} or {:error, reason}.
Reducer that accumulates token usage maps by summing numeric fields.
Use as the schema reducer for the usage key:
Graph.new(llm_usage: {%{}, &ChatModel.merge_usage/2})
Returns a node function that calls an LLM provider.
Options
:provider- module implementingLangEx.LLM(explicit):model- model string like"gpt-4o"or"claude-sonnet-4-20250514"(auto-resolves provider):messages_key- state key holding the message list (default::messages):usage_key- state key accumulating token usage (default::llm_usage); only written when the key exists in the graph state schema:tools- list of%LangEx.Tool{}definitions for function calling:resilient- route calls throughLangEx.LLM.Resilientfor retries with backoff.truefor defaults, or a keyword list ofResilientoptions (:max_retries,:retry_base_ms,:fallback, ...)- All other opts forwarded to
provider.chat/2(:api_key,:temperature, etc.). Any opt given as{:from_state, fn state -> value end}is resolved from the node's state on each call — useful for per-run callbacks like:on_thinkingwhose context isn't known when the graph is built.
Either :provider or :model must be given. When :model is a string and
:provider is absent, the provider is resolved via LangEx.LLM.Registry.init_chat_model/2.
Tool execution is handled by a separate LangEx.Tool.Node in the graph,
not by the LLM node itself.
Token usage accounting
When the provider implements chat_with_usage/2, token counts are
attached to the [:lang_ex, :llm, :chat, :stop] telemetry event as
:usage metadata. To also accumulate usage in graph state, declare
the usage key in the schema with merge_usage/2 as the reducer:
Graph.new(
messages: {[], &Message.add_messages/2},
llm_usage: {%{}, &ChatModel.merge_usage/2}
)Examples
Graph.add_node(:llm, ChatModel.node(model: "gpt-4o"))
Graph.add_node(:llm, ChatModel.node(model: "gpt-4o",
tools: [%LangEx.Tool{name: "search", ...}]
))
@spec structured( [LangEx.Message.t()], keyword() ) :: {:ok, map()} | {:error, term()}
One-shot structured extraction outside a graph node.
Forces the provider to answer via a synthetic respond tool whose
parameters are :schema, decodes the tool call (falling back to decoding
JSON content), and validates that the schema's top-level required keys
are present.
On a schema validation failure (:no_structured_output or a missing
required key) the model is re-asked with the validation error appended as
feedback, up to :max_retries times — turning intermittent malformed
output into self-corrections. Provider/transport errors are returned
immediately (use :resilient for those).
Options
:schema(required) - JSON-schema map describing the desired shape:max_retries- validation-feedback retries (default2;0disables):strategy-:tool(default; a syntheticrespondtool, works with any provider) or:provider(forces the tool via the provider's nativetool_choice, for adapters that support it):resilient-trueorLangEx.LLM.Resilientoptions to retry on transient failures:provider/:modeland other options are forwarded to the provider
Returns {:ok, map} or {:error, reason}:
{:error, :no_structured_output}- the model returned nothing decodable{:error, {:missing_required, keys}}- required keys were absent{:error, term}- the provider call itself failed
Returns a node function that asks the LLM for a structured result.
The model is given a synthetic respond tool whose parameters are the
provided JSON-schema; calling it yields the structured data, which is
decoded and written to the :into state key (default :structured). A
clean assistant message carrying the JSON is appended to the messages so
the conversation stays valid. Works with any provider that supports tool
calling — no provider-specific configuration required.
Options
:schema(required) - JSON-schema map describing the desired shape:into- state key to write the decoded result to (default:structured):messages_key- state key holding the message list (default:messages):provider/:modeland other options are forwarded to the provider, exactly likenode/1
Example
Graph.add_node(:extract, ChatModel.structured_node(
model: "gpt-4o",
into: :analysis,
schema: %{
type: "object",
properties: %{sentiment: %{type: "string"}, score: %{type: "number"}},
required: ["sentiment", "score"]
}
))
Validate a decoded structured result against a JSON-schema's top-level
required keys. Keys are compared as strings.