test(realtime): assert guardrail block on backend wire traffic instead of model refusal wording - #32388
Conversation
…d of model refusal wording test_text_message_blocked_by_guardrail_no_ai_response classified the model's reply against a safe_markers keyword list to decide whether the guardrail had blocked the message. gpt-realtime words its refusal of the guardrail's "say exactly" voice prompt nondeterministically, so any new phrasing outside the list turned CI red on unrelated PRs; the list had already been extended in #28191, #28200 and #29477, and drifted again to "Sorry, I can't comply with that request" (11 of the 13 failed realtime_translation_testing runs since 2026-06-24, e.g. CircleCI job 2009316 on #32380). Record every frame the proxy sends to the backend through a RecordingBackendWebSocket wrapper and assert the invariant the product actually guarantees: the blocked phrase never reaches OpenAI, only the guardrail's own conversation.item.create and response.create are forwarded (the client's reflexive response.create is dropped), and the blocked phrase never appears in AI output. Replace the fixed 0.3s/3.0s sleeps with an event-driven wait for response.done; client frames are processed sequentially so no inter-message sleep is needed. Verified by mutation: disabling the response.create drop fails the response.create count assertion, and disabling the guardrail fails the guardrail_violation assertion.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Greptile SummaryThis is a test-only fix for a recurring CI flake in
Confidence Score: 5/5Safe to merge; the change is confined to a single test file, touches no production code, and the new assertions are demonstrably stricter than the ones they replace. No production code is changed. The new RecordingBackendWebSocket wrapper is a simple pass-through that only records outgoing frames. The replaced safe_markers keyword check was the documented root cause of 11 CI failures; the new wire-traffic assertions are both more deterministic and more semantically correct. The PR includes 25 consecutive green runs of the fixed test and 20 green runs of the full file as reproduction evidence. No files require special attention. The only changed file is the test itself, and the logic change is straightforward and well-motivated.
|
| Filename | Overview |
|---|---|
| tests/llm_translation/realtime/test_realtime_guardrails_openai.py | Replaces flaky wording-based guardrail assertions with deterministic wire-traffic recording via new RecordingBackendWebSocket; removes fixed sleeps in favour of event-driven waits; no logic regressions found. |
Reviews (1): Last reviewed commit: "test(realtime): assert guardrail block o..." | Re-trigger Greptile
Relevant issues
No GitHub issue; fixes the recurring
realtime_translation_testingCI flake that turns unrelated PRs red, e.g. job 2009316 on #32380Linear ticket
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
@greptileaito re-request a review after pushing changes)Delays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
Screenshots / Proof of Fix
All runs below hit the real OpenAI realtime API (
wss://api.openai.com/v1/realtime?model=gpt-realtime) with no mocks, billing real tokensBefore (CI): 11 of the 13 failed
realtime_translation_testingruns between 2026-06-24 and 2026-07-07 fail on this exact assertion, each with a differently worded refusal that thesafe_markerslist does not contain (jobs 1930076, 1937431, 1951307, 1954778, 1956799, 1959775, 1965566, 1972283, 1978635, 2001790, 2009316). From job 2009316, PR #32380 at commit 64c4cfe:The list had already been extended for the same reason in #28191, #28200 and #29477; the model's live phrasing keeps drifting past it
Before (local, old test at litellm_internal_staging commit 46d9742): the flake tracks the model's current phrasing distribution and did not reproduce tonight, so the CI numbers above are the reproduction evidence
After (branch litellm_fix_realtime_guardrail_flake, commit a2b68b7): 25 consecutive green runs of the previously flaky test, then 20 consecutive green runs of the whole file
The new assertions were mutation-checked against the live API at commit a2b68b7 to prove they still guard the feature. Disabling the
response.createdrop inclient_ack_messagesfails withand disabling the guardrail entirely fails with
Type
✅ Test
Changes
test_text_message_blocked_by_guardrail_no_ai_responseused to decide whether the guardrail worked by classifying the model's reply against asafe_markerskeyword list. gpt-realtime words its refusal of the guardrail's "say exactly" voice prompt nondeterministically, so any phrasing outside the list turned CI red even though the guardrail had blocked correctly; in every recent CI failure the model's reply was a refusal of the guardrail's own prompt, which is only possible when the blocked message never reached OpenAIThe test now asserts the invariant the product actually guarantees, on the wire instead of on wording. A
RecordingBackendWebSocketwrapper records every frame the proxy sends to the backend, and the test asserts the blocked phrase never reaches OpenAI, that only the guardrail's ownconversation.item.createandresponse.createare forwarded (the client's reflexiveresponse.createis dropped), and that the blocked phrase never appears in AI output. The fixed 0.3s/3.0s sleeps are replaced with an event-driven wait forresponse.done, and the two client frames are now sent back-to-back, which additionally pins the sequential ordering guarantee ofclient_ack_messagesNo product change:
client_ack_messagesprocesses client frames sequentially, so the guardrail verdict always lands before the follow-upresponse.createis read; the enforcement itself was never racy