BerriAI/litellm · error · HTTPException
Microsoft Purview DLP: Token-id completion prompts cannot be
Error message
Microsoft Purview DLP: Token-id completion prompts cannot be scanned for DLP in blocking mode
What it means
Fail-closed HTTPException from the Purview pre-call hook on /v1/completions (text_completion/atext_completion). Token-id prompts — a flat list[int], list[list[int]], or any mixed list containing token-id sub-arrays — carry no scannable text, so Purview cannot evaluate them; blocking mode therefore rejects them with 400 instead of letting content bypass DLP. Note the hook deliberately ignores a crafted 'messages' key when 'prompt' is present.
Source
Thrown at litellm/proxy/guardrails/guardrail_hooks/microsoft_purview/purview_dlp.py:413
prompt_text: str | None = None
if call_type in ("responses", "aresponses"):
# Route Responses API calls to the responses-specific extractor
# before the generic ``messages`` branch. This mirrors
# ``async_logging_hook`` and ensures ``instructions`` (system
# prompt) content is included in the DLP scan, and prevents a
# crafted ``messages`` key in the request from being scanned in
# place of the actual ``input``.
prompt_text = self._responses_api_input_to_str(data, raise_on_failure=True)
elif call_type in ("text_completion", "atext_completion"):
raw_prompt: Final = data.get("prompt")
# Reject every token-id prompt shape Purview cannot evaluate —
# flat ``list[int]`` (single prompt), ``list[list[int]]`` (multi-prompt
# batches), and mixed lists that include any token-id sub-array.
# Empty/whitespace-only strings also yield ``prompt_text is None`` but
# contain no sensitive data and pass through harmlessly below.
if self.is_token_id_prompt(raw_prompt):
raise HTTPException(
status_code=400,
detail={
"error": (
"Microsoft Purview DLP: Token-id completion prompts "
"cannot be scanned for DLP in blocking mode"
),
},
)
prompt_text = self.completion_prompt_to_str(raw_prompt)
else:
messages: Final[list | None] = data.get("messages")
if messages:
prompt_text = self.get_prompt_text_for_dlp(cast(list[Any], messages))
if not prompt_text:
return data
await self._check_content(View on GitHub (pinned to 77b7c6c40c)
Solutions
- Send the prompt as a string, or a list of strings, instead of token ids: prompt="..." or prompt=["...", "..."]
- Or move the workload to /chat/completions (messages), which the guardrail can scan natively
- If token privacy is the reason for token-id prompts, that traffic fundamentally cannot pass a text DLP scan — keep it on an unguarded deployment
Example fix
# before client.completions.create(model="my-model", prompt=[9122, 233, 908]) # after client.completions.create(model="my-model", prompt="Summarize the incident report")
Defensive patterns
Strategy: type-guard
Validate before calling
def is_token_id_prompt(prompt) -> bool:
if isinstance(prompt, list) and prompt:
if all(isinstance(t, int) for t in prompt):
return True
if any(isinstance(p, list) for p in prompt):
return True
return False
# before sending to a Purview-guarded /v1/completions route:
if is_token_id_prompt(payload["prompt"]):
raise ValueError("blocking DLP cannot scan token-id prompts; send text") Type guard
from typing import Any
def is_scannable_completion_prompt(prompt: Any) -> bool:
"""True when prompt is text LiteLLM's DLP scan can evaluate."""
if isinstance(prompt, str):
return True
if isinstance(prompt, list):
return all(isinstance(p, str) for p in prompt)
return False Prevention
- Default your SDK calls to string prompts; only use token arrays on routes known to be unguarded
- Encode this rule in your API gateway/client wrapper: reject token-id prompts before they hit the proxy
- Document for the team: token-id inputs are structurally incompatible with text-based DLP scanning
When it happens
Trigger: Calling /v1/completions with prompt=[1234, 5678, ...] or prompt=[[...], [...]] through a model with a blocking Purview guardrail (mode pre_call); some client SDKs defaulting to token arrays when given pre-tokenized input
Common situations: Porting token-level workflows or caching-by-token-id pipelines behind a LiteLLM proxy; research tooling that passes BPE ids directly; upgrading a completion endpoint to add DLP and discovering the old payload shape is now rejected
Related errors
- Microsoft Purview DLP: Responses API input could not be tran
- Microsoft Purview DLP: No proxy-authenticated user identity;
- Microsoft Purview: tenant_id is required
- Microsoft Purview: client_id is required
- Microsoft Purview: client_secret (or api_key) is required
AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18).
Data as JSON: /api/errors/ec0053e17d79dbdf.
Report an issue: GitHub.