Models, budgets and limits

View Source

ExAgent uses official stock ReqLLM as its general backend. Custom adapters implement ExAgent.Model; the Test adapter runs locally. A model being present in a catalogue is distinct from its tools, output and streaming profile being qualified for ExAgent.

Resolve a text model

The application supplies credentials. This example resolves configuration; calling ExAgent.run/3 with it makes a real provider request:

{:ok, model} = ExAgent.Model.resolve({:openai, id: "gpt-4o-mini"},
  api_key: System.fetch_env!("OPENAI_API_KEY")
)
agent = ExAgent.new(model: model, instructions: "Be concise.")

Use instance credentials for explicit ownership. ReqLLM's startup environment loading is described in Getting started. Never place a key in a prompt or persist the complete live model.

Configure Chat tools and streaming

The explicit profile binds the protocol, tools and reasoning capability:

model = ExAgent.Models.ReqLLM.new(
  model: %{
    provider: :openai,
    id: "gpt-4o-mini",
    capabilities: %{tools: %{enabled: true}, reasoning: %{enabled: false}},
    extra: %{wire: %{protocol: "openai_chat"}}
  },
  api_key: System.fetch_env!("OPENAI_API_KEY"),
  tool_profile: :chat_tools_v1
)

These declarations configure admission; they do not prove the endpoint behaves correctly. Qualify the exact gateway/model combination you deploy. The supported profiles describe the available tool/output combinations. Unrepresented reasoning, modality and continuation combinations reject explicitly.

Native output is separate: output_profile: :chat_json_schema_v1 plus agent output_mode: :native. See Tools and output.

Route OpenRouter Chat tools explicitly

Use tool_profile: :openrouter_chat_tools_v1 for a qualified OpenRouter Chat endpoint. The host supplies the exact model ID and truthful capabilities:

model = ExAgent.Models.ReqLLM.new(
  model: %{
    provider: :openrouter,
    id: qualified_model_id,
    capabilities: %{tools: %{enabled: true}, reasoning: %{enabled: false}},
    extra: %{wire: %{protocol: "openai_chat"}}
  },
  api_key: System.fetch_env!("OPENROUTER_API_KEY"),
  tool_profile: :openrouter_chat_tools_v1,
  provider_options: [
    openrouter_provider: %{only: [qualified_provider_id], allow_fallbacks: false}
  ]
)

Routing admits order, only, ignore, allow_fallbacks and require_parameters. Provider lists contain 1–32 bounded identifiers. Optional app_title and app_referer are attribution; other wire settings remain closed. The mandatory tool argument envelope and validation still apply.

For a reasoning-capable model, reasoning_mode: :none additionally requires declared support for effort "none" and disabling thinking. It sends one max_tokens budget. Declaring those capabilities does not establish real provider compatibility. This tools profile does not enable native JSON output.

The route and reasoning mode are bound before the first request and in tool history. Changing them requires a fresh conversation; an existing approval or uncertain request cannot resume through another route. Earlier unbound OpenRouter history is rejected. See Migration for continuation version 4 and the unaffected OpenAI history versions.

Bound requests and tool calls

agent = ExAgent.new(
  model: "test",
  usage_limits: %ExAgent.UsageLimits{request_limit: 3, tool_calls_limit: 2}
)
{:ok, result} = ExAgent.run(agent, "Hello!")
result.request_count
# => 1

Request and tool counters are host observations. Limits apply before admission of the next operation. Scoped child runs inherit ancestor authority and budgets; a child cannot enlarge the parent's allowance. Parent usage is inclusive of its children, so adding every span's totals would count delegated work twice.

For token/cost dimensions, read ExAgent.UsageLimits and ExAgent.CostGuard. Strict metric accounting requires a finite request limit. Provider-reported or normalized tokens and estimated cost have quality/status fields. Unknown usage or cost is not zero, and an estimated threshold is not a guaranteed invoice ceiling.

Separate the timeout boundaries

BoundaryMeaning
ModelSettings.timeoutReceive inactivity timeout; takes precedence over the model's receive setting.
ReqLLM total_timeoutPositive millisecond bound on one buffered model operation.
Run deadlineAbsolute monotonic milliseconds, inherited and narrowed by child scope.
Continuation lease and active-time budgetBounds one owned continuation attempt; not permission to retry an uncertain effect.

Stopping an owned task closes framework resources. It cannot roll back external IO or guarantee cancellation of arbitrary application callbacks. Host retention limits apply after stock decoding; no upstream hard RAM bound before decoding is promised. Migration documents option precedence and failure shapes.

An incomplete response stays in RunError.partial.pending_response, outside executable history. Its duplicate in RunError.reason may carry an explicit payload_omitted marker when that error copy would exceed 4 KiB. Read the cause from the bounded RequestError and diagnostic partial data from RunError; neither authorizes tools or proves that a turn completed. Arbitrary oversized error control still fails the original retention limit.

API: ExAgent.Models.ReqLLM, ExAgent.Model, ExAgent.ModelSettings.