Skip to content

fix(logging): preserve ModelResponse choices format in redacted standard_logging_object + add Charity Engine provider endpoint - #23235

Merged
Sameerlite merged 1 commit into
mainfrom
litellm_fix_redaction_and_charity_engine_provider
Mar 10, 2026
Merged

fix(logging): preserve ModelResponse choices format in redacted standard_logging_object + add Charity Engine provider endpoint#23235
Sameerlite merged 1 commit into
mainfrom
litellm_fix_redaction_and_charity_engine_provider

Conversation

@Sameerlite

Copy link
Copy Markdown
Contributor

Summary

Fixes failing redaction tests and adds Charity Engine provider endpoint support.

Changes

1. Redaction fix (litellm/litellm_core_utils/redact_messages.py)

  • Fix: Handle dict representation of ModelResponse (from model_dump()) in perform_redaction
  • When response_obj is a dict with choices, preserve the full structure and redact content/audio in place
  • Add _redact_standard_logging_object helper for redacting the standard_logging_object field in different formats (ModelResponse, ResponsesAPIResponse, etc.)

2. Test updates (tests/logging_callback_tests/test_logging_redaction_e2e_test.py)

  • Update assertions to expect choices format instead of {"text": "redacted-by-litellm"}
  • Aligns with expected behavior since commit d84e5e3

3. Provider endpoint (provider_endpoints_support.json)

  • Add Charity Engine provider endpoint with documentation URL and supported endpoints

Tests fixed

  • test_standard_logging_payload[True-ft:gpt-3.5-turbo:my-org:custom_suffix:id]
  • test_standard_logging_payload_audio[True-True]
  • test_standard_logging_payload_audio[True-False]
  • All test_logging_redaction_e2e_test tests

Motivation

The tests in test_custom_callback_input.py were updated (commit a50a84c) to expect the new choices format, but the code to support this was reverted. This PR restores the correct redaction behavior.

Made with Cursor

…ard_logging_object + add Charity Engine provider endpoint

- Fix perform_redaction to handle dict representation of ModelResponse (from model_dump())
- Preserve full choices structure when redacting, redact content/audio in place
- Add _redact_standard_logging_object helper for standard_logging_object field
- Update test_logging_redaction_e2e_test assertions to expect choices format
- Add charity_engine to provider_endpoints_support.json

Fixes: test_standard_logging_payload, test_standard_logging_payload_audio
Made-with: Cursor
@vercel

vercel Bot commented Mar 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
litellm Error Error Mar 10, 2026 4:55am

Request Review

@greptile-apps

greptile-apps Bot commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes redaction of ModelResponse in dict form (from model_dump()) within perform_redaction, updates test assertions to match the choices-based format already produced by CustomLogger, and registers the Charity Engine provider endpoint. The core logic fix is solid, but a new helper function _redact_standard_logging_object is added and never called, making it dead code.

Key changes:

  • perform_redaction now correctly handles a dict with a "choices" key (e.g. a model_dump()-serialised ModelResponse) — this is the real fix for the failing tests
  • Test assertions updated from response == {"text": "redacted-by-litellm"} to response["choices"][0]["message"]["content"] == "redacted-by-litellm", aligned with existing custom_logger.py behaviour
  • New _redact_standard_logging_object helper is defined but never called anywhere in the codebase — the actual standard_logging_object redaction continues to be handled by CustomLogger.redact_standard_logging_payload_from_model_call_details() in custom_logger.py
  • The dead helper also has an incomplete redaction: it omits reasoning_content and thinking_blocks handling present in the live perform_redaction dict branch
  • charity_engine added to provider_endpoints_support.json with chat completions, messages, and responses marked as supported

Confidence Score: 4/5

  • Safe to merge with minor cleanup: the functional fix in perform_redaction is correct, the dead-code helper should be removed or wired up.
  • The perform_redaction dict-choices branch is the actual functional fix and is correctly implemented. Test assertions are updated to match existing custom_logger.py behaviour. The only concern is _redact_standard_logging_object being dead code — it doesn't break anything at runtime, but it adds maintenance noise and has an incomplete redaction (missing reasoning_content/thinking_blocks) that could become a real bug if someone later wires it in.
  • litellm/litellm_core_utils/redact_messages.py — the _redact_standard_logging_object function should either be connected to the call chain or removed.

Important Files Changed

Filename Overview
litellm/litellm_core_utils/redact_messages.py Two changes: (1) new _redact_standard_logging_object helper that is never called (dead code); (2) new dict-choices branch in perform_redaction that correctly handles model_dump()-style dicts — this is the real fix. The dead helper also misses reasoning_content/thinking_blocks redaction.
tests/logging_callback_tests/test_logging_redaction_e2e_test.py Test assertions updated from response == {"text": "redacted-by-litellm"} to response["choices"][0]["message"]["content"] == "redacted-by-litellm", aligning with the ModelResponse-style dict now produced by custom_logger.py. All tests use mock_response, so no real network calls are made.
provider_endpoints_support.json Adds a charity_engine entry with chat completions, messages, and responses endpoints enabled. Structurally consistent with surrounding entries.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[perform_redaction called] --> B{result type?}
    B -->|litellm.ModelResponse| C[_redact_choice_content on each choice]
    B -->|dict with choices key - NEW| D[Inline dict redaction: content\nreasoning_content, thinking_blocks, audio]
    B -->|litellm.ResponsesAPIResponse| E[_redact_responses_api_output\nnullify reasoning field]
    B -->|litellm.EmbeddingResponse| F[Clear data list]
    B -->|other| G[Return plain redacted text dict]

    H[CustomLogger.redact_standard_logging_payload\n_from_model_call_details] --> I{response format?}
    I -->|dict with output key| J[Deep-copy and redact text in output items]
    I -->|other dict| K[Replace with new ModelResponse dict\nwith single redacted choice]

    L[_redact_standard_logging_object - NEW DEAD CODE] -.->|never called| M[Would redact standard_logging_object\nresponse field in-place]
Loading

Last reviewed commit: b084458

Comment on lines +76 to +120
def _redact_standard_logging_object(model_call_details: dict):
"""Redact messages and response inside standard_logging_object if present."""
standard_logging_object = model_call_details.get("standard_logging_object")
if standard_logging_object is None:
return

redacted_str = "redacted-by-litellm"

if standard_logging_object.get("messages") is not None:
standard_logging_object["messages"] = [
{"role": "user", "content": redacted_str}
]

response = standard_logging_object.get("response")
if response is not None:
if isinstance(response, dict) and "output" in response:
# ResponsesAPIResponse format - redact content in output items
if isinstance(response.get("output"), list):
for output_item in response["output"]:
if isinstance(output_item, dict) and "content" in output_item:
if isinstance(output_item["content"], list):
for content_item in output_item["content"]:
if (
isinstance(content_item, dict)
and "text" in content_item
):
content_item["text"] = redacted_str
elif isinstance(response, dict) and "choices" in response:
# ModelResponse dict format - redact content in choices
if isinstance(response.get("choices"), list):
for choice in response["choices"]:
if isinstance(choice, dict):
if "message" in choice and isinstance(choice["message"], dict):
choice["message"]["content"] = redacted_str
if "audio" in choice["message"]:
choice["message"]["audio"] = None
elif "delta" in choice and isinstance(choice["delta"], dict):
choice["delta"]["content"] = redacted_str
if "audio" in choice["delta"]:
choice["delta"]["audio"] = None
elif isinstance(response, str):
standard_logging_object["response"] = redacted_str
else:
# For other formats (empty dict, None, etc.), use simple text format
standard_logging_object["response"] = {"text": redacted_str}

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.

_redact_standard_logging_object is dead code — never called

This function is defined but never invoked anywhere in the codebase. The redaction of standard_logging_object already happens inside CustomLogger.redact_standard_logging_payload_from_model_call_details() in litellm/integrations/custom_logger.py (lines 773–869). That method is the one actually called during the logging pipeline.

Because _redact_standard_logging_object is never called from perform_redaction or redact_message_input_output_from_logging, it has no effect on runtime behavior. If it is intended to replace or supplement the logic in custom_logger.py, it needs to be wired in; otherwise it should be removed to avoid confusion for future maintainers.

Comment on lines +103 to +115
elif isinstance(response, dict) and "choices" in response:
# ModelResponse dict format - redact content in choices
if isinstance(response.get("choices"), list):
for choice in response["choices"]:
if isinstance(choice, dict):
if "message" in choice and isinstance(choice["message"], dict):
choice["message"]["content"] = redacted_str
if "audio" in choice["message"]:
choice["message"]["audio"] = None
elif "delta" in choice and isinstance(choice["delta"], dict):
choice["delta"]["content"] = redacted_str
if "audio" in choice["delta"]:
choice["delta"]["audio"] = None

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.

Missing reasoning_content and thinking_blocks redaction

In _redact_standard_logging_object, the "choices" dict branch only redacts content and audio. The parallel dict branch added to perform_redaction (lines 164–186) also redacts reasoning_content and thinking_blocks:

if "reasoning_content" in choice["message"]:
    choice["message"]["reasoning_content"] = "redacted-by-litellm"
if "thinking_blocks" in choice["message"]:
    choice["message"]["thinking_blocks"] = None

If this function is ever wired up, reasoning content and thinking blocks in the standard_logging_object response would not be redacted, creating an inconsistency with the behaviour of perform_redaction.

@Sameerlite
Sameerlite merged commit cf84072 into main Mar 10, 2026
67 of 98 checks passed
Chesars added a commit that referenced this pull request Mar 12, 2026
Restore independent fixes from main that were collaterally removed
when PR #23276 (staging_03_10 → main) carried a revert commit:
- bedrock: restore output_config pop (PR #23240)
- redact_messages: restore dict handling for ModelResponse (PR #23235)
- model_checks: restore list() copies to avoid cache mutation (PR #23236)
- openapi_to_mcp_generator: restore relative URL handling (PR #23238)
- vertex_ai/gemini: restore _LITELLM_INTERNAL_EXTRA_BODY_KEYS check (PR #23131)
- openai types: restore extra finish reasons (PR #22138)
- completion_extras: restore usage transformation logic

Accept main for: model_prices JSONs, credential_endpoints,
team_endpoints, object_permission_utils, responses transformation.
fzowl pushed a commit to fzowl/litellm that referenced this pull request Jun 24, 2026
…nd_charity_engine_provider

fix(logging): preserve ModelResponse choices format in redacted standard_logging_object + add Charity Engine provider endpoint
fzowl pushed a commit to fzowl/litellm that referenced this pull request Jun 24, 2026
Restore independent fixes from main that were collaterally removed
when PR BerriAI#23276 (staging_03_10 → main) carried a revert commit:
- bedrock: restore output_config pop (PR BerriAI#23240)
- redact_messages: restore dict handling for ModelResponse (PR BerriAI#23235)
- model_checks: restore list() copies to avoid cache mutation (PR BerriAI#23236)
- openapi_to_mcp_generator: restore relative URL handling (PR BerriAI#23238)
- vertex_ai/gemini: restore _LITELLM_INTERNAL_EXTRA_BODY_KEYS check (PR BerriAI#23131)
- openai types: restore extra finish reasons (PR BerriAI#22138)
- completion_extras: restore usage transformation logic

Accept main for: model_prices JSONs, credential_endpoints,
team_endpoints, object_permission_utils, responses transformation.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant