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

  1. Send the prompt as a string, or a list of strings, instead of token ids: prompt="..." or prompt=["...", "..."]
  2. Or move the workload to /chat/completions (messages), which the guardrail can scan natively
  3. 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

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


AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18). Data as JSON: /api/errors/ec0053e17d79dbdf. Report an issue: GitHub.