mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-12 23:01:41 +00:00
fix(proxy): anchor metadata dict in _process_response/_process_error so pop() mutates the real dict
This commit is contained in:
parent
d8fd9a20ed
commit
4625ccbaa2
2 changed files with 95 additions and 6 deletions
|
|
@ -255,9 +255,11 @@ class OpenAIModerationGuardrail(OpenAIGuardrailBase, CustomGuardrail):
|
|||
|
||||
Follows the Model Armor pattern (model_armor.py:325-360).
|
||||
"""
|
||||
metadata = (
|
||||
(request_data.get("metadata") or {}) if isinstance(request_data, dict) else {}
|
||||
)
|
||||
if isinstance(request_data, dict):
|
||||
metadata = request_data.get("metadata") or {}
|
||||
request_data["metadata"] = metadata # anchor so pop() mutates the real dict
|
||||
else:
|
||||
metadata = {}
|
||||
|
||||
# .pop() cleans up the internal key so it doesn't leak to downstream
|
||||
# loggers. Falls back to "allow" when no moderation call was made
|
||||
|
|
@ -294,9 +296,11 @@ class OpenAIModerationGuardrail(OpenAIGuardrailBase, CustomGuardrail):
|
|||
else "guardrail_failed_to_respond"
|
||||
)
|
||||
|
||||
metadata = (
|
||||
(request_data.get("metadata") or {}) if isinstance(request_data, dict) else {}
|
||||
)
|
||||
if isinstance(request_data, dict):
|
||||
metadata = request_data.get("metadata") or {}
|
||||
request_data["metadata"] = metadata # anchor so pop() mutates the real dict
|
||||
else:
|
||||
metadata = {}
|
||||
|
||||
# Use the stashed moderation response if available, fall back to exception
|
||||
guardrail_response: Union[dict, Exception, str] = metadata.pop(
|
||||
|
|
|
|||
|
|
@ -730,3 +730,88 @@ async def test_openai_moderation_post_call_request_data_passthrough():
|
|||
assert guardrail_info_list is not None
|
||||
assert isinstance(guardrail_info_list[0]["guardrail_response"], dict)
|
||||
assert "results" in guardrail_info_list[0]["guardrail_response"]
|
||||
|
||||
|
||||
def test_openai_moderation_process_response_metadata_none_edge_case():
|
||||
"""
|
||||
Test that _process_response anchors the metadata dict back into
|
||||
request_data when metadata is None, so pop() doesn't operate on a
|
||||
temporary and the moderation response is correctly logged.
|
||||
"""
|
||||
with patch.dict(os.environ, {"OPENAI_API_KEY": "test-key"}):
|
||||
guardrail = OpenAIModerationGuardrail(
|
||||
guardrail_name="test-openai-moderation",
|
||||
event_hook="post_call",
|
||||
)
|
||||
|
||||
mod_dict = {"id": "modr-test", "model": "omni-moderation-latest", "results": []}
|
||||
|
||||
# Simulate apply_guardrail having stashed the response but metadata
|
||||
# was None initially — apply_guardrail anchors it, so metadata is a
|
||||
# real dict with the stashed key by the time _process_response runs.
|
||||
request_data = {"metadata": {"_openai_moderation_response": mod_dict}}
|
||||
|
||||
guardrail._process_response(
|
||||
response={"inputs": {}},
|
||||
request_data=request_data,
|
||||
)
|
||||
|
||||
# Full moderation dict should be logged, not "allow"
|
||||
info_list = request_data["metadata"].get(
|
||||
"standard_logging_guardrail_information"
|
||||
)
|
||||
assert info_list is not None
|
||||
assert info_list[0]["guardrail_response"] == mod_dict
|
||||
|
||||
# Internal key should have been cleaned up by pop()
|
||||
assert "_openai_moderation_response" not in request_data["metadata"]
|
||||
|
||||
|
||||
def test_openai_moderation_process_error_metadata_none_edge_case():
|
||||
"""
|
||||
Test that _process_error anchors the metadata dict back into
|
||||
request_data when metadata starts as None (or {}), so pop() doesn't
|
||||
operate on a temporary.
|
||||
"""
|
||||
from fastapi import HTTPException
|
||||
|
||||
with patch.dict(os.environ, {"OPENAI_API_KEY": "test-key"}):
|
||||
guardrail = OpenAIModerationGuardrail(
|
||||
guardrail_name="test-openai-moderation",
|
||||
event_hook="post_call",
|
||||
)
|
||||
|
||||
mod_dict = {
|
||||
"id": "modr-test",
|
||||
"model": "omni-moderation-latest",
|
||||
"results": [{"flagged": True, "categories": {"hate": True}}],
|
||||
}
|
||||
|
||||
# metadata is None — exercises the or {} anchor
|
||||
request_data: dict = {"metadata": None}
|
||||
|
||||
# Simulate stashing the response then calling _process_error
|
||||
# (normally apply_guardrail stashes, then the decorator calls
|
||||
# _process_error on HTTPException)
|
||||
# First anchor metadata like apply_guardrail does:
|
||||
metadata = request_data.get("metadata") or {}
|
||||
request_data["metadata"] = metadata
|
||||
metadata["_openai_moderation_response"] = mod_dict
|
||||
|
||||
exc = HTTPException(status_code=400, detail="Violated policy")
|
||||
with pytest.raises(HTTPException):
|
||||
guardrail._process_error(
|
||||
e=exc,
|
||||
request_data=request_data,
|
||||
)
|
||||
|
||||
# Full moderation dict should be logged, not the exception
|
||||
info_list = request_data["metadata"].get(
|
||||
"standard_logging_guardrail_information"
|
||||
)
|
||||
assert info_list is not None
|
||||
assert info_list[0]["guardrail_response"] == mod_dict
|
||||
assert info_list[0]["guardrail_status"] == "guardrail_intervened"
|
||||
|
||||
# Internal key cleaned up
|
||||
assert "_openai_moderation_response" not in request_data["metadata"]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue