litellm/tests/unit/test_together_ai_model_metadata.py
yuneng-jiang f6882246d4
test: move tests/test_litellm root and small trees into tests/unit (#43186)
* ci: run the unit_selection.sh shard files on every event instead of only fork pull requests

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* ci: rename fork-flag to unit-flag now that it applies on every event

* test: move tests/test_litellm root and small trees into tests/unit

Pure renames, no content changes. Follow-up commits in this PR fix
references, merge the three files that already existed in tests/unit,
keep live-provider tests in tests/test_litellm and wire CI.

* test: carry tests/test_litellm conftest isolation into tests/unit

Callback lists, routing fallbacks, cached HTTP clients, logger state, AWS,
proxy-URL and keychain env, and session-end client cleanup now reset for
unit tests too. The environment isolation owns its MonkeyPatch so a test's
own monkeypatch is undone before the model-cost teardown runs.

* test: merge, split and prune the moved root and small-tree tests

Merge batches/test_batch_utils.py and the chat_completions and messages
dispatch tests into the files that already existed in tests/unit. Keep
the live Gemini interactions tests, the async image-fetch format test and
the OpenAI embedding scorer test in tests/test_litellm since they need
real network or keys. Put test_router.py under tests/unit/test_router so
the existing package no longer shadows it. Delete eight tests the audit
found superseded by stronger ones kept in this move.

* ci: run the moved root and small-tree tests under their legacy flags

Add the misc and responses-caching-types flags to unit_selection.sh and
CircleCI, extend enterprise-routing and mcp-integration, and point the
legacy GHA shards, Makefile, redis-compat workflow, merge smoke manifest
and change classifier at the new paths.

* test: make the new tests/unit directories packages

tests/unit/test_package_layout.py requires every directory to carry an
__init__.py, and without one the moved and retained
test_litellm_responses_bridge.py modules collide on import.

* test: scope the unit socket block to tests/unit in shared sessions

The GHA shards collect the legacy test-path and the unit selection in one
pytest session. The unit conftest's loopback-only block leaked into legacy
modules that reach the network at import. The legacy conftest now lifts the
restriction at collect and setup time, and the unit conftest re-applies it
when collecting its own modules.

* test: give the shard-script tests their own GITHUB_OUTPUT

They only passed where the runner set it. The CircleCI unit job's env
allowlist drops it, so the script's redirect failed there.

* test: point the router and module-deletion checks at tests/unit

router_code_coverage and code_qa_check_tests only searched tests/test_litellm,
so the moved router tests no longer counted. The two silent-experiment tests
the audit deleted were the only direct callers of those methods; they are
replaced with tests that assert the forwarded shadow request and the
recursion guard.

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-25 11:30:43 -07:00

128 lines
5.4 KiB
Python

import json
from pathlib import Path
from typing import Final
import pytest
from pydantic import TypeAdapter
REPO_ROOT: Final = Path(__file__).parents[2]
CostMap = dict[str, dict[str, object]]
COST_MAP_ADAPTER: Final = TypeAdapter(CostMap)
SERVERLESS_CHAT_MODELS: Final = (
"together_ai/moonshotai/Kimi-K3",
"together_ai/zai-org/GLM-5.2",
"together_ai/zai-org/GLM-5.3",
"together_ai/zai-org/GLM-5.3-Flash",
"together_ai/deepseek-ai/DeepSeek-V4-Pro-0813",
"together_ai/deepseek-ai/DeepSeek-V4-Flash-0731",
"together_ai/MiniMaxAI/MiniMax-M3",
"together_ai/thinkingmachines/Inkling",
"together_ai/thinkingmachines/Inkling-Small",
"together_ai/Qwen/Qwen3.8-2.4T-A95B",
"together_ai/Qwen/Qwen3.7-Max",
"together_ai/Qwen/Qwen3.7-Plus",
"together_ai/Qwen/Qwen3.6-Plus",
"together_ai/Qwen/Qwen3.5-9B",
"together_ai/meta-models/Muse-Glimmer-30B",
"together_ai/google/gemma-4-31B-it",
"together_ai/arize-ai/qwen-2-1.5b-instruct",
"together_ai/Prism-ML/Ternary-Bonsai-27B",
"together_ai/openai/gpt-oss-120b",
"together_ai/openai/gpt-oss-20b",
"together_ai/meta-llama/Llama-3.3-70B-Instruct-Turbo",
)
DEPRECATED_MODELS: Final = {
"together_ai/nvidia/nemotron-3-ultra-550b-a55b": "2026-08-27",
"together_ai/pearl-ai/gemma-4-31b-it": "2026-08-27",
"together_ai/deepseek-ai/DeepSeek-V4-Pro": "2026-08-27",
"together_ai/moonshotai/Kimi-K2.7-Code": "2026-08-27",
"together_ai/google/gemma-3n-E4B-it": "2026-08-25",
"together_ai/meta-llama/Llama-Guard-4-12B": "2026-08-25",
"together_ai/Qwen/Qwen3-235B-A22B-Instruct-2507-tput": "2026-07-10",
"together_ai/Qwen/Qwen3.5-397B-A17B": "2026-06-29",
"together_ai/Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8": "2026-06-04",
"together_ai/moonshotai/Kimi-K2.5": "2026-05-21",
"together_ai/deepseek-ai/DeepSeek-R1": "2026-05-14",
"together_ai/deepseek-ai/DeepSeek-V3.1": "2026-05-14",
"together_ai/Qwen/Qwen3-235B-A22B-Thinking-2507": "2026-04-16",
"together_ai/mistralai/Mixtral-8x7B-Instruct-v0.1": "2026-04-16",
"together_ai/zai-org/GLM-4.5-Air-FP8": "2026-04-02",
"together_ai/zai-org/GLM-4.7": "2026-04-02",
"together_ai/mistralai/Mistral-Small-24B-Instruct-2501": "2026-04-02",
"together_ai/Qwen/Qwen3-Next-80B-A3B-Instruct": "2026-04-02",
"together_ai/meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8": "2026-03-31",
"together_ai/meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo": "2026-03-06",
"together_ai/moonshotai/Kimi-K2-Instruct-0905": "2026-03-06",
"together_ai/meta-llama/Llama-3.2-3B-Instruct-Turbo": "2026-03-06",
"together_ai/Qwen/Qwen3-Next-80B-A3B-Thinking": "2026-02-25",
"together_ai/meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo": "2026-02-25",
"together_ai/Qwen/Qwen3-235B-A22B-fp8-tput": "2026-02-06",
"together_ai/meta-llama/Llama-4-Scout-17B-16E-Instruct": "2026-02-06",
"together_ai/Qwen/Qwen2.5-72B-Instruct-Turbo": "2026-02-06",
"together_ai/meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo": "2026-02-06",
"together_ai/deepseek-ai/DeepSeek-R1-0528-tput": "2026-02-03",
"together_ai/mistralai/Mistral-7B-Instruct-v0.1": "2025-11-13",
"together_ai/meta-llama/Llama-3.3-70B-Instruct-Turbo-Free": "2025-11-13",
}
@pytest.fixture(scope="module")
def cost_map() -> CostMap:
with open(REPO_ROOT / "model_prices_and_context_window.json") as f:
return COST_MAP_ADAPTER.validate_python(json.load(f))
def test_together_chat_entries_never_carry_context_length_as_output_ceiling(cost_map: CostMap):
inflated = sorted(
model
for model, info in cost_map.items()
if info.get("litellm_provider") == "together_ai"
and info.get("mode") == "chat"
and "max_output_tokens" in info
and info["max_output_tokens"] == info.get("max_input_tokens")
)
assert inflated == []
def _successor(info: dict[str, object]) -> str | None:
metadata = info.get("metadata")
if not isinstance(metadata, dict):
return None
successor = metadata.get("successor")
return successor if isinstance(successor, str) else None
def test_together_backup_cost_map_in_sync(cost_map: CostMap):
with open(REPO_ROOT / "litellm" / "model_prices_and_context_window_backup.json") as f:
backup = COST_MAP_ADAPTER.validate_python(json.load(f))
together_main = {k: v for k, v in cost_map.items() if k.startswith("together_ai/")}
together_backup = {k: v for k, v in backup.items() if k.startswith("together_ai/")}
assert together_backup == together_main
CACHED_INPUT_MODELS: Final = (
"together_ai/moonshotai/Kimi-K3",
"together_ai/zai-org/GLM-5.2",
"together_ai/meta-models/Muse-Glimmer-30B",
"together_ai/Qwen/Qwen3.8-2.4T-A95B",
"together_ai/deepseek-ai/DeepSeek-V4-Pro-0813",
"together_ai/deepseek-ai/DeepSeek-V4-Flash-0731",
"together_ai/thinkingmachines/Inkling",
"together_ai/MiniMaxAI/MiniMax-M3",
"together_ai/thinkingmachines/Inkling-Small",
"together_ai/moonshotai/Kimi-K2.7-Code",
"together_ai/deepseek-ai/DeepSeek-V4-Pro",
"together_ai/nvidia/nemotron-3-ultra-550b-a55b",
"together_ai/Qwen/Qwen3.7-Max",
)
def test_together_prompt_caching_flag_implies_cache_read_rate(cost_map: CostMap):
for model, info in cost_map.items():
if model.startswith("together_ai/") and info.get("supports_prompt_caching"):
assert "cache_read_input_token_cost" in info, f"{model} flags caching without a cache read rate"