LangEx.LLM.ChatModel (LangEx v0.9.0)

Copy Markdown View Source

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

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

merge_usage(current, new)

@spec merge_usage(map() | nil, map()) :: map()

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

node(opts)

@spec node(keyword()) :: (map() -> map())

Returns a node function that calls an LLM provider.

Options

  • :provider - module implementing LangEx.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 through LangEx.LLM.Resilient for retries with backoff. true for defaults, or a keyword list of Resilient options (:max_retries, :retry_base_ms, :fallback, ...)
  • All other opts forwarded to provider.chat/2 (:api_key, :temperature, etc.)

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", ...}]
))

structured(messages, opts)

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

Options

  • :schema (required) - JSON-schema map describing the desired shape
  • :resilient - true or LangEx.LLM.Resilient options to retry on transient failures
  • :provider / :model and 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

structured_node(opts)

@spec structured_node(keyword()) :: (map() -> map())

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 / :model and other options are forwarded to the provider, exactly like node/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_structured(data, schema)

@spec validate_structured(map() | nil, map()) :: {:ok, map()} | {:error, term()}

Validate a decoded structured result against a JSON-schema's top-level required keys. Keys are compared as strings.