pub enum ModelError {
Unreachable {
model: ModelId,
detail: String,
},
Refused {
model: ModelId,
detail: String,
},
Egress {
model: ModelId,
detail: String,
},
RateLimited {
model: ModelId,
detail: String,
retry_after: Option<u64>,
},
Interrupted {
model: ModelId,
usage: Usage,
detail: String,
},
Unavailable {
model: ModelId,
detail: String,
},
Unaccounted {
model: ModelId,
detail: String,
},
Unusable {
model: ModelId,
usage: Usage,
detail: String,
},
}Expand description
Why a completion failed.
Variants§
Unreachable
Never reached the provider.
Refused
The provider refused before generating: bad request, unknown model, a content filter on the input. Nothing was metered.
Egress
This plane refused to connect: the driver’s host is not one the
deployment’s Egress allowlist grants.
Distinct from Refused, which says the
provider declined. Both are DidNotHappen and neither spends a retry
attempt, so the distinction buys no different recovery — it buys the
operator the right half of the system to go and look at. Reported as a
provider refusal, an egress misconfiguration sends somebody to a
vendor’s status page.
RateLimited
Rate-limited before generating.
Separate from Refused because the response is
different: this one is worth retrying, and it is the one case here where
retrying is unambiguously safe.
retry_after is the provider’s own Retry-After, in seconds, when it
named one. Carried rather than discarded because the window it names is
the only number that makes retrying useful: a computed backoff measured
in hundreds of milliseconds spends every permitted attempt inside a
window measured in tens of seconds, and reports the provider as down.
Interrupted
It generated, and then the stream died.
The expensive case. The tokens counted here have been spent whatever happens next.
It reached the provider, and nothing came back that says whether it generated.
A non-streaming 5xx, or a response that could not be read. The honest
position is that this is unknowable from here, and both guesses are
wrong in a different way: calling it Interrupted makes a transient blip
fatal, and calling it free lets a retry loop spend real money against a
ceiling that reads zero.
Treated as safe to repeat, because a completion does not change the world — so repeating is a correctness no-op and only a cost. The documented price is that the spend ceiling may under-count by at most one call per occurrence.
A driver that can see partial usage must report
Interrupted instead — which is what both
shipped drivers do when streaming, and why they stream by default. Where
the provider makes even that impossible, the answer is
Unaccounted, not this.
Unaccounted
It generated, the stream died, and the cost is unknowable.
The state OpenAI’s Responses stream can produce and Anthropic’s cannot.
Usage appears there only in the terminal event, so a connection cut after
four hundred tokens of deltas leaves the driver certain that generation
happened and ignorant of what it cost.
Neither neighbour says that, which is why this variant exists rather than being folded into one of them:
Unavailablemeans it may never have generated, and is therefore safe to repeat. Here we watched it generate; asking again buys a second bill for the same question.Interruptedcarries aUsage, and filling it with zeroes is the “guess free” failure this crate refuses everywhere else — it reads as this cost nothing rather than as nobody knows.
So it is Disposition::Landed with no usage, and the under-count is
admitted rather than hidden: the budget will be short by whatever this
call generated. What the variant buys is that the runtime stops paying
twice for it. A caller who needs the true figure has the provider’s
response id and a Recovery policy to reconcile with; a driver quietly
making a second unjournaled request to find out is not the answer.
Unusable
It answered, and the answer was not usable — truncated JSON, a refusal where a tool call was required. Metered, because it generated.
Implementations§
Source§impl ModelError
impl ModelError
Sourcepub const fn disposition(&self) -> Disposition
pub const fn disposition(&self) -> Disposition
What this failure says about whether the call reached the provider.
Sourcepub const fn usage(&self) -> Usage
pub const fn usage(&self) -> Usage
What was consumed before the failure.
Enumerated rather than defaulted, because the default is free and
this is what the token and cost ceilings are computed from. A variant
added later that carries a usage would compile, pass every test here,
and report nothing consumed — which is the direction the ceilings exist
to prevent. Unaccounted reports zero deliberately and says so on its
own documentation: what is unknown there is the amount, not whether it
happened, and its disposition already carries the latter.
Trait Implementations§
Source§impl Debug for ModelError
impl Debug for ModelError
Source§impl Display for ModelError
impl Display for ModelError
Source§impl Error for ModelError
impl Error for ModelError
1.30.0 · Source§fn source(&self) -> Option<&(dyn Error + 'static)>
fn source(&self) -> Option<&(dyn Error + 'static)>
1.0.0 · Source§fn description(&self) -> &str
fn description(&self) -> &str
use the Display impl or to_string()
Auto Trait Implementations§
impl Freeze for ModelError
impl RefUnwindSafe for ModelError
impl Send for ModelError
impl Sync for ModelError
impl Unpin for ModelError
impl UnsafeUnpin for ModelError
impl UnwindSafe for ModelError
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more