fix(friendliai): sync serverless catalog with the models API

The catalog carried only meta-llama-3.1-70b-instruct and
meta-llama-3.1-8b-instruct. Both are retired; neither is served by
https://api.friendli.ai/serverless/v1/models anymore, so their pricing and
8k context windows were the only FriendliAI data litellm exposed.

Every field is taken from that endpoint, which is public and unauthenticated:
pricing.input, pricing.output and pricing.input_cache_read map onto the token
costs, context_length onto max_input_tokens, max_completion_tokens onto
max_output_tokens and max_tokens, and functionality.tool_call,
functionality.parallel_tool_call and functionality.structured_output onto the
capability flags. Qwen3-235B-A22B-Instruct-2507 reports a deprecation_date of
2026-08-05 and carries it through.

tests/test_litellm/test_friendliai_model_metadata.py pins the lineup, asserts
the retired keys stay absent, and asserts the root JSON and the bundled backup
never drift apart for this provider
This commit is contained in:
robust-WJL 2026-07-27 15:05:22 +09:00
parent 24123269cc
commit daa43de167
3 changed files with 318 additions and 24 deletions

View file

@ -17198,30 +17198,116 @@
"output_cost_per_token": 0.0,
"source": "https://fireworks.ai/pricing"
},
"friendliai/meta-llama-3.1-70b-instruct": {
"input_cost_per_token": 6e-07,
"friendliai/LGAI-EXAONE/K-EXAONE-236B-A23B": {
"cache_read_input_token_cost": 1e-07,
"input_cost_per_token": 2e-07,
"litellm_provider": "friendliai",
"max_input_tokens": 8192,
"max_output_tokens": 8192,
"max_tokens": 8192,
"max_input_tokens": 262144,
"max_output_tokens": 262144,
"max_tokens": 262144,
"mode": "chat",
"output_cost_per_token": 6e-07,
"output_cost_per_token": 8e-07,
"supports_function_calling": true,
"supports_parallel_function_calling": true,
"supports_prompt_caching": true,
"supports_response_schema": true,
"supports_system_messages": true,
"supports_tool_choice": true
},
"friendliai/MiniMaxAI/MiniMax-M2.5": {
"cache_read_input_token_cost": 6e-08,
"input_cost_per_token": 3e-07,
"litellm_provider": "friendliai",
"max_input_tokens": 196608,
"max_output_tokens": 196608,
"max_tokens": 196608,
"mode": "chat",
"output_cost_per_token": 1.2e-06,
"supports_function_calling": true,
"supports_parallel_function_calling": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_system_messages": true,
"supports_tool_choice": true
},
"friendliai/Qwen/Qwen3-235B-A22B-Instruct-2507": {
"deprecation_date": "2026-08-05",
"input_cost_per_token": 2e-07,
"litellm_provider": "friendliai",
"max_input_tokens": 262144,
"max_output_tokens": 262144,
"max_tokens": 262144,
"mode": "chat",
"output_cost_per_token": 8e-07,
"supports_function_calling": true,
"supports_parallel_function_calling": true,
"supports_response_schema": true,
"supports_system_messages": true,
"supports_tool_choice": true
},
"friendliai/meta-llama-3.1-8b-instruct": {
"input_cost_per_token": 1e-07,
"friendliai/deepseek-ai/DeepSeek-V3.2": {
"cache_read_input_token_cost": 2.5e-07,
"input_cost_per_token": 5e-07,
"litellm_provider": "friendliai",
"max_input_tokens": 8192,
"max_output_tokens": 8192,
"max_tokens": 8192,
"max_input_tokens": 163840,
"max_output_tokens": 163840,
"max_tokens": 163840,
"mode": "chat",
"output_cost_per_token": 1e-07,
"output_cost_per_token": 1.5e-06,
"supports_function_calling": true,
"supports_parallel_function_calling": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_system_messages": true,
"supports_tool_choice": true
},
"friendliai/google/gemma-4-31B-it": {
"input_cost_per_token": 1.4e-07,
"litellm_provider": "friendliai",
"max_input_tokens": 262144,
"max_output_tokens": 262144,
"max_tokens": 262144,
"mode": "chat",
"output_cost_per_token": 4e-07,
"supports_function_calling": true,
"supports_parallel_function_calling": true,
"supports_response_schema": true,
"supports_system_messages": true,
"supports_tool_choice": true,
"supports_vision": true
},
"friendliai/zai-org/GLM-5.1": {
"cache_read_input_token_cost": 2.6e-07,
"input_cost_per_token": 1.4e-06,
"litellm_provider": "friendliai",
"max_input_tokens": 202752,
"max_output_tokens": 202752,
"max_tokens": 202752,
"mode": "chat",
"output_cost_per_token": 4.4e-06,
"supports_function_calling": true,
"supports_parallel_function_calling": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_system_messages": true,
"supports_tool_choice": true
},
"friendliai/zai-org/GLM-5.2": {
"cache_read_input_token_cost": 2.6e-07,
"input_cost_per_token": 1.4e-06,
"litellm_provider": "friendliai",
"max_input_tokens": 1048576,
"max_output_tokens": 1048576,
"max_tokens": 1048576,
"mode": "chat",
"output_cost_per_token": 4.4e-06,
"supports_function_calling": true,
"supports_parallel_function_calling": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_system_messages": true,
"supports_tool_choice": true

View file

@ -17198,30 +17198,116 @@
"output_cost_per_token": 0.0,
"source": "https://fireworks.ai/pricing"
},
"friendliai/meta-llama-3.1-70b-instruct": {
"input_cost_per_token": 6e-07,
"friendliai/LGAI-EXAONE/K-EXAONE-236B-A23B": {
"cache_read_input_token_cost": 1e-07,
"input_cost_per_token": 2e-07,
"litellm_provider": "friendliai",
"max_input_tokens": 8192,
"max_output_tokens": 8192,
"max_tokens": 8192,
"max_input_tokens": 262144,
"max_output_tokens": 262144,
"max_tokens": 262144,
"mode": "chat",
"output_cost_per_token": 6e-07,
"output_cost_per_token": 8e-07,
"supports_function_calling": true,
"supports_parallel_function_calling": true,
"supports_prompt_caching": true,
"supports_response_schema": true,
"supports_system_messages": true,
"supports_tool_choice": true
},
"friendliai/MiniMaxAI/MiniMax-M2.5": {
"cache_read_input_token_cost": 6e-08,
"input_cost_per_token": 3e-07,
"litellm_provider": "friendliai",
"max_input_tokens": 196608,
"max_output_tokens": 196608,
"max_tokens": 196608,
"mode": "chat",
"output_cost_per_token": 1.2e-06,
"supports_function_calling": true,
"supports_parallel_function_calling": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_system_messages": true,
"supports_tool_choice": true
},
"friendliai/Qwen/Qwen3-235B-A22B-Instruct-2507": {
"deprecation_date": "2026-08-05",
"input_cost_per_token": 2e-07,
"litellm_provider": "friendliai",
"max_input_tokens": 262144,
"max_output_tokens": 262144,
"max_tokens": 262144,
"mode": "chat",
"output_cost_per_token": 8e-07,
"supports_function_calling": true,
"supports_parallel_function_calling": true,
"supports_response_schema": true,
"supports_system_messages": true,
"supports_tool_choice": true
},
"friendliai/meta-llama-3.1-8b-instruct": {
"input_cost_per_token": 1e-07,
"friendliai/deepseek-ai/DeepSeek-V3.2": {
"cache_read_input_token_cost": 2.5e-07,
"input_cost_per_token": 5e-07,
"litellm_provider": "friendliai",
"max_input_tokens": 8192,
"max_output_tokens": 8192,
"max_tokens": 8192,
"max_input_tokens": 163840,
"max_output_tokens": 163840,
"max_tokens": 163840,
"mode": "chat",
"output_cost_per_token": 1e-07,
"output_cost_per_token": 1.5e-06,
"supports_function_calling": true,
"supports_parallel_function_calling": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_system_messages": true,
"supports_tool_choice": true
},
"friendliai/google/gemma-4-31B-it": {
"input_cost_per_token": 1.4e-07,
"litellm_provider": "friendliai",
"max_input_tokens": 262144,
"max_output_tokens": 262144,
"max_tokens": 262144,
"mode": "chat",
"output_cost_per_token": 4e-07,
"supports_function_calling": true,
"supports_parallel_function_calling": true,
"supports_response_schema": true,
"supports_system_messages": true,
"supports_tool_choice": true,
"supports_vision": true
},
"friendliai/zai-org/GLM-5.1": {
"cache_read_input_token_cost": 2.6e-07,
"input_cost_per_token": 1.4e-06,
"litellm_provider": "friendliai",
"max_input_tokens": 202752,
"max_output_tokens": 202752,
"max_tokens": 202752,
"mode": "chat",
"output_cost_per_token": 4.4e-06,
"supports_function_calling": true,
"supports_parallel_function_calling": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_system_messages": true,
"supports_tool_choice": true
},
"friendliai/zai-org/GLM-5.2": {
"cache_read_input_token_cost": 2.6e-07,
"input_cost_per_token": 1.4e-06,
"litellm_provider": "friendliai",
"max_input_tokens": 1048576,
"max_output_tokens": 1048576,
"max_tokens": 1048576,
"mode": "chat",
"output_cost_per_token": 4.4e-06,
"supports_function_calling": true,
"supports_parallel_function_calling": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_system_messages": true,
"supports_tool_choice": true

View file

@ -0,0 +1,122 @@
"""Regression tests for the FriendliAI serverless catalog entries.
The catalog previously carried only ``friendliai/meta-llama-3.1-70b-instruct``
and ``friendliai/meta-llama-3.1-8b-instruct``, both retired and no longer
served by ``https://api.friendli.ai/serverless/v1/models``. These tests pin the
current serverless lineup, assert the retired entries stay gone, and assert the
root JSON and the bundled backup JSON never drift apart for this provider.
"""
import json
from pathlib import Path
from typing import Final, NamedTuple
import pytest
class FriendliModel(NamedTuple):
key: str
input_cost_per_token: float
output_cost_per_token: float
cache_read_input_token_cost: float | None
max_input_tokens: int
max_output_tokens: int
EXPECTED_MODELS: Final[tuple[FriendliModel, ...]] = (
FriendliModel(
"friendliai/LGAI-EXAONE/K-EXAONE-236B-A23B", 2e-07, 8e-07, 1e-07, 262144, 262144
),
FriendliModel(
"friendliai/MiniMaxAI/MiniMax-M2.5", 3e-07, 1.2e-06, 6e-08, 196608, 196608
),
FriendliModel(
"friendliai/Qwen/Qwen3-235B-A22B-Instruct-2507", 2e-07, 8e-07, None, 262144, 262144
),
FriendliModel(
"friendliai/deepseek-ai/DeepSeek-V3.2", 5e-07, 1.5e-06, 2.5e-07, 163840, 163840
),
FriendliModel(
"friendliai/google/gemma-4-31B-it", 1.4e-07, 4e-07, None, 262144, 262144
),
FriendliModel(
"friendliai/zai-org/GLM-5.1", 1.4e-06, 4.4e-06, 2.6e-07, 202752, 202752
),
FriendliModel(
"friendliai/zai-org/GLM-5.2", 1.4e-06, 4.4e-06, 2.6e-07, 1048576, 1048576
),
)
RETIRED_KEYS: Final[tuple[str, ...]] = (
"friendliai/meta-llama-3.1-70b-instruct",
"friendliai/meta-llama-3.1-8b-instruct",
)
_REPO_ROOT: Final[Path] = Path(__file__).resolve().parents[2]
ROOT_PATH: Final[Path] = _REPO_ROOT / "model_prices_and_context_window.json"
BACKUP_PATH: Final[Path] = (
_REPO_ROOT / "litellm" / "model_prices_and_context_window_backup.json"
)
CATALOG_PATHS: Final[tuple[Path, ...]] = (ROOT_PATH, BACKUP_PATH)
def _load(path: Path) -> dict[str, dict[str, object]]:
return json.loads(path.read_text(encoding="utf-8"))
@pytest.mark.parametrize("path", CATALOG_PATHS, ids=lambda p: p.name)
@pytest.mark.parametrize("expected", EXPECTED_MODELS, ids=lambda m: m.key)
def test_serverless_model_pricing_and_context(path: Path, expected: FriendliModel) -> None:
entry = _load(path).get(expected.key)
assert entry is not None, f"{expected.key} missing from {path.name}"
assert entry["litellm_provider"] == "friendliai"
assert entry["mode"] == "chat"
assert entry["input_cost_per_token"] == expected.input_cost_per_token
assert entry["output_cost_per_token"] == expected.output_cost_per_token
assert entry.get("cache_read_input_token_cost") == expected.cache_read_input_token_cost
assert entry["max_input_tokens"] == expected.max_input_tokens
assert entry["max_output_tokens"] == expected.max_output_tokens
assert entry["max_tokens"] == expected.max_output_tokens
@pytest.mark.parametrize("path", CATALOG_PATHS, ids=lambda p: p.name)
@pytest.mark.parametrize("expected", EXPECTED_MODELS, ids=lambda m: m.key)
def test_serverless_models_advertise_tool_calling(
path: Path, expected: FriendliModel
) -> None:
entry = _load(path)[expected.key]
assert entry["supports_function_calling"] is True
assert entry["supports_parallel_function_calling"] is True
assert entry["supports_response_schema"] is True
assert entry["supports_tool_choice"] is True
@pytest.mark.parametrize("path", CATALOG_PATHS, ids=lambda p: p.name)
@pytest.mark.parametrize("key", RETIRED_KEYS)
def test_retired_llama_entries_absent(path: Path, key: str) -> None:
assert key not in _load(path), f"{key} is retired and must not be re-added"
def test_prompt_caching_flag_tracks_cache_read_cost() -> None:
catalog = _load(ROOT_PATH)
for expected in EXPECTED_MODELS:
entry = catalog[expected.key]
assert entry.get("supports_prompt_caching", False) is (
expected.cache_read_input_token_cost is not None
), f"{expected.key} prompt-caching flag disagrees with cache_read cost"
def test_deprecating_model_carries_iso_date() -> None:
entry = _load(ROOT_PATH)["friendliai/Qwen/Qwen3-235B-A22B-Instruct-2507"]
assert entry["deprecation_date"] == "2026-08-05"
def test_root_and_backup_agree_on_every_friendliai_entry() -> None:
root = {k: v for k, v in _load(ROOT_PATH).items() if k.startswith("friendliai/")}
backup = {k: v for k, v in _load(BACKUP_PATH).items() if k.startswith("friendliai/")}
assert root == backup
def test_catalog_covers_exactly_the_expected_lineup() -> None:
keys = {k for k in _load(ROOT_PATH) if k.startswith("friendliai/")}
assert keys == {m.key for m in EXPECTED_MODELS}