Skip to content

Improve modelsdev package#2182

Merged
dgageot merged 15 commits intodocker:mainfrom
dgageot:modelerrs
Mar 19, 2026
Merged

Improve modelsdev package#2182
dgageot merged 15 commits intodocker:mainfrom
dgageot:modelerrs

Conversation

@dgageot
Copy link
Member

@dgageot dgageot commented Mar 19, 2026

No description provided.

dgageot added 15 commits March 19, 2026 19:24
ExtractHTTPStatusCode + IsRetryableStatusCode already handle numeric
status codes (500, 502, 429, 401, etc.) via regex with word boundaries.
Having the same codes as bare substrings in the retryable/non-retryable
pattern lists caused false positives (e.g., "processed 500 items"
would match). Remove the numeric patterns and keep only the descriptive
text patterns.

Assisted-By: docker-agent
The only caller outside the package is ClassifyModelError itself
(as a fallback path). ClassifyModelError is the intended public API
and provides strictly more information (retryable + rateLimited +
retryAfter). Rename to isRetryableModelError to reduce API surface.

Assisted-By: docker-agent
Now that all providers (Anthropic, OpenAI, Gemini) wrap their errors in
*StatusError via WrapHTTPError, the provider-specific type assertions
(anthropic.Error, genai.APIError) in ExtractHTTPStatusCode are redundant.
ClassifyModelError already checks *StatusError first.

Replace the SDK-specific checks with a *StatusError check, keeping the
regex fallback for unwrapped errors. This removes the coupling between
the error classification package and provider-specific SDKs.

Assisted-By: docker-agent
…tusCode

The regex match is a simple integer string. strconv.Atoi is simpler,
faster, and already imported. This also removes the fmt import.

Assisted-By: docker-agent
math/rand/v2 is the preferred package since Go 1.22. The API is
compatible (rand.Float64 exists in both).

Assisted-By: docker-agent
…l vars

The retryablePatterns and nonRetryablePatterns slices were local
variables inside isRetryableModelError, causing a new allocation on
every call. Promote them to package-level vars (like
contextOverflowPatterns) so they are allocated once.

Assisted-By: docker-agent
SleepWithContext and CalculateBackoff are general-purpose utilities not
specific to model errors. Move them to a new pkg/backoff package for
clarity and reusability. MaxRetryAfterWait moves there too.

Deprecated wrapper variables are left in modelerrors to maintain
backward compatibility. The runtime/fallback package now imports
pkg/backoff directly.

Assisted-By: docker-agent
StatusError.Error() previously delegated directly to the wrapped error,
discarding the status code. This made debugging harder since log lines
wouldn't reveal the HTTP status. Now Error() returns a message like
"HTTP 429: rate limit exceeded" for better observability.

Assisted-By: docker-agent
Callers were constructing &ContextOverflowError{Underlying: err}
directly. Adding a constructor improves encapsulation and allows
future field additions without breaking callers. Update
runtime/fallback.go to use the constructor.

Assisted-By: docker-agent
Add tests for:
- ClassifyModelError with ContextOverflowError wrapping a StatusError
- ExtractHTTPStatusCode with *StatusError in the error chain
- FormatError with a wrapped ContextOverflowError (via fmt.Errorf)
- NewContextOverflowError constructor usage
- ContextOverflowError with nil Underlying (Unwrap returns nil)

Assisted-By: docker-agent
Bedrock was the only provider not wrapping SDK errors in
*modelerrors.StatusError. This meant ClassifyModelError had to fall back
to regex parsing for Bedrock errors.

Add wrapBedrockError() which extracts the HTTP status code and
Retry-After header from smithyhttp.ResponseError, and call it from both
the stream adapter Recv() and CreateChatCompletionStream().

Assisted-By: docker-agent
CalculateBackoff and SleepWithContext were deprecated in favor of
backoff.Calculate and backoff.SleepWithContext. No callers use the
modelerrors versions — all code imports pkg/backoff directly.

Remove the deprecated wrappers to reduce API surface.

Assisted-By: docker-agent
Only called within the package (by WrapHTTPError and tests).
Rename to parseRetryAfterHeader to reduce the public API surface.

Assisted-By: docker-agent
Both are only used within the package. Rename to isRetryableStatusCode
and extractHTTPStatusCode to reduce the public API surface.

Assisted-By: docker-agent
No callers reference modelerrors.MaxRetryAfterWait — all usage goes
through backoff.MaxRetryAfterWait directly. Remove the constant and
the now-unused backoff import.

Assisted-By: docker-agent
@dgageot dgageot requested a review from a team as a code owner March 19, 2026 18:52
Copy link

@docker-agent docker-agent bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟢 APPROVE

This PR refactors error handling and backoff utilities with no bugs found in the changed code.

Changes reviewed:

  • ✅ New pkg/backoff package with exponential backoff calculation
  • ✅ Bedrock error wrapping with StatusError for retry classification
  • ✅ Refactored modelerrors package (unexported internal functions)
  • ✅ Updated pkg/runtime/fallback.go to use new utilities

Verification notes:

  • Nil pointer checks are correctly implemented in wrap.go
  • Backoff calculations match test expectations
  • Error classification logic is sound
  • Resource management follows existing patterns

The code is well-tested and ready to merge.

@dgageot dgageot merged commit a3e4acb into docker:main Mar 19, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants