Skip to content
中文

Degradation and resilience ​

In real environments failures are normal: provider rate limits, embedding timeouts, an index that is not ready, deleted material, exhausted budgets, user cancellation. Resilience does not mean every request succeeds. It means failures stay explainable, recoverable and free of fabricated results.

Classify the failure first ​

FailureTypical causeRetryable
Model callTimeout, rate limit, 5xx, authentication failureRate limits and transient faults may retry with a bound; authentication does not
Tool executionInvalid input, unauthorized, business rejection, side-effect failureRead-only queries may retry; writes need an idempotency key
RetrievalEmbedding timeout, index not ready, no hitsMay fall back to lexical or a whole-document read
CorpusMissing file, empty corpus, stale revisionDo not retry; state that the material is unavailable
Budget and cancellationContext overflow, user cancellation, deadline reachedDo not retry; end under the original contract

The class decides the action. Treating every failure as "try again" amplifies rate limits, duplicates side effects and hides real configuration problems.

Three responses ​

  1. Bounded retry: only for transient, idempotent, cancellable calls. Bound attempts, interval and total deadline, and keep propagating signal.
  2. Degrade: narrow the capability while preserving real sources and identity. For example, fall back to lexical search when retrieval fails, or to a whole-document read when vectors are unavailable.
  3. Fail explicitly: when no safe fallback exists, state the failure or ask a clarifying question instead of inventing a result.

Degradation never means switching to a looser permission path or bypassing validation. Identity, authorization, budgets and cancellation stay in force on every fallback path.

A handling matrix ​

ScenarioRecommended actionAvoid
Model rate limitBounded retry with the original request and cancellationUnbounded retries or an unauthorized model
Model authentication failureFail with a clear error and record the configuration sourceSilently falling back to a generic answer
Invalid tool inputReturn a validation error and clarify if neededRewriting the user's intent and executing again
Write timeoutReconcile the real commit using the idempotency keyRetrying blindly and writing twice
Embedding failureFall back to lexical searchPresenting stale vectors as new results
Index not readyWhole-document read or lexical searchTreating "not ready" as "no material"
No retrieval hitsState that no material existsFilling the gap with general knowledge
Budget exceededFewer candidates or reads in passesTruncating rules or evidence
User cancellationStop further calls and keep the reasonRecording cancellation as success

Timeouts and cancellation ​

  • Give each layer its own timeout and make the total deadline cover the whole chain. An inner timeout should not outlive an outer one.
  • Propagate cancellation through signal. External side effects that already happened cannot be pretended away.
  • Timeout and cancellation are different outcomes. Timeouts may retry; cancellations usually should not.
  • When a stream is interrupted, do not send a fabricated successful terminator.

What not to do ​

  • Loosen authorization, skip source checks or trust client-asserted identity to raise the success rate.
  • Turn retrieval failure into "there is no such material", making users believe the fact does not exist.
  • Treat stale index copies, old caches or a previous answer as current facts.
  • Keep calling tools or models after cancellation and discard late results.
  • Cover every failure with one canned reply, making the problem invisible.

Observability ​

Record the failure stage, tool or retriever ID, error code, attempt count, whether degradation happened, its reason, total latency and cancellation reason. Never log secrets, full prompts or unmasked personal data.

Logs should answer "why did this request degrade", not only "did it succeed". When degradation rises, check dependency health before tuning prompts.

Verify with MockGPT ​

Failure paths should be reproducible. Offline verification with MockGPT can inject HTTP errors, network rejection, stream interruption and cancellation to confirm that:

  • Retry counts are actually bounded;
  • Degradation really happens instead of a silent success;
  • A failed write is reconciled using its idempotency key;
  • Cancellation stops subsequent calls.

Health checks for real dependencies belong to deployment. Simulation only verifies code branches.

A decision order ​

ts
type Outcome =
  | { kind: 'answer'; sources: string[] }
  | { kind: 'unknown'; reason: 'no-material' }
  | { kind: 'degraded'; reason: 'retrieval-unavailable' }
  | { kind: 'failed'; reason: string };

async function answer(question: string): Promise<Outcome> {
  const sources = await retrieve(question).catch(() => undefined);
  if (!sources) return { kind: 'degraded', reason: 'retrieval-unavailable' };
  if (!sources.length) return { kind: 'unknown', reason: 'no-material' };
  return { kind: 'answer', sources };
}

The example only shows the order: separate "retrieval unavailable" from "no material", then choose answer, degradation or failure. Real authorization, budgets and cancellation checks still happen through their own interfaces.

Common issues ​

SymptomCauseFix
More requests during rate limitingRetries are unbounded or lack backoffBound attempts, interval and total deadline
A failure looks like "no material"Degradation reasons are not separatedDistinguish retrieval-unavailable from no-material
Writes happen twiceA timeout was retried directlyUse an idempotency key and reconcile the commit
Output continues after cancellationsignal is not propagatedCheck cancellation before every layer call
Degradation goes unnoticedOnly success metrics are recordedTrack degradation and failure reasons separately

Next ​

API reference, usage examples and developer guides.