Skip to content

fix(datadog_llm_obs): keep guardrail_cost_by_unit on redacted spans - #39848

Merged
mateo-berri merged 1 commit into
litellm_internal_stagingfrom
litellm_datadog_guardrail_cost_by_unit_audit_field
Sep 5, 2026
Merged

fix(datadog_llm_obs): keep guardrail_cost_by_unit on redacted spans#39848
mateo-berri merged 1 commit into
litellm_internal_stagingfrom
litellm_datadog_guardrail_cost_by_unit_audit_field

Conversation

@mateo-berri

@mateo-berri mateo-berri commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • Every fresh PR's integrations / Run tests job fails on staging since 01:24Z
  • guardrail_cost_by_unit was never classified for redacted Datadog spans
  • Redacted Datadog LLM Observability spans silently drop the per-counter guardrail cost

How it solves it:

  • Classifies guardrail_cost_by_unit as an audit field, kept under redaction
  • Points the block comment at the test that actually enforces the classification

#39196 added guardrail_cost_by_unit to the guardrail record and #39702 added the allow-list that decides what a redacted Datadog span keeps, plus a test that fails for any record field with no decision. Each PR's CI ran on a merge ref that predated the other, so staging only broke once both had landed

User Flow

Before: an operator who redacts prompts in Datadog LLM Observability sees each guardrail's total cost on the span but never its per-counter split

  1. The proxy admin runs the proxy with callbacks: ["datadog_llm_obs"], turn_off_message_logging: true, and a Bedrock guardrail that has pricing configured
  2. A developer sends POST https://litellm-domain/v1/chat/completions with a prompt the guardrail evaluates and gets a 200 back
  3. The operator opens that request's span at https://app.datadoghq.com/llm/traces and expands the guardrail entry under the span's metadata
  4. The entry shows guardrail_cost and guardrail_usage, and the prompt-carrying fields read REDACTED_BY_LITELM, but guardrail_cost_by_unit is absent, so the cost cannot be reconciled per usage counter

After: the same span carries the per-counter cost split next to the total

  1. The proxy admin runs the proxy with callbacks: ["datadog_llm_obs"], turn_off_message_logging: true, and a Bedrock guardrail that has pricing configured
  2. A developer sends POST https://litellm-domain/v1/chat/completions with a prompt the guardrail evaluates and gets a 200 back
  3. The operator opens that request's span at https://app.datadoghq.com/llm/traces and expands the guardrail entry under the span's metadata
  4. The entry now shows guardrail_cost_by_unit with one cost per usage counter alongside guardrail_cost and guardrail_usage, while the prompt-carrying fields still read REDACTED_BY_LITELM

Relevant issues

Linear ticket

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests (the regression test already exists: test_a_redacted_span_carries_every_declared_guardrail_field from fix(datadog_llm_obs): keep the guardrail audit record under message redaction #39702 is what fails before this PR and passes after it)
  • The handful of test files covering my change pass locally, e.g. uv run pytest tests/test_litellm/<your_test_file>.py -v. Leave the suites (make test-unit-*, make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more
  • My PR passes all required CI/CD checks (e.g., lint, schema.d.ts sync check, etc.)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

The observable surface is the span JSON the Datadog logger serializes, which the existing test builds through the real logger with redaction on, so the run below is that serializer's output plus the CI job every PR now hits

Before (853fed8)

  1. Any PR opened or pushed after 01:24Z runs integrations / Run tests on a merge ref carrying both feat(guardrails): roll up Bedrock guardrail cost per usage counter #39196 and fix(datadog_llm_obs): keep the guardrail audit record under message redaction #39702, and fails, e.g. fix(deps): raise the gitpython floor to 3.1.59 for four new advisories #39553 at b5e5212 (job):
FAILED tests/test_litellm/integrations/datadog/test_datadog_llm_obs.py::test_a_redacted_span_carries_every_declared_guardrail_field - AssertionError: assert {'guardrail_name', 'end_time', 'guardrail_usage', ..., 'guardrail_cost', 'risk_score', 'guardrail_provider', 'violation_categories', 'patterns_checked'} == {..., 'guardrail_cost_by_unit', ...}
= 1 failed, 3411 passed, 3 skipped, 210 warnings, 2 subtests passed, 3 rerun in 164.77s (0:02:44) =
  1. The same serializer at staging's tip locally:
$ uv run pytest tests/test_litellm/integrations/datadog/test_datadog_llm_obs.py -k redacted_span_carries_every_declared -q
FAILED tests/test_litellm/integrations/datadog/test_datadog_llm_obs.py::test_a_redacted_span_carries_every_declared_guardrail_field
1 failed, 67 deselected in 0.81s

After (d4fd658)

  1. This PR's own integrations / Run tests jobs pass: integrations / Run tests shards on this PR: default, 3.10, 3.11, 3.13, 3.14, all passing

  2. The same serializer at this tip locally, whole Datadog file:

$ uv run pytest tests/test_litellm/integrations/datadog/test_datadog_llm_obs.py -q
68 passed, 31 warnings in 1.86s

Type

🐛 Bug Fix

Caveats (if any)

Low

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

Note

Low Risk
Single allow-list entry for observability redaction; no auth, spend logic, or prompt-field handling changes.

Overview
Adds guardrail_cost_by_unit to AUDIT_GUARDRAIL_FIELDS in litellm/types/utils.py, so Datadog LLM Observability redaction treats per-counter guardrail cost as non-prompt audit data and keeps it on redacted spans alongside totals like guardrail_cost and guardrail_usage.

Updates the guardrail field-classification comment to reference test_a_redacted_span_carries_every_declared_guardrail_field, which enforces that every declared guardrail field is either prompt-carrying or audit. Without this entry, redacted span serialization dropped guardrail_cost_by_unit and CI failed once that field existed on the guardrail record.

Reviewed by Cursor Bugbot for commit d4fd658. Bugbot is set up for automated code reviews on this repo. Configure here.

  • d4fd658 passes /live-pr-risk: the only consumer of AUDIT_GUARDRAIL_FIELDS is _CLASSIFIED_GUARDRAIL_FIELDS feeding the redaction filter in datadog_llm_obs.py; PROMPT_CARRYING_GUARDRAIL_FIELDS and the spend-log redaction in spend_tracking_utils.py are untouched; the field's three producers (guardrail_cost.py, bedrock_guardrails.py, usage_tracking.py) and their tests are unchanged; on this tip tests/test_litellm/integrations/datadog/test_datadog_llm_obs.py goes from the one failing test at base to 68 passed; not verified live: Datadog intake of the redacted span, which already receives this field on unredacted spans

@mateo-berri
mateo-berri requested a review from a team September 5, 2026 01:47
@greptile-apps

greptile-apps Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR ensures redacted Datadog LLM Observability spans retain per-counter guardrail costs.

  • Adds guardrail_cost_by_unit to the audit-safe guardrail field allow-list.
  • Corrects the nearby comment to reference the test enforcing complete field classification.
  • Continues redacting prompt-carrying fields while preserving numeric guardrail accounting metadata.

Confidence Score: 5/5

The PR appears safe to merge; the new field has an audit-only numeric shape and the redaction path preserves it without weakening prompt redaction.

The added allow-list entry aligns with the field’s numeric cost-accounting contract, uses the same counter keys as the already-retained usage map, and passes mapping values through unchanged.

Important Files Changed

Filename Overview
litellm/types/utils.py Correctly classifies guardrail_cost_by_unit as audit metadata retained on redacted Datadog spans.

Reviews (1): Last reviewed commit: "fix(datadog_llm_obs): keep guardrail_cos..." | Re-trigger Greptile

@codspeed-hq

codspeed-hq Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_datadog_guardrail_cost_by_unit_audit_field (d4fd658) with litellm_internal_staging (639b3f4)

Open in CodSpeed

@mateo-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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 d4fd658. Configure here.

@codecov

codecov Bot commented Sep 5, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants