mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-14 23:21:35 +00:00
feat(friendli): auto-sync Friendli model metadata into price registry
Add Friendli /serverless/v1/models as a data source to the weekly
auto_update_price_and_context_window_file.py sync.
For each Friendli model we emit a friendliai/{id} entry. When the
model's base_model already exists in litellm (under any provider
prefix), capability flags (supports_prompt_caching, supports_vision,
reasoning effort flags, etc.) are inherited from that curated entry;
Friendli-supplied pricing, context/output limits, modalities, and
reasoning options always override.
Reasoning option type=effort values (none/minimal/low/medium/high/
xhigh/max) are mapped to the matching litellm supports_*_reasoning_effort
boolean flags. budget_tokens with min=-1 means 'no budget limit' and
is left implicit -- litellm has no equivalent field.
6 new entries added (K-EXAONE-236B, MiniMax-M2.5, DeepSeek-V3.2,
GLM-5.1, gemma-4-31B-it, GLM-5.2); schema re-validated.
backup JSON intentionally untouched: ci_cd/check_files_match.py keeps
it in sync with main during CI, so it stays out of this diff.
This commit is contained in:
parent
49da936efb
commit
e8311b586c
2 changed files with 325 additions and 2 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import asyncio
|
||||
import aiohttp
|
||||
import json
|
||||
from typing import Any, Optional
|
||||
|
||||
# Asynchronously fetch data from a given URL
|
||||
async def fetch_data(url):
|
||||
|
|
@ -21,6 +22,203 @@ async def fetch_data(url):
|
|||
print("Error fetching data from URL:", e)
|
||||
return None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Friendli
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
FRIENDLI_API_URL = "https://api.friendli.ai/serverless/v1/models"
|
||||
FRIENDLI_PROVIDER = "friendliai"
|
||||
|
||||
# litellm model-entry keys that Friendli API provides directly.
|
||||
# These override the base model's value when set.
|
||||
FRIENDLI_OVERRIDE_KEYS = (
|
||||
"context_length",
|
||||
"max_completion_tokens",
|
||||
"pricing",
|
||||
"reasoning_options",
|
||||
"input_modalities",
|
||||
"output_modalities",
|
||||
"interleaved",
|
||||
"description",
|
||||
)
|
||||
|
||||
# Keys copied from the base-model entry (if one exists in litellm) to the
|
||||
# Friendli entry, so that manually-curated capability metadata is inherited.
|
||||
INHERITABLE_BASE_KEYS = (
|
||||
"supports_reasoning",
|
||||
"supports_function_calling",
|
||||
"supports_parallel_function_calling",
|
||||
"supports_response_schema",
|
||||
"supports_system_messages",
|
||||
"supports_tool_choice",
|
||||
"supports_vision",
|
||||
"supports_pdf_input",
|
||||
"supports_prompt_caching",
|
||||
"supports_assistant_prefill",
|
||||
"supports_low_reasoning_effort",
|
||||
"supports_minimal_reasoning_effort",
|
||||
"supports_max_reasoning_effort",
|
||||
"supports_xhigh_reasoning_effort",
|
||||
"supports_none_reasoning_effort",
|
||||
"supports_adaptive_thinking",
|
||||
"supports_output_config",
|
||||
"supports_native_structured_output",
|
||||
)
|
||||
|
||||
# litellm reasoning-effort boolean flags keyed by the effort string returned by
|
||||
# the Friendli API ("none", "minimal", "low", "medium", "high", "xhigh", "max").
|
||||
EFFORT_FLAG_MAP = {
|
||||
"none": "supports_none_reasoning_effort",
|
||||
"minimal": "supports_minimal_reasoning_effort",
|
||||
"low": "supports_low_reasoning_effort",
|
||||
"medium": "supports_low_reasoning_effort", # ponytail: litellm has no "medium" flag; medium implies low. Upgrade when a medium flag is added.
|
||||
"high": "supports_max_reasoning_effort", # ponytail: litellm only has low/minimal/none/xhigh/max, not a standalone "high". Map high→max.
|
||||
"xhigh": "supports_xhigh_reasoning_effort",
|
||||
"max": "supports_max_reasoning_effort",
|
||||
}
|
||||
|
||||
|
||||
def _find_base_model_entry(base_model: str, local_data: dict) -> Optional[str]:
|
||||
"""Return the litellm key for ``base_model`` if one already exists.
|
||||
|
||||
Friendli ``base_model`` is a canonical model id like ``zhipuai/glm-5.2`` or
|
||||
``minimax/minimax-m2.5``. litellm stores the same model under various provider
|
||||
prefixes (``zai/glm-5.2``, ``cloudflare/@cf/zai-org/glm-5.2`` etc). We match the
|
||||
tail of the base_model against every existing key so capability flags are
|
||||
inherited from whichever provider entry is already curated.
|
||||
"""
|
||||
if not base_model:
|
||||
return None
|
||||
bm_tail = base_model.split("/")[-1].lower()
|
||||
# Exact key match (base_model itself could be a litellm key).
|
||||
if base_model in local_data:
|
||||
return base_model
|
||||
# Tail match against every key's last segment.
|
||||
for key in local_data:
|
||||
if key.startswith("sample_spec") or key == "fallback_generalizations":
|
||||
continue
|
||||
if key.split("/")[-1].lower() == bm_tail:
|
||||
return key
|
||||
return None
|
||||
|
||||
|
||||
def _effort_flags(reasoning_options: list) -> dict:
|
||||
"""Map Friendli ``reasoning_options`` effort values to litellm boolean flags."""
|
||||
flags: dict[str, bool] = {}
|
||||
for opt in reasoning_options or []:
|
||||
if opt.get("type") == "effort":
|
||||
for val in opt.get("values", []):
|
||||
flag = EFFORT_FLAG_MAP.get(val)
|
||||
if flag:
|
||||
flags[flag] = True
|
||||
return flags
|
||||
|
||||
|
||||
def _pricing(pricing: dict) -> dict:
|
||||
"""Convert Friendli pricing dict → litellm cost fields."""
|
||||
out: dict[str, Any] = {}
|
||||
if not pricing:
|
||||
return out
|
||||
if "input" in pricing:
|
||||
out["input_cost_per_token"] = float(pricing["input"])
|
||||
if "output" in pricing:
|
||||
out["output_cost_per_token"] = float(pricing["output"])
|
||||
if "input_cache_read" in pricing and pricing["input_cache_read"] is not None:
|
||||
out["cache_read_input_token_cost"] = float(pricing["input_cache_read"])
|
||||
return out
|
||||
|
||||
|
||||
def _modalities(input_mods: list, output_mods: list) -> dict:
|
||||
"""Convert Friendli modality lists to litellm capability flags."""
|
||||
out: dict[str, Any] = {}
|
||||
if "image" in (input_mods or []):
|
||||
out["supports_vision"] = True
|
||||
out["supports_image_input"] = True
|
||||
return out
|
||||
|
||||
|
||||
def transform_friendli_data(data: list, local_data: dict) -> dict:
|
||||
"""Transform the Friendli /models response into litellm model entries.
|
||||
|
||||
For each Friendli model we build a ``friendliai/{id}`` entry. When the
|
||||
model's ``base_model`` already exists in litellm (under any provider prefix)
|
||||
we inherit capability flags; Friendli-supplied pricing/limits/modalities/
|
||||
reasoning always override.
|
||||
"""
|
||||
transformed: dict[str, dict] = {}
|
||||
for model in data:
|
||||
model_id = model["id"]
|
||||
base_model = model.get("base_model") or ""
|
||||
entry: dict[str, Any] = {
|
||||
"litellm_provider": FRIENDLI_PROVIDER,
|
||||
}
|
||||
|
||||
# --- Inherit capability flags from an existing base-model entry ---
|
||||
base_key = _find_base_model_entry(base_model, local_data)
|
||||
if base_key:
|
||||
base_entry = local_data[base_key]
|
||||
for k in INHERITABLE_BASE_KEYS:
|
||||
if k in base_entry:
|
||||
entry[k] = base_entry[k]
|
||||
|
||||
# --- Override with Friendli-supplied values ---
|
||||
ctx = model.get("context_length")
|
||||
if ctx is not None:
|
||||
entry["max_input_tokens"] = int(ctx)
|
||||
entry["max_tokens"] = int(ctx)
|
||||
max_out = model.get("max_completion_tokens")
|
||||
if max_out is not None:
|
||||
entry["max_output_tokens"] = int(max_out)
|
||||
|
||||
# Pricing
|
||||
entry.update(_pricing(model.get("pricing", {})))
|
||||
|
||||
# Reasoning
|
||||
if model.get("reasoning") is True:
|
||||
entry["supports_reasoning"] = True
|
||||
# Effort flags (if present) override inherited ones.
|
||||
entry.update(_effort_flags(model.get("reasoning_options", [])))
|
||||
|
||||
# Functionality
|
||||
func = model.get("functionality", {})
|
||||
if func.get("tool_call") is True:
|
||||
entry["supports_function_calling"] = True
|
||||
if func.get("parallel_tool_call") is True:
|
||||
entry["supports_parallel_function_calling"] = True
|
||||
if func.get("structured_output") is True:
|
||||
entry["supports_response_schema"] = True
|
||||
entry["supports_native_structured_output"] = True
|
||||
if func.get("system_messages") is True:
|
||||
entry["supports_system_messages"] = True
|
||||
if func.get("tool_choice") is True:
|
||||
entry["supports_tool_choice"] = True
|
||||
|
||||
# Modalities
|
||||
entry.update(_modalities(
|
||||
model.get("input_modalities", []),
|
||||
model.get("output_modalities", []),
|
||||
))
|
||||
|
||||
# Mode
|
||||
entry["mode"] = model.get("mode", "chat")
|
||||
|
||||
# Description → comment (free-form)
|
||||
desc = model.get("description")
|
||||
if desc:
|
||||
entry["comment"] = desc
|
||||
|
||||
# Deprecation
|
||||
dep = model.get("deprecation_date")
|
||||
if dep:
|
||||
entry["deprecation_date"] = dep.split("T")[0]
|
||||
|
||||
# Source URL for traceability
|
||||
entry["source"] = FRIENDLI_API_URL
|
||||
|
||||
transformed[f"{FRIENDLI_PROVIDER}/{model_id}"] = entry
|
||||
return transformed
|
||||
|
||||
# Synchronize local data with remote data
|
||||
def sync_local_data_with_remote(local_data, remote_data):
|
||||
# Update existing keys in local_data with values from remote_data
|
||||
|
|
@ -143,9 +341,14 @@ def main():
|
|||
vercel_data = asyncio.run(fetch_data(vercel_ai_gateway_url))
|
||||
# Transform the fetched Vercel AI Gateway data
|
||||
vercel_data = transform_vercel_ai_gateway_data(vercel_data)
|
||||
|
||||
# Fetch Friendli data (no auth required for the public /models endpoint)
|
||||
friendli_data = asyncio.run(fetch_data(FRIENDLI_API_URL))
|
||||
# Transform Friendli data, inheriting capability flags from existing base-model entries
|
||||
friendli_data = transform_friendli_data(friendli_data, local_data)
|
||||
|
||||
# Combine both datasets
|
||||
all_remote_data = {**openrouter_data, **vercel_data}
|
||||
all_remote_data = {**openrouter_data, **vercel_data, **friendli_data}
|
||||
|
||||
# If both local and openrouter data are available, synchronize and save
|
||||
if local_data and all_remote_data:
|
||||
|
|
|
|||
|
|
@ -50691,5 +50691,125 @@
|
|||
"supports_response_schema": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true
|
||||
},
|
||||
"friendliai/LGAI-EXAONE/K-EXAONE-236B-A23B": {
|
||||
"litellm_provider": "friendliai",
|
||||
"max_input_tokens": 262144,
|
||||
"max_tokens": 262144,
|
||||
"max_output_tokens": 262144,
|
||||
"input_cost_per_token": 2e-07,
|
||||
"output_cost_per_token": 8e-07,
|
||||
"cache_read_input_token_cost": 1e-07,
|
||||
"supports_reasoning": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_native_structured_output": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"mode": "chat",
|
||||
"comment": "Open multilingual MoE model for reasoning, agentic tool use, and long-context work with strong Korean capabilities",
|
||||
"deprecation_date": "2026-08-20",
|
||||
"source": "https://api.friendli.ai/serverless/v1/models"
|
||||
},
|
||||
"friendliai/MiniMaxAI/MiniMax-M2.5": {
|
||||
"litellm_provider": "friendliai",
|
||||
"max_input_tokens": 196608,
|
||||
"max_tokens": 196608,
|
||||
"max_output_tokens": 196608,
|
||||
"input_cost_per_token": 3e-07,
|
||||
"output_cost_per_token": 1.2e-06,
|
||||
"cache_read_input_token_cost": 6e-08,
|
||||
"supports_reasoning": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_native_structured_output": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"mode": "chat",
|
||||
"comment": "Prior MiniMax coding model for agent workflows, office edits, and automation",
|
||||
"source": "https://api.friendli.ai/serverless/v1/models"
|
||||
},
|
||||
"friendliai/deepseek-ai/DeepSeek-V3.2": {
|
||||
"litellm_provider": "friendliai",
|
||||
"supports_reasoning": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_assistant_prefill": true,
|
||||
"max_input_tokens": 163840,
|
||||
"max_tokens": 163840,
|
||||
"max_output_tokens": 163840,
|
||||
"input_cost_per_token": 5e-07,
|
||||
"output_cost_per_token": 1.5e-06,
|
||||
"cache_read_input_token_cost": 2.5e-07,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_native_structured_output": true,
|
||||
"supports_system_messages": true,
|
||||
"mode": "chat",
|
||||
"comment": "DeepSeek chat model for instruction following, coding, and analysis",
|
||||
"source": "https://api.friendli.ai/serverless/v1/models"
|
||||
},
|
||||
"friendliai/zai-org/GLM-5.1": {
|
||||
"litellm_provider": "friendliai",
|
||||
"supports_reasoning": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_prompt_caching": true,
|
||||
"max_input_tokens": 202752,
|
||||
"max_tokens": 202752,
|
||||
"max_output_tokens": 202752,
|
||||
"input_cost_per_token": 1.4e-06,
|
||||
"output_cost_per_token": 4.4e-06,
|
||||
"cache_read_input_token_cost": 2.6e-07,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_native_structured_output": true,
|
||||
"supports_system_messages": true,
|
||||
"mode": "chat",
|
||||
"comment": "Strong GLM coding model for agentic engineering, terminals, and repository generation",
|
||||
"source": "https://api.friendli.ai/serverless/v1/models"
|
||||
},
|
||||
"friendliai/google/gemma-4-31B-it": {
|
||||
"litellm_provider": "friendliai",
|
||||
"supports_vision": true,
|
||||
"max_input_tokens": 262144,
|
||||
"max_tokens": 262144,
|
||||
"max_output_tokens": 262144,
|
||||
"input_cost_per_token": 1.4e-07,
|
||||
"output_cost_per_token": 4e-07,
|
||||
"supports_reasoning": true,
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_native_structured_output": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_image_input": true,
|
||||
"mode": "chat",
|
||||
"comment": "Largest Gemma 4 instruction model for open, self-hosted chat and reasoning",
|
||||
"source": "https://api.friendli.ai/serverless/v1/models"
|
||||
},
|
||||
"friendliai/zai-org/GLM-5.2": {
|
||||
"litellm_provider": "friendliai",
|
||||
"supports_reasoning": true,
|
||||
"supports_function_calling": true,
|
||||
"max_input_tokens": 1048576,
|
||||
"max_tokens": 1048576,
|
||||
"max_output_tokens": 1048576,
|
||||
"input_cost_per_token": 1.4e-06,
|
||||
"output_cost_per_token": 4.4e-06,
|
||||
"cache_read_input_token_cost": 2.6e-07,
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_native_structured_output": true,
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"mode": "chat",
|
||||
"comment": "Open flagship GLM for long-horizon coding agents and million-token context work",
|
||||
"source": "https://api.friendli.ai/serverless/v1/models"
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue