diff --git a/.github/workflows/sync-together-ai-models.yml b/.github/workflows/sync-together-ai-models.yml new file mode 100644 index 00000000000..f1a8a841d0f --- /dev/null +++ b/.github/workflows/sync-together-ai-models.yml @@ -0,0 +1,68 @@ +name: Sync Together AI model registry + +on: + schedule: + - cron: "30 6 * * *" + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + sync_together_ai_models: + if: github.repository == 'BerriAI/litellm' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + with: + ref: litellm_internal_staging + persist-credentials: false + - name: Set up uv + uses: ./.github/actions/setup-uv-with-retries + with: + version: "0.10.9" + - name: Look for an already-open sync PR + id: existing + run: | + open_pr="$(gh pr list --repo "$GITHUB_REPOSITORY" --state open --limit 1000 --json headRefName \ + --jq '[.[].headRefName | select(startswith("litellm_together_registry_sync_"))] | first // empty')" + echo "open_pr=$open_pr" >> "$GITHUB_OUTPUT" + if [ -n "$open_pr" ]; then + echo "An open sync PR already exists on branch $open_pr; skipping this run." + fi + env: + GH_TOKEN: ${{ secrets.GH_TOKEN }} + - name: Run the sync + if: steps.existing.outputs.open_pr == '' + run: | + uv run --frozen python scripts/sync_together_ai_models.py --write --pr-body-file "$RUNNER_TEMP/pr_body.md" + env: + TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }} + - name: Regenerate the JSON schema + if: steps.existing.outputs.open_pr == '' + run: | + uv run --frozen python ci_cd/generate_model_prices_schema.py + - name: Create a pull request when the registry changed + if: steps.existing.outputs.open_pr == '' + run: | + if git diff --quiet; then + echo "Registry already in sync; no PR needed." + exit 0 + fi + branch="litellm_together_registry_sync_$(date +'%Y-%m-%d')" + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git checkout -b "$branch" + git add model_prices_and_context_window.json \ + litellm/model_prices_and_context_window_backup.json \ + model_prices_and_context_window.schema.json + git commit -m "feat(models): sync together_ai model registry $(date +'%Y-%m-%d')" + gh auth setup-git + git push origin "$branch" + gh pr create --title "feat(models): sync together_ai model registry" \ + --body-file "$RUNNER_TEMP/pr_body.md" \ + --head "$branch" \ + --base litellm_internal_staging + env: + GH_TOKEN: ${{ secrets.GH_TOKEN }} diff --git a/scripts/sync_together_ai_models.py b/scripts/sync_together_ai_models.py new file mode 100644 index 00000000000..97b308fb660 --- /dev/null +++ b/scripts/sync_together_ai_models.py @@ -0,0 +1,539 @@ +"""Sync the together_ai entries of model_prices_and_context_window.json with Together's live serverless catalog. + +Pulls ``GET https://api.together.ai/v1/models?serverless`` plus the deprecations doc, maps API fields onto +registry fields, merges the reviewed capability rules below for everything the API cannot express, and diffs +the result against the registry. Dry run (the default) prints the diff summary and the generated PR body; +``--write`` applies the changes to the root cost map and its ``litellm/`` backup copy. + +Policy highlights: +- Prices arrive per 1M tokens with float artifacts and are normalized to clean per-token values. +- A registry entry absent from the serverless catalog is marked with ``deprecation_date`` from the docs + deprecation table, never deleted; absences with no docs date are surfaced for a human call. +- Availability comes from the API: a model the docs list as removed but the API still serves stays live, + with the conflict surfaced as a warning. +- Manually curated values the API cannot express (``metadata.successor``, ``max_output_tokens`` on existing + entries, capability flags no rule covers) are never overwritten; conflicts are surfaced instead. +""" + +import argparse +import json +import os +import re +import sys +from collections.abc import Mapping, Sequence +from dataclasses import dataclass, field +from pathlib import Path +from types import MappingProxyType +from typing import Final + +import httpx +from pydantic import BaseModel, TypeAdapter, ValidationError + +MODELS_URL: Final = "https://api.together.ai/v1/models?serverless" +DEPRECATIONS_URL: Final = "https://docs.together.ai/docs/deprecations.md" +PROVIDER: Final = "together_ai" +PREFIX: Final = "together_ai/" +SOURCE_URL: Final = "https://docs.together.ai/docs/serverless-models" +COST_MAP_RELPATHS: Final = ( + "model_prices_and_context_window.json", + "litellm/model_prices_and_context_window_backup.json", +) + +TYPE_TO_MODE: Final = MappingProxyType({"chat": "chat", "embedding": "embedding", "moderation": "chat"}) + + +class SyncError(RuntimeError): + pass + + +class CatalogPricing(BaseModel): + input: float + output: float + cached_input: float | None = None + + +class CatalogModel(BaseModel): + id: str + type: str + context_length: int | None = None + pricing: CatalogPricing + + +CATALOG_ADAPTER: Final = TypeAdapter(list[CatalogModel]) + +RegistryEntry = dict[str, object] +CostMap = dict[str, object] + + +@dataclass(frozen=True, slots=True) +class CapabilityRule: + model_id: str + fields: Mapping[str, bool | int] + provenance: str + + +def _rule(model_id: str, provenance: str, **fields: bool | int) -> CapabilityRule: + return CapabilityRule(model_id=model_id, fields=MappingProxyType(dict(fields)), provenance=provenance) + + +_TOOLS: Final = MappingProxyType( + { + "supports_function_calling": True, + "supports_parallel_function_calling": True, + "supports_response_schema": True, + "supports_tool_choice": True, + } +) + +CAPABILITY_RULES: Final = ( + _rule( + "MiniMaxAI/MiniMax-M3", + "reviewed for the LIT-5968 backfill against https://www.together.ai/models/minimax-m3", + **_TOOLS, + supports_reasoning=True, + supports_vision=True, + ), + _rule("Prism-ML/Ternary-Bonsai-27B", "reviewed for the LIT-5968 backfill; no tool or vision support documented"), + _rule( + "Qwen/Qwen3.5-9B", + "reviewed for the LIT-5968 backfill against https://www.together.ai/models/qwen3-5-9b", + **_TOOLS, + supports_reasoning=True, + supports_vision=True, + ), + _rule( + "Qwen/Qwen3.6-Plus", + "reviewed for the LIT-5968 backfill; hybrid reasoning model without a documented tools contract", + supports_reasoning=True, + ), + _rule("Qwen/Qwen3.7-Max", "reviewed for the LIT-5968 backfill; no tool or vision support documented"), + _rule("Qwen/Qwen3.7-Plus", "reviewed for the LIT-5968 backfill; no tool or vision support documented"), + _rule("Qwen/Qwen3.8-2.4T-A95B", "reviewed for the LIT-5968 backfill; no tool or vision support documented"), + _rule("arize-ai/qwen-2-1.5b-instruct", "reviewed for the LIT-5968 backfill; no tool or vision support documented"), + _rule( + "deepseek-ai/DeepSeek-V4-Flash-0731", + "reviewed for the LIT-5968 backfill against https://www.together.ai/models/deepseek-v4-flash", + **_TOOLS, + ), + _rule( + "deepseek-ai/DeepSeek-V4-Pro", + "reviewed for the LIT-5968 backfill against https://www.together.ai/models/deepseek-v4-pro", + **_TOOLS, + supports_reasoning=True, + ), + _rule( + "deepseek-ai/DeepSeek-V4-Pro-0813", + "reviewed for the LIT-5968 backfill against https://www.together.ai/models/deepseek-v4-pro", + **_TOOLS, + ), + _rule("google/gemma-3n-E4B-it", "reviewed for the LIT-5968 backfill; no tool or vision support documented"), + _rule( + "google/gemma-4-31B-it", + "reviewed for the LIT-5968 backfill against https://www.together.ai/models/gemma-4-31b-it", + **_TOOLS, + supports_vision=True, + ), + _rule( + "intfloat/multilingual-e5-large-instruct", + "embedding dims per https://huggingface.co/intfloat/multilingual-e5-large-instruct", + output_vector_size=1024, + ), + _rule( + "meta-llama/Llama-3.3-70B-Instruct-Turbo", + "reviewed for the LIT-5968 backfill against https://docs.together.ai/docs/function-calling", + **_TOOLS, + ), + _rule( + "meta-llama/Llama-Guard-4-12B", + "moderation classifier with a chat-shaped API; no tools per the LIT-5968 backfill review", + ), + _rule("meta-models/Muse-Glimmer-30B", "reviewed for the LIT-5968 backfill; no tool or vision support documented"), + _rule( + "moonshotai/Kimi-K2.7-Code", + "reviewed for the LIT-5968 backfill against https://www.together.ai/models/kimi-k2-7-code", + **_TOOLS, + supports_vision=True, + ), + _rule( + "moonshotai/Kimi-K3", + "reviewed for the LIT-5968 backfill against https://www.together.ai/models/kimi-k3", + **_TOOLS, + supports_reasoning=True, + supports_vision=True, + ), + _rule( + "nvidia/nemotron-3-ultra-550b-a55b", + "reviewed for the LIT-5968 backfill against https://www.together.ai/models/nemotron-3-ultra", + **_TOOLS, + supports_reasoning=True, + ), + _rule( + "openai/gpt-oss-120b", + "reviewed for the LIT-5968 backfill against https://www.together.ai/models/gpt-oss-120b", + **_TOOLS, + supports_reasoning=True, + ), + _rule( + "openai/gpt-oss-20b", + "reviewed for the LIT-5968 backfill against https://www.together.ai/models/gpt-oss-20b", + **_TOOLS, + ), + _rule("pearl-ai/gemma-4-31b-it", "reviewed for the LIT-5968 backfill; no tool or vision support documented"), + _rule( + "thinkingmachines/Inkling", + "reviewed for the LIT-5968 backfill against https://www.together.ai/models/inkling", + **_TOOLS, + ), + _rule("thinkingmachines/Inkling-Small", "reviewed for the LIT-5968 backfill; no tool or vision support documented"), + _rule( + "zai-org/GLM-5.2", + "reviewed for the LIT-5968 backfill against https://www.together.ai/models/glm-5-2", + **_TOOLS, + supports_reasoning=True, + ), +) + +RULES_BY_ID: Final = MappingProxyType({rule.model_id: rule for rule in CAPABILITY_RULES}) + + +@dataclass(frozen=True, slots=True) +class DeprecationDoc: + removal_dates: Mapping[str, str] + redirects: Mapping[str, str] + + +_REDIRECT_ROW: Final = re.compile(r"^\|\s*`([^`]+)`\s*\|\s*`([^`]+)`\s*\|") +_REMOVAL_ROW: Final = re.compile(r"^\|\s*(\d{4}-\d{2}-\d{2})\s*\|\s*`([^`]+)`\s*\|") + + +def _section(markdown: str, heading: str) -> str: + level: Final = heading.split(" ", 1)[0] + start: Final = markdown.find(f"\n{heading}\n") + if start < 0: + return "" + body: Final = markdown[start + 1 + len(heading) :] + next_heading: Final = re.search(rf"^{re.escape(level)} ", body, flags=re.MULTILINE) + return body[: next_heading.start()] if next_heading else body + + +def parse_deprecations(markdown: str) -> DeprecationDoc: + redirect_rows: Final = tuple( + m.groups() + for m in (_REDIRECT_ROW.match(line) for line in _section(markdown, "## Active model redirects").splitlines()) + if m + ) + inference: Final = _section(_section(markdown, "## Deprecation history"), "### Inference") + removal_rows: Final = tuple(m.groups() for m in (_REMOVAL_ROW.match(line) for line in inference.splitlines()) if m) + if not redirect_rows or not removal_rows: + raise SyncError( + "deprecations doc parsed to zero redirect or removal rows; the table format at " + f"{DEPRECATIONS_URL} changed and the parser needs updating" + ) + removal_dates: Final = {model: date for date, model in reversed(removal_rows)} + return DeprecationDoc( + removal_dates=MappingProxyType(dict(reversed(removal_dates.items()))), + redirects=MappingProxyType({original: target for original, target in redirect_rows}), + ) + + +def per_token(price_per_million: float) -> float: + return float(f"{price_per_million / 1e6:.6g}") + + +def _resolve_name(name: str, universe: frozenset[str]) -> str | None: + if name in universe: + return name + suffix_matches: Final = tuple(candidate for candidate in universe if candidate.endswith(f"/{name}")) + return suffix_matches[0] if len(suffix_matches) == 1 else None + + +def resolve_successor(model_id: str, doc: DeprecationDoc, live_ids: frozenset[str]) -> str | None: + canonical: Final = live_ids | frozenset(doc.removal_dates) + redirects: Final = { + (_resolve_name(raw_source, canonical) or raw_source): (_resolve_name(raw_target, canonical) or raw_target) + for raw_source, raw_target in doc.redirects.items() + } + seen: Final = set() + current = model_id # rebind-ok: walks the redirect chain + while current in redirects and current not in seen: + seen.add(current) + current = redirects[current] # rebind-ok: walks the redirect chain + return current if current != model_id and current in live_ids else None + + +@dataclass(frozen=True, slots=True) +class SyncOutcome: + cost_map: CostMap + added: tuple[str, ...] = () + updated: tuple[str, ...] = () + deprecated: tuple[str, ...] = () + reappeared: tuple[str, ...] = () + warnings: tuple[str, ...] = () + skipped_types: Mapping[str, int] = field(default_factory=dict) + + @property + def has_changes(self) -> bool: + return bool(self.added or self.updated or self.deprecated or self.reappeared) + + +def _api_fields(model: CatalogModel) -> RegistryEntry: + cached: Final = model.pricing.cached_input + return { + "input_cost_per_token": per_token(model.pricing.input), + "output_cost_per_token": per_token(model.pricing.output), + **({"cache_read_input_token_cost": per_token(cached), "supports_prompt_caching": True} if cached else {}), + **({"max_input_tokens": model.context_length} if model.context_length is not None else {}), + } + + +def _new_entry(model: CatalogModel, mode: str) -> RegistryEntry: + rule: Final = RULES_BY_ID.get(model.id) + length_fields: Final = ( + {} + if model.context_length is None + else {"max_input_tokens": model.context_length, "max_tokens": model.context_length} + | ({"max_output_tokens": model.context_length} if mode == "chat" else {}) + ) + merged: Final = { + **_api_fields(model), + **length_fields, + "litellm_provider": PROVIDER, + "mode": mode, + "source": SOURCE_URL, + **(dict(rule.fields) if rule else {}), + } + return dict(sorted(merged.items())) + + +def _updated_entry(entry: RegistryEntry, model: CatalogModel) -> tuple[RegistryEntry, tuple[str, ...]]: + rule: Final = RULES_BY_ID.get(model.id) + desired: Final = {**_api_fields(model), **(dict(rule.fields) if rule else {})} + dropped: Final = () if model.pricing.cached_input else ("cache_read_input_token_cost", "supports_prompt_caching") + changes: Final = tuple( + f"{name}: {entry.get(name)!r} -> {value!r}" for name, value in desired.items() if entry.get(name) != value + ) + tuple( + f"{name}: {entry[name]!r} removed (no longer in the catalog pricing)" for name in dropped if name in entry + ) + merged: Final = {name: value for name, value in {**entry, **desired}.items() if name not in dropped} + return dict(sorted(merged.items())), changes + + +def _with_new_keys_in_block(original: CostMap, result: CostMap, new_keys: Sequence[str]) -> CostMap: + provider_keys: Final = tuple(key for key in original if key.startswith(PREFIX)) + if not new_keys or not provider_keys: + return result + block_end: Final = provider_keys[-1] + return { + key: value + for existing in original + for key, value in ( + (existing, result[existing]), + *((new, result[new]) for new in sorted(new_keys) if existing == block_end), + ) + } + + +def compute_sync(cost_map: CostMap, catalog: Sequence[CatalogModel], doc: DeprecationDoc) -> SyncOutcome: + live_ids: Final = frozenset(model.id for model in catalog) + token_models: Final = {model.id: model for model in catalog if model.type in TYPE_TO_MODE} + skipped: Final = { + model.type: sum(1 for m in catalog if m.type == model.type) + for model in catalog + if model.type not in TYPE_TO_MODE + } + registry_ids: Final = {key.removeprefix(PREFIX): key for key in cost_map if key.startswith(PREFIX)} + + added: Final[list[str]] = [] + updated: Final[list[str]] = [] + deprecated: Final[list[str]] = [] + reappeared: Final[list[str]] = [] + warnings: Final[list[str]] = [] + result: Final[CostMap] = dict(cost_map) + + for model_id, model in sorted(token_models.items()): + mode: Final = TYPE_TO_MODE[model.type] + key: Final = f"{PREFIX}{model_id}" + if model_id in doc.removal_dates: + warnings.append( + f"`{key}` is listed as removed on {doc.removal_dates[model_id]} in the docs but the serverless " + "catalog still serves it; availability kept from the API" + ) + entry = result.get(key) + if not isinstance(entry, dict): + result[key] = _new_entry(model, mode) + added.append(key) + if model.type == "chat" and model_id not in RULES_BY_ID: + warnings.append( + f"`{key}` added without a capability rule; review its tools/vision/reasoning support and add one" + ) + continue + if entry.get("mode") != mode: + warnings.append( + f"`{key}` has curated mode {entry.get('mode')!r} but the catalog maps to {mode!r}; left unchanged" + ) + new_entry, changes = _updated_entry(entry, model) + if "deprecation_date" in new_entry: + new_entry.pop("deprecation_date") + reappeared.append(key) + if changes: + updated.append(f"{key}: " + "; ".join(changes)) + if changes or key in reappeared: + result[key] = new_entry + + for model_id, key in sorted(registry_ids.items()): + if model_id in token_models: + continue + entry = result.get(key) + if not isinstance(entry, dict): + continue + removal_date: Final = doc.removal_dates.get(model_id) + successor: Final = resolve_successor(model_id, doc, live_ids) + metadata = entry.get("metadata") + curated_successor: Final = metadata.get("successor") if isinstance(metadata, dict) else None + new_entry = dict(entry) + if removal_date is not None and entry.get("deprecation_date") != removal_date: + if "deprecation_date" in entry: + warnings.append( + f"`{key}` has curated deprecation_date {entry.get('deprecation_date')!r} but the docs list " + f"{removal_date!r}; left unchanged" + ) + else: + new_entry["deprecation_date"] = removal_date + if removal_date is None and "deprecation_date" not in entry: + warnings.append( + f"`{key}` is absent from the serverless catalog with no removal date in the docs; " + "needs a human deprecation call" + ) + if successor is not None: + desired_successor: Final = f"{PREFIX}{successor}" + if curated_successor is None: + new_entry["metadata"] = dict( + sorted({**(metadata if isinstance(metadata, dict) else {}), "successor": desired_successor}.items()) + ) + elif curated_successor != desired_successor: + warnings.append( + f"`{key}` has curated successor {curated_successor!r} but the docs redirects resolve to " + f"{desired_successor!r}; left unchanged" + ) + if new_entry != entry: + result[key] = dict(sorted(new_entry.items())) + deprecated.append(f"{key}: " + ", ".join(sorted(set(new_entry) - set(entry)) or ["updated"])) + + return SyncOutcome( + cost_map=_with_new_keys_in_block(cost_map, result, tuple(added)), + added=tuple(added), + updated=tuple(updated), + deprecated=tuple(deprecated), + reappeared=tuple(reappeared), + warnings=tuple(warnings), + skipped_types=MappingProxyType(skipped), + ) + + +def _section_block(title: str, lines: Sequence[str], backtick: bool) -> str: + bullets: Final = "\n".join(f"- `{line}`" if backtick else f"- {line}" for line in lines) or "- none" + return f"### {title} ({len(lines)})\n{bullets}\n" + + +def render_pr_body(outcome: SyncOutcome) -> str: + skipped: Final = ", ".join(f"{kind} ({count})" for kind, count in sorted(outcome.skipped_types.items())) or "none" + return ( + "Automated daily sync of the together_ai entries in model_prices_and_context_window.json against " + f"`GET {MODELS_URL}` and {DEPRECATIONS_URL} by scripts/sync_together_ai_models.py.\n" + "\n" + f"{_section_block('Added', outcome.added, backtick=True)}" + "\n" + f"{_section_block('Updated', outcome.updated, backtick=True)}" + "\n" + f"{_section_block('Marked deprecated', outcome.deprecated, backtick=True)}" + "\n" + f"{_section_block('Returned to the catalog', outcome.reappeared, backtick=True)}" + "\n" + f"{_section_block('Warnings needing a human call', outcome.warnings, backtick=False)}" + "\n" + f"Catalog model types outside the sync's token-pricing scope, skipped: {skipped}\n" + ) + + +def render_summary(outcome: SyncOutcome) -> str: + return ( + f"added={len(outcome.added)} updated={len(outcome.updated)} deprecated={len(outcome.deprecated)} " + f"reappeared={len(outcome.reappeared)} warnings={len(outcome.warnings)}" + ) + + +def load_catalog(raw: bytes) -> list[CatalogModel]: + parsed: Final = json.loads(raw) + entries: Final = parsed.get("data") if isinstance(parsed, dict) else parsed + try: + catalog: Final = CATALOG_ADAPTER.validate_python(entries) + except ValidationError as error: + raise SyncError(f"the catalog response no longer matches the expected shape: {error}") from error + if not any(model.type in TYPE_TO_MODE for model in catalog): + raise SyncError( + "the catalog response contains no token-priced models; refusing to mark the whole registry deprecated" + ) + return catalog + + +def _fetch(url: str, headers: Mapping[str, str]) -> bytes: + response: Final = httpx.get(url, headers=dict(headers), timeout=30, follow_redirects=True) + if response.status_code != 200: + raise SyncError(f"GET {url} returned {response.status_code}") + return response.content + + +def _serialize(cost_map: CostMap) -> str: + return json.dumps(cost_map, indent=4, ensure_ascii=False) + "\n" + + +def main(argv: Sequence[str]) -> int: + parser: Final = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--write", action="store_true", help="apply the sync to the cost map files (default: dry run)") + parser.add_argument("--models-json", type=Path, help="recorded catalog response to use instead of the live API") + parser.add_argument( + "--deprecations-md", type=Path, help="recorded deprecations doc to use instead of the live docs" + ) + parser.add_argument("--pr-body-file", type=Path, help="write the generated PR body to this path") + parser.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parent.parent) + args: Final = parser.parse_args(argv) + + if args.models_json is not None: + catalog_raw: Final = args.models_json.read_bytes() + else: + api_key: Final = os.environ.get("TOGETHER_API_KEY") + if not api_key: + raise SyncError("TOGETHER_API_KEY is not set and --models-json was not given") + catalog_raw = _fetch(MODELS_URL, {"Authorization": f"Bearer {api_key}"}) # rebind-ok: branch-dependent source + catalog: Final = load_catalog(catalog_raw) + markdown: Final = ( + args.deprecations_md.read_text() if args.deprecations_md is not None else _fetch(DEPRECATIONS_URL, {}).decode() + ) + doc: Final = parse_deprecations(markdown) + + cost_map_path: Final = args.repo_root / COST_MAP_RELPATHS[0] + cost_map: Final = json.loads(cost_map_path.read_text()) + outcome: Final = compute_sync(cost_map, catalog, doc) + body: Final = render_pr_body(outcome) + + if args.pr_body_file is not None: + args.pr_body_file.write_text(body) + if args.write and outcome.has_changes: + for relpath in COST_MAP_RELPATHS: + (args.repo_root / relpath).write_text(_serialize(outcome.cost_map)) + print(render_summary(outcome)) + print() + print(body) + if not args.write: + print("dry run: no files were touched") + elif not outcome.has_changes: + print("registry already in sync: no files were touched") + return 0 + + +if __name__ == "__main__": + try: + raise SystemExit(main(sys.argv[1:])) + except SyncError as error: + print(f"SYNC FAILED: {error}", file=sys.stderr) + raise SystemExit(1) from error diff --git a/tests/test_litellm/fixtures/together_ai_sync/deprecations.md b/tests/test_litellm/fixtures/together_ai_sync/deprecations.md new file mode 100644 index 00000000000..b75e0825cee --- /dev/null +++ b/tests/test_litellm/fixtures/together_ai_sync/deprecations.md @@ -0,0 +1,442 @@ +> ## Documentation Index +> Fetch the complete documentation index at: https://docs.together.ai/llms.txt +> Use this file to discover all available pages before exploring further. + +# Deprecations + +> Together AI's model lifecycle policy, including upgrades, redirects, and deprecation schedules. + +Together AI regularly updates the platform with new open-source models. This page describes the model lifecycle policy and lists active redirects and scheduled deprecations. + +## Model lifecycle policy + +Together AI follows a structured approach to introducing new models, upgrading existing models, and deprecating older versions, so you can rely on predictable behavior. + +### Model upgrades (redirects) + +An **upgrade** is a model release that is materially the same model lineage with targeted improvements and no fundamental changes to how developers use or reason about it. + +A model qualifies as an upgrade when **one or more** of the following are true (and none of the "new model" criteria apply): + +* Same modality and task profile (e.g., instruct → instruct, reasoning → reasoning). +* Same architecture family (e.g., DeepSeek-V3 → DeepSeek-V3-0324). +* Post-training or fine-tuning improvements, bug fixes, safety tuning, or small data refresh. +* Behavior is strongly compatible (prompting patterns and evals are similar). +* Pricing change is none or small (≤10% increase). + +**Outcome:** The current endpoint redirects to the upgraded version after a **3-day notice**. The old version remains available via dedicated endpoints. + +### New models (no redirect) + +A **new model** is a release with materially different capabilities, costs, or operating characteristics, so a silent redirect would be misleading. + +Any of the following triggers classification as a new model: + +* Modality shift (e.g., reasoning-only ↔ instruct/hybrid, text → multimodal). +* Architecture shift (e.g., Qwen3 → Qwen3-Next, Llama 3 → Llama 4). +* Large behavior shift (prompting patterns, output style, or verbosity materially different). +* Experimental flag by provider (e.g., DeepSeek-V3-Exp). +* Large price change (>10% increase or pricing structure change). +* Benchmark deltas that meaningfully change task positioning. +* Safety policy or system prompt changes that noticeably affect outputs. + +**Outcome:** No automatic redirect. Together AI announces the new model and deprecates the old one on a **2-week timeline** (both are available during this window). You must explicitly switch model IDs. + +## Active model redirects + +The following models are redirected to newer versions. Requests to the original model ID are automatically routed to the upgraded version: + +| Original model | Redirects to | Notes | +| :----------------------------------- | :---------------------------------------- | :---------------------------------------- | +| `mistralai/Mistral-7B-Instruct-v0.3` | `mistralai/Ministral-3-14B-Instruct-2512` | Same lineage, upgraded version | +| `Kimi-K2` | `Kimi-K2-0905` | Same architecture, improved post-training | +| `DeepSeek-V3` | `DeepSeek-V3.1` | Same architecture, targeted improvements | +| `DeepSeek-V3-0324` | `DeepSeek-V3.1` | Same architecture, targeted improvements | +| `DeepSeek-R1` | `DeepSeek-R1-0528` | Same architecture, targeted improvements | + + + If you need to use the original model version, you can always deploy it as a [dedicated endpoint](/docs/dedicated-endpoints). + + +## Deprecation policy + +| Model type | Deprecation notice | Notes | +| :--------------------------- | :---------------------------------- | :------------------------------------------------------- | +| Preview model | \<24 hours of notice, after 30 days | Clearly marked in docs and playground with "Preview" tag | +| Serverless endpoint | 2 or 3 weeks\* | | +| On-demand dedicated endpoint | 2 or 3 weeks\* | | + +\*Depends on usage and whether a newer version of the model is available. + +* If you use a model scheduled for deprecation, you receive an email notification. +* All changes appear on this page. +* Each deprecated model has a specified removal date. +* After the removal date, the model is no longer available via its serverless endpoint, but migration options are described below. + +## Migration options + +When a model is deprecated on the serverless platform, you have three options: + +1. **On-demand dedicated endpoint** (if supported): + * Reserved solely for you. You choose the underlying hardware. + * Charged on a price-per-minute basis. + * Endpoints can be dynamically spun up and down. +2. **Monthly reserved dedicated endpoint:** + * Reserved solely for you. + * Charged on a month-by-month basis. + * Can be requested via this [form](https://together.ai/monthly-reserved). +3. **Migrate to a newer serverless model:** + * Switch to an updated model on the serverless platform. + +## Migration steps + +1. Review the deprecation table below to find your current model. +2. Check if on-demand dedicated endpoints are supported for your model. +3. Decide on your preferred migration option. +4. If you choose a new serverless model, test your application thoroughly before migrating. +5. Update your API calls to use the new model or dedicated endpoint. + +## Deprecation history + +### Inference + +The table below lists all models removed from serverless inference, most recent first. + +| Removal date | Model | Supported by on-demand dedicated endpoints | +| :-------------------------- | :-------------------------------------------------- | :----------------------------------------- | +| 2026-08-21 | `deepcogito/cogito-v2-1-671b` | No | +| 2026-08-04 | `google/gemma-3n-E4B-it` | No | +| 2026-07-10 | `Qwen/Qwen3-235B-A22B-Instruct-2507-tput` | Yes | +| 2026-07-10 | `meta-llama/Meta-Llama-3-8B-Instruct-Lite` | No | +| 2026-07-10 | `zai-org/GLM-5.1` | Yes | +| 2026-06-29 | `Qwen/Qwen3.5-397B-A17B` | Yes | +| 2026-06-22 | `zai-org/GLM-5` | No | +| 2026-06-11 | `mistralai/Voxtral-Mini-3B-2507` | No | +| 2026-06-04 | `Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8` | Yes | +| 2026-05-27 | `black-forest-labs/FLUX.1-krea-dev` | No | +| 2026-05-21 | `moonshotai/Kimi-K2.5` | No | +| 2026-05-14 | `deepseek-ai/DeepSeek-R1` | No | +| 2026-05-14 | `deepseek-ai/DeepSeek-V3.1` | Yes | +| 2026-05-14 | `Qwen/Qwen3-Coder-Next-FP8` | Yes | +| 2026-04-16 | `Qwen/Qwen3-VL-8B-Instruct` | Yes | +| 2026-04-16 | `Qwen/Qwen3-235B-A22B-Thinking-2507` | Yes | +| 2026-04-16 | `mistralai/Mixtral-8x7B-Instruct-v0.1` | Yes | +| 2026-04-03 | `ServiceNow-AI/Apriel-1.5-15b-Thinker` | No | +| 2026-04-03 | `ServiceNow-AI/Apriel-1.6-15b-Thinker` | No | +| 2026-04-02 | `zai-org/GLM-4.5-Air-FP8` | No | +| 2026-04-02 | `zai-org/GLM-4.7` | No | +| 2026-04-02 | `mistralai/Mistral-Small-24B-Instruct-2501` | No | +| 2026-04-02 | `Qwen/Qwen3-Next-80B-A3B-Instruct` | Yes | +| 2026-03-31 | `meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8` | Yes | +| 2026-03-06 | `mixedbread-ai/Mxbai-Rerank-Large-V2` | No | +| 2026-03-06 | `meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo` | Yes | +| 2026-03-06 | `Qwen/Qwen3-235B-A22B-Thinking-2507` | Yes | +| 2026-03-06 | `moonshotai/Kimi-K2-Thinking` | No | +| 2026-03-06 | `moonshotai/Kimi-K2-Instruct-0905` | No | +| 2026-03-06 | `meta-llama/Llama-3.2-3B-Instruct-Turbo` | No | +| 2026-02-25 | `black-forest-labs/FLUX.1-dev` | No | +| 2026-02-25 | `black-forest-labs/FLUX.1-dev-lora` | No | +| 2026-02-25 | `black-forest-labs/FLUX.1-Kontext-dev` | No | +| 2026-02-25 | `Qwen/Qwen3-VL-32B-Instruct` | No | +| 2026-02-25 | `meta-llama/Llama-3.2-3B-Instruct-Turbo-Classifier` | No | +| 2026-02-25 | `mistralai/Ministral-3-14B-Instruct` | No | +| 2026-02-25 | `Qwen/Qwen3-Next-80B-A3B-Thinking` | No | +| 2026-02-25 | `Alibaba-NLP/gte-modernbert-base` | No | +| 2026-02-25 | `BAAI/bge-base-en-v1.5-vllm` | No | +| 2026-02-25 | `meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo` | No | +| 2026-02-25 | `meta-llama/Llama-Guard-3-11B-Vision-Turbo` | No | +| 2026-02-25 | `meta-llama/LlamaGuard-2-8b` | No | +| 2026-02-25 | `marin-community/Marin-8B-Instruct` | No | +| 2026-02-25 | `nvidia/Nvidia-Nemotron-Nano-9B-v2` | No | +| 2026-02-06 | `togethercomputer/m2-bert-80M-32k-retrieval` | No | +| 2026-02-06 | `Salesforce/Llama-Rank-V1` | No | +| 2026-02-06 | `togethercomputer/Refuel-Llm-V2` | No | +| 2026-02-06 | `togethercomputer/Refuel-Llm-V2-Small` | No | +| 2026-02-06 | `Qwen/Qwen3-235B-A22B-fp8-tput` | No | +| 2026-02-06 | `qwen-qwen2-5-14b-instruct-lora` | No | +| 2026-02-06 | `meta-llama/Llama-4-Scout-17B-16E-Instruct` | Yes | +| 2026-02-06 | `Qwen/Qwen2.5-72B-Instruct-Turbo` | No | +| 2026-02-06 | `meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo` | No | +| 2026-02-06 | `BAAI/bge-large-en-v1.5` | No | +| 2026-02-03 | `deepseek-ai/DeepSeek-R1-0528-tput` | No | +| 2026-01-05 | `Qwen/Qwen2.5-VL-72B-Instruct` | No | +| 2025-12-23 | `deepseek-ai/DeepSeek-R1-Distill-Llama-70B` | No | +| 2025-12-23 | `meta-llama/Meta-Llama-3-70B-Instruct-Turbo` | No | +| 2025-12-23 | `black-forest-labs/FLUX.1-schnell-free` | No | +| 2025-12-23 | `meta-llama/Meta-Llama-Guard-3-8B` | No | +| 2025-11-19 | `deepcogito/cogito-v2-preview-deepseek-671b` | No | +| 2025-07-25 | `arcee-ai/caller` | No | +| 2025-07-25 | `arcee-ai/arcee-blitz` | No | +| 2025-07-25 | `arcee-ai/virtuoso-medium-v2` | No | +| 2025-11-17 | `arcee-ai/virtuoso-large` | No | +| 2025-11-17 | `arcee-ai/maestro-reasoning` | No | +| 2025-11-17 | `arcee_ai/arcee-spotlight` | No | +| 2025-11-17 | `arcee-ai/coder-large` | No | +| 2025-11-13 | `deepseek-ai/DeepSeek-R1-Distill-Qwen-14B` | No | +| 2025-11-13 | `mistralai/Mistral-7B-Instruct-v0.1` | No | +| 2025-11-13 | `Qwen/Qwen2.5-Coder-32B-Instruct` | No | +| 2025-11-13 | `Qwen/QwQ-32B` | No | +| 2025-11-13 | `deepseek-ai/DeepSeek-R1-Distill-Llama-70B-free` | No | +| 2025-11-13 | `meta-llama/Llama-3.3-70B-Instruct-Turbo-Free` | No | +| 2025-08-28 | `Qwen/Qwen2-VL-72B-Instruct` | No | +| 2025-08-28 | `nvidia/Llama-3.1-Nemotron-70B-Instruct-HF` | No | +| 2025-08-28 | `perplexity-ai/r1-1776` | No | +| 2025-08-28 | `meta-llama/Meta-Llama-3-8B-Instruct` | No | +| 2025-08-28 | `google/gemma-2-27b-it` | No | +| 2025-08-28 | `Qwen/Qwen2-72B-Instruct` | No | +| 2025-08-28 | `meta-llama/Llama-Vision-Free` | No | +| 2025-08-28 | `Qwen/Qwen2.5-14B` | No | +| 2025-08-28 | `meta-llama-llama-3-3-70b-instruct-lora` | No | +| 2025-08-28 | `meta-llama/Llama-3.2-11B-Vision-Instruct-Turbo` | No | +| 2025-08-28 | `NousResearch/Nous-Hermes-2-Mixtral-8x7B-DPO` | No | +| 2025-08-28 | `deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B` | No | +| 2025-08-28 | `black-forest-labs/FLUX.1-depth` | No | +| 2025-08-28 | `black-forest-labs/FLUX.1-redux` | No | +| 2025-08-28 | `meta-llama/Llama-3-8b-chat-hf` | No | +| 2025-08-28 | `black-forest-labs/FLUX.1-canny` | No | +| 2025-08-28 | `meta-llama/Llama-3.2-90B-Vision-Instruct-Turbo` | No | +| 2025-06-13 | `gryphe-mythomax-l2-13b` | No | +| 2025-06-13 | `mistralai-mixtral-8x22b-instruct-v0-1` | No | +| 2025-06-13 | `mistralai-mixtral-8x7b-v0-1` | No | +| 2025-06-13 | `togethercomputer-m2-bert-80m-2k-retrieval` | No | +| 2025-06-13 | `togethercomputer-m2-bert-80m-8k-retrieval` | No | +| 2025-06-13 | `whereisai-uae-large-v1` | No | +| 2025-06-13 | `google-gemma-2-9b-it` | No | +| 2025-06-13 | `google-gemma-2b-it` | No | +| 2025-06-13 | `gryphe-mythomax-l2-13b-lite` | No | +| 2025-05-16 | `meta-llama-llama-3-2-3b-instruct-turbo-lora` | No | +| 2025-05-16 | `meta-llama-meta-llama-3-8b-instruct-turbo` | No | +| 2025-04-24 | `meta-llama/Llama-2-13b-chat-hf` | No | +| 2025-04-24 | `meta-llama-meta-llama-3-70b-instruct-turbo` | No | +| 2025-04-24 | `meta-llama-meta-llama-3-1-8b-instruct-turbo-lora` | No | +| 2025-04-24 | `meta-llama-meta-llama-3-1-70b-instruct-turbo-lora` | No | +| 2025-04-24 | `meta-llama-llama-3-2-1b-instruct-lora` | No | +| 2025-04-24 | `microsoft-wizardlm-2-8x22b` | No | +| 2025-04-24 | `upstage-solar-10-7b-instruct-v1` | No | +| 2025-04-14 | `stabilityai/stable-diffusion-xl-base-1.0` | No | +| 2025-04-04 | `meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo-lora` | No | +| 2025-03-27 | `mistralai/Mistral-7B-v0.1` | No | +| 2025-03-25 | `Qwen/QwQ-32B-Preview` | No | +| 2025-03-13 | `databricks-dbrx-instruct` | No | +| 2025-03-11 | `meta-llama/Meta-Llama-3-70B-Instruct-Lite` | No | +| 2025-03-08 | `Meta-Llama/Llama-Guard-7b` | No | +| 2025-02-06 | `sentence-transformers/msmarco-bert-base-dot-v5` | No | +| 2025-02-06 | `bert-base-uncased` | No | +| 2024-10-29 | `Qwen/Qwen1.5-72B-Chat` | No | +| 2024-10-29 | `Qwen/Qwen1.5-110B-Chat` | No | +| 2024-10-07 | `NousResearch/Nous-Hermes-2-Yi-34B` | No | +| 2024-10-07 | `NousResearch/Hermes-3-Llama-3.1-405B-Turbo` | No | +| 2024-08-22 | `NousResearch/Nous-Hermes-2-Mistral-7B-DPO` | No | +| 2024-08-22 | `SG161222/Realistic_Vision_V3.0_VAE` | No | +| 2024-08-22 | `meta-llama/Llama-2-70b-chat-hf` | No | +| 2024-08-22 | `mistralai/Mixtral-8x22B` | No | +| 2024-08-22 | `Phind/Phind-CodeLlama-34B-v2` | No | +| 2024-08-22 | `meta-llama/Meta-Llama-3-70B` | No | +| 2024-08-22 | `teknium/OpenHermes-2p5-Mistral-7B` | No | +| 2024-08-22 | `openchat/openchat-3.5-1210` | No | +| 2024-08-22 | `WizardLM/WizardCoder-Python-34B-V1.0` | No | +| 2024-08-22 | `NousResearch/Nous-Hermes-2-Mixtral-8x7B-SFT` | No | +| 2024-08-22 | `NousResearch/Nous-Hermes-Llama2-13b` | No | +| 2024-08-22 | `zero-one-ai/Yi-34B-Chat` | No | +| 2024-08-22 | `codellama/CodeLlama-34b-Instruct-hf` | No | +| 2024-08-22 | `codellama/CodeLlama-34b-Python-hf` | No | +| 2024-08-22 | `teknium/OpenHermes-2-Mistral-7B` | No | +| 2024-08-22 | `Qwen/Qwen1.5-14B-Chat` | No | +| 2024-08-22 | `stabilityai/stable-diffusion-2-1` | No | +| 2024-08-22 | `meta-llama/Llama-3-8b-hf` | No | +| 2024-08-22 | `prompthero/openjourney` | No | +| 2024-08-22 | `runwayml/stable-diffusion-v1-5` | No | +| 2024-08-22 | `wavymulder/Analog-Diffusion` | No | +| 2024-08-22 | `Snowflake/snowflake-arctic-instruct` | No | +| 2024-08-22 | `deepseek-ai/deepseek-coder-33b-instruct` | No | +| 2024-08-22 | `Qwen/Qwen1.5-7B-Chat` | No | +| 2024-08-22 | `Qwen/Qwen1.5-32B-Chat` | No | +| 2024-08-22 | `cognitivecomputations/dolphin-2.5-mixtral-8x7b` | No | +| 2024-08-22 | `garage-bAInd/Platypus2-70B-instruct` | No | +| 2024-08-22 | `google/gemma-7b-it` | No | +| 2024-08-22 | `meta-llama/Llama-2-7b-chat-hf` | No | +| 2024-08-22 | `Qwen/Qwen1.5-32B` | No | +| 2024-08-22 | `Open-Orca/Mistral-7B-OpenOrca` | No | +| 2024-08-22 | `codellama/CodeLlama-13b-Instruct-hf` | No | +| 2024-08-22 | `NousResearch/Nous-Capybara-7B-V1p9` | No | +| 2024-08-22 | `lmsys/vicuna-13b-v1.5` | No | +| 2024-08-22 | `Undi95/ReMM-SLERP-L2-13B` | No | +| 2024-08-22 | `Undi95/Toppy-M-7B` | No | +| 2024-08-22 | `meta-llama/Llama-2-13b-hf` | No | +| 2024-08-22 | `codellama/CodeLlama-70b-Instruct-hf` | No | +| 2024-08-22 | `snorkelai/Snorkel-Mistral-PairRM-DPO` | No | +| 2024-08-22 | `togethercomputer/LLaMA-2-7B-32K-Instruct` | No | +| 2024-08-22 | `Austism/chronos-hermes-13b` | No | +| 2024-08-22 | `Qwen/Qwen1.5-72B` | No | +| 2024-08-22 | `zero-one-ai/Yi-34B` | No | +| 2024-08-22 | `codellama/CodeLlama-7b-Instruct-hf` | No | +| 2024-08-22 | `togethercomputer/evo-1-131k-base` | No | +| 2024-08-22 | `codellama/CodeLlama-70b-hf` | No | +| 2024-08-22 | `WizardLM/WizardLM-13B-V1.2` | No | +| 2024-08-22 | `meta-llama/Llama-2-7b-hf` | No | +| 2024-08-22 | `google/gemma-7b` | No | +| 2024-08-22 | `Qwen/Qwen1.5-1.8B-Chat` | No | +| 2024-08-22 | `Qwen/Qwen1.5-4B-Chat` | No | +| 2024-08-22 | `lmsys/vicuna-7b-v1.5` | No | +| 2024-08-22 | `zero-one-ai/Yi-6B` | No | +| 2024-08-22 | `Nexusflow/NexusRaven-V2-13B` | No | +| 2024-08-22 | `google/gemma-2b` | No | +| 2024-08-22 | `Qwen/Qwen1.5-7B` | No | +| 2024-08-22 | `NousResearch/Nous-Hermes-llama-2-7b` | No | +| 2024-08-22 | `togethercomputer/alpaca-7b` | No | +| 2024-08-22 | `Qwen/Qwen1.5-14B` | No | +| 2024-08-22 | `codellama/CodeLlama-70b-Python-hf` | No | +| 2024-08-22 | `Qwen/Qwen1.5-4B` | No | +| 2024-08-22 | `togethercomputer/StripedHyena-Hessian-7B` | No | +| 2024-08-22 | `allenai/OLMo-7B-Instruct` | No | +| 2024-08-22 | `togethercomputer/RedPajama-INCITE-7B-Instruct` | No | +| 2024-08-22 | `togethercomputer/LLaMA-2-7B-32K` | No | +| 2024-08-22 | `togethercomputer/RedPajama-INCITE-7B-Base` | No | +| 2024-08-22 | `Qwen/Qwen1.5-0.5B-Chat` | No | +| 2024-08-22 | `microsoft/phi-2` | No | +| 2024-08-22 | `Qwen/Qwen1.5-0.5B` | No | +| 2024-08-22 | `togethercomputer/RedPajama-INCITE-7B-Chat` | No | +| 2024-08-22 | `togethercomputer/RedPajama-INCITE-Chat-3B-v1` | No | +| 2024-08-22 | `togethercomputer/GPT-JT-Moderation-6B` | No | +| 2024-08-22 | `Qwen/Qwen1.5-1.8B` | No | +| 2024-08-22 | `togethercomputer/RedPajama-INCITE-Instruct-3B-v1` | No | +| 2024-08-22 | `togethercomputer/RedPajama-INCITE-Base-3B-v1` | No | +| 2024-08-22 | `WhereIsAI/UAE-Large-V1` | No | +| 2024-08-22 | `allenai/OLMo-7B` | No | +| 2024-08-22 | `togethercomputer/evo-1-8k-base` | No | +| 2024-08-22 | `WizardLM/WizardCoder-15B-V1.0` | No | +| 2024-08-22 | `codellama/CodeLlama-13b-Python-hf` | No | +| 2024-08-22 | `allenai-olmo-7b-twin-2t` | No | +| 2024-08-22 | `sentence-transformers/msmarco-bert-base-dot-v5` | No | +| 2024-08-22 | `codellama/CodeLlama-7b-Python-hf` | No | +| 2024-08-22 | `hazyresearch/M2-BERT-2k-Retrieval-Encoder-V1` | No | +| 2024-08-22 | `bert-base-uncased` | No | +| 2024-08-22 | `mistralai/Mistral-7B-Instruct-v0.1-json` | No | +| 2024-08-22 | `mistralai/Mistral-7B-Instruct-v0.1-tools` | No | +| 2024-08-22 | `togethercomputer-codellama-34b-instruct-json` | No | +| 2024-08-22 | `togethercomputer-codellama-34b-instruct-tools` | No | +| **Notes on model support:** | | | + +* The support column reflects the current [supported models](/docs/dedicated-endpoints/models) catalog for dedicated model inference and is updated automatically as the catalog changes. +* Models marked "Yes" can be deployed as on-demand dedicated endpoints, either under the listed ID or as the underlying base model of a serving variant (for example, a deprecated `-FP8` or `-Turbo` ID). +* Models marked "No" are not available as on-demand endpoints and require migration to a different model or a monthly reserved dedicated endpoint. + +### Fine-tuning + +The table below lists all models removed from the fine-tuning service, most recent first. These models can no longer be used as a base model for a fine-tuning job. Where a close equivalent exists, the suggested replacement is listed. A blank cell means there is no direct equivalent. See [Supported models](/docs/fine-tuning/supported-models) for the full list of models available today. + +| Removal date | Model | Suggested replacement | +| :----------- | :------------------------------------------------------ | :------------------------------------------------ | +| 2026-07-29 | `nvidia/NVIDIA-Nemotron-Nano-9B-v2` | `Qwen/Qwen3.5-9B` | +| 2026-07-29 | `Qwen/Qwen3-Next-80B-A3B-Instruct` | `Qwen/Qwen3.5-122B-A10B` | +| 2026-07-29 | `Qwen/Qwen3-Next-80B-A3B-Thinking` | `Qwen/Qwen3.5-122B-A10B` | +| 2026-07-29 | `Qwen/Qwen3-0.6B` | `Qwen/Qwen3.5-0.8B` | +| 2026-07-29 | `Qwen/Qwen3-0.6B-Base` | `Qwen/Qwen3.5-0.8B` | +| 2026-07-29 | `Qwen/Qwen3-1.7B` | `Qwen/Qwen3.5-2B` | +| 2026-07-29 | `Qwen/Qwen3-1.7B-Base` | `Qwen/Qwen3.5-2B` | +| 2026-07-29 | `Qwen/Qwen3-4B` | `Qwen/Qwen3.5-4B` | +| 2026-07-29 | `Qwen/Qwen3-4B-Base` | `Qwen/Qwen3.5-4B` | +| 2026-07-29 | `Qwen/Qwen3-8B` | `Qwen/Qwen3.5-9B` | +| 2026-07-29 | `Qwen/Qwen3-8B-Base` | `Qwen/Qwen3.5-9B` | +| 2026-07-29 | `Qwen/Qwen3-14B` | `Qwen/Qwen3.5-27B` | +| 2026-07-29 | `Qwen/Qwen3-14B-Base` | `Qwen/Qwen3.5-27B` | +| 2026-07-29 | `Qwen/Qwen3-32B` | `Qwen/Qwen3.5-27B` | +| 2026-07-29 | `Qwen/Qwen3-30B-A3B-Base` | `Qwen/Qwen3.6-35B-A3B` | +| 2026-07-29 | `Qwen/Qwen3-30B-A3B` | `Qwen/Qwen3.6-35B-A3B` | +| 2026-07-29 | `Qwen/Qwen3-30B-A3B-Instruct-2507` | `Qwen/Qwen3.6-35B-A3B` | +| 2026-07-29 | `Qwen/Qwen3-235B-A22B` | `Qwen/Qwen3.5-397B-A17B` | +| 2026-07-29 | `Qwen/Qwen3-235B-A22B-Instruct-2507` | `Qwen/Qwen3.5-397B-A17B` | +| 2026-07-29 | `Qwen/Qwen3-Coder-30B-A3B-Instruct` | `Qwen/Qwen3.6-35B-A3B` | +| 2026-07-29 | `Qwen/Qwen3-Coder-480B-A35B-Instruct` | | +| 2026-07-29 | `Qwen/Qwen3-VL-8B-Instruct` | `Qwen/Qwen3.5-9B` | +| 2026-07-29 | `Qwen/Qwen3-VL-32B-Instruct` | | +| 2026-07-29 | `Qwen/Qwen3-VL-30B-A3B-Instruct` | `Qwen/Qwen3.5-4B` | +| 2026-07-29 | `Qwen/Qwen3-VL-235B-A22B-Instruct` | | +| 2026-07-29 | `Qwen/Qwen2.5-72B-Instruct` | `meta-llama/Llama-3.3-70B-Instruct-Reference` | +| 2026-07-29 | `Qwen/Qwen2.5-72B` | `meta-llama/Llama-3.3-70B-Instruct-Reference` | +| 2026-07-29 | `Qwen/Qwen2.5-32B-Instruct` | `Qwen/Qwen3.5-27B` | +| 2026-07-29 | `Qwen/Qwen2.5-32B` | `Qwen/Qwen3.5-27B` | +| 2026-07-29 | `Qwen/Qwen2.5-14B-Instruct` | `Qwen/Qwen3.5-27B` | +| 2026-07-29 | `Qwen/Qwen2.5-14B` | `Qwen/Qwen3.5-27B` | +| 2026-07-29 | `Qwen/Qwen2.5-7B-Instruct` | `Qwen/Qwen3.5-9B` | +| 2026-07-29 | `Qwen/Qwen2.5-7B` | `Qwen/Qwen3.5-9B` | +| 2026-07-29 | `Qwen/Qwen2.5-3B-Instruct` | `Qwen/Qwen3.5-4B` | +| 2026-07-29 | `Qwen/Qwen2.5-3B` | `Qwen/Qwen3.5-4B` | +| 2026-07-29 | `Qwen/Qwen2.5-1.5B-Instruct` | `Qwen/Qwen3.5-2B` | +| 2026-07-29 | `Qwen/Qwen2.5-1.5B` | `Qwen/Qwen3.5-2B` | +| 2026-07-29 | `Qwen/Qwen2-72B-Instruct` | `meta-llama/Llama-3.3-70B-Instruct-Reference` | +| 2026-07-29 | `Qwen/Qwen2-72B` | `meta-llama/Llama-3.3-70B-Instruct-Reference` | +| 2026-07-29 | `Qwen/Qwen2-7B-Instruct` | `Qwen/Qwen3.5-9B` | +| 2026-07-29 | `Qwen/Qwen2-7B` | `Qwen/Qwen3.5-9B` | +| 2026-07-29 | `Qwen/Qwen2-1.5B-Instruct` | `Qwen/Qwen3.5-2B` | +| 2026-07-29 | `Qwen/Qwen2-1.5B` | `Qwen/Qwen3.5-2B` | +| 2026-07-29 | `moonshotai/Kimi-K2.5` | `moonshotai/Kimi-K2.6` | +| 2026-07-29 | `moonshotai/Kimi-K2-Thinking` | `moonshotai/Kimi-K2.6` | +| 2026-07-29 | `moonshotai/Kimi-K2-Instruct-0905` | `moonshotai/Kimi-K2.6` | +| 2026-07-29 | `moonshotai/Kimi-K2-Instruct` | `moonshotai/Kimi-K2.6` | +| 2026-07-29 | `moonshotai/Kimi-K2-Base` | `moonshotai/Kimi-K2.6` | +| 2026-07-29 | `zai-org/GLM-5` | `zai-org/GLM-5.1` | +| 2026-07-29 | `zai-org/GLM-4.7` | `zai-org/GLM-5.1` | +| 2026-07-29 | `zai-org/GLM-4.6` | `zai-org/GLM-5.1` | +| 2026-07-29 | `deepseek-ai/DeepSeek-R1-0528` | `deepseek-ai/DeepSeek-V3.1` | +| 2026-07-29 | `deepseek-ai/DeepSeek-R1` | `deepseek-ai/DeepSeek-V3.1` | +| 2026-07-29 | `deepseek-ai/DeepSeek-V3-0324` | `deepseek-ai/DeepSeek-V3.1` | +| 2026-07-29 | `deepseek-ai/DeepSeek-V3` | `deepseek-ai/DeepSeek-V3.1` | +| 2026-07-29 | `deepseek-ai/DeepSeek-V3.1-Base` | `deepseek-ai/DeepSeek-V3.1` | +| 2026-07-29 | `deepseek-ai/DeepSeek-V3-Base` | `deepseek-ai/DeepSeek-V3.1` | +| 2026-07-29 | `deepseek-ai/DeepSeek-R1-Distill-Llama-70B` | `meta-llama/Llama-3.3-70B-Instruct-Reference` | +| 2026-07-29 | `deepseek-ai/DeepSeek-R1-Distill-Llama-70B-32k` | `meta-llama/Llama-3.3-70B-Instruct-Reference` | +| 2026-07-29 | `deepseek-ai/DeepSeek-R1-Distill-Llama-70B-131k` | `meta-llama/Llama-3.3-70B-Instruct-Reference` | +| 2026-07-29 | `deepseek-ai/DeepSeek-R1-Distill-Qwen-14B` | `Qwen/Qwen3.5-27B` | +| 2026-07-29 | `deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B` | `Qwen/Qwen3.5-2B` | +| 2026-07-29 | `meta-llama/Llama-4-Scout-17B-16E` | `meta-llama/Llama-4-Scout-17B-16E-Instruct` | +| 2026-07-29 | `meta-llama/Llama-4-Maverick-17B-128E` | `meta-llama/Llama-4-Maverick-17B-128E-Instruct` | +| 2026-07-29 | `meta-llama/Llama-3.3-70B-32k-Instruct-Reference` | `meta-llama/Llama-3.3-70B-Instruct-Reference` | +| 2026-07-29 | `meta-llama/Llama-3.3-70B-131k-Instruct-Reference` | `meta-llama/Llama-3.3-70B-Instruct-Reference` | +| 2026-07-29 | `meta-llama/Llama-3.2-3B-Instruct` | `meta-llama/Meta-Llama-3.1-8B-Instruct-Reference` | +| 2026-07-29 | `meta-llama/Llama-3.2-3B` | `meta-llama/Meta-Llama-3.1-8B-Instruct-Reference` | +| 2026-07-29 | `meta-llama/Llama-3.2-1B-Instruct` | `meta-llama/Meta-Llama-3.1-8B-Instruct-Reference` | +| 2026-07-29 | `meta-llama/Llama-3.2-1B` | `meta-llama/Meta-Llama-3.1-8B-Instruct-Reference` | +| 2026-07-29 | `meta-llama/Meta-Llama-3.1-8B-131k-Instruct-Reference` | `meta-llama/Meta-Llama-3.1-8B-Instruct-Reference` | +| 2026-07-29 | `meta-llama/Meta-Llama-3.1-8B-Reference` | `meta-llama/Meta-Llama-3.1-8B-Instruct-Reference` | +| 2026-07-29 | `meta-llama/Meta-Llama-3.1-8B-131k-Reference` | `meta-llama/Meta-Llama-3.1-8B-Instruct-Reference` | +| 2026-07-29 | `meta-llama/Meta-Llama-3.1-70B-Instruct-Reference` | `meta-llama/Llama-3.3-70B-Instruct-Reference` | +| 2026-07-29 | `meta-llama/Meta-Llama-3.1-70B-32k-Instruct-Reference` | `meta-llama/Llama-3.3-70B-Instruct-Reference` | +| 2026-07-29 | `meta-llama/Meta-Llama-3.1-70B-131k-Instruct-Reference` | `meta-llama/Llama-3.3-70B-Instruct-Reference` | +| 2026-07-29 | `meta-llama/Meta-Llama-3.1-70B-Reference` | `meta-llama/Llama-3.3-70B-Instruct-Reference` | +| 2026-07-29 | `meta-llama/Meta-Llama-3.1-70B-32k-Reference` | `meta-llama/Llama-3.3-70B-Instruct-Reference` | +| 2026-07-29 | `meta-llama/Meta-Llama-3.1-70B-131k-Reference` | `meta-llama/Llama-3.3-70B-Instruct-Reference` | +| 2026-07-29 | `meta-llama/Meta-Llama-3.1-405B-Instruct-Reference` | | +| 2026-07-29 | `meta-llama/Meta-Llama-3.1-405B-Reference` | | +| 2026-07-29 | `meta-llama/Meta-Llama-3.1-405B-10k-Instruct-Reference` | | +| 2026-07-29 | `meta-llama/Meta-Llama-3.1-405B-10k-Reference` | | +| 2026-07-29 | `meta-llama/Meta-Llama-3.1-405B-8k-Instruct-Reference` | | +| 2026-07-29 | `meta-llama/Meta-Llama-3.1-405B-8k-Reference` | | +| 2026-07-29 | `meta-llama/Meta-Llama-3-8B-Instruct` | `meta-llama/Meta-Llama-3.1-8B-Instruct-Reference` | +| 2026-07-29 | `meta-llama/Meta-Llama-3-8B` | `meta-llama/Meta-Llama-3.1-8B-Instruct-Reference` | +| 2026-07-29 | `meta-llama/Meta-Llama-3-70B-Instruct` | `meta-llama/Llama-3.3-70B-Instruct-Reference` | +| 2026-07-29 | `google/gemma-3-270m` | `Qwen/Qwen3.5-0.8B` | +| 2026-07-29 | `google/gemma-3-270m-it` | `Qwen/Qwen3.5-0.8B` | +| 2026-07-29 | `google/gemma-3-1b-it` | `google/gemma-4-26B-A4B-it` | +| 2026-07-29 | `google/gemma-3-1b-pt` | `google/gemma-4-26B-A4B-it` | +| 2026-07-29 | `google/gemma-3-4b-it` | `google/gemma-4-26B-A4B-it` | +| 2026-07-29 | `google/gemma-3-4b-it-VLM` | `google/gemma-4-26B-A4B-it` | +| 2026-07-29 | `google/gemma-3-4b-pt` | `google/gemma-4-26B-A4B-it` | +| 2026-07-29 | `google/gemma-3-12b-it` | `google/gemma-4-26B-A4B-it` | +| 2026-07-29 | `google/gemma-3-12b-it-VLM` | `google/gemma-4-31B-it-VLM` | +| 2026-07-29 | `google/gemma-3-12b-pt` | `google/gemma-4-26B-A4B-it` | +| 2026-07-29 | `google/gemma-3-27b-it` | `google/gemma-4-31B-it` | +| 2026-07-29 | `google/gemma-3-27b-it-VLM` | `google/gemma-4-31B-it-VLM` | +| 2026-07-29 | `google/gemma-3-27b-pt` | `google/gemma-4-31B-it` | +| 2026-07-29 | `mistralai/Mixtral-8x7B-v0.1` | `mistralai/Mixtral-8x7B-Instruct-v0.1` | +| 2026-07-29 | `mistralai/Mistral-7B-Instruct-v0.2` | `mistralai/Mixtral-8x7B-Instruct-v0.1` | +| 2026-07-29 | `mistralai/Mistral-7B-v0.1` | `mistralai/Mixtral-8x7B-Instruct-v0.1` | +| 2026-07-29 | `togethercomputer/llama-2-7b-chat` | `meta-llama/Meta-Llama-3.1-8B-Instruct-Reference` | + +## Recommended actions + +* Regularly check this page for updates on model deprecations. +* Plan your migration well in advance of the removal date to ensure a smooth transition. +* If you have any questions or need assistance with migration, contact the Together AI support team. + +For the most up-to-date information on model availability, support, and recommended alternatives, check the API documentation or contact the Together AI support team. diff --git a/tests/test_litellm/fixtures/together_ai_sync/models_serverless.json b/tests/test_litellm/fixtures/together_ai_sync/models_serverless.json new file mode 100644 index 00000000000..4988f0820bc --- /dev/null +++ b/tests/test_litellm/fixtures/together_ai_sync/models_serverless.json @@ -0,0 +1 @@ +[{"id":"moonshotai/Kimi-K3","uuid":"endpoint-kk-moonshotai-kimi-k3","object":"model","created":1785049898,"type":"chat","running":false,"display_name":"Kimi K3","organization":"Moonshot AI","link":"https://huggingface.co/moonshotai","license":"other","context_length":1048576,"config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":3,"output":15,"base":0,"finetune":0,"cached_input":0.3,"image_pixel":0,"transcribe":0,"image":0,"video":0}},{"id":"zai-org/GLM-5.2","uuid":"endpoint-83348bee-b0fb-4aad-8ba4-72545469cb9e","object":"model","created":0,"type":"chat","running":false,"display_name":"GLM 5.2","organization":"Zai Org","link":"https://huggingface.co/api/models/nvidia/GLM-5.2-NVFP4","context_length":1048575,"config":{"chat_template":"[gMASK]\n{%- set effective_reasoning_effort = 'high' if reasoning_effort is defined and reasoning_effort == 'high' else 'max' -%}\n{%- if (enable_thinking is not defined or enable_thinking) and effective_reasoning_effort is not none -%}<|system|>Reasoning Effort: {{ effective_reasoning_effort | capitalize }}{%- endif -%}\n{%- if tools -%}\n{%- macro tool_to_json(tool) -%}\n {%- set ns_tool = namespace(first=true) -%}\n {{ '{' -}}\n {%- for k, v in tool.items() -%}\n {%- if k != 'defer_loading' and k != 'strict' -%}\n {%- if not ns_tool.first -%}{{- ', ' -}}{%- endif -%}\n {%- set ns_tool.first = false -%}\n \"{{ k }}\": {{ v | tojson(ensure_ascii=False) }}\n {%- endif -%}\n {%- endfor -%}\n {{- '}' -}}\n{%- endmacro -%}\n<|system|>\n# Tools\n\nYou may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within XML tags:\n\n{% for tool in tools %}\n{%- if 'function' in tool -%}\n {%- set tool = tool['function'] -%}\n{%- endif -%}\n{% if tool.defer_loading is not defined or not tool.defer_loading %}\n{{ tool_to_json(tool) }}\n{% endif %}\n{% endfor %}\n\n\nFor each function call, output the function name and arguments within the following XML format:\n{function-name}{arg-key-1}{arg-value-1}{arg-key-2}{arg-value-2}...{%- endif -%}\n{%- macro visible_text(content) -%}\n {%- if content is string -%}\n {{- content }}\n {%- elif content is iterable and content is not mapping -%}\n {%- for item in content -%}\n {%- if item is mapping and item.type == 'text' -%}\n {{- item.text }}\n {%- elif item is string -%}\n {{- item }}\n {%- elif item is mapping and item.type in ['image', 'image_url', 'video', 'video_url', 'audio', 'audio_url', 'input_audio'] -%}\n {%- set media_type = item.type | replace('_url', '') | replace('input_', '') -%}\n {{- \"You are unable to process this \" ~ media_type ~ \" because you don't have multi-modal input ability. Try different methods.\" }}\n {%- endif -%}\n {%- endfor -%}\n {%- else -%}\n {{- content }}\n {%- endif -%}\n{%- endmacro -%}\n{%- set ns = namespace(last_user_index=-1) -%}\n{%- for m in messages %}\n {%- if m.role == 'user' %}\n {%- set ns.last_user_index = loop.index0 -%}\n {%- endif %}\n{%- endfor %}\n{%- for m in messages -%}\n{%- if m.role == 'user' -%}<|user|>{{ visible_text(m.content) }}\n{%- elif m.role == 'assistant' -%}\n<|assistant|>\n{%- set content = visible_text(m.content) %}\n{%- if m.reasoning_content is string %}\n {%- set reasoning_content = m.reasoning_content %}\n{%- elif '' in content %}\n {%- set reasoning_content = content.split('')[0].split('')[-1] %}\n {%- set content = content.split('')[-1] %}\n{%- endif %}\n{%- if ((clear_thinking is defined and not clear_thinking) or loop.index0 > ns.last_user_index) and reasoning_content is defined -%}\n{{ '' + reasoning_content + ''}}\n{%- else -%}\n{{ '' }}\n{%- endif -%}\n{%- if content.strip() -%}\n{{ content.strip() }}\n{%- endif -%}\n{% if m.tool_calls %}\n{% for tc in m.tool_calls %}\n{%- if tc.function %}\n {%- set tc = tc.function %}\n{%- endif %}\n{{- '' + tc.name -}}\n{% set _args = tc.arguments %}{% for k, v in _args.items() %}{{ k }}{{ v | tojson(ensure_ascii=False) if v is not string else v }}{% endfor %}{% endfor %}\n{% endif %}\n{%- elif m.role == 'tool' -%}\n{%- if loop.first or (messages[loop.index0 - 1].role != \"tool\") %}\n {{- '<|observation|>' -}}\n{%- endif %}\n{%- if m.content is string -%}\n {{- '' + m.content + '' -}}\n{%- elif m.content is iterable and m.content is not mapping and m.content and m.content.0.type == \"tool_reference\" -%}\n {{- '\\n' -}}\n {% for tr in m.content %}\n {%- for tool in tools -%}\n {%- if 'function' in tool -%}\n {%- set tool = tool['function'] -%}\n {%- endif -%}\n {%- if tool.name == tr.name -%}\n {{- tool_to_json(tool) + '\\n' -}}\n {%- endif -%}\n {%- endfor -%}\n {%- endfor -%}\n {{- '' -}}\n{%- elif m.content is iterable and m.content is not mapping and m.content and m.content.0 is mapping and m.content.0.output is defined -%}\n {%- for tr in m.content -%}\n {{- '' + tr.output + '' -}}\n {%- endfor -%}\n{%- else -%}\n {{- '' + visible_text(m.content) + '' -}}\n{% endif -%}\n{%- elif m.role == 'system' -%}\n<|system|>{{ visible_text(m.content) }}\n{%- endif -%}\n{%- endfor -%}\n{%- if add_generation_prompt -%}\n <|assistant|>{{- '' if (enable_thinking is defined and not enable_thinking) else '' -}}\n{%- endif -%}\n","stop":[],"bos_token":null,"eos_token":"<|endoftext|>"},"pricing":{"hourly":0,"input":1.4,"output":4.4,"base":0,"finetune":0,"cached_input":0.25999999999999995,"image_pixel":0,"transcribe":0,"image":0,"video":0}},{"id":"meta-models/Muse-Glimmer-30B","uuid":"endpoint-3da50849-cf6c-4e18-b44a-0dec9a699874","object":"model","created":0,"type":"chat","running":false,"display_name":"Muse Glimmer 30B","organization":"Meta","link":"https://huggingface.co/api/models/togethercomputer/onyx_final_hf-fp8-mlp","context_length":131072,"config":{"chat_template":null,"stop":["<|end_of_text|>"],"bos_token":"<|begin_of_text|>","eos_token":"<|end_of_text|>"},"pricing":{"hourly":0,"input":0.35,"output":1.5,"base":0,"finetune":0,"cached_input":0.04,"image_pixel":0,"transcribe":0,"image":0,"video":0}},{"id":"Qwen/Qwen3.8-2.4T-A95B","uuid":"endpoint-494c76e2-129e-41ee-9ab9-e25c9a3ff08c","object":"model","created":0,"type":"chat","running":false,"display_name":"Qwen3.8-2.4T-A95B","organization":"Qwen","context_length":1010000,"config":{"chat_template":"{%- set image_count = namespace(value=0) %}\n{%- set video_count = namespace(value=0) %}\n{%- macro render_content(content, do_vision_count, is_system_content=false) %}\n {%- if content is string %}\n {{- content }}\n {%- elif content is iterable and content is not mapping %}\n {%- for item in content %}\n {%- if 'image' in item or 'image_url' in item or item.type == 'image' %}\n {%- if is_system_content %}\n {{- raise_exception('System message cannot contain images.') }}\n {%- endif %}\n {%- if do_vision_count %}\n {%- set image_count.value = image_count.value + 1 %}\n {%- endif %}\n {%- if add_vision_id %}\n {{- 'Picture ' ~ image_count.value ~ ': ' }}\n {%- endif %}\n {{- '<|vision_start|><|image_pad|><|vision_end|>' }}\n {%- elif 'video' in item or item.type == 'video' %}\n {%- if is_system_content %}\n {{- raise_exception('System message cannot contain videos.') }}\n {%- endif %}\n {%- if do_vision_count %}\n {%- set video_count.value = video_count.value + 1 %}\n {%- endif %}\n {%- if add_vision_id %}\n {{- 'Video ' ~ video_count.value ~ ': ' }}\n {%- endif %}\n {{- '<|vision_start|><|video_pad|><|vision_end|>' }}\n {%- elif 'text' in item %}\n {{- item.text }}\n {%- else %}\n {{- raise_exception('Unexpected item type in content.') }}\n {%- endif %}\n {%- endfor %}\n {%- elif content is none or content is undefined %}\n {{- '' }}\n {%- else %}\n {{- raise_exception('Unexpected content type.') }}\n {%- endif %}\n{%- endmacro %}\n{%- if not messages %}\n {{- raise_exception('No messages provided.') }}\n{%- endif %}\n{%- set reasoning_instructions = '' %}\n{%- if enable_thinking is undefined or enable_thinking is true %}\n {%- set resolved_reasoning_effort = reasoning_effort|default('xhigh') %}\n {%- if resolved_reasoning_effort not in ('xhigh', 'medium', 'low') %}\n {{- raise_exception('Unexpected reasoning effort ' ~ reasoning_effort ~ '. Supported types are xhigh (default), medium, and low.') }}\n {%- endif %}\n {%- if resolved_reasoning_effort == 'xhigh' %}\n {%- set reasoning_instructions = 'Reasoning effort is set to xhigh. Please think carefully through the task, validate key assumptions, consider plausible alternatives, and prioritize correctness, consistency, and clarity in the final answer.' %}\n {%- elif resolved_reasoning_effort == 'low' %}\n {%- set reasoning_instructions = 'Reasoning effort is set to low. Keep your thinking brief and focused, moving directly to the conclusion without unnecessary elaboration.' %}\n {%- endif %}\n{%- endif %}\n{%- if tools and tools is iterable and tools is not mapping %}\n {{- '<|im_start|>system\\n' }}\n {%- if reasoning_instructions %}\n {{- reasoning_instructions + '\\n\\n' }}\n {%- endif %}\n {{- \"# Tools\\n\\nYou have access to the following functions:\\n\\n\" }}\n {%- for tool in tools %}\n {{- \"\\n\" }}\n {{- tool | tojson }}\n {%- endfor %}\n {{- \"\\n\" }}\n {{- '\\n\\nIf you choose to call a function ONLY reply in the following format with NO suffix:\\n\\n\\n\\n\\nvalue_1\\n\\n\\nThis is the value for the second parameter\\nthat can span\\nmultiple lines\\n\\n\\n\\n\\n\\nReminder:\\n- Function calls MUST follow the specified format: an inner block must be nested within XML tags\\n- Required parameters MUST be specified\\n- You may provide optional reasoning for your function call in natural language BEFORE the function call, but NOT after\\n- If there is no function call available, answer the question like normal with your current knowledge and do not tell the user about function calls\\n' }}\n {%- if messages[0].role == 'system' %}\n {%- set content = render_content(messages[0].content, false, true)|trim %}\n {%- if content %}\n {{- '\\n\\n' + content }}\n {%- endif %}\n {%- endif %}\n {{- '<|im_end|>\\n' }}\n{%- else %}\n {%- if messages[0].role == 'system' %}\n {%- set content = render_content(messages[0].content, false, true)|trim %}\n {%- if content %}\n {{- '<|im_start|>system\\n' + (reasoning_instructions + '\\n\\n' if reasoning_instructions else '') + content + '<|im_end|>\\n' }}\n {%- elif reasoning_instructions %}\n {{- '<|im_start|>system\\n' + reasoning_instructions + '<|im_end|>\\n' }}\n {%- endif %}\n {%- elif reasoning_instructions %}\n {{- '<|im_start|>system\\n' + reasoning_instructions + '<|im_end|>\\n' }}\n {%- endif %}\n{%- endif %}\n{%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %}\n{%- for message in messages[::-1] %}\n {%- set index = (messages|length - 1) - loop.index0 %}\n {%- if ns.multi_step_tool and message.role == \"user\" %}\n {%- set content = render_content(message.content, false)|trim %}\n {%- if not(content.startswith('') and content.endswith('')) %}\n {%- set ns.multi_step_tool = false %}\n {%- set ns.last_query_index = index %}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if ns.multi_step_tool %}\n {{- raise_exception('No user query found in messages.') }}\n{%- endif %}\n{%- for message in messages %}\n {%- set content = render_content(message.content, true)|trim %}\n {%- if message.role == \"system\" %}\n {%- if not loop.first %}\n {{- raise_exception('System message must be at the beginning.') }}\n {%- endif %}\n {%- elif message.role == \"user\" %}\n {{- '<|im_start|>' + message.role + '\\n' + content + '<|im_end|>' + '\\n' }}\n {%- elif message.role == \"assistant\" %}\n {%- set reasoning_content = '' %}\n {%- if message.reasoning_content is string %}\n {%- set reasoning_content = message.reasoning_content %}\n {%- endif %}\n {%- set reasoning_content = reasoning_content|trim %}\n {%- if preserve_thinking is undefined or preserve_thinking is true or loop.index0 > ns.last_query_index %}\n {{- '<|im_start|>' + message.role + '\\n\\n' + reasoning_content + '\\n\\n\\n' + content }}\n {%- else %}\n {{- '<|im_start|>' + message.role + '\\n' + content }}\n {%- endif %}\n {%- if message.tool_calls and message.tool_calls is iterable and message.tool_calls is not mapping %}\n {%- for tool_call in message.tool_calls %}\n {%- if tool_call.function is defined %}\n {%- set tool_call = tool_call.function %}\n {%- endif %}\n {%- if loop.first %}\n {%- if content|trim %}\n {{- '\\n\\n\\n\\n' }}\n {%- else %}\n {{- '\\n\\n' }}\n {%- endif %}\n {%- else %}\n {{- '\\n\\n\\n' }}\n {%- endif %}\n {%- if tool_call.arguments is defined and tool_call.arguments != '' %}\n {%- for args_name, args_value in tool_call.arguments|items %}\n {{- '\\n' }}\n {%- set args_value = args_value | string if args_value is string else args_value | tojson | safe %}\n {{- args_value }}\n {{- '\\n\\n' }}\n {%- endfor %}\n {%- endif %}\n {{- '\\n' }}\n {%- endfor %}\n {%- endif %}\n {{- '<|im_end|>\\n' }}\n {%- elif message.role == \"tool\" %}\n {%- if loop.previtem and loop.previtem.role != \"tool\" %}\n {{- '<|im_start|>user' }}\n {%- endif %}\n {{- '\\n\\n' }}\n {{- content }}\n {{- '\\n' }}\n {%- if not loop.last and loop.nextitem.role != \"tool\" %}\n {{- '<|im_end|>\\n' }}\n {%- elif loop.last %}\n {{- '<|im_end|>\\n' }}\n {%- endif %}\n {%- else %}\n {{- raise_exception('Unexpected message role.') }}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n {%- if enable_thinking is defined and enable_thinking is false %}\n {{- '\\n\\n\\n\\n' }}\n {%- else %}\n {{- '\\n' }}\n {%- endif %}\n{%- endif %}","stop":["<|im_end|>"],"bos_token":null,"eos_token":"<|im_end|>"},"pricing":{"hourly":0,"input":2.5,"output":6.25,"base":0,"finetune":0,"cached_input":0.5,"image_pixel":0,"transcribe":0,"image":0,"video":0}},{"id":"deepseek-ai/DeepSeek-V4-Pro-0813","object":"model","created":1786804181,"type":"chat","running":false,"display_name":"DeepSeek V4 Pro 0813","organization":"DeepSeek","context_length":1048576,"config":{"chat_template":null,"stop":["<|end▁of▁sentence|>"],"bos_token":"<|begin▁of▁sentence|>","eos_token":"<|end▁of▁sentence|>"},"pricing":{"hourly":0,"input":1.32,"output":3.96,"base":0,"finetune":0,"cached_input":0.12999999999999998,"image_pixel":0,"transcribe":0,"image":0,"video":0}},{"id":"deepseek-ai/DeepSeek-V4-Flash-0731","uuid":"endpoint-59e1bfe8-dcfd-4902-8e59-8e9585cfab4e","object":"model","created":0,"type":"chat","running":false,"display_name":"Deepseek V4 Flash 0731","organization":"Deepseek AI","context_length":1048576,"config":{"chat_template":null,"stop":["<|end▁of▁sentence|>"],"bos_token":"<|begin▁of▁sentence|>","eos_token":"<|end▁of▁sentence|>"},"pricing":{"hourly":0,"input":0.13999999999999999,"output":0.27999999999999997,"base":0,"finetune":0,"cached_input":0.030000000000000002,"image_pixel":0,"transcribe":0,"image":0,"video":0}},{"id":"thinkingmachines/Inkling","uuid":"endpoint-8b0aa8da-8d35-4a01-be0b-eca731d64568","object":"model","created":0,"type":"chat","running":false,"display_name":"Inkling FP4","organization":"Thinking Machines","link":"https://huggingface.co/api/models/thinkingmachines/Inkling-NVFP4","license":"apache-2.0","context_length":524288,"config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":1,"output":4.05,"base":0,"finetune":0,"cached_input":0.17,"image_pixel":0,"transcribe":0,"image":0,"video":0}},{"id":"MiniMaxAI/MiniMax-M3","uuid":"endpoint-5dea048e-3527-4287-8da8-5e61214b9f64","object":"model","created":0,"type":"chat","running":false,"display_name":"MiniMax M3","organization":"MiniMaxAI","context_length":524288,"config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0.3,"output":1.2,"base":0,"finetune":0,"cached_input":0.060000000000000005,"image_pixel":0,"transcribe":0,"image":0,"video":0}},{"id":"thinkingmachines/Inkling-Small","object":"model","created":1785387855,"type":"chat","running":false,"display_name":"Inkling Small","organization":"Thinking Machines","link":"https://huggingface.co/api/models/thinkingmachines/Inkling-Small","context_length":524288,"config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0.5,"output":1.2,"base":0,"finetune":0,"cached_input":0.1,"image_pixel":0,"transcribe":0,"image":0,"video":0}},{"id":"moonshotai/Kimi-K2.7-Code","uuid":"endpoint-b8ae5f69-a244-43dd-a6ac-957653518387","object":"model","created":0,"type":"chat","running":false,"display_name":"Kimi K2.7 Code","organization":"Moonshot AI","link":"https://huggingface.co/api/models/togethercomputer/Kimi-K2.7-Code-FP4","context_length":262144,"config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0.95,"output":4,"base":0,"finetune":0,"cached_input":0.19,"image_pixel":0,"transcribe":0,"image":0,"video":0}},{"id":"deepseek-ai/DeepSeek-V4-Pro","uuid":"endpoint-94151073-7212-43f8-9357-42a6043e1eef","object":"model","created":0,"type":"chat","running":false,"display_name":"Deepseek V4 Pro","organization":"Deepseek","context_length":512000,"config":{"chat_template":null,"stop":["<|end▁of▁sentence|>"],"bos_token":"<|begin▁of▁sentence|>","eos_token":"<|end▁of▁sentence|>"},"pricing":{"hourly":0,"input":1.74,"output":3.48,"base":0,"finetune":0,"cached_input":0.2,"image_pixel":0,"transcribe":0,"image":0,"video":0}},{"id":"nvidia/nemotron-3-ultra-550b-a55b","uuid":"endpoint-0f2ee6f7-0ad9-42e9-89df-cab8904dc46c","object":"model","created":0,"type":"chat","running":false,"display_name":"NVIDIA Nemotron 3 Ultra 550B A55B NVFP4","organization":"NVIDIA","context_length":512288,"config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0.6,"output":3.6,"base":0,"finetune":0,"cached_input":0.2,"image_pixel":0,"transcribe":0,"image":0,"video":0}},{"id":"Qwen/Qwen3.7-Max","uuid":"endpoint-ba47b6c3-f84c-435c-9d86-d8142b17031b","object":"model","created":1779386434,"type":"chat","running":false,"display_name":"Qwen3.7 Max","organization":"Qwen","context_length":1000000,"config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":1.25,"output":3.75,"base":0,"finetune":0,"cached_input":0.125,"image_pixel":0,"transcribe":0,"image":0,"video":0}},{"id":"google/gemma-4-31B-it","uuid":"endpoint-155df9cc-8c2f-4a04-8840-728681211a34","object":"model","created":0,"type":"chat","running":false,"display_name":"Gemma 4 31B-it FP8","organization":"Google","link":"https://huggingface.co/api/models/google/gemma-4-31B-it","license":"apache-2.0","context_length":262144,"config":{"chat_template":null,"stop":[""],"bos_token":"","eos_token":""},"pricing":{"hourly":0,"input":0.39,"output":0.9700000000000001,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":0}},{"id":"pearl-ai/gemma-4-31b-it","object":"model","created":1778777629,"type":"chat","running":false,"display_name":"Pearl-ai Gemma-4-31B-it-pearl","organization":"pearl.ai","link":"https://huggingface.co/pearl-ai/Gemma-4-31B-it-pearl","context_length":262144,"config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0.27999999999999997,"output":0.86,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":0}},{"id":"openai/gpt-oss-120b","uuid":"endpoint-cf361a3e-47d0-4dfc-851a-97098881e6a2","object":"model","created":1754414557,"type":"chat","running":false,"display_name":"OpenAI GPT-OSS 120B","organization":"OpenAI","link":"https://huggingface.co/openai/gpt-oss-120b","license":"other","context_length":131072,"config":{"chat_template":null,"stop":["<|return|>"],"bos_token":"<|startoftext|>","eos_token":"<|return|>"},"pricing":{"hourly":0,"input":0.15,"output":0.6,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":0}},{"id":"openai/gpt-oss-20b","uuid":"endpoint-f382c20a-6806-4ac2-abfb-d00d7a0b0c2b","object":"model","created":1774480577,"type":"chat","running":false,"display_name":"OpenAI GPT-OSS 20B","organization":"OpenAI","link":"https://huggingface.co/api/models/openai/gpt-oss-20b","license":"apache-2.0","context_length":131072,"config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0.05,"output":0.2,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":0}},{"id":"Qwen/Qwen3.5-9B","uuid":"endpoint-71bb7894-08d4-4882-bb72-7c257c234513","object":"model","created":0,"type":"chat","running":false,"display_name":"Qwen3.5 9B FP8","organization":"Qwen","link":"https://huggingface.co/api/models/togethercomputer/Qwen3.5-9B-FP8-MLP","context_length":262144,"config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":"<|im_end|>"},"pricing":{"hourly":0,"input":0.17,"output":0.25,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":0}},{"id":"meta-llama/Llama-3.3-70B-Instruct-Turbo","object":"model","created":1733466629,"type":"chat","running":false,"display_name":"Meta Llama 3.3 70B Instruct Turbo","organization":"Meta","link":"https://huggingface.co/meta-llama/Llama-3.3-70B-Instruct","license":"Llama-3.3 (Other)","context_length":131072,"config":{"chat_template":"{{- bos_token }}\n{%- if custom_tools is defined %}\n {%- set tools = custom_tools %}\n{%- endif %}\n{%- if not tools_in_user_message is defined %}\n {%- set tools_in_user_message = true %}\n{%- endif %}\n{%- if not date_string is defined %}\n {%- set date_string = \"26 Jul 2024\" %}\n{%- endif %}\n{%- if not tools is defined %}\n {%- set tools = none %}\n{%- endif %}\n\n{#- This block extracts the system message, so we can slot it into the right place. #}\n{%- if messages[0]['role'] == 'system' %}\n {%- set system_message = messages[0]['content']|trim %}\n {%- set messages = messages[1:] %}\n{%- else %}\n {%- set system_message = \"\" %}\n{%- endif %}\n\n{#- System message + builtin tools #}\n{{- \"<|start_header_id|>system<|end_header_id|>\\n\\n\" }}\n{%- if builtin_tools is defined or tools is not none %}\n {{- \"Environment: ipython\\n\" }}\n{%- endif %}\n{%- if builtin_tools is defined %}\n {{- \"Tools: \" + builtin_tools | reject('equalto', 'code_interpreter') | join(\", \") + \"\\n\\n\"}}\n{%- endif %}\n{{- \"Cutting Knowledge Date: December 2023\\n\" }}\n{{- \"Today Date: \" + date_string + \"\\n\\n\" }}\n{%- if tools is not none and not tools_in_user_message %}\n {{- \"You have access to the following functions. To call a function, please respond with JSON for a function call.\" }}\n {{- 'Respond in the format {\"name\": function name, \"parameters\": dictionary of argument name and its value}.' }}\n {{- \"Do not use variables.\\n\\n\" }}\n {%- for t in tools %}\n {{- t | tojson(indent=4) }}\n {{- \"\\n\\n\" }}\n {%- endfor %}\n{%- endif %}\n{{- system_message }}\n{{- \"<|eot_id|>\" }}\n\n{#- Custom tools are passed in a user message with some extra guidance #}\n{%- if tools_in_user_message and not tools is none %}\n {#- Extract the first user message so we can plug it in here #}\n {%- if messages | length != 0 %}\n {%- set first_user_message = messages[0]['content']|trim %}\n {%- set messages = messages[1:] %}\n {%- else %}\n {{- raise_exception(\"Cannot put tools in the first user message when there's no first user message!\") }}\n{%- endif %}\n {{- '<|start_header_id|>user<|end_header_id|>\\n\\n' -}}\n {{- \"Given the following functions, please respond with a JSON for a function call \" }}\n {{- \"with its proper arguments that best answers the given prompt.\\n\\n\" }}\n {{- 'Respond in the format {\"name\": function name, \"parameters\": dictionary of argument name and its value}.' }}\n {{- \"Do not use variables.\\n\\n\" }}\n {%- for t in tools %}\n {{- t | tojson(indent=4) }}\n {{- \"\\n\\n\" }}\n {%- endfor %}\n {{- first_user_message + \"<|eot_id|>\"}}\n{%- endif %}\n\n{%- for message in messages %}\n {%- if not (message.role == 'ipython' or message.role == 'tool' or 'tool_calls' in message) %}\n {{- '<|start_header_id|>' + message['role'] + '<|end_header_id|>\\n\\n'+ message['content'] | trim + '<|eot_id|>' }}\n {%- elif 'tool_calls' in message %}\n {%- if not message.tool_calls|length == 1 %}\n {{- raise_exception(\"This model only supports single tool-calls at once!\") }}\n {%- endif %}\n {%- set tool_call = message.tool_calls[0].function %}\n {%- if builtin_tools is defined and tool_call.name in builtin_tools %}\n {{- '<|start_header_id|>assistant<|end_header_id|>\\n\\n' -}}\n {{- \"<|python_tag|>\" + tool_call.name + \".call(\" }}\n {%- for arg_name, arg_val in tool_call.arguments | items %}\n {{- arg_name + '=\"' + arg_val + '\"' }}\n {%- if not loop.last %}\n {{- \", \" }}\n {%- endif %}\n {%- endfor %}\n {{- \")\" }}\n {%- else %}\n {{- '<|start_header_id|>assistant<|end_header_id|>\\n\\n' -}}\n {{- '{\"name\": \"' + tool_call.name + '\", ' }}\n {{- '\"parameters\": ' }}\n {{- tool_call.arguments | tojson }}\n {{- \"}\" }}\n {%- endif %}\n {%- if builtin_tools is defined %}\n {#- This means we're in ipython mode #}\n {{- \"<|eom_id|>\" }}\n {%- else %}\n {{- \"<|eot_id|>\" }}\n {%- endif %}\n {%- elif message.role == \"tool\" or message.role == \"ipython\" %}\n {{- \"<|start_header_id|>ipython<|end_header_id|>\\n\\n\" }}\n {%- if message.content is mapping or message.content is iterable %}\n {{- message.content | tojson }}\n {%- else %}\n {{- message.content }}\n {%- endif %}\n {{- \"<|eot_id|>\" }}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|start_header_id|>assistant<|end_header_id|>\\n\\n' }}\n{%- endif %}\n","stop":["<|eot_id|>","<|eom_id|>"],"bos_token":"<|begin_of_text|>","eos_token":"<|eot_id|>"},"pricing":{"hourly":0,"input":1.0399999999999998,"output":1.0399999999999998,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":0}},{"id":"google/gemma-3n-E4B-it","uuid":"endpoint-290b90f1-cdb9-46c1-a919-9a73822375c3","object":"model","created":1750955040,"type":"chat","running":false,"display_name":"Gemma 3N E4B Instruct","organization":"Google","link":"https://huggingface.co/google/gemma-3n-E4B-it","license":"gemma","context_length":32768,"config":{"chat_template":null,"stop":[""],"bos_token":"","eos_token":""},"pricing":{"hourly":0,"input":0.060000000000000005,"output":0.12000000000000001,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":0}},{"id":"hexgrad/Kokoro-82M","object":"model","created":1773163054,"type":"audio","running":false,"display_name":"Kokoro 82M","organization":"Hexgrad","link":"https://huggingface.co/hexgrad/Kokoro-82M","license":"apache2","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":4,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":0}},{"id":"canopylabs/orpheus-3b-0.1-ft","object":"model","created":1755731205,"type":"audio","running":false,"display_name":"Orpheus 3B 0.1 FT","organization":"Canopy Labs","link":"https://huggingface.co/canopylabs/orpheus-3b-0.1-ft","license":"apache2","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":15,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":0}},{"id":"openai/whisper-large-v3","uuid":"endpoint-b0eaec1e-3edb-48c3-85a9-1af9b5ce09fb","object":"model","created":0,"type":"transcribe","running":false,"display_name":"Whisper large-v3","organization":"OpenAI","link":"https://huggingface.co/openai/whisper-large-v3","license":"apache2","context_length":1,"config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0.27,"output":0.85,"base":0,"finetune":0,"image_pixel":0,"transcribe":{"price_per_minute":0.0015},"image":0,"video":0}},{"id":"black-forest-labs/FLUX.1-kontext-pro","object":"model","created":0,"type":"image","running":false,"display_name":"FLUX.1 Kontext [pro]","organization":"Black Forest Labs","context_length":0,"config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":{"price_per_megapixel":0.04,"min_steps":0},"transcribe":0,"image":0,"video":0}},{"id":"black-forest-labs/FLUX.1-kontext-max","object":"model","created":0,"type":"image","running":false,"display_name":"FLUX.1 Kontext [max]","organization":"Black Forest Labs","context_length":0,"config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":{"price_per_megapixel":0.08,"min_steps":0},"transcribe":0,"image":0,"video":0}},{"id":"black-forest-labs/FLUX.2-dev","uuid":"endpoint-268047b1-b295-4d9b-bc9f-239d375768ab","object":"model","created":1764086551,"type":"image","running":false,"display_name":"FLUX.2 [dev]","organization":"Black Forest Labs","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":{"example_price":0.0154,"example_description":"starting price per image"},"video":0}},{"id":"black-forest-labs/FLUX.2-flex","uuid":"endpoint-3d15053d-a558-487c-b0f8-068e9dfd781f","object":"model","created":1764090764,"type":"image","running":false,"display_name":"FLUX.2 [flex]","organization":"Black Forest Labs","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":{"example_price":0.03,"example_description":"per text-to-image"},"video":0}},{"id":"black-forest-labs/FLUX.2-pro","uuid":"endpoint-f6f3da91-6f41-4b38-b61c-40f60902b714","object":"model","created":1764070232,"type":"image","running":false,"display_name":"FLUX.2 [pro]","organization":"Black Forest Labs","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":{"example_price":0.03,"example_description":"per text-to-image image"},"video":0}},{"id":"black-forest-labs/FLUX.2-max","object":"model","created":0,"type":"image","running":false,"display_name":"FLUX.2 [max]","organization":"Black Forest Labs","context_length":0,"config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":{"price_per_megapixel":0.07,"min_steps":50},"transcribe":0,"image":0,"video":0}},{"id":"black-forest-labs/FLUX.1.1-pro","uuid":"endpoint-071376f6-db8a-44cf-9706-7ba0c9c14833","object":"model","created":0,"type":"image","running":false,"display_name":"FLUX1.1 [pro]","organization":"Black Forest Labs","link":"https://huggingface.co/black-forest-labs/FLUX.1-schnell","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":{"price_per_megapixel":0.04,"min_steps":0},"transcribe":0,"image":0,"video":0}},{"id":"meta-llama/Llama-Guard-4-12B","uuid":"endpoint-2095a2fc-bb95-48df-b94f-f95ccb22fa93","object":"model","created":1746038486,"type":"moderation","running":false,"display_name":"Llama Guard 4 12B","organization":"Meta","link":"https://huggingface.co/meta-llama/Llama-Guard-4-12B","license":"other llama4","context_length":1048576,"config":{"chat_template":"{%- if messages | length % 2 == 0 -%}\n {%- set role = 'Agent' -%}\n{%- else -%}\n {%- set role = 'User' -%}\n{%- endif -%}\n\n{%- if categories is not defined -%}\n {%- set ns = namespace(contains_image=false) -%}\n\n {%- for message in messages -%}\n {%- for content in message['content'] | selectattr('type', 'equalto', 'image') -%}\n {%- set ns.contains_image = true -%}\n {%- endfor -%}\n {%- endfor -%}\n\n {%- if ns.contains_image -%}\n {%- set categories = {\n \"S1\": \"Violent Crimes.\",\n \"S2\": \"Non-Violent Crimes.\",\n \"S3\": \"Sex Crimes.\",\n \"S4\": \"Child Exploitation.\",\n \"S5\": \"Defamation.\",\n \"S6\": \"Specialized Advice.\",\n \"S7\": \"Privacy.\",\n \"S8\": \"Intellectual Property.\",\n \"S9\": \"Indiscriminate Weapons.\",\n \"S10\": \"Hate.\",\n \"S11\": \"Self-Harm.\",\n \"S12\": \"Sexual Content.\",\n \"S13\": \"Elections.\",\n } -%}\n {%- else -%}\n {%- set categories = {\n \"S1\": \"Violent Crimes.\",\n \"S2\": \"Non-Violent Crimes.\",\n \"S3\": \"Sex Crimes.\",\n \"S4\": \"Child Exploitation.\",\n \"S5\": \"Defamation.\",\n \"S6\": \"Specialized Advice.\",\n \"S7\": \"Privacy.\",\n \"S8\": \"Intellectual Property.\",\n \"S9\": \"Indiscriminate Weapons.\",\n \"S10\": \"Hate.\",\n \"S11\": \"Self-Harm.\",\n \"S12\": \"Sexual Content.\",\n \"S13\": \"Elections.\",\n \"S14\": \"Code Interpreter Abuse.\",\n } -%}\n {%- endif -%}\n{%- endif -%}\n\n{{ \"<|begin_of_text|><|header_start|>user<|header_end|>\\n\" }}\n{{ \"Task: Check if there is unsafe content in '\" + role + \"' messages in conversations according to our safety policy with the below categories.\\n\" }}\n{{ \"\\n\" }}\n\n{%- for key in categories -%}\n {%- if key not in excluded_category_keys -%}\n {{ key + \": \" + categories[key] + \"\\n\" }}\n {%- endif -%}\n{%- endfor -%}\n\n{{ \"\\n\" }}\n{{ \"\\n\" }}\n\n{%- for message in messages -%}\n {%- if (message['role'] == 'user') != (loop.index0 % 2 == 0) -%}\n {{ raise_exception('Conversation roles must alternate user/assistant/user/assistant/...') }}\n {%- endif -%}\n\n {%- set content = message['content'] -%}\n\n {%- if message['role'] == 'user' -%}\n {%- set role = 'User' -%}\n {%- elif message['role'] == 'assistant' -%}\n {%- set role = 'Agent' -%}\n {%- endif -%}\n\n {#— #1 Accumulate all images for this message —#}\n {%- set ns = namespace(img_tokens='') -%}\n {%- for img in message.content | selectattr('type', 'equalto', 'image') -%}\n {%- set ns.img_tokens = ns.img_tokens + '<|image|>' -%}\n {%- endfor -%}\n\n {#— #2 Then emit all text —#}\n {%- for txt in message.content | selectattr('type','equalto','text') -%}\n {{ role + ': ' }}{{ ns.img_tokens }}{{ txt.text | trim }}\\n\\n\n\n {#— clear the buffer so only first text prints images —#}\n {%- if loop.first -%}\n {%- set ns.img_tokens = '' -%}\n {%- endif -%}\n {%- endfor -%}\n{%- endfor -%}\n\n{{ \"\\n\" }}\n{{ \"Provide your safety assessment for ONLY THE LAST \" + role + \" message in the above conversation:\" }}\n{{ \" - First line must read 'safe' or 'unsafe'.\" }}\n{{ \" - If unsafe, a second line must include a comma-separated list of violated categories. <|eot|><|header_start|>assistant<|header_end|>\" }}","stop":["<|eot|>"],"bos_token":"<|begin_of_text|>","eos_token":"<|eot|>"},"pricing":{"hourly":0,"input":0.2,"output":0.2,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":0}},{"id":"intfloat/multilingual-e5-large-instruct","uuid":"endpoint-b1b563e5-5ec2-4577-9017-16b52ac5c841","object":"model","created":1745513588,"type":"embedding","running":false,"display_name":"Multilingual E5 Large Instruct","organization":"Intfloat","link":"https://huggingface.co/api/models/intfloat/multilingual-e5-large-instruct","license":"mit","context_length":514,"config":{"chat_template":null,"stop":[""],"bos_token":"","eos_token":""},"pricing":{"hourly":0,"input":0.02,"output":0.02,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":0}},{"id":"arize-ai/qwen-2-1.5b-instruct","uuid":"endpoint-22ce9f16-299a-47cc-b88f-c59cfb1d235e","object":"model","created":1745522693,"type":"chat","running":false,"display_name":"Arize AI Qwen 2 1.5B Instruct","organization":"Togethercomputer","link":"https://huggingface.co/api/models/togethercomputer/arize-ai-qwen-2-1.5b-instruct","context_length":32768,"config":{"chat_template":"{% for message in messages %}{% if loop.first and messages[0]['role'] != 'system' %}{{ '<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n' }}{% endif %}{{'<|im_start|>' + message['role'] + '\n' + message['content'] + '<|im_end|>' + '\n'}}{% endfor %}{% if add_generation_prompt %}{{ '<|im_start|>assistant\n' }}{% endif %}","stop":["<|im_end|>"],"bos_token":"<|endoftext|>","eos_token":"<|im_end|>"},"pricing":{"hourly":0,"input":0.1,"output":0.1,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":0}},{"id":"nvidia/parakeet-tdt-0.6b-v3","uuid":"endpoint-3fbe0c47-5c71-4f52-92fb-abaff932f05f","object":"model","created":0,"type":"transcribe","running":false,"display_name":"Nvidia Parakeet TDT 0.6B V3","organization":"Nvidia","link":"https://huggingface.co/nvidia/parakeet-tdt-0.6b-v3","context_length":448,"config":{"chat_template":null,"stop":["<|endoftext|>"],"bos_token":"<|endoftext|>","eos_token":"<|endoftext|>"},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":{"price_per_minute":0.0015},"image":0,"video":0}},{"id":"openai/gpt-image-1.5","uuid":"endpoint-11f45afc-3f72-41d1-b93e-902e220f4d5a","object":"model","created":1765980893,"type":"image","running":false,"display_name":"GPT Image 1.5","organization":"OpenAI","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":{"example_price":0.034,"example_description":"/opt/homebrew/bin/zsh.009 - /opt/homebrew/bin/zsh.199 per image based on quality"},"video":0}},{"id":"Wan-AI/Wan2.6-image","uuid":"endpoint-7dc7f98d-c562-4b5a-b710-c24875a6b471","object":"model","created":1769618722,"type":"image","running":false,"display_name":"Wan 2.6 Image","organization":"Wan-AI","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":{"example_price":0.03,"example_description":"per output image"},"video":0}},{"id":"google/veo-3.0-fast-audio","uuid":"endpoint-8bdb9924-b64e-4f44-ad5f-c979e578e7f4","object":"model","created":1759884907,"type":"video","running":false,"display_name":"Google Veo 3.0 Fast + Audio","organization":"Google","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":1.2,"example_description":"1080p / 8s"}}},{"id":"vidu/vidu-q1","uuid":"endpoint-fea0b805-4d7e-45ec-8b1b-856c932f152c","object":"model","created":1759884996,"type":"video","running":false,"display_name":"Vidu Q1","organization":"Vidu","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":0.22,"example_description":"1080p / 5s"}}},{"id":"cartesia/sonic","object":"model","created":1773696454,"type":"audio","running":false,"display_name":"Cartesia Sonic","organization":"Cartesia","link":"https://www.cartesia.ai","context_length":0,"config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":65,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":0}},{"id":"ByteDance-Seed/Seedream-3.0","uuid":"endpoint-c2769196-9347-46e4-815a-9c7abf5b8d50","object":"model","created":1759884740,"type":"image","running":false,"display_name":"ByteDance Seedream 3.0","organization":"ByteDance","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":{"example_price":0.018,"example_description":"720x1280"},"video":0}},{"id":"ByteDance-Seed/Seedream-4.0","uuid":"endpoint-e27a4640-becc-4a5a-92f4-3940b7be23e8","object":"model","created":1759884757,"type":"image","running":false,"display_name":"ByteDance Seedream 4.0","organization":"ByteDance","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":{"example_price":0.03,"example_description":"720x1280"},"video":0}},{"id":"Rundiffusion/Juggernaut-Lightning-Flux","uuid":"endpoint-63c3e50f-b9eb-41e3-a3ed-7242665874e4","object":"model","created":1759884814,"type":"image","running":false,"display_name":"Juggernaut Lightning Flux by RunDiffusion","organization":"RunDiffusion","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":{"example_price":0.0017,"example_description":"720x1280"},"video":0}},{"id":"google/veo-3.0-audio","uuid":"endpoint-ced52ba5-3cb0-46a3-aa92-d7a2f59d6bd9","object":"model","created":1759884892,"type":"video","running":false,"display_name":"Google Veo 3.0 + Audio","organization":"Google","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":3.2,"example_description":"720p / 8s"}}},{"id":"kwaivgI/kling-2.1-master","uuid":"endpoint-5e489acf-5401-4843-97b7-8a830648bd3c","object":"model","created":1759884953,"type":"video","running":false,"display_name":"Kling 2.1 Master","organization":"kwaivgI","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":0.924,"example_description":"1080p / 5s"}}},{"id":"ideogram/ideogram-3.0","uuid":"endpoint-3d82f587-56ba-45df-817d-854cd2117f41","object":"model","created":1759884808,"type":"image","running":false,"display_name":"Ideogram 3.0","organization":"ideogram","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":{"example_price":0.06,"example_description":"720x1280"},"video":0}},{"id":"kwaivgI/kling-2.1-pro","uuid":"endpoint-8fa3e87a-9f35-45fc-8157-8ed046498ba6","object":"model","created":1759884948,"type":"video","running":false,"display_name":"Kling 2.1 Pro","organization":"kwaivgI","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":0.3234,"example_description":"1080p / 5s"}}},{"id":"google/veo-2.0","uuid":"endpoint-ad40ee70-5f82-4283-b2d8-2813a2773022","object":"model","created":1759884886,"type":"video","running":false,"display_name":"Google Veo 2.0","organization":"Google","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":2.5,"example_description":"720p / 5s"}}},{"id":"openai/sora-2","uuid":"endpoint-c4adc1b3-6ac2-491a-b4b0-e0c3b3fea40f","object":"model","created":1760480340,"type":"video","running":false,"display_name":"Sora 2","organization":"OpenAI","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":0.8,"example_description":"720p / 8s"}}},{"id":"kwaivgI/kling-2.1-standard","uuid":"endpoint-09e526e5-8428-4841-8242-c883b8600a8c","object":"model","created":1759884940,"type":"video","running":false,"display_name":"Kling 2.1 Standard","organization":"kwaivgI","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":0.1848,"example_description":"720p / 5s"}}},{"id":"google/veo-3.0-fast","uuid":"endpoint-92bc9b5a-365e-48e2-bc37-e278671310cb","object":"model","created":1759884913,"type":"video","running":false,"display_name":"Google Veo 3.0 Fast","organization":"Google","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":0.8,"example_description":"1080p / 8s"}}},{"id":"google/gemini-3-pro-image","uuid":"endpoint-d2f07d30-6a03-4f98-a52d-cdc5461cf639","object":"model","created":1763662095,"type":"image","running":false,"display_name":"Gemini 3 (Nano Banana Pro)","organization":"Google","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":{"example_price":0.134,"example_description":"1080p & 2K resolutions costs $0.134/image and 4K resolutions costs $0.24 per image"},"video":0}},{"id":"vidu/vidu-2.0","uuid":"endpoint-31518301-3076-47c8-b42f-542569955820","object":"model","created":1759885002,"type":"video","running":false,"display_name":"Vidu 2.0","organization":"Vidu","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":0.8,"example_description":"720p / 8s"}}},{"id":"openai/sora-2-pro","uuid":"endpoint-03b9298b-8624-4c29-8055-941df060eda4","object":"model","created":1760480692,"type":"video","running":false,"display_name":"Sora 2 Pro","organization":"OpenAI","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":3,"example_description":"1080p / 8s"}}},{"id":"pixverse/pixverse-v5","uuid":"endpoint-1588b5bc-5923-4672-be92-3199a579a18f","object":"model","created":1759884975,"type":"video","running":false,"display_name":"PixVerse v5","organization":"PixVerse","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":0.299,"example_description":"1080p / 5s"}}},{"id":"stabilityai/stable-diffusion-xl-base-1.0","uuid":"endpoint-5bbe64a1-3798-4ad5-bfd5-aee40eca9564","object":"model","created":1759884771,"type":"image","running":false,"display_name":"SD XL","organization":"stabilityai","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":{"example_price":0.0019,"example_description":"720x1280"},"video":0}},{"id":"ByteDance/Seedance-1.0-lite","uuid":"endpoint-5467de41-51aa-4d08-98b5-8cd34dc19906","object":"model","created":1759884873,"type":"video","running":false,"display_name":"ByteDance Seedance 1.0 Lite","organization":"ByteDance","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":0.143,"example_description":"720p / 5s"}}},{"id":"cartesia/sonic-3","object":"model","created":1774464715,"type":"audio","running":false,"display_name":"Cartesia Sonic 3","organization":"Cartesia","link":"https://www.cartesia.ai","context_length":448,"config":{"chat_template":null,"stop":["<|endoftext|>"],"bos_token":"<|endoftext|>","eos_token":"<|endoftext|>"},"pricing":{"hourly":0,"input":65,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":0}},{"id":"ByteDance/Seedance-1.0-pro","uuid":"endpoint-9419195a-e048-4865-bf8b-89343a3e9b84","object":"model","created":1759884879,"type":"video","running":false,"display_name":"ByteDance Seedance 1.0 Pro","organization":"ByteDance","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":0.565,"example_description":"720p / 5s"}}},{"id":"google/imagen-4.0-fast","uuid":"endpoint-3ba3bc6f-fe2b-4446-9ec0-71e82ac3348d","object":"model","created":1759884793,"type":"image","running":false,"display_name":"Google Imagen 4.0 Fast","organization":"Google","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":{"example_price":0.02,"example_description":"720x1280"},"video":0}},{"id":"google/flash-image-2.5","uuid":"endpoint-e9655a27-b014-43b4-bff1-b343a0206e07","object":"model","created":1759884801,"type":"image","running":false,"display_name":"Gemini Flash Image 2.5 (Nano Banana)","organization":"Google","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":{"example_price":0.039,"example_description":"720x1280"},"video":0}},{"id":"minimax/hailuo-02","uuid":"endpoint-68520084-c967-42b6-bff4-a63b660bd0cf","object":"model","created":1759884967,"type":"video","running":false,"display_name":"MiniMax Hailuo 02","organization":"MiniMaxAI","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":0.56,"example_description":"768p / 10s"}}},{"id":"google/imagen-4.0-ultra","uuid":"endpoint-40d2690e-57a7-4e89-987d-2a3e44c1302d","object":"model","created":1759884786,"type":"image","running":false,"display_name":"Google Imagen 4.0 Ultra","organization":"Google","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":{"example_price":0.06,"example_description":"720x1280"},"video":0}},{"id":"google/imagen-4.0-preview","uuid":"endpoint-b6561013-bc17-4aa3-9a76-89174973977b","object":"model","created":1759884778,"type":"image","running":false,"display_name":"Google Imagen 4.0 Preview","organization":"Google","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":{"example_price":0.04,"example_description":"720x1280"},"video":0}},{"id":"RunDiffusion/Juggernaut-pro-flux","uuid":"endpoint-1f51e977-a298-40aa-a0c6-d5865c37bc38","object":"model","created":1759884821,"type":"image","running":false,"display_name":"Juggernaut Pro Flux by RunDiffusion 1.0.0","organization":"RunDiffusion","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":{"example_price":0.0049,"example_description":"720x1280"},"video":0}},{"id":"Qwen/Qwen-Image","uuid":"endpoint-d4d29f48-ce86-4533-863a-23e9245f6570","object":"model","created":1759884857,"type":"image","running":false,"display_name":"Qwen Image","organization":"Qwen","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":{"example_price":0.0058,"example_description":"720x1280"},"video":0}},{"id":"google/veo-3.0","uuid":"endpoint-test-duplicate-001","object":"model","created":1778817876,"type":"video","running":false,"display_name":"Duplicate Test","organization":"Google","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":0.08,"example_description":"test"}}},{"id":"kwaivgI/kling-1.6-standard","uuid":"endpoint-9f6794ed-52f7-414f-8974-d3b1ffb8702f","object":"model","created":1759884920,"type":"video","running":false,"display_name":"Kling 1.6 Standard","organization":"kwaivgI","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":0.185,"example_description":"720p / 5s"}}},{"id":"minimax/video-01-director","uuid":"endpoint-d5929bff-e81e-4bab-8b20-17cb99936a68","object":"model","created":1759884960,"type":"video","running":false,"display_name":"MiniMax 01 Director","organization":"MiniMaxAI","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{}}},{"id":"cartesia/sonic-2","object":"model","created":1774464715,"type":"audio","running":false,"display_name":"Cartesia Sonic 2","organization":"Cartesia","context_length":448,"config":{"chat_template":null,"stop":["<|endoftext|>"],"bos_token":"<|endoftext|>","eos_token":"<|endoftext|>"},"pricing":{"hourly":0,"input":65,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":0}},{"id":"pixverse/pixverse-v5.6","uuid":"endpoint-5e8550be-7faf-411e-81ee-92773d4a1304","object":"model","created":1769621066,"type":"video","running":false,"display_name":"PixVerse v5.6","organization":"PixVerse","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":0.1326,"example_description":"$0.1031 - $0.221 per 5 sec video without audio. Audio is an additional $0.1326"}}},{"id":"Qwen/Qwen-Image-2.0-Pro","uuid":"endpoint-ea16bed3-cfd1-477b-ad95-1ac0f28bfec2","object":"model","created":1773318281,"type":"image","running":false,"display_name":"Qwen Image 2.0 Pro","organization":"Qwen","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":{"example_price":0.075,"example_description":"per image"},"video":0}},{"id":"google/flash-image-3.1","uuid":"endpoint-f0e10a8e-9250-4bcc-b1a9-ae34f3ecdaec","object":"model","created":1772535344,"type":"image","running":false,"display_name":"Gemini 3.1 Flash Image (Nano Banana 2)","organization":"Google","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":{"example_price":0.04657,"example_description":"0.04657 for 512x512. For every input image used, it's an additional $0.00028. When using grounded search, $0.014 will be added on top."},"video":0}},{"id":"Qwen/Qwen-Image-2.0","uuid":"endpoint-9bd5c294-1a2e-4ffb-bf28-482e01eee56f","object":"model","created":1773251084,"type":"image","running":false,"display_name":"Qwen Image 2.0","organization":"Qwen","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":{"example_price":0.035,"example_description":"per image"},"video":0}},{"id":"Wan-AI/wan2.7-t2v","uuid":"endpoint-4e24da5f-2274-44ad-8bf3-36dc47a8114a","object":"model","created":1775245808,"type":"video","running":false,"display_name":"Wan 2.7 T2V","organization":"Wan-AI","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":0.1,"example_description":"per 5 seconds of video"}}},{"id":"Wan-AI/wan2.7-i2v","uuid":"endpoint-47e29650-3293-4538-bc90-fa3f07b159dc","object":"model","created":1775254675,"type":"video","running":false,"display_name":"Wan 2.7 I2V","organization":"Wan-AI","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":0.1,"example_description":"per 5 seconds of video"}}},{"id":"Wan-AI/wan2.7-r2v","uuid":"endpoint-819be224-66c1-424d-8d79-7d527bcf278c","object":"model","created":1775257231,"type":"video","running":false,"display_name":"Wan 2.7 R2V","organization":"Wan-AI","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":0.1,"example_description":"per 5 seconds of video"}}},{"id":"vidu/vidu-q3","uuid":"endpoint-002dc245-03bd-4e03-bdb0-e3fd55e25aba","object":"model","created":1776175177,"type":"video","running":false,"display_name":"Vidu Q3","organization":"Vidu","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":0.0975,"example_description":"0.0455 - 0.1040 per second depending on resolution"}}},{"id":"vidu/vidu-q3-turbo","uuid":"endpoint-1381491a-63c3-4513-abdc-15005e5e85a3","object":"model","created":1776175206,"type":"video","running":false,"display_name":"Vidu Q3 Turbo","organization":"Vidu","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":0.195,"example_description":"0.13 - 0.26 per second depending on resolution"}}},{"id":"google/veo-3.1-test-debug","uuid":"endpoint-test-debug-001","object":"model","created":0,"type":"video","running":false,"display_name":"Veo 3.1 Debug Test","organization":"Google","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":0.08,"example_description":"test"}}},{"id":"pixverse/pixverse-v6","uuid":"endpoint-9782553a-d1f6-4641-b70f-cf3664e95a8a","object":"model","created":1776953730,"type":"video","running":false,"display_name":"PixVerse v6","organization":"PixVerse","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":0.09,"example_description":"0.090/s at 1080p without audio. 0.115/s with audio"}}},{"id":"ByteDance/Seedance-2.0","uuid":"endpoint-1d17df31-ca97-4848-869e-be0f68b096a7","object":"model","created":1776942761,"type":"video","running":false,"display_name":"ByteDance Seedance 2.0","organization":"ByteDance","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":0.16,"example_description":"Text/Image to Video at 720P: $0.16/sec & Video-to-Video at 720P: from $0.28/sec"}}},{"id":"Qwen/Qwen3.6-Plus","uuid":"endpoint-78f9d01e-0c22-47dc-b2b2-6aa0e2f3570c-v2","object":"model","created":1777340375,"type":"chat","running":false,"display_name":"Qwen3.6 Plus","organization":"Qwen","context_length":1000000,"config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0.5,"output":3,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":0}},{"id":"HappyHorse/HappyHorse-1.0-T2V","object":"model","created":1777283507,"type":"video","running":false,"display_name":"","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":0.1,"example_description":"per 5 seconds of video"}}},{"id":"alibaba/happyhorse-1.0-t2v","uuid":"endpoint-e65e99d1-97f1-443f-94e2-dd139e102897","object":"model","created":1777714549,"type":"video","running":false,"display_name":"HappyHorse 1.0 T2V","organization":"Alibaba","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":0.24,"example_description":"Text to Video at 720P: $0.14/sec and $0.24/sec at 1080p"}}},{"id":"alibaba/happyhorse-1.0-r2v","uuid":"endpoint-320deb45-9a43-46b2-8393-32b466ce9bce","object":"model","created":1777717813,"type":"video","running":false,"display_name":"HappyHorse 1.0 R2V","organization":"Alibaba","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":0.24,"example_description":"Text/Image to Video at 720P: $0.14/sec and $0.24/sec at 1080p"}}},{"id":"alibaba/happyhorse-1.0-i2v","uuid":"endpoint-0fdc51d3-6dd3-4f2c-bce8-418ab47b36ea","object":"model","created":1777717851,"type":"video","running":false,"display_name":"HappyHorse 1.0 I2V","organization":"Alibaba","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":0.24,"example_description":"Text/Image to Video at 720P: $0.14/sec and $0.24/sec at 1080p"}}},{"id":"ByteDance/Seedream-5.0-lite","uuid":"endpoint-90244fc5-096f-4bca-b5f2-79664175e2c4","object":"model","created":1778252567,"type":"image","running":false,"display_name":"ByteDance Seedream 5.0 Lite","organization":"ByteDance","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":{"example_price":0.035,"example_description":"Pricing is $0.035 for both 2K & 3K outputs"},"video":0}},{"id":"google/veo-3.1","uuid":"endpoint-b0a69f31-f14c-4825-9c01-cf20b5aeece9","object":"model","created":1776790993,"type":"video","running":false,"display_name":"Veo 3.1","organization":"Google","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":0.08,"example_description":"0.08/ per 4s at 720p without audio. .60/s with audio"}}},{"id":"google/veo-3.1-lite","uuid":"endpoint-0a06c93a-68ce-48f6-bfbf-d9a0337a073b","object":"model","created":1778615460,"type":"video","running":false,"display_name":"Veo 3.1 Lite","organization":"Google","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":0.05,"example_description":"0.05/s at 1080p without audio. 0.80/s with audio."}}},{"id":"nvidia/nemotron-3.5-asr-streaming-0.6b","uuid":"endpoint-cd9d043d-92ac-4320-af6a-2638e934861a","object":"model","created":0,"type":"transcribe","running":false,"display_name":"Nvidia Nemotron 3.5 ASR Streaming 0.6B","organization":"Nvidia","link":"https://huggingface.co/nvidia/nemotron-3.5-asr-streaming-0.6b","license":"apache-2.0","context_length":448,"config":{"chat_template":null,"stop":[],"bos_token":"<|endoftext|>","eos_token":"<|endoftext|>"},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":{"price_per_minute":0.0015},"image":0,"video":0}},{"id":"nvidia/nemotron-3-asr-streaming-0.6b","uuid":"endpoint-614e0569-b81e-4234-b08e-976d81913415","object":"model","created":0,"type":"transcribe","running":false,"display_name":"Nvidia Nemotron 3 ASR Streaming 0.6B","organization":"Nvidia","link":"https://huggingface.co/nvidia/nemotron-speech-streaming-en-0.6b","license":"apache-2.0","context_length":448,"config":{"chat_template":null,"stop":[],"bos_token":"<|endoftext|>","eos_token":"<|endoftext|>"},"pricing":{"hourly":0,"input":0.45,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":{"price_per_minute":0.0015},"image":0,"video":0}},{"id":"ideogram/ideogram-4.0","uuid":"endpoint-0304633d-06c9-4d89-a093-eaf52cc62aae","object":"model","created":1780584367,"type":"image","running":false,"display_name":"Ideogram 4.0","organization":"ideogram","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":{"example_price":0.06,"example_description":"per image price ranging from 0.03 - 0.10 per based on size and quality"},"video":0}},{"id":"openai/gpt-image-2","uuid":"endpoint-3a75d1cd-a76f-4277-b7f6-a6c62d05901b","object":"model","created":1776938977,"type":"image","running":false,"display_name":"GPT Image 2","organization":"OpenAI","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":{"example_price":0.053,"example_description":"0.006 - 0.165 per image based on size and quality"},"video":0}},{"id":"Qwen/Qwen3.7-Plus","uuid":"endpoint-ddc9fb60-6793-469c-ab42-a6db76013f67","object":"model","created":1781532368,"type":"chat","running":false,"display_name":"Qwen3.7 Plus","organization":"Qwen","context_length":1000000,"config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0.32,"output":1.28,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":0}},{"id":"alibaba/happyhorse-1.1-t2v","uuid":"endpoint-bae418aa-f3a0-42b7-bf16-25639335bee5","object":"model","created":1782485613,"type":"video","running":false,"display_name":"HappyHorse 1.1 T2V","organization":"Alibaba","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":0.14,"example_description":"Text to Video at 720P: $0.14/sec and $0.18/sec at 1080p"}}},{"id":"alibaba/happyhorse-1.1-i2v","uuid":"endpoint-1d482f72-1593-4648-949f-09481c618521","object":"model","created":1782485593,"type":"video","running":false,"display_name":"HappyHorse 1.1 I2V","organization":"Alibaba","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":0.14,"example_description":"Text/Image to Video at 720P: $0.14/sec and $0.18/sec at 1080p"}}},{"id":"alibaba/happyhorse-1.1-r2v","uuid":"endpoint-87cf37d3-6892-40ce-b1ff-56d5aeb80c44","object":"model","created":1782485628,"type":"video","running":false,"display_name":"HappyHorse 1.1 R2V","organization":"Alibaba","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":0.14,"example_description":"Text/Image to Video at 720P: $0.14/sec and $0.18/sec at 1080p"}}},{"id":"google/flash-image-3.1-lite","uuid":"endpoint-acb856f2-4ab1-440e-ba58-2bd6cea1b536","object":"model","created":1782846618,"type":"image","running":false,"display_name":"Gemini 3.1 Flash-Lite Image (Nano Banana 2 Lite)","organization":"Google","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":{"example_price":0.069,"example_description":"price per image"},"video":0}},{"id":"Prism-ML/Ternary-Bonsai-27B","uuid":"endpoint-6c5092a2-b920-4be3-9e45-1c5cb7eee78f","object":"model","created":0,"type":"chat","running":false,"display_name":"Ternary Bonsai 27B","organization":"Prism Ml","link":"https://huggingface.co/api/models/prism-ml/Ternary-Bonsai-27B-AWQ-4bit","license":"apache-2.0","context_length":262144,"config":{"chat_template":null,"stop":["<|im_end|>"],"bos_token":null,"eos_token":"<|im_end|>"},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":0}},{"id":"prunaai/p-image-ideogram","uuid":"endpoint-c045bc1c-6174-4d1c-bee0-716fed7e4609","object":"model","created":1785844762,"type":"image","running":false,"display_name":"P-Image-Ideogram","organization":"Pruna AI","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":{"example_price":0.00225,"example_description":"Pricing starts at $0.00225 per image"},"video":0}},{"id":"black-forest-labs/FLUX-3","uuid":"endpoint-bec520ab-d414-4fad-aad8-d801da1cff65","object":"model","created":1785896986,"type":"video","running":false,"display_name":"FLUX 3","organization":"Black Forest Labs","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":0.17,"example_description":"T2V @ 720p is $0.17/s, T2V @ 1080p is $0.29/s, V2V @720 is $0.43/s, V2V @1080p is $0.54/s"}}},{"id":"ByteDance/Seedance-2.5","uuid":"endpoint-d0ba33d4-1c4e-43db-9f3f-c8a4c2885dad","object":"model","created":1786388202,"type":"video","running":false,"display_name":"ByteDance Seedance 2.5","organization":"ByteDance","config":{"chat_template":null,"stop":[],"bos_token":null,"eos_token":null},"pricing":{"hourly":0,"input":0,"output":0,"base":0,"finetune":0,"image_pixel":0,"transcribe":0,"image":0,"video":{"example_price":0.115,"example_description":"480P: $0.115/sec & 720P: from $0.249/sec"}}}] \ No newline at end of file diff --git a/tests/test_litellm/test_sync_together_ai_models.py b/tests/test_litellm/test_sync_together_ai_models.py new file mode 100644 index 00000000000..7c1287e94b8 --- /dev/null +++ b/tests/test_litellm/test_sync_together_ai_models.py @@ -0,0 +1,351 @@ +import importlib.util +import json +from pathlib import Path +from types import MappingProxyType + +import pytest + +ROOT = Path(__file__).resolve().parents[2] +SCRIPT = ROOT / "scripts" / "sync_together_ai_models.py" +FIXTURES = Path(__file__).resolve().parent / "fixtures" / "together_ai_sync" + +_spec = importlib.util.spec_from_file_location("sync_together_ai_models", SCRIPT) +assert _spec is not None and _spec.loader is not None +sync = importlib.util.module_from_spec(_spec) +_spec.loader.exec_module(sync) + +RECORDED_CATALOG = sync.load_catalog(FIXTURES.joinpath("models_serverless.json").read_bytes()) +RECORDED_DOC = sync.parse_deprecations(FIXTURES.joinpath("deprecations.md").read_text()) + + +def _doc(removal_dates: dict[str, str], redirects: dict[str, str] | None = None) -> object: + return sync.DeprecationDoc( + removal_dates=MappingProxyType(removal_dates), + redirects=MappingProxyType(redirects or {}), + ) + + +def _chat_model(model_id: str, ctx: int = 4096, price: float = 1.0, cached: float | None = None) -> object: + return sync.CatalogModel( + id=model_id, + type="chat", + context_length=ctx, + pricing=sync.CatalogPricing(input=price, output=price, cached_input=cached), + ) + + +@pytest.mark.parametrize( + ("per_million", "expected"), + [ + (3, 3e-06), + (15, 1.5e-05), + (1.4, 1.4e-06), + (0.25999999999999995, 2.6e-07), + (0.060000000000000005, 6e-08), + (1.0399999999999998, 1.04e-06), + (0, 0.0), + ], +) +def test_per_token_normalizes_float_artifacts(per_million: float, expected: float) -> None: + assert sync.per_token(per_million) == expected + + +def test_parse_deprecations_recorded_fixture() -> None: + assert dict(RECORDED_DOC.redirects) == { + "mistralai/Mistral-7B-Instruct-v0.3": "mistralai/Ministral-3-14B-Instruct-2512", + "Kimi-K2": "Kimi-K2-0905", + "DeepSeek-V3": "DeepSeek-V3.1", + "DeepSeek-V3-0324": "DeepSeek-V3.1", + "DeepSeek-R1": "DeepSeek-R1-0528", + } + assert len(RECORDED_DOC.removal_dates) == 208 + assert RECORDED_DOC.removal_dates["google/gemma-3n-E4B-it"] == "2026-08-04" + + +def test_parse_deprecations_duplicate_rows_keep_most_recent_date() -> None: + assert RECORDED_DOC.removal_dates["Qwen/Qwen3-235B-A22B-Thinking-2507"] == "2026-04-16" + + +@pytest.mark.parametrize( + "markdown", + [ + "# Deprecations\n\nNothing here anymore.\n", + "\n## Active model redirects\n\n| A | B |\n| --- | --- |\n| `x` | `y` |\n\n## Something else\n", + "\n## Deprecation history\n\n### Inference\n\n| Date | Model | R |\n| --- | --- | --- |\n| 2026-01-01 | `m` | No |\n", + ], +) +def test_parse_deprecations_raises_when_a_table_parses_empty(markdown: str) -> None: + with pytest.raises(sync.SyncError): + sync.parse_deprecations(markdown) + + +def test_load_catalog_raises_on_shape_change() -> None: + with pytest.raises(sync.SyncError): + sync.load_catalog(b'[{"id": "x", "type": "chat"}]') + + +def test_load_catalog_raises_when_no_token_models_remain() -> None: + only_video = json.dumps([{"id": "v", "type": "video", "pricing": {"input": 0, "output": 0}}]).encode() + with pytest.raises(sync.SyncError): + sync.load_catalog(only_video) + + +def test_recorded_catalog_counts() -> None: + assert len(RECORDED_CATALOG) == 102 + assert sum(1 for model in RECORDED_CATALOG if model.type in sync.TYPE_TO_MODE) == 26 + assert sum(1 for model in RECORDED_CATALOG if model.pricing.cached_input) == 13 + + +def test_added_chat_model_matches_reviewed_registry_shape() -> None: + outcome = sync.compute_sync({}, RECORDED_CATALOG, RECORDED_DOC) + assert len(outcome.added) == 26 + assert not outcome.deprecated + assert outcome.cost_map["together_ai/moonshotai/Kimi-K3"] == { + "cache_read_input_token_cost": 3e-07, + "input_cost_per_token": 3e-06, + "litellm_provider": "together_ai", + "max_input_tokens": 1048576, + "max_output_tokens": 1048576, + "max_tokens": 1048576, + "mode": "chat", + "output_cost_per_token": 1.5e-05, + "source": "https://docs.together.ai/docs/serverless-models", + "supports_function_calling": True, + "supports_parallel_function_calling": True, + "supports_prompt_caching": True, + "supports_reasoning": True, + "supports_response_schema": True, + "supports_tool_choice": True, + "supports_vision": True, + } + + +def test_added_embedding_model_has_no_output_token_cap() -> None: + outcome = sync.compute_sync({}, RECORDED_CATALOG, RECORDED_DOC) + assert outcome.cost_map["together_ai/intfloat/multilingual-e5-large-instruct"] == { + "input_cost_per_token": 2e-08, + "litellm_provider": "together_ai", + "max_input_tokens": 514, + "max_tokens": 514, + "mode": "embedding", + "output_cost_per_token": 2e-08, + "output_vector_size": 1024, + "source": "https://docs.together.ai/docs/serverless-models", + } + + +def test_moderation_type_maps_to_chat_mode() -> None: + outcome = sync.compute_sync({}, RECORDED_CATALOG, RECORDED_DOC) + guard = outcome.cost_map["together_ai/meta-llama/Llama-Guard-4-12B"] + assert guard["mode"] == "chat" + assert guard["max_output_tokens"] == 1048576 + + +def test_docs_removed_but_live_model_stays_live_with_warning() -> None: + outcome = sync.compute_sync({}, RECORDED_CATALOG, RECORDED_DOC) + gemma = outcome.cost_map["together_ai/google/gemma-3n-E4B-it"] + assert "deprecation_date" not in gemma + assert any("gemma-3n-E4B-it" in warning and "2026-08-04" in warning for warning in outcome.warnings) + + +def test_price_change_updates_api_fields_and_keeps_curated_ones() -> None: + registry = { + "together_ai/acme/chat-1": { + "input_cost_per_token": 9e-07, + "litellm_provider": "together_ai", + "max_input_tokens": 4096, + "max_output_tokens": 2048, + "max_tokens": 2048, + "mode": "chat", + "output_cost_per_token": 9e-07, + "supports_audio_input": True, + } + } + outcome = sync.compute_sync(registry, [_chat_model("acme/chat-1", ctx=8192, price=2.0)], _doc({"x": "2026-01-01"})) + entry = outcome.cost_map["together_ai/acme/chat-1"] + assert entry["input_cost_per_token"] == 2e-06 + assert entry["max_input_tokens"] == 8192 + assert entry["max_output_tokens"] == 2048 + assert entry["supports_audio_input"] is True + assert len(outcome.updated) == 1 + assert "input_cost_per_token" in outcome.updated[0] + + +def test_cached_input_appearing_and_disappearing() -> None: + registry = { + "together_ai/acme/chat-1": { + "cache_read_input_token_cost": 1e-07, + "input_cost_per_token": 1e-06, + "litellm_provider": "together_ai", + "max_input_tokens": 4096, + "mode": "chat", + "output_cost_per_token": 1e-06, + "supports_prompt_caching": True, + }, + "together_ai/acme/chat-2": { + "input_cost_per_token": 1e-06, + "litellm_provider": "together_ai", + "max_input_tokens": 4096, + "mode": "chat", + "output_cost_per_token": 1e-06, + }, + } + catalog = [_chat_model("acme/chat-1"), _chat_model("acme/chat-2", cached=0.25999999999999995)] + outcome = sync.compute_sync(registry, catalog, _doc({"x": "2026-01-01"})) + assert "cache_read_input_token_cost" not in outcome.cost_map["together_ai/acme/chat-1"] + assert "supports_prompt_caching" not in outcome.cost_map["together_ai/acme/chat-1"] + assert outcome.cost_map["together_ai/acme/chat-2"]["cache_read_input_token_cost"] == 2.6e-07 + assert outcome.cost_map["together_ai/acme/chat-2"]["supports_prompt_caching"] is True + + +def test_capability_rule_backfills_existing_entry() -> None: + registry = { + "together_ai/moonshotai/Kimi-K3": { + "input_cost_per_token": 3e-06, + "litellm_provider": "together_ai", + "max_input_tokens": 1048576, + "mode": "chat", + "output_cost_per_token": 1.5e-05, + } + } + kimi = next(model for model in RECORDED_CATALOG if model.id == "moonshotai/Kimi-K3") + outcome = sync.compute_sync(registry, [kimi], _doc({"x": "2026-01-01"})) + assert outcome.cost_map["together_ai/moonshotai/Kimi-K3"]["supports_reasoning"] is True + assert any("supports_reasoning" in line for line in outcome.updated) + + +def test_disappeared_model_gets_docs_date_and_is_never_deleted() -> None: + registry = { + "together_ai/acme/gone": { + "input_cost_per_token": 1e-06, + "litellm_provider": "together_ai", + "mode": "chat", + "output_cost_per_token": 1e-06, + } + } + outcome = sync.compute_sync(registry, [_chat_model("acme/alive")], _doc({"acme/gone": "2026-07-01"})) + assert outcome.cost_map["together_ai/acme/gone"]["deprecation_date"] == "2026-07-01" + assert outcome.deprecated == ("together_ai/acme/gone: deprecation_date",) + + +def test_disappeared_model_without_docs_date_warns_instead() -> None: + registry = { + "together_ai/acme/gone": { + "input_cost_per_token": 1e-06, + "litellm_provider": "together_ai", + "mode": "chat", + "output_cost_per_token": 1e-06, + } + } + outcome = sync.compute_sync(registry, [_chat_model("acme/alive")], _doc({"other": "2026-07-01"})) + assert "deprecation_date" not in outcome.cost_map["together_ai/acme/gone"] + assert not outcome.deprecated + assert any("acme/gone" in warning and "human" in warning for warning in outcome.warnings) + + +def test_curated_deprecation_date_is_never_overwritten() -> None: + registry = { + "together_ai/acme/gone": { + "deprecation_date": "2026-06-15", + "input_cost_per_token": 1e-06, + "litellm_provider": "together_ai", + "mode": "chat", + "output_cost_per_token": 1e-06, + } + } + outcome = sync.compute_sync(registry, [_chat_model("acme/alive")], _doc({"acme/gone": "2026-07-01"})) + assert outcome.cost_map["together_ai/acme/gone"]["deprecation_date"] == "2026-06-15" + assert any("2026-06-15" in warning and "2026-07-01" in warning for warning in outcome.warnings) + + +def test_redirect_chain_resolves_to_final_live_model() -> None: + doc = _doc({"acme/a": "2026-01-01"}, redirects={"acme/a": "acme/b", "acme/b": "acme/c"}) + live = frozenset({"acme/c"}) + assert sync.resolve_successor("acme/a", doc, live) == "acme/c" + + +def test_redirect_dead_end_yields_no_successor() -> None: + doc = _doc({"acme/a": "2026-01-01"}, redirects={"acme/a": "acme/b"}) + assert sync.resolve_successor("acme/a", doc, frozenset({"acme/other"})) is None + + +def test_redirect_short_names_resolve_by_unique_suffix() -> None: + doc = _doc({"moonshotai/Kimi-K2": "2026-01-01"}, redirects={"Kimi-K2": "Kimi-K2-0905"}) + live = frozenset({"moonshotai/Kimi-K2-0905"}) + assert sync.resolve_successor("moonshotai/Kimi-K2", doc, live) == "moonshotai/Kimi-K2-0905" + + +def test_successor_written_only_when_not_curated() -> None: + registry = { + "together_ai/acme/a": { + "input_cost_per_token": 1e-06, + "litellm_provider": "together_ai", + "mode": "chat", + "output_cost_per_token": 1e-06, + }, + "together_ai/acme/b": { + "input_cost_per_token": 1e-06, + "litellm_provider": "together_ai", + "metadata": {"successor": "together_ai/acme/curated"}, + "mode": "chat", + "output_cost_per_token": 1e-06, + }, + } + doc = _doc({"acme/a": "2026-01-01", "acme/b": "2026-01-01"}, redirects={"acme/a": "acme/c", "acme/b": "acme/c"}) + outcome = sync.compute_sync(registry, [_chat_model("acme/c")], doc) + assert outcome.cost_map["together_ai/acme/a"]["metadata"] == {"successor": "together_ai/acme/c"} + assert outcome.cost_map["together_ai/acme/b"]["metadata"] == {"successor": "together_ai/acme/curated"} + assert any("acme/curated" in warning for warning in outcome.warnings) + + +def test_reappearance_clears_deprecation_date() -> None: + registry = { + "together_ai/acme/back": { + "deprecation_date": "2026-05-01", + "input_cost_per_token": 1e-06, + "litellm_provider": "together_ai", + "max_input_tokens": 4096, + "mode": "chat", + "output_cost_per_token": 1e-06, + } + } + outcome = sync.compute_sync(registry, [_chat_model("acme/back")], _doc({"x": "2026-01-01"})) + assert "deprecation_date" not in outcome.cost_map["together_ai/acme/back"] + assert outcome.reappeared == ("together_ai/acme/back",) + + +def test_new_chat_model_without_rule_is_flagged() -> None: + outcome = sync.compute_sync({}, [_chat_model("acme/unreviewed")], _doc({"x": "2026-01-01"})) + assert any("acme/unreviewed" in warning and "capability rule" in warning for warning in outcome.warnings) + + +def test_new_keys_land_at_the_end_of_the_provider_block() -> None: + registry = { + "aaa": {"mode": "chat"}, + "together_ai/acme/old": { + "input_cost_per_token": 1e-06, + "litellm_provider": "together_ai", + "mode": "chat", + "output_cost_per_token": 1e-06, + }, + "zzz": {"mode": "chat"}, + } + outcome = sync.compute_sync(registry, [_chat_model("acme/old"), _chat_model("acme/new")], _doc({"x": "2026-01-01"})) + assert list(outcome.cost_map) == ["aaa", "together_ai/acme/old", "together_ai/acme/new", "zzz"] + + +def test_sync_is_idempotent_over_the_repo_cost_map() -> None: + cost_map = json.loads((ROOT / "model_prices_and_context_window.json").read_text()) + first = sync.compute_sync(cost_map, RECORDED_CATALOG, RECORDED_DOC) + second = sync.compute_sync(first.cost_map, RECORDED_CATALOG, RECORDED_DOC) + assert not second.has_changes + assert second.cost_map == first.cost_map + + +def test_pr_body_lists_every_section_and_the_skipped_types() -> None: + outcome = sync.compute_sync({}, RECORDED_CATALOG, RECORDED_DOC) + body = sync.render_pr_body(outcome) + assert "### Added (26)" in body + assert "### Warnings needing a human call" in body + assert "image (29)" in body + assert "video (38)" in body