fix(guardrails): skip malformed guardrail entries instead of failing the usage batch

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
yucheng 2026-09-14 20:50:41 +00:00
parent 7ebb169a4d
commit 586d51f15e
2 changed files with 27 additions and 5 deletions

View file

@ -356,12 +356,12 @@ async def process_spend_logs_guardrail_usage(
"flagged_count": 0,
}
)
index_rows_by_key: Final[dict[tuple[object, object], dict[str, object]]] = {}
index_rows_by_key: Final[dict[tuple[str, str], dict[str, object]]] = {}
for payload in logs_to_process:
request_id = payload.get("request_id")
start_time = _parse_payload_start_time(payload)
if not request_id or start_time is None:
if not isinstance(request_id, str) or not request_id or start_time is None:
continue
date_key = _date_str(start_time)
@ -370,13 +370,14 @@ async def process_spend_logs_guardrail_usage(
{
e["guardrail_name"]: e["guardrail_id"]
for e in entries
if e.get("guardrail_id") and e.get("guardrail_name")
if e.get("guardrail_id") and isinstance(e.get("guardrail_name"), str)
}
)
for entry in entries:
guardrail_name = entry.get("guardrail_name") or ""
raw_name = entry.get("guardrail_name")
guardrail_name = raw_name if isinstance(raw_name, str) else ""
guardrail_id = entry.get("guardrail_id") or ids_by_name.get(guardrail_name) or guardrail_name
if not guardrail_id:
if not isinstance(guardrail_id, str) or not guardrail_id:
continue
action = guardrail_status_to_action(entry.get("guardrail_status"))
if action != "not_run":

View file

@ -405,6 +405,27 @@ async def test_not_run_entry_shares_index_key_with_evaluated_sibling_of_same_nam
assert (metrics_create["guardrail_id"], metrics_create["requests_evaluated"]) == ("cf-uuid", 1)
@pytest.mark.asyncio
async def test_malformed_not_run_entry_does_not_drop_the_batch():
prisma = _prisma()
payload = _payload("r1")
payload["metadata"] = json.dumps(
{
"guardrail_information": [
{"guardrail_name": ["not", "a", "string"], "guardrail_status": "not_run"},
{"guardrail_name": "cf", "guardrail_id": "cf-uuid", "guardrail_status": "success"},
]
}
)
await process_spend_logs_guardrail_usage(prisma, [payload])
index_rows = prisma.db.litellm_spendlogguardrailindex.create_many.call_args.kwargs["data"]
assert [row["guardrail_id"] for row in index_rows] == ["cf-uuid"]
metrics_create = prisma.db.litellm_dailyguardrailmetrics.upsert.call_args.kwargs["data"]["create"]
assert metrics_create["requests_evaluated"] == 1
@pytest.mark.asyncio
async def test_batch_of_only_not_run_entries_writes_no_metrics_row():
prisma = _prisma()