fix(logging): preserve ModelResponse choices format in redacted standard_logging_object + add Charity Engine provider endpoint - #23235
Conversation
…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
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR fixes redaction of Key changes:
Confidence Score: 4/5
|
| 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]
Last reviewed commit: b084458
| 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} |
There was a problem hiding this comment.
_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.
| 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 |
There was a problem hiding this comment.
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"] = NoneIf 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.
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.
…nd_charity_engine_provider fix(logging): preserve ModelResponse choices format in redacted standard_logging_object + add Charity Engine provider endpoint
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.
Summary
Fixes failing redaction tests and adds Charity Engine provider endpoint support.
Changes
1. Redaction fix (
litellm/litellm_core_utils/redact_messages.py)model_dump()) inperform_redactionresponse_objis a dict withchoices, preserve the full structure and redact content/audio in place_redact_standard_logging_objecthelper for redacting thestandard_logging_objectfield in different formats (ModelResponse, ResponsesAPIResponse, etc.)2. Test updates (
tests/logging_callback_tests/test_logging_redaction_e2e_test.py)choicesformat instead of{"text": "redacted-by-litellm"}3. Provider endpoint (
provider_endpoints_support.json)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]test_logging_redaction_e2e_testtestsMotivation
The tests in
test_custom_callback_input.pywere updated (commit a50a84c) to expect the newchoicesformat, but the code to support this was reverted. This PR restores the correct redaction behavior.Made with Cursor