mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-20 00:11:50 +00:00
fix(guardrails): snapshot model_call_details before serializing noma v2 scan payload
This commit is contained in:
parent
b930e2fc2b
commit
4505b53c6f
2 changed files with 53 additions and 4 deletions
|
|
@ -131,9 +131,18 @@ class NomaV2Guardrail(CustomGuardrail):
|
|||
logging_obj: Optional["LiteLLMLoggingObj"],
|
||||
application_id: Optional[str],
|
||||
) -> dict:
|
||||
payload_request_data = self._sanitize_payload_for_transport(request_data)
|
||||
if logging_obj is not None:
|
||||
payload_request_data["litellm_logging_obj"] = getattr(logging_obj, "model_call_details", None)
|
||||
model_call_details = getattr(logging_obj, "model_call_details", None) if logging_obj is not None else None
|
||||
request_data_to_send = (
|
||||
{
|
||||
**request_data,
|
||||
"litellm_logging_obj": dict(model_call_details)
|
||||
if isinstance(model_call_details, dict)
|
||||
else model_call_details,
|
||||
}
|
||||
if logging_obj is not None
|
||||
else request_data
|
||||
)
|
||||
payload_request_data = self._sanitize_payload_for_transport(request_data_to_send)
|
||||
|
||||
payload: dict[str, Any] = {
|
||||
"inputs": inputs,
|
||||
|
|
@ -157,7 +166,7 @@ class NomaV2Guardrail(CustomGuardrail):
|
|||
|
||||
try:
|
||||
json_str = json.dumps(payload, default=_default)
|
||||
except (ValueError, TypeError):
|
||||
except (ValueError, TypeError, RuntimeError):
|
||||
json_str = safe_dumps(payload)
|
||||
|
||||
safe_payload = safe_json_loads(json_str, default={})
|
||||
|
|
|
|||
|
|
@ -236,6 +236,46 @@ class TestNomaV2Configuration:
|
|||
assert "logging_obj" not in payload
|
||||
assert request_data["litellm_logging_obj"] == "<Logging object>"
|
||||
|
||||
def test_build_scan_payload_snapshots_live_model_call_details(
|
||||
self, noma_v2_guardrail
|
||||
):
|
||||
"""Regression test: model_call_details is mutated by logging handlers on other
|
||||
threads, so serializing it by reference raises RuntimeError('dictionary changed
|
||||
size during iteration')."""
|
||||
|
||||
class _MutatingOnSerialize:
|
||||
def __init__(self, target: dict) -> None:
|
||||
self._target = target
|
||||
|
||||
def model_dump(self) -> dict:
|
||||
self._target[f"late_key_{len(self._target)}"] = "written_by_logging"
|
||||
return {"serialized": True}
|
||||
|
||||
model_call_details: dict = {
|
||||
"model": "gpt-4.1-mini",
|
||||
"call_type": "acompletion",
|
||||
}
|
||||
model_call_details["standard_callback_dynamic_params"] = _MutatingOnSerialize(
|
||||
model_call_details
|
||||
)
|
||||
|
||||
class _LoggingObj:
|
||||
def __init__(self) -> None:
|
||||
self.model_call_details = model_call_details
|
||||
|
||||
payload = noma_v2_guardrail._build_scan_payload(
|
||||
inputs={"texts": ["hello"]},
|
||||
request_data={"messages": [{"role": "user", "content": "hello"}]},
|
||||
input_type="request",
|
||||
logging_obj=_LoggingObj(),
|
||||
application_id="test-app",
|
||||
)
|
||||
|
||||
sent = payload["request_data"]["litellm_logging_obj"]
|
||||
assert sent["model"] == "gpt-4.1-mini"
|
||||
assert sent["standard_callback_dynamic_params"] == {"serialized": True}
|
||||
assert not any(key.startswith("late_key_") for key in sent)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_call_noma_scan_sanitizes_response_model_dump_object(
|
||||
self, noma_v2_guardrail
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue