litellm/tests/test_litellm/test_gpt_realtime_mode.py
tin-berri 4e19127350
fix(pricing): add undated azure aliases for gpt-audio-mini and gpt-realtime-mini (#37867)
* fix(pricing): add undated azure aliases for gpt-audio-mini and gpt-realtime-mini

Azure deployments are commonly created against the undated model name,
and the cost-tracking docs say to set base_model to azure/<model> — but
only the dated -2025-10-06 entries existed for these two models (the
openai provider has undated aliases for both). base_model:
azure/gpt-audio-mini therefore resolved to nothing and, depending on the
fallback path, text tokens billed at $0 while audio tokens billed fine.

Mirror the -2025-10-06 entries as undated aliases, exactly like the
undated openai entries mirror their newest dated variant.

Fixes #33170

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(pricing): assert undated azure audio aliases exactly mirror their dated entries

Review follow-up: COST_FIELDS missed realtime-specific cost keys
(cache_creation_input_audio_token_cost, cache_read_input_token_cost,
input_cost_per_image). Full-entry equality catches drift on every field.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(pricing): mirror updated mode=realtime on the undated gpt-realtime-mini alias

Upstream changed the dated entry's mode from chat to realtime after this
branch was cut; the undated alias must stay a byte-for-byte mirror.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(pricing): mirror the new deprecation_date onto the undated gpt-audio-mini alias

* test(pricing): use shared local_model_cost_map fixture so get_model_info's lru_cache never crosses maps

---------

Co-authored-by: Mihidum Hettiyahandi <55163074+mihidumh@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 18:44:19 -07:00

91 lines
3.3 KiB
Python

import json
import typing
from pathlib import Path
import pytest
import litellm
from litellm.types.utils import ModelInfoBase
REALTIME_ONLY_GPT_MODELS = (
"azure/gpt-realtime-2025-08-28",
"azure/gpt-realtime-1.5-2026-02-23",
"azure/gpt-realtime-mini",
"azure/gpt-realtime-mini-2025-10-06",
"gpt-realtime",
"gpt-realtime-1.5",
"gpt-realtime-2",
"gpt-realtime-2.1",
"gpt-realtime-2.1-mini",
"gpt-realtime-mini",
"gpt-realtime-2025-08-28",
"gpt-realtime-mini-2025-10-06",
"gpt-realtime-mini-2025-12-15",
)
REALTIME_ONLY_GPT_MODELS_WITHOUT_ENDPOINTS = (
"azure/eu/gpt-4o-mini-realtime-preview-2024-12-17",
"azure/eu/gpt-4o-realtime-preview-2024-10-01",
"azure/eu/gpt-4o-realtime-preview-2024-12-17",
"azure/gpt-4o-mini-realtime-preview-2024-12-17",
"azure/gpt-4o-realtime-preview-2024-10-01",
"azure/gpt-4o-realtime-preview-2024-12-17",
"azure/us/gpt-4o-mini-realtime-preview-2024-12-17",
"azure/us/gpt-4o-realtime-preview-2024-10-01",
"azure/us/gpt-4o-realtime-preview-2024-12-17",
"gpt-4o-mini-realtime-preview",
"gpt-4o-mini-realtime-preview-2024-12-17",
"gpt-4o-realtime-preview",
"gpt-4o-realtime-preview-2024-12-17",
"gpt-4o-realtime-preview-2025-06-03",
)
ALL_REALTIME_ONLY_GPT_MODELS = REALTIME_ONLY_GPT_MODELS + REALTIME_ONLY_GPT_MODELS_WITHOUT_ENDPOINTS
def _load_cost_map() -> dict:
json_path = Path(__file__).parents[2] / "model_prices_and_context_window.json"
with open(json_path) as f:
return json.load(f)
def test_realtime_is_a_valid_mode_literal():
hints = typing.get_type_hints(ModelInfoBase, include_extras=False)
assert "realtime" in typing.get_args(hints["mode"])
@pytest.mark.parametrize("model", REALTIME_ONLY_GPT_MODELS)
def test_realtime_only_gpt_models_are_mode_realtime(model):
"""These models only serve /v1/realtime and are rejected by /v1/chat/completions
("This is not a chat model ..."), so they must not be tagged mode=chat."""
info = _load_cost_map()[model]
assert info["supported_endpoints"] == ["/v1/realtime"]
assert info["mode"] == "realtime"
@pytest.mark.parametrize("model", REALTIME_ONLY_GPT_MODELS_WITHOUT_ENDPOINTS)
def test_realtime_only_gpt_4o_models_are_mode_realtime(model):
"""gpt-4o(-mini)-realtime-preview are realtime-only and must not be mode=chat."""
assert _load_cost_map()[model]["mode"] == "realtime"
def test_get_model_info_reports_realtime_mode(monkeypatch):
"""get_model_info must resolve the retag against the bundled cost map, not the
hosted map fetched from main, which lags this repo until the next promotion."""
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
monkeypatch.setattr(litellm, "model_cost", litellm.get_model_cost_map(url=""))
litellm.get_model_info.cache_clear()
try:
assert litellm.get_model_info("gpt-realtime-mini")["mode"] == "realtime"
finally:
litellm.get_model_info.cache_clear()
def test_backup_matches_main_for_realtime_models():
repo_root = Path(__file__).parents[2]
with open(repo_root / "model_prices_and_context_window.json") as f:
main_cost = json.load(f)
with open(repo_root / "litellm" / "model_prices_and_context_window_backup.json") as f:
backup_cost = json.load(f)
for model in ALL_REALTIME_ONLY_GPT_MODELS:
assert backup_cost.get(model) == main_cost.get(model)