fix(proxy): redact a refused request as soon as the pre-call walk ends

This commit is contained in:
yucheng 2026-10-31 14:24:00 -07:00
parent 190ffbe57b
commit 1e5ae915c8
2 changed files with 23 additions and 3 deletions

View file

@ -2633,10 +2633,9 @@ class ProxyBaseLLMRequestProcessing:
skip_guardrails=skip_guardrails,
routing_probe=routing_probe,
)
except Exception:
finally:
if routing_probe is not None and routing_probe.refusal is not None:
self._redact_request_its_skipped_guardrails_never_scanned()
raise
await _enforce_guardrail_added_tag_budgets(
data=self.data,
tags_before_guardrails=self._tags_before_guardrails,
@ -3078,7 +3077,6 @@ class ProxyBaseLLMRequestProcessing:
phase_event("litellm.request.pre_call_completed")
refusal: Final = None if self._routing_probe is None else self._routing_probe.refusal
if refusal is not None:
self._redact_request_its_skipped_guardrails_never_scanned()
raise refusal
_post_call_guardrails_active: Final = self._has_post_call_guardrails()

View file

@ -12991,6 +12991,13 @@ class _ModelMappingHook(CustomLogger):
return data
class _TaggingHook(CustomLogger):
"""A customer callback that adds a tag to the request."""
async def async_pre_call_hook(self, user_api_key_dict, cache, data, call_type):
return {**data, "metadata": {**(data.get("metadata") or {}), "tags": ["over-budget-tag"]}}
class _EmailMaskingHook(CustomLogger):
async def async_pre_call_hook(self, user_api_key_dict, cache, data, call_type):
_mask_emails(data)
@ -13419,6 +13426,21 @@ class TestUnroutableModelFailureLogHidesUnscannedContent:
assert (first.status, limited.status, guardrail.calls) == (400, 429, 0)
assert _shows_no_raw_content(limited), limited.failure_dumps
@pytest.mark.asyncio
async def test_failure_log_hides_content_when_a_tag_budget_rejects_after_a_skipped_guardrail(
self, monkeypatch: pytest.MonkeyPatch
):
async def over_budget(**_: object) -> None:
raise litellm.BudgetExceededError(current_cost=10.0, max_budget=5.0)
monkeypatch.setattr(litellm.proxy.common_request_processing, "tag_max_budget_check_for_tags", over_budget)
guardrail: Final = _VendorGuardrail(behavior="mask")
sent: Final = await _send(monkeypatch, [_TaggingHook(), guardrail])
assert (sent.status, guardrail.calls) == (422, 0), sent.message
assert _shows_no_raw_content(sent), sent.failure_dumps
@pytest.mark.asyncio
async def test_built_in_hook_after_a_skipped_guardrail_is_not_sent_the_content_it_would_have_masked(
self, monkeypatch: pytest.MonkeyPatch