Skip to content

Commit ce87c41

Browse files
authored
fix(tests): migrate realtime + rerank tests off shut-down upstream models (BerriAI#28191)
* fix(tests): use gpt-realtime in realtime guardrails test OpenAI shut down gpt-4o-realtime-preview-2024-12-17 on 2026-05-07, so the live OpenAI realtime guardrails integration test now fails with model_not_found (session.created never arrives, _wait_for_event times out). Point OPENAI_REALTIME_URL at the current GA model, gpt-realtime. Scope limited to this test: the pricing-catalog JSON keeps the retired entries intentionally (historical cost calc + separate Azure timeline), and the Azure realtime cost-calc test is unaffected. * fix(tests): mock nvidia_nim rerank instead of hitting EOL'd endpoint NVIDIA reached end-of-life for the hosted nvidia/llama-3.2-nv-rerankqa-1b-v2 rerank API on 2026-05-18 with no published replacement, so the live BaseLLMRerankTest.test_basic_rerank for nvidia_nim now returns HTTP 410 ("Gone"). NVIDIA's hosted catalog rotates on a schedule, so swapping in another live model would only defer the failure. Override test_basic_rerank in TestNvidiaNim to mock the sync/async HTTP transport (same pattern as test_nvidia_nim_rerank_ranking_endpoint in this file) and inject a fake NVIDIA_NIM_API_KEY via monkeypatch. The request/response transformation and cost calculation stay covered offline. Scope limited to nvidia_nim; other BaseLLMRerankTest providers untouched. * fix(tests): migrate remaining realtime tests off shut-down gpt-4o-realtime-preview OpenAI's 2026-05-07 shutdown removed the entire gpt-4o-realtime-preview family, including the undated 'gpt-4o-realtime-preview' alias (not just the dated snapshot fixed earlier). Three live tests still connected with the dead alias and failed with messages_received=1 (an error event instead of session.created): - test_openai_realtime_simple.py: get_model() -> gpt-realtime (drives TestOpenAIRealtime.test_realtime_connection / test_realtime_with_query_params) - test_openai_realtime.py: test_openai_realtime_direct_call_no_intent and test_openai_realtime_direct_call_with_intent -> openai/gpt-realtime (the with_intent test shares the same dead alias even though it was not in the failing set this run) Mocked unit tests (test_realtime_query_params_construction, test_realtime_query_params_use_normalized_model_name) are left as-is: they never hit the network and assert string plumbing only. Also fixes test_text_message_blocked_by_guardrail_no_ai_response, which now connects (the earlier URL swap worked) but tripped a model-wording-brittle assertion. The guardrail flow asks the model to voice the block message verbatim; gpt-4o-realtime-preview complied (output contained 'blocked'), gpt-realtime refuses verbatim-repeat instructions ('I'm sorry, but I can't repeat that message.'). Since the original user message is blocked before it reaches OpenAI, the refusal is still a safe outcome. Assertion #3 now accepts both voicing and refusal, and adds a hard check that the blocked phrase never leaks into AI output.
1 parent 8c66252 commit ce87c41

4 files changed

Lines changed: 86 additions & 14 deletions

File tree

tests/llm_translation/realtime/test_openai_realtime.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,9 @@ def headers(self):
101101

102102
try:
103103
await litellm._arealtime(
104-
model="openai/gpt-4o-realtime-preview",
104+
# OpenAI shut down the gpt-4o-realtime-preview family (incl. the
105+
# undated alias) on 2026-05-07; gpt-realtime is the GA successor.
106+
model="openai/gpt-realtime",
105107
websocket=websocket_client,
106108
api_key=os.environ.get("OPENAI_API_KEY"),
107109
timeout=60,
@@ -249,14 +251,16 @@ def headers(self):
249251
websocket_client = RealTimeWebSocketClient()
250252
caught_exception = None
251253

254+
# OpenAI shut down the gpt-4o-realtime-preview family (incl. the undated
255+
# alias) on 2026-05-07; gpt-realtime is the GA successor.
252256
query_params: RealtimeQueryParams = {
253-
"model": "openai/gpt-4o-realtime-preview",
257+
"model": "openai/gpt-realtime",
254258
"intent": "chat",
255259
}
256260

257261
try:
258262
await litellm._arealtime(
259-
model="openai/gpt-4o-realtime-preview",
263+
model="openai/gpt-realtime",
260264
websocket=websocket_client,
261265
api_key=os.environ.get("OPENAI_API_KEY"),
262266
query_params=query_params,

tests/llm_translation/realtime/test_openai_realtime_simple.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ class TestOpenAIRealtime(BaseRealtimeTest):
2121
"""
2222

2323
def get_model(self) -> str:
24-
return "gpt-4o-realtime-preview"
24+
# OpenAI shut down the entire gpt-4o-realtime-preview family
25+
# (including the undated alias) on 2026-05-07. gpt-realtime is the
26+
# current GA realtime model.
27+
return "gpt-realtime"
2528

2629
def get_api_key_env_var(self) -> str:
2730
return "OPENAI_API_KEY"

tests/llm_translation/realtime/test_realtime_guardrails_openai.py

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,7 @@
2626
from litellm.types.guardrails import GuardrailEventHooks
2727

2828
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
29-
OPENAI_REALTIME_URL = (
30-
"wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2024-12-17"
31-
)
29+
OPENAI_REALTIME_URL = "wss://api.openai.com/v1/realtime?model=gpt-realtime"
3230

3331
pytestmark = pytest.mark.skipif(
3432
not OPENAI_API_KEY,
@@ -192,10 +190,35 @@ async def test_text_message_blocked_by_guardrail_no_ai_response():
192190
len(transcript_deltas) >= 1
193191
), f"Expected guardrail message in transcript delta, got: {event_types}"
194192

195-
# 3. No *real* AI response should have been generated.
196-
# The guardrail may produce its own response (e.g. "Content blocked: ...")
197-
# via response.cancel + conversation.item.create + response.create.
198-
# We allow the guardrail's own block message but NOT original AI content.
193+
# 3. No *real* AI response to the blocked content should have been
194+
# generated. The original user message is blocked BEFORE it is
195+
# forwarded to OpenAI, so the only thing the model ever sees is the
196+
# guardrail's "say exactly: <block message>" prompt
197+
# (see realtime_streaming.py). Two safe outcomes are possible:
198+
# - the model voices the block message verbatim (older realtime
199+
# snapshots did this -> text contains "blocked"), or
200+
# - the model declines to repeat it (gpt-realtime tends to refuse
201+
# verbatim-repeat instructions, e.g. "I'm sorry, but I can't
202+
# repeat that message.").
203+
# Both mean the blocked prompt itself was never answered, so we
204+
# accept either. The hard invariant is that the blocked phrase must
205+
# never leak into AI output, and the model must not have produced a
206+
# normal answer to the user (which would have neither a block nor a
207+
# refusal marker).
208+
safe_markers = (
209+
"block",
210+
"guardrail",
211+
"content filter",
212+
"policy",
213+
"can't repeat",
214+
"cannot repeat",
215+
"won't repeat",
216+
"can't assist",
217+
"can't help",
218+
"unable to",
219+
"i'm sorry",
220+
"i am sorry",
221+
)
199222
done_events = [e for e in client_events if e.get("type") == "response.done"]
200223
for done in done_events:
201224
output = done.get("response", {}).get("output", [])
@@ -205,11 +228,12 @@ async def test_text_message_blocked_by_guardrail_no_ai_response():
205228
for c in item.get("content", [])
206229
]
207230
real_ai_text = " ".join(ai_texts).strip()
208-
# Allow guardrail-generated block messages (contain "Content blocked" or "blocked")
209231
if real_ai_text:
210232
assert (
211-
"blocked" in real_ai_text.lower()
212-
or "guardrail" in real_ai_text.lower()
233+
BLOCKED_PHRASE not in real_ai_text
234+
), f"Blocked phrase leaked into AI response: {real_ai_text!r}"
235+
assert any(
236+
marker in real_ai_text.lower() for marker in safe_markers
213237
), f"AI responded with non-guardrail content even though message was blocked: {real_ai_text!r}"
214238

215239
finally:

tests/llm_translation/test_nvidia_nim.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,3 +262,44 @@ def get_base_rerank_call_args(self) -> dict:
262262
def get_expected_cost(self) -> float:
263263
"""Nvidia NIM rerank models are free (cost = 0.0)"""
264264
return 0.0
265+
266+
@pytest.mark.asyncio()
267+
@pytest.mark.parametrize("sync_mode", [True, False])
268+
async def test_basic_rerank(self, sync_mode, monkeypatch):
269+
"""
270+
Override the base live rerank test with a mocked HTTP layer.
271+
272+
NVIDIA reached end-of-life for the hosted
273+
nvidia/llama-3.2-nv-rerankqa-1b-v2 rerank API on 2026-05-18 and
274+
published no replacement model, so a live call now returns HTTP 410
275+
("Gone"). NVIDIA's hosted catalog rotates on a schedule, so pointing
276+
at another live model would only defer the same failure. Mock the
277+
transport instead (same pattern as
278+
test_nvidia_nim_rerank_ranking_endpoint above) so the request/response
279+
transformation and cost calculation stay covered offline.
280+
"""
281+
monkeypatch.setenv("NVIDIA_NIM_API_KEY", "fake-api-key")
282+
283+
mock_response = MagicMock()
284+
mock_response.status_code = 200
285+
mock_response.headers = {}
286+
mock_response.text = ""
287+
mock_response.json.return_value = {
288+
"rankings": [
289+
{"index": 0, "logit": 0.95},
290+
{"index": 1, "logit": 0.75},
291+
],
292+
"usage": {"total_tokens": 7},
293+
}
294+
295+
with (
296+
patch(
297+
"litellm.llms.custom_httpx.http_handler.HTTPHandler.post",
298+
return_value=mock_response,
299+
),
300+
patch(
301+
"litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post",
302+
return_value=mock_response,
303+
),
304+
):
305+
await super().test_basic_rerank(sync_mode=sync_mode)

0 commit comments

Comments
 (0)