Normandy.LLMError exception
(normandy v2.2.0)
View Source
A failed LLM call.
Adapters return {:error, %Normandy.LLMError{}} from Normandy.Agents.Model.converse/3;
Once retries (if configured) are exhausted, Normandy.Agents.BaseAgent.run/3 returns it
as {:error, %Normandy.LLMError{}} and run!/3 raises it.
type: :unsupported means the client can't do what was asked (a client without
streaming answering Normandy.Agents.Model.stream/3); it is never retryable.
type: :max_tokens means the answer stopped before it was complete: the response hit
the request's max_tokens, or the context window (model_context_window_exceeded; the
message names which). type: :refusal means the model declined to answer. Both are
retryable?: false; :reason is {:stop_reason, reason}, never model text, and
:usage is what the call billed. Normandy builds them from the response's
stop_reason, so every client gets them.
type: :pause_turn means the final call after max_tool_iterations was still paused
(stop_reason pause_turn) after three continuations. It is retryable?: false;
:reason is {:stop_reason, :pause_turn}, never model text, and :usage is the paused
response's own when it carries any (BaseAgent's do not: each call's usage is on its
llm_call span). The turn core builds it, so no circuit breaker counts it.
:type— failure class (seeerror_type/0):status— HTTP status when there was one, elsenil:retryable?— whether a retry can help; set by the adapter that classified the failure:message—"LLM call failed: <type> (<status>): <detail>":reason— the provider's original error, for debugging:usage— raw token usage already paid for before the call failed (for example the first response plus JSON-parse retries that ended in:invalid_output),nilwhen no call was billed:usage_counts— the same usage normalized byNormandy.Telemetry.Usage.normalize/1(:input_tokens,:output_tokens, ...),nilwhen:usageis
The llm_call span's [:normandy, :agent, :llm_call, :exception] metadata cannot carry
extra keys, so this usage is read from its reason: metadata.reason.usage and
metadata.reason.usage_counts.
The message never includes the client, its options or req_options.
Summary
Functions
Builds an error. :type and :retryable? are required; :status, :detail (text for
the message) and :reason are optional.
Attaches the raw usage already billed before the failure (and its normalized counts).
A nil usage is ignored, and the first usage wins: an error that already carries usage
is returned unchanged.
Returns an LLMError unchanged; wraps any other term as type: :unknown,
retryable?: false, keeping the term in :reason (never in the message).
Types
@type error_type() ::
:rate_limit
| :overloaded
| :server
| :transport
| :authentication
| :permission
| :not_found
| :invalid_request
| :invalid_output
| :max_tokens
| :refusal
| :pause_turn
| :stream
| :circuit_open
| :unsupported
| :unknown
@type t() :: %Normandy.LLMError{ __exception__: true, message: String.t(), reason: term(), retryable?: boolean(), status: non_neg_integer() | nil, type: error_type(), usage: map() | struct() | nil, usage_counts: Normandy.Telemetry.Usage.normalized() | nil }
Functions
Builds an error. :type and :retryable? are required; :status, :detail (text for
the message) and :reason are optional.
Attaches the raw usage already billed before the failure (and its normalized counts).
A nil usage is ignored, and the first usage wins: an error that already carries usage
is returned unchanged.
Returns an LLMError unchanged; wraps any other term as type: :unknown,
retryable?: false, keeping the term in :reason (never in the message).