MCPcopy Index your code
hub / github.com/docker/docker-agent / handleModelError

Method handleModelError

pkg/runtime/fallback.go:399–469  ·  view source on GitHub ↗

handleModelError classifies err and decides what to do next: - retryDecisionReturn — context cancelled while sleeping; caller returns ctx.Err() - retryDecisionBreak — non-retryable error or 429 with fallbacks; skip to next model - retryDecisionContinue — retryable error or 429 without fallbacks

(
	ctx context.Context,
	err error,
	a *agent.Agent,
	modelEntry modelWithFallback,
	attempt int,
	hasFallbacks bool,
	primaryFailedWithNonRetryable *bool,
)

Source from the content-addressed store, hash-verified

397// Side-effect: sets *primaryFailedWithNonRetryable when the primary model fails with a
398// non-retryable (or rate-limited-with-fallbacks) error.
399func (e *fallbackExecutor) handleModelError(
400 ctx context.Context,
401 err error,
402 a *agent.Agent,
403 modelEntry modelWithFallback,
404 attempt int,
405 hasFallbacks bool,
406 primaryFailedWithNonRetryable *bool,
407) retryDecision {
408 retryable, rateLimited, retryAfter := modelerrors.ClassifyModelError(err)
409
410 if rateLimited {
411 // Gate: only retry on 429 if opt-in is enabled AND no fallbacks exist.
412 // Default behavior (retryOnRateLimit=false) treats 429 as non-retryable,
413 // identical to today's behavior before this feature was added.
414 if !e.retryOnRateLimit || hasFallbacks {
415 slog.WarnContext(ctx, "Rate limited, treating as non-retryable",
416 "agent", a.Name(),
417 "model", modelEntry.provider.ID().String(),
418 "retry_on_rate_limit_enabled", e.retryOnRateLimit,
419 "has_fallbacks", hasFallbacks,
420 "error", err)
421 if !modelEntry.isFallback {
422 *primaryFailedWithNonRetryable = true
423 }
424 return retryDecisionBreak
425 }
426
427 // Opt-in enabled, no fallbacks → retry same model after honouring Retry-After (or backoff).
428 waitDuration := retryAfter
429 if waitDuration <= 0 {
430 waitDuration = backoff.Calculate(attempt)
431 } else if waitDuration > backoff.MaxRetryAfterWait {
432 slog.WarnContext(ctx, "Retry-After exceeds maximum, capping",
433 "agent", a.Name(),
434 "model", modelEntry.provider.ID().String(),
435 "retry_after", retryAfter,
436 "max", backoff.MaxRetryAfterWait)
437 waitDuration = backoff.MaxRetryAfterWait
438 }
439 slog.WarnContext(ctx, "Rate limited, retrying (opt-in enabled)",
440 "agent", a.Name(),
441 "model", modelEntry.provider.ID().String(),
442 "attempt", attempt+1,
443 "wait", waitDuration,
444 "retry_after_from_header", retryAfter > 0,
445 "error", err)
446 if !backoff.SleepWithContext(ctx, waitDuration) {
447 return retryDecisionReturn
448 }
449 return retryDecisionContinue
450 }
451
452 if !retryable {
453 slog.ErrorContext(ctx, "Non-retryable error from model",
454 "agent", a.Name(),
455 "model", modelEntry.provider.ID().String(),
456 "error", err)

Callers 1

classifyAttemptErrorMethod · 0.95

Calls 6

ClassifyModelErrorFunction · 0.92
CalculateFunction · 0.92
SleepWithContextFunction · 0.92
NameMethod · 0.65
IDMethod · 0.65
StringMethod · 0.45

Tested by

no test coverage detected