fix(quota): run input guardrails before the rate-limit reservation (#542) - #561
Conversation
) A guardrail-blocked request burned an RPM/RPD/RPS/RPH slot: /v1/messages, /v1/responses, /v1/embeddings ran check_input AFTER crate::quota::enforce, and Reservation::drop only releases the concurrency permit — it never refunds the request counter. So a content-policy refusal counted against the caller's quota. /v1/chat/completions already runs guardrails BEFORE the reservation specifically to avoid this. Hoist the resolve-chain + check_input block above quota::enforce on all three surfaces (messages pre-existing; responses + embeddings widened by #541/#544). Budget pre-check ordering is unchanged. Not fixed via Reservation::drop refund — that would also refund slots on upstream failures, a separate policy. Test: blocked_request_does_not_consume_rate_limit_slot — RPM=1, a blocked request then a benign one; the benign request still returns 200 (pre-fix it got 429 because the block burned the slot). fmt + clippy clean; 408 lib tests pass.
ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Free Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughThree request handlers reorder quota enforcement to occur after input guardrail checks, preventing content-policy blocks from consuming rate-limit slots. A test verifies blocked requests preserve the quota reservation. ChangesGuardrail-before-quota reordering
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Note 🎁 Summarized by CodeRabbit FreeYour organization has reached its limit of developer seats under the Pro Plan. For new users, CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please add seats to your subscription by visiting https://app.coderabbit.ai/login.If you believe this is a mistake and have available seats, please assign one to the pull request author through the subscription management page using the link above. Comment |
What
A guardrail-blocked request burned an RPM/RPD/RPS/RPH slot.
/v1/messages,/v1/responses,/v1/embeddingsrancheck_inputaftercrate::quota::enforce, andReservation::droponly releases the concurrency permit — it never refunds the request counter. So a content-policy refusal counted against the caller's quota./v1/chat/completionsalready runs guardrails before the reservation precisely to avoid this (its own code comment says so).How
Hoist the resolve-chain +
check_inputblock abovequota::enforceon all three surfaces. (messages was pre-existing; responses + embeddings inherited the ordering from #541/#544.) The budget pre-check ordering is unchanged. Deliberately not fixed via aReservation::droprefund — that would also refund slots on upstream 5xx/timeouts, a separate policy decision affecting every surface.Test plan
blocked_request_does_not_consume_rate_limit_slot: API key capped at RPM=1, a blocking guardrail; a blocked request (422) followed by a benign one — the benign request still returns 200 (pre-fix it got 429 because the block burned the only slot). The existing per-surface input-block tests still pass (the guardrail blocks correctly in its new position). fmt + clippy clean; 408aisix-proxylib tests pass.Surfaced by the independent pre-fix audit of the #719 follow-up batch. Refs #542, #719.
Summary by CodeRabbit
Improvements
Tests