mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-05 02:41:56 +00:00
fix(agent_365): treat a verdict without a boolean allowed field as unavailable
A 200 body that is JSON but lacks a boolean allowed was recorded as a Defender Block and rejected with 400. It is malformed, so it now routes through the same unreachable_fallback handling as non-JSON and non-object bodies Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
a1ab5d42cc
commit
aee5b7a0d8
2 changed files with 38 additions and 1 deletions
|
|
@ -304,11 +304,17 @@ class Agent365Guardrail(CustomGuardrail):
|
|||
reason="the Agent 365 endpoint returned a non-object JSON body",
|
||||
)
|
||||
verdict: Final[_EvaluateResponse] = parsed_verdict
|
||||
allowed: Final = verdict.get("allowed")
|
||||
if not isinstance(allowed, bool):
|
||||
return self._handle_unavailable(
|
||||
data=data,
|
||||
tool_name=tool_name,
|
||||
reason="the Agent 365 endpoint returned a verdict without a boolean 'allowed' field",
|
||||
)
|
||||
raw_defender: Final = verdict.get("defender")
|
||||
defender: Final = raw_defender if isinstance(raw_defender, dict) else _DefenderResult()
|
||||
raw_correlation_id: Final = verdict.get("correlationId")
|
||||
correlation_id: Final = raw_correlation_id if isinstance(raw_correlation_id, str) else None
|
||||
allowed: Final = verdict.get("allowed") is True
|
||||
self._record_verdict(
|
||||
data=data,
|
||||
verdict="Allow" if allowed else "Block",
|
||||
|
|
|
|||
|
|
@ -592,6 +592,37 @@ class TestMalformedResponses:
|
|||
await _run(guardrail, _mcp_data())
|
||||
assert exc_info.value.status_code == 503
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"verdict",
|
||||
[{}, {"allowed": None}, {"allowed": "true"}, {"allowed": 1}, {"allowed": "false"}],
|
||||
ids=["missing", "null", "string-true", "int-one", "string-false"],
|
||||
)
|
||||
async def test_evaluate_non_boolean_allowed_fail_closed(self, verdict: dict):
|
||||
handler: Final = FakeHandler([_token_response(), _response(200, verdict)])
|
||||
guardrail: Final = _make_guardrail(handler)
|
||||
data: Final = _mcp_data()
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await _run(guardrail, data)
|
||||
assert exc_info.value.status_code == 503
|
||||
assert "boolean 'allowed'" in exc_info.value.detail["message"]
|
||||
assert _guardrail_info(data)["guardrail_response"]["verdict"] == "Unavailable"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"verdict",
|
||||
[{}, {"allowed": None}, {"allowed": "true"}, {"allowed": 1}, {"allowed": "false"}],
|
||||
ids=["missing", "null", "string-true", "int-one", "string-false"],
|
||||
)
|
||||
async def test_evaluate_non_boolean_allowed_fail_open(self, verdict: dict):
|
||||
handler: Final = FakeHandler([_token_response(), _response(200, verdict)])
|
||||
guardrail: Final = _make_guardrail(handler, unreachable_fallback="fail_open")
|
||||
data: Final = _mcp_data()
|
||||
result: Final = await _run(guardrail, data)
|
||||
assert result is data
|
||||
assert _guardrail_info(data)["guardrail_response"]["verdict"] == "Unscanned"
|
||||
assert _guardrail_info(data)["guardrail_status"] == "guardrail_failed_to_respond"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bad_expires_in_still_allows(self):
|
||||
handler: Final = FakeHandler(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue