Gateway error clarity: local stop-limit rejection + output-ceiling clamp - #633
Merged
Conversation
…error A route may support stop sequences yet cap how many (Gemini's direct lane accepts at most 5). Preflight only checked the boolean supports_stop_sequences, so a longer list was forwarded and came back as the provider's opaque 'provider rejected the request; verify the request fields' 4xx (reproduced in production: stop with 6 sequences 400s on the gemini direct lane, 5 passes). Add maximum_stop_sequences to GatewayDeploymentCapabilities (default None = unbounded, non-breaking) and enforce it in preflight_gateway_request with a field-specific ProviderParameterError(param='stop', code='too_many_stop_sequences') naming the limit, matching every other pre-dispatch parameter rejection. Populating the per-lane value (gemini=5) from the catalog is a separate catalog-authoring step. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t worst case maximum_attempt_cost_micro_usd used the raw caller maximum_output_tokens, so an unbounded or very large value inflated the worst case past MAXIMUM_MICRO_USD and returned None, which fails a fundable request closed and mis-terminalizes it as a quota refusal. The physical call can never emit more than the deployment's own ceiling, so clamp to it before the estimate. Settlement still charges actual tokens, so this only tightens a conservative bound. Closes the caller-driven source of the None worst case (the catalog-capability source was fixed separately). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
Greptile SummaryThe PR adds an optional deployment-level stop-sequence limit with a named local parameter error and makes attempt-cost estimation clamp caller output limits to the deployment ceiling.
Confidence Score: 5/5The PR appears safe to merge with no actionable correctness or security failures identified. Native admission rejects output limits above the resolved route capacity before reservation and dispatch, while the new optional stop limit remains backward compatible and is enforced whenever catalog authors configure it.
|
| Filename | Overview |
|---|---|
| exp/common/models/catalog.py | Adds a backward-compatible optional stop-sequence count capability with positive-value validation. |
| exp/runtime/models/providers/protocol.py | Rejects configured over-limit stop lists locally with a stable parameter-specific error. |
| exp/runtime/models/providers/protocol_test.py | Covers over-limit rejection, at-limit acceptance, and the default unbounded behavior. |
| exp/runtime/gateway/budgets.py | Uses the deployment output ceiling when calculating the conservative maximum attempt cost; native admission independently prevents larger limits from reaching dispatch. |
| exp/runtime/gateway/budgets_test.py | Verifies huge caller output limits remain bounded by deployment capacity instead of producing an unknown cost. |
Reviews (1): Last reviewed commit: "Gateway: clamp caller output ceiling to ..." | Re-trigger Greptile
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Gateway error clarity: two shipped fixes, three scoped follow-ups
Each fix is independent (drop either commit). Both trace to failure modes reproduced end to end against the live gateway this week, and both are Python on the admission/reservation path that survives the Rust data-plane migration (#629). No provider content, secrets, or free-text bodies enter any error, ledger, or log.
Shipped
1. Reject over-limit stop lists locally with a named parameter error (
3c40b4c3)A route can support stop sequences yet cap the count — Gemini's direct lane accepts at most 5.
preflight_gateway_requestonly checked the booleansupports_stop_sequences, so a longer list was forwarded to Google and returned the opaqueprovider rejected the request; verify the request fields4xx.stopwith 6 sequences 400s on the gemini direct lane; 5 passes (byte-identical to the incident rows).maximum_stop_sequencestoGatewayDeploymentCapabilities(defaultNone= unbounded, non-breaking) and enforces it in preflight withProviderParameterError(param="stop", code="too_many_stop_sequences")naming the limit — matching every other pre-dispatch parameter rejection.invalid_request. After: local named rejection an agent can self-correct from.maximum_stop_sequenceson the gemini deployment capability in the launch catalog).3. Clamp the caller output ceiling to the deployment before the cost worst case (
c44f052b)maximum_attempt_cost_micro_usdused the raw callermaximum_output_tokens, so an unbounded/huge value inflated the worst case pastMAXIMUM_MICRO_USD, returnedNone, and failed a fundable request closed — mis-terminalized as a quota refusal (the caller-driven twin of the catalog-capabilityNonesource fixed in platform #877).Validation
ruff format/checkclean,ty checkclean, and 826 passed acrossexp/common/models,exp/runtime/gateway,exp/runtime/models/providers(incl. the two added regression tests). Nothing merged.Scoped follow-ups (Rust data plane — not in this PR)
These are engine-owned in the native gateway and need the Rust build/parity-golden cycle; documented with exact targets rather than shipped unverified:
exp/runtime/gateway/native/src/admission.rs): the admit and reserve seams both surfacePublicError::internal()("gateway admission failed before provider dispatch") with no way to tell authority vs accounting vs wire-resolution apart. Add a coarse content-free cause tag. (M4, reproduced exactly via bounded fault injection.)exp/runtime/gateway/native/src/upstream.rs): dead lanes hang ~25s to the prod ceiling before failing (glm-5-turbo, qwen3.8-27b, others). Add a bounded connect/first-byte timeout — scoped to connect/first-byte, not total generation, so slow reasoning models are unaffected. This is genuinely a tuning knob: recommend a config value with a conservative default rather than a hardcode.upstream.rs, mirrored inexp/runtime/models/providers/errors.py): the genericinvalid_requestmapping discards the provider's sanitized status/machine code that's already available — include the code/param (still no free-text body) so agents self-correct. (Same family as fix Skeleton: LLM-as-environment world model harness #1; the live 4xx for direct lanes is emitted in Rust.)Note on scope vs. the original request
The brief named Python symbols (
gemini_requests.py, the transport classifier) as the serving encoders. Post-#629 the live data plane is Rust:GatewayWireProfile's own docstring confirms the native plane builds provider payloads and parses streams, so the outbound stop-sequence emission and generic 4xx mapping live in Rust (native/src/), while the Python provider modules serve the resolved-client/offline path. Fix #1 is therefore placed inpreflight_gateway_request— the Python admission guard that runs before dispatch regardless of encoder — which is both correct and encoder-independent. Fixes #2/#4/#5 are Rust and listed above.🤖 Generated with Claude Code