fix(datadog_llm_obs): keep the guardrail audit record under message redaction - #39702
Conversation
…edaction Redaction nulled `guardrail_information` on the span whole, so an operator running `turn_off_message_logging` (or a caller sending `x-litellm-enable-message-redaction`) lost the record of which guardrails ran, what they returned, and what they masked. Four of the record's fields can quote the prompt; the rest report what the guardrail decided without reproducing it. Replace only those four, the way `_sanitize_guardrail_information_for_spend_logs` already does for spend logs, and declare the field list once in `litellm/types/utils.py` so both readers share it.
|
@greptileai please review the current head 844dcff |
|
bugbot run |
Merging this PR will not alter performance
Comparing |
There was a problem hiding this comment.
Devin Review found 2 potential issues.
1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)
| "detection_method", | ||
| "confidence_score", | ||
| "patterns_checked", | ||
| "alert_recipients", |
There was a problem hiding this comment.
alert_recipients has no writer anywhere in the repo, and it names operator alert destinations rather than caller prompt text. Keeping it
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 844dcff. Configure here.
Greptile SummaryThe PR preserves guardrail audit metadata on redacted Datadog spans while replacing prompt-carrying guardrail fields and reusing the same field classification in spend-log sanitization.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| litellm/integrations/datadog/datadog_llm_obs.py | Preserves allow-listed guardrail audit fields during message redaction and normalizes guardrail records before latency calculation. |
| litellm/proxy/spend_tracking/spend_tracking_utils.py | Reuses the shared prompt-carrying guardrail field classification without changing spend-log sanitization behavior. |
| litellm/types/utils.py | Defines shared classifications for prompt-carrying and audit-safe guardrail fields. |
| tests/test_litellm/integrations/datadog/test_datadog_llm_obs.py | Exercises redacted Datadog span output across normal, caller-header, custom-field, and irregular guardrail-record shapes. |
Reviews (2): Last reviewed commit: "fix(datadog_llm_obs): keep a lone guardr..." | Re-trigger Greptile
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
…the span Review round 1. A guardrail that writes the metadata key itself leaves a single record where the type says list, which Prometheus already normalizes at `_guardrail_overhead_seconds`. Redaction dropped that shape and the latency extraction raised on it, so the span was lost outright. Normalize once and use it in both places. The new tests now drive `create_llm_obs_payload` instead of reading the module's private helpers and the record's declared field names.
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 64fc74b. Configure here.
|
@greptileai please review the current head 64fc74b |
e2741b5
into
litellm_internal_staging
TLDR
Problem this solves:
How it solves it:
User Flow
Before: a compliance engineer who turned message logging off cannot answer "did a guardrail catch anything on that request"
turn_off_message_logging: trueand turns on a PII guardrailmail alice@acme.com and bob@acme.comguardrail_information: nullx-litellm-enable-message-redaction: trueto their own requestAfter: the same span still hides the prompt and now carries the guardrail record
turn_off_message_logging: trueand turns on a PII guardrailguardrail_name: presidio-pii,guardrail_status, its timings, andmasked_entity_count: {"EMAIL_ADDRESS": 2}redacted-by-litellm, and the guardrail's own copy of it readsREDACTED_BY_LITELMx-litellm-enable-message-redaction: truestill hides their own prompt and no longer removes the guardrail recordRelevant issues
Linear ticket
Refs LIT-6728
Pre-Submission checklist
uv run pytest tests/test_litellm/<your_test_file>.py -vScreenshots / Proof of Fix
Both cases are end to end against real Datadog LLM Observability (us5) with a real OpenAI call and a real Presidio guardrail; the spans are read back through Datadog's own LLM Obs search API. Nothing is mocked.
Shared setup:
Read-back command used in every step below:
Before (aec083c,
litellm_internal_stagingwith #39402 as merged)Case 1: operator sets turn_off_message_logging
The span records that 88 ms went to guardrails and gives no way to find out which one, whether it intervened, or what it masked.
Case 2: ordinary key sends the redaction header
turn_off_message_logging: falsein the configAfter (64fc74b, this PR)
Case 1: operator sets turn_off_message_logging
{ "input": {"value": "redacted-by-litellm", "messages": [{"content": "redacted-by-litellm", "role": "user"}]}, "metadata": { "applied_guardrails": ["presidio-pii"], "guardrail_information": [ {"guardrail_name": "presidio-pii", "guardrail_provider": "presidio", "guardrail_mode": "pre_call", "guardrail_status": "success", "guardrail_response": "REDACTED_BY_LITELM", "masked_entity_count": {"EMAIL_ADDRESS": 2}, "start_time": 1788555468.743473, "end_time": 1788555468.80831, "duration": 0.064839}, {"guardrail_name": "presidio-pii", "guardrail_provider": "presidio", "guardrail_mode": "post_call", "guardrail_status": "success", "guardrail_response": "REDACTED_BY_LITELM", "start_time": 1788555470.004475, "end_time": 1788555470.031875, "duration": 0.027402} ], "latency_metrics": {"guardrail_overhead_time_ms": 92.241} } }The prompt is still gone. The record of what the guardrail did is back.
Case 2: ordinary key sends the redaction header
The key still redacts its own prompt and can no longer erase what the guardrail caught.
Type
🐛 Bug Fix
Caveats (if any)
Medium
nullto an array under redactionnullin the last day would see the type changeLow
requester_metadata,prompt_management_metadata,mcp_tool_call_metadata,vector_store_request_metadatarouting_decisionis still re-emitted flattened, not restored nestedReviewer notes
redacted-by-litellmand the guardrail's own copy of the prompt readsREDACTED_BY_LITELMguardrail_informationis typed as a list, and Prometheus already normalizes the same shape at_guardrail_overhead_seconds_get_latency_metricsand the span was dropped, on both sides of the diffTesting
tests/test_litellm/integrations/datadog/test_datadog_llm_obs.py— 68 passeddatadog_llm_obs.pyto the merge-base version fails 5 of the new tests, and dropping the single-record normalization alone fails one moreguardrail_overhead_time_msstill computed on both sides; spend-log sanitizer output byte-identical base vs head; 4 workers with 8-way concurrency (16/16 200s, one span shape, no prompt text); paused-Postgres fault leg (3/3 200s, no malformed records)ran /live-pr-risk and found no regressions/backward incompatible risks
Note
Low Risk
Observability-only redaction behavior change: redacted spans expose guardrail audit metadata instead of null, with stricter field filtering; spend-log sanitizer uses the same constant with no logic change beyond import.
Overview
When message redaction is on (
turn_off_message_logging, per-request header, etc.), Datadog LLM Observability spans no longer dropguardrail_informationentirely. They now redact only the four prompt-carrying fields (guardrail_request,guardrail_response,match_details,classification) toREDACTED_BY_LITELM, while keeping audit fields such as name, provider, mode, status, timings, and masked-entity counts.PROMPT_CARRYING_GUARDRAIL_FIELDSandAUDIT_GUARDRAIL_FIELDSare defined inlitellm/types/utils.pyand reused by the Datadog logger and spend-log sanitizer (replacing a local duplicate). Redacted spans use an allow-list: unclassified guardrail keys are stripped so custom fields cannot leak prompts._guardrail_entriesnormalizes list vs single-dictguardrail_informationfor redaction andguardrail_overhead_time_mscomputation. New unit tests cover audit survival, header redaction, odd shapes, and full field coverage.Reviewed by Cursor Bugbot for commit 64fc74b. Bugbot is set up for automated code reviews on this repo. Configure here.