BerriAI/litellm · error · HTTPException
Microsoft Purview DLP: upstream policy evaluation failed
Error message
Microsoft Purview DLP: upstream policy evaluation failed
What it means
Runtime HTTPException raised from the Purview guardrail hook when the upstream Microsoft Purview DLP policy-evaluation call returns an HTTP error status (httpx.HTTPStatusError). LiteLLM maps upstream 401/403 to client-facing 502 (a proxy-side credential/consent problem the caller cannot fix), passes other upstream statuses through, and forwards any Retry-After header. It only raises when block_on_violation/fail-closed mode is on; in logging-only mode the same failure is just logged and the request proceeds.
Source
Thrown at litellm/proxy/guardrails/guardrail_hooks/microsoft_purview/purview_dlp.py:155
except HTTPException:
status = "guardrail_failed_to_respond"
raise
except httpx.HTTPStatusError as exc:
# Preserve the upstream Graph API status code (e.g. 429, 503) so
# callers can distinguish a transient infrastructure error from a
# DLP policy block (signaled separately as HTTP 400 below) and can
# implement retry-after handling on rate limits. 401/403 upstream
# responses indicate a proxy-side credential / consent problem the
# caller can do nothing about, so they are mapped to 502.
status = "guardrail_failed_to_respond"
if block_on_violation:
upstream_status: Final = exc.response.status_code
client_status: Final = 502 if upstream_status in (401, 403) else upstream_status
headers: dict[str, str] | None = None
retry_after: Final = exc.response.headers.get("retry-after")
if retry_after:
headers = {"Retry-After": retry_after}
raise HTTPException(
status_code=client_status,
detail={
"error": "Microsoft Purview DLP: upstream policy evaluation failed",
"activity": activity,
"upstream_status": upstream_status,
"exception": str(exc),
},
headers=headers,
) from exc
verbose_proxy_logger.warning(
"Purview DLP: API/network error in logging-only mode (not re-raised): %s",
exc,
)
except Exception as exc:
status = "guardrail_failed_to_respond"
if block_on_violation:
raise HTTPException(
status_code=400,View on GitHub (pinned to 77b7c6c40c)
Solutions
- For 502 responses: re-check tenant_id/client_id/client_secret in the guardrail config, rotate the secret if expired, and verify the app registration's Purview API permissions + admin consent
- For 429: honor the forwarded Retry-After header, add jittered client-side retry, and reduce request rate or scale Purview quota
- For passthrough 5xx: check Azure status / retry idempotent requests
- Temporarily set block_on_violation/log-only mode to keep traffic flowing while diagnosing, then re-enable fail-closed
Example fix
# before (client, no retry-aware handling)
resp = client.chat.completions.create(model="gpt-4o", messages=msgs)
# after: retry on 429/5xx honoring Retry-After
import time, httpx
for attempt in range(5):
try:
resp = client.chat.completions.create(model="gpt-4o", messages=msgs)
break
except httpx.HTTPStatusError as e:
sc = e.response.status_code
if sc in (429, 500, 502, 503) and attempt < 4:
time.sleep(float(e.response.headers.get("retry-after", 2 ** attempt)))
continue
raise Defensive patterns
Strategy: retry
Validate before calling
# Before relying on Purview at runtime, smoke-test the credential path once at startup
import httpx
def purview_credentials_reachable(cfg) -> bool:
# token fetch exercises tenant/client/secret without touching DLP policy
r = httpx.post(
f"https://login.microsoftonline.com/{cfg['tenant_id']}/oauth2/v2.0/token",
data={"client_id": cfg["client_id"], "client_secret": cfg["client_secret"],
"grant_type": "client_credentials", "scope": "https://purview.azure.net/.default"},
)
return r.status_code == 200 Try / catch
# Client-side: classify by mapped status
# 502 -> upstream 401/403, proxy credential problem: do NOT retry, alert operator
# 429 -> retry honoring Retry-After (the proxy forwards it)
# 5xx -> retry with backoff
for attempt in range(5):
try:
out = call_llm(payload)
break
except httpx.HTTPStatusError as e:
sc = e.response.status_code
if sc == 502 and "Purview" in e.response.text:
alert("purview credentials broken"); raise
if sc in (429, 500, 502, 503) and attempt < 4:
time.sleep(float(e.response.headers.get("retry-after", 2 ** attempt))); continue
raise Prevention
- Monitor Purview guardrail failure rates by upstream_status; alert on 401/403->502 immediately
- Keep the Entra secret in a rotation pipeline with expiry alerts well before it lapses
- Decide explicitly per deployment whether guardrail failure should block (fail closed) or log (logging-only) and test that path
When it happens
Trigger: Expired Entra client secret or wrong tenant/client_id → Purview returns 401 → client sees 502; missing Purview consent/DLP permissions → 403 → 502; Purview throttling → 429 passes through with Retry-After; Purview 5xx or 400 passes through verbatim
Common situations: Secret rotated in Azure but not in config.yaml; Purview DLP policies not onboarded or the app lacks the required API permissions; network egress blocked so the token or evaluation call fails; intermittent 429s under load
Related errors
- Microsoft Purview: tenant_id is required
- Microsoft Purview: client_id is required
- Microsoft Purview: client_secret (or api_key) is required
- OpenAI Moderation API request failed
- Microsoft Purview: guardrail_name is required
AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18).
Data as JSON: /api/errors/ea42591ee7720839.
Report an issue: GitHub.