ExAgent. Model behaviour
(ExAgent v2.2.1)
View Source
Behaviour that every provider model implements.
A "model" is any struct whose module @behaviours ExAgent.Model. The
struct carries the model's own state (base URL, API key, configured
options…). Callers never invoke a provider module directly — they go through
the dispatch functions in this module (request/4, request_stream/4, …),
which resolve the implementer from the struct's __struct__.
This uses Elixir behaviours plus struct dispatch so providers can stay small and explicit.
Summary
Callbacks
Optional static configuration binding for persisted continuation, without IO.
Declares capabilities (optional; extra capabilities default to false). The core rejects
function tools and tool-based structured output when supports_tools is false.
Explicit native output requires supports_json_schema_output: true before
request admission. Native JSON/thinking flags do not describe tool-output mode and
are not used to silently downgrade it. Streaming requires its own callback.
Make a non-streaming request and return a full Message.Response.
Make a lazy streaming request. Events are {:text_delta, binary}, optional
{:thinking_delta, binary} (separate from output text), optional
{:usage, cumulative_request_usage}, and exactly one terminal
{:response, response, final_model} or {:error, reason}. Usage snapshots
replace earlier snapshots for this request; they are not additive deltas.
When params.tool_call_deltas is true, implementations may also emit
{:tool_call_delta, %{index: index, name: name_or_nil, fragment: binary}}
as tool-call arguments are generated: the first event for an index carries
the tool name, later ones carry raw argument JSON fragments. They are a
preview only; the terminal response stays authoritative.
Implementations must release resources when enumeration halts. A stream
ending without a terminal is a protocol error. Implementations may return
{:error, _} directly when streaming is unsupported.
Optional no-IO preflight required by persisted continuation. Validate restored logical history, provider/endpoint binding and codec/settings using the trusted current model and tool inventory. Never dispatch a request from this callback. Ordinary run/request consumers do not require it.
Functions
Returns the bounded, portable static continuation binding; no Model IO.
Resolve a custom Model struct, Test label, or stock ReqLLM model spec.
Types
@type messages() :: [ExAgent.Message.t()]
@type model() :: struct()
Callbacks
Optional static configuration binding for persisted continuation, without IO.
Return {:ok, json_data} (at most 4096 encoded JSON bytes) or {:error, reason}.
Exclude credentials, transient state and runtime counters. The normalized value
must be deterministic across equivalent configurations. Absent callbacks return
{:ok, nil}. Frame restore checks both the trusted template and app-codec result;
a changed binding rejects before resuming effects, including a first uncertain
Model request with no response history. Callback errors are normalized, not echoed.
@callback profile(model()) :: ExAgent.ModelProfile.t()
Declares capabilities (optional; extra capabilities default to false). The core rejects
function tools and tool-based structured output when supports_tools is false.
Explicit native output requires supports_json_schema_output: true before
request admission. Native JSON/thinking flags do not describe tool-output mode and
are not used to silently downgrade it. Streaming requires its own callback.
@callback request( model :: model(), messages(), ExAgent.ModelSettings.t() | nil, ExAgent.ModelRequestParameters.t() ) :: {:ok, ExAgent.Message.Response.t(), model()} | {:error, term()}
Make a non-streaming request and return a full Message.Response.
Implementations return the (possibly updated) model struct alongside the
response. Real providers return the model unchanged; stateful models (e.g.
the script-driven Test) thread their internal state through the run this
way, avoiding globals.
@callback request_stream( model :: model(), messages(), ExAgent.ModelSettings.t() | nil, ExAgent.ModelRequestParameters.t() ) :: Enumerable.t() | {:error, term()}
Make a lazy streaming request. Events are {:text_delta, binary}, optional
{:thinking_delta, binary} (separate from output text), optional
{:usage, cumulative_request_usage}, and exactly one terminal
{:response, response, final_model} or {:error, reason}. Usage snapshots
replace earlier snapshots for this request; they are not additive deltas.
When params.tool_call_deltas is true, implementations may also emit
{:tool_call_delta, %{index: index, name: name_or_nil, fragment: binary}}
as tool-call arguments are generated: the first event for an index carries
the tool name, later ones carry raw argument JSON fragments. They are a
preview only; the terminal response stays authoritative.
Implementations must release resources when enumeration halts. A stream
ending without a terminal is a protocol error. Implementations may return
{:error, _} directly when streaming is unsupported.
@callback validate_resume( model(), messages(), ExAgent.ModelSettings.t(), ExAgent.ModelRequestParameters.t() ) :: :ok | {:error, term()}
Optional no-IO preflight required by persisted continuation. Validate restored logical history, provider/endpoint binding and codec/settings using the trusted current model and tool inventory. Never dispatch a request from this callback. Ordinary run/request consumers do not require it.
Functions
Returns the bounded, portable static continuation binding; no Model IO.
@spec profile(model()) :: ExAgent.ModelProfile.t()
@spec request( model(), messages(), ExAgent.ModelSettings.t() | nil, ExAgent.ModelRequestParameters.t() ) :: {:ok, ExAgent.Message.Response.t(), model()} | {:error, term()}
@spec request_stream( model(), messages(), ExAgent.ModelSettings.t() | nil, ExAgent.ModelRequestParameters.t() ) :: Enumerable.t()
@spec resolve(model() | String.t() | map() | tuple(), keyword()) :: {:ok, model()} | {:error, term()}
Resolve a custom Model struct, Test label, or stock ReqLLM model spec.
Custom structs are returned unchanged. Catalogue strings, explicit maps, stock
tuples and LLMDB.Model specs resolve through public ReqLLM.model/1 and use
ExAgent.Models.ReqLLM. Catalogue membership does not enable tools or streaming.
Pass instance credentials and an explicitly qualified profile with resolve/2,
or construct ExAgent.Models.ReqLLM directly. No credentials are read from the
environment by this resolver. opencode: and zai: shortcuts require explicit
spec/endpoint/auth migration; they never silently choose another provider.