mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-14 09:03:16 +00:00
fix(proxy): give an older Codex a translated entry for models past its version
Codex's own entries name the oldest release that can read them in minimal_client_version. A release older than that can fail to decode the entry, such as a reasoning effort it has no name for, which fails the whole catalog, so it now gets the translated entry, whose levels are trimmed to that release
This commit is contained in:
parent
95264b4e6d
commit
fa8558d8ca
2 changed files with 62 additions and 1 deletions
|
|
@ -32,6 +32,7 @@ from pathlib import Path
|
|||
from types import MappingProxyType
|
||||
from typing import TYPE_CHECKING, Final, Literal, TypeAlias
|
||||
|
||||
from packaging.version import Version
|
||||
from pydantic import ConfigDict, TypeAdapter, ValidationError
|
||||
|
||||
from litellm._logging import verbose_proxy_logger
|
||||
|
|
@ -109,6 +110,7 @@ class CodexStockModel(LiteLLMBaseModel):
|
|||
supported_reasoning_levels: tuple[CodexReasoningLevel, ...] = ()
|
||||
context_window: int | None = None
|
||||
max_context_window: int | None = None
|
||||
minimal_client_version: str | tuple[int, ...] | None = None
|
||||
|
||||
|
||||
class CodexStockCatalog(LiteLLMBaseModel):
|
||||
|
|
@ -278,6 +280,19 @@ def _stock_for(row: CodexCatalogRow, stock: Mapping[str, CodexStockModel]) -> Co
|
|||
return next((stock[name] for name in names if name in stock), None)
|
||||
|
||||
|
||||
def _readable_by(stock_model: CodexStockModel, client_version: str | None) -> bool:
|
||||
"""Whether Codex's own server would hand this entry to the requesting release: one at or past the entry's
|
||||
`minimal_client_version`. An older release can fail to decode it, and one entry it cannot decode (a reasoning
|
||||
effort it has no name for) fails the whole catalog, so it gets a translated entry. No version keeps the entry."""
|
||||
minimum: Final = stock_model.minimal_client_version
|
||||
if minimum is None or client_version is None:
|
||||
return True
|
||||
try:
|
||||
return Version(client_version) >= Version(minimum if isinstance(minimum, str) else ".".join(map(str, minimum)))
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
||||
def _tier_fields(stock_model: CodexStockModel, tiers: tuple[CodexServiceTier, ...]) -> Mapping[str, object]:
|
||||
"""The tier fields of an entry offering `tiers`. Codex turns `/fast` on off a speed tier as much as off a
|
||||
service tier, so a speed tier stays only while a service tier billed the same way is still offered, and the
|
||||
|
|
@ -370,7 +385,8 @@ def _entry(
|
|||
alias_defaults: CodexAliasDefaults,
|
||||
client_version: str | None,
|
||||
) -> CodexCatalogEntry:
|
||||
stock_model: Final = _stock_for(row, stock)
|
||||
matched: Final = _stock_for(row, stock)
|
||||
stock_model: Final = matched if matched is not None and _readable_by(matched, client_version) else None
|
||||
stock_tiers: Final = MappingProxyType(
|
||||
{tier.id: tier for tier in (stock_model.service_tiers if stock_model is not None else ())}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -180,6 +180,51 @@ def test_a_model_codex_knows_keeps_its_own_reasoning_levels(levels):
|
|||
assert (entry["default_reasoning_level"], entry["supported_reasoning_levels"]) == ("medium", levels)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("minimum", ["0.153.0", [0, 153, 0]], ids=["string", "parts"])
|
||||
@pytest.mark.parametrize(
|
||||
("client_version", "display_name", "levels"),
|
||||
[
|
||||
("0.153.0", "GPT-6.1 Terra", ["low", "max"]),
|
||||
("0.166.0", "GPT-6.1 Terra", ["low", "max"]),
|
||||
(None, "GPT-6.1 Terra", ["low", "max"]),
|
||||
("0.152.9", "gpt-6.1-terra", ["low", "max"]),
|
||||
("0.137.0", "gpt-6.1-terra", ["low"]),
|
||||
("not-a-version", "gpt-6.1-terra", ["low"]),
|
||||
],
|
||||
)
|
||||
def test_a_codex_older_than_an_entrys_minimal_version_gets_an_entry_it_can_decode(
|
||||
minimum, client_version, display_name, levels
|
||||
):
|
||||
"""Codex 0.137 rejects the whole catalog on an effort it has no name for, such as the `max` Codex's own
|
||||
gpt-6.1-terra entry offers, so a release older than the entry's `minimal_client_version` gets the translated
|
||||
entry, whose levels are trimmed to that release."""
|
||||
stock = CodexStockModel.model_validate(
|
||||
{
|
||||
"slug": "gpt-6.1-terra",
|
||||
"display_name": "GPT-6.1 Terra",
|
||||
"priority": 3,
|
||||
"visibility": "list",
|
||||
"minimal_client_version": minimum,
|
||||
"default_reasoning_level": "low",
|
||||
"supported_reasoning_levels": [{"effort": "low"}, {"effort": "max"}],
|
||||
}
|
||||
)
|
||||
|
||||
(entry,) = json.loads(
|
||||
codex_models_response_json(
|
||||
(_row("gpt-6.1-terra", supported_reasoning_efforts=("low", "max")),),
|
||||
stock={"gpt-6.1-terra": stock},
|
||||
instructions=_PROMPT,
|
||||
client_version=client_version,
|
||||
).json
|
||||
)["models"]
|
||||
|
||||
assert (entry["display_name"], [level["effort"] for level in entry["supported_reasoning_levels"]]) == (
|
||||
display_name,
|
||||
levels,
|
||||
)
|
||||
|
||||
|
||||
def test_known_model_keeps_codex_stock_entry_listed_under_the_served_id():
|
||||
(entry,) = _models(_row("gpt-daybreak"))
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue