mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
Merge pull request #38257 from BerriAI/litellm_together_registry_sync
feat(models): add daily Together AI model registry sync script and workflow
This commit is contained in:
commit
5337c68dd3
5 changed files with 1401 additions and 0 deletions
68
.github/workflows/sync-together-ai-models.yml
vendored
Normal file
68
.github/workflows/sync-together-ai-models.yml
vendored
Normal file
|
|
@ -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 }}
|
||||
539
scripts/sync_together_ai_models.py
Normal file
539
scripts/sync_together_ai_models.py
Normal file
|
|
@ -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
|
||||
442
tests/test_litellm/fixtures/together_ai_sync/deprecations.md
Normal file
442
tests/test_litellm/fixtures/together_ai_sync/deprecations.md
Normal file
|
|
@ -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 |
|
||||
|
||||
<Tip>
|
||||
If you need to use the original model version, you can always deploy it as a [dedicated endpoint](/docs/dedicated-endpoints).
|
||||
</Tip>
|
||||
|
||||
## 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.
|
||||
File diff suppressed because one or more lines are too long
351
tests/test_litellm/test_sync_together_ai_models.py
Normal file
351
tests/test_litellm/test_sync_together_ai_models.py
Normal file
|
|
@ -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
|
||||
Loading…
Add table
Reference in a new issue