"""Layered model-catalog resolution (ModelCatalog.resolve_entry). Covers per-field authority merging (user > live > corrections > snapshot > synthesized), glob corrections matching, per-1k → per-Mtok cost adaptation, cold-instance resolution, override validation — plus a parity net asserting the legacy resolve paths still return today's exact values. """ from __future__ import annotations import tomllib from importlib import resources import pytest from opensquilla.provider import model_catalog as model_catalog_module from opensquilla.provider.catalog_types import ModelCatalogEntry from opensquilla.provider.model_catalog import ( ModelCatalog, resolve_effective_context_window, ) def _install_corrections(monkeypatch: pytest.MonkeyPatch, toml_text: str) -> None: """Route the corrections layer at parsed TOML text (packaged file is empty).""" tables = model_catalog_module._normalize_corrections(tomllib.loads(toml_text)) monkeypatch.setattr(model_catalog_module, "_corrections_tables", lambda: tables) # --------------------------------------------------------------------------- # Cold instance — a bare ModelCatalog() with no network warmup must resolve # through corrections + snapshot + synthesized (ensemble.py builds bare # instances per member). # --------------------------------------------------------------------------- def test_cold_instance_resolves_snapshot_known_model() -> None: entry = ModelCatalog().resolve_entry("gpt-5.5", provider="openai") assert entry.source == "snapshot" assert entry.provider_id == "openai" assert entry.model_id == "gpt-5.5" assert entry.context_window == 1_050_000 assert entry.max_output_tokens == 128_000 assert entry.supports_reasoning is True assert entry.supports_tools is True assert entry.supports_vision is True # The snapshot never knows the streaming dialect. assert entry.reasoning_format == "none" # The snapshot vendors models.dev cost keys verbatim. assert entry.input_cost_per_mtok == pytest.approx(5.0) assert entry.output_cost_per_mtok == pytest.approx(30.0) assert entry.cache_read_cost_per_mtok == pytest.approx(0.5) def test_cold_instance_synthesizes_unknown_model() -> None: entry = ModelCatalog().resolve_entry("model-that-does-not-exist-anywhere") assert entry.source == "synthesized" assert entry.context_window == 32_768 assert entry.max_output_tokens == 8_192 assert entry.supports_tools is True assert entry.supports_reasoning is False assert entry.supports_vision is False assert entry.input_cost_per_mtok is None assert entry.output_cost_per_mtok is None assert entry.quality_prior is None # --------------------------------------------------------------------------- # Per-field authority merging # --------------------------------------------------------------------------- def _populated_catalog() -> ModelCatalog: catalog = ModelCatalog() catalog._populate_from_data( [ { "id": "vendor/model-x", "name": "Vendor Model X", "context_length": 200_000, "top_provider": {"max_completion_tokens": 30_000}, "supported_parameters": ["tools", "reasoning"], "architecture": {"input_modalities": ["text", "image"]}, "pricing": {"prompt": "0.0000025", "completion": "0.00001"}, } ] ) return catalog def test_user_overrides_beat_live_per_field() -> None: catalog = _populated_catalog() catalog.set_user_overrides( {"openrouter/vendor/model-x": {"context_window": 111, "quality_prior": 0.9}} ) entry = catalog.resolve_entry("vendor/model-x", provider="openrouter") assert entry.source == "user" # User-set fields win outright… assert entry.context_window == 111 assert entry.quality_prior == 0.9 # …while the live layer still fills everything the user left unset. assert entry.max_output_tokens == 30_000 assert entry.display_name == "Vendor Model X" assert entry.supports_reasoning is True assert entry.supports_vision is True assert entry.reasoning_format == "openrouter" def test_live_beats_corrections_and_corrections_fill_unset( monkeypatch: pytest.MonkeyPatch, ) -> None: catalog = ModelCatalog() catalog._populate_from_data( [ { "id": "vendor/model-y", "context_length": 100_000, "top_provider": {"max_completion_tokens": 2_048}, "supported_parameters": ["tools"], } ] ) _install_corrections( monkeypatch, """ [openrouter."vendor/model-y"] max_output_tokens = 9999 supports_reasoning = true reasoning_format = "deepseek" quality_prior = 0.5 """, ) entry = catalog.resolve_entry("vendor/model-y", provider="openrouter") assert entry.source == "live" # Live knows these — the correction must NOT override them… assert entry.max_output_tokens == 2_048 assert entry.supports_reasoning is False # …but fields live left unset are filled from corrections. assert entry.reasoning_format == "deepseek" assert entry.quality_prior == 0.5 def test_corrections_beat_snapshot_and_snapshot_fills_unset( monkeypatch: pytest.MonkeyPatch, ) -> None: _install_corrections( monkeypatch, """ [openai."gpt-4"] context_window = 7777 supports_vision = true """, ) entry = ModelCatalog().resolve_entry("gpt-4", provider="openai") assert entry.source == "corrections" assert entry.context_window == 7_777 # corrections beat snapshot's 8192 assert entry.supports_vision is True # corrections beat snapshot's False assert entry.max_output_tokens == 8_192 # snapshot fills unset fields assert entry.supports_tools is True assert entry.supports_reasoning is False def test_snapshot_beats_synthesized_but_floor_fills_gaps( monkeypatch: pytest.MonkeyPatch, ) -> None: # A correction that carries only metadata still gets the synthesized # budget floor for the numeric fields nothing knows. _install_corrections( monkeypatch, """ [zhipu."glm-9-experimental"] family = "glm" """, ) entry = ModelCatalog().resolve_entry("glm-9-experimental", provider="zhipu") assert entry.source == "corrections" assert entry.family == "glm" assert entry.context_window == 32_768 assert entry.max_output_tokens == 8_192 # --------------------------------------------------------------------------- # Glob corrections matching # --------------------------------------------------------------------------- def test_glob_corrections_match_lowercased_model_and_exact_key_wins( monkeypatch: pytest.MonkeyPatch, ) -> None: _install_corrections( monkeypatch, """ [zhipu."glm-9-pro"] display_name = "Exact GLM-9 Pro" context_window = 123456 [zhipu."glm-9*"] display_name = "Glob GLM-9" max_output_tokens = 4242 supports_reasoning = true """, ) catalog = ModelCatalog() # Uppercase spelling still matches: model ids are lowercased for lookup. exact = catalog.resolve_entry("GLM-9-Pro", provider="zhipu") assert exact.display_name == "Exact GLM-9 Pro" # exact key beats the glob assert exact.context_window == 123_456 assert exact.max_output_tokens == 4_242 # glob fills what exact left unset assert exact.supports_reasoning is True globbed = catalog.resolve_entry("glm-9-mini", provider="zhipu") assert globbed.source == "corrections" assert globbed.display_name == "Glob GLM-9" assert globbed.context_window == 32_768 # synthesized floor fills the rest # Corrections are provider-scoped: same model id, other provider → none. other = catalog.resolve_entry("glm-9-mini", provider="openai") assert other.source == "synthesized" assert other.display_name == "" def test_corrections_drop_unknown_and_mistyped_fields() -> None: tables = model_catalog_module._normalize_corrections( { "vendor": { "model-z": { "context_window": 5_000, "supports_reasoning": "true", # string, not bool → dropped "bogus_field": 1, # unknown → dropped } } } ) assert tables == {"vendor": {"model-z": {"context_window": 5_000}}} def test_packaged_corrections_file_parses_with_expected_tables() -> None: text = ( resources.files("opensquilla.provider") .joinpath("catalog_overrides.toml") .read_text(encoding="utf-8") ) payload = tomllib.loads(text) # Window/pricing corrections (retired static tables) + the transcribed # capability ladder (see the "capability ladder migration" section). assert set(payload) == { "moonshot", "anthropic", "openrouter", "dashscope", "volcengine", "volcengine_coding_plan", "byteplus", "deepseek", "gemini", "zhipu", "qianfan", "tencent_tokenhub", "tencent_token_plan", "tokenrhythm", "kimi_coding_openai", "kimi_coding_anthropic", "minimax", "minimax_cn", "minimax_global", "minimax_openai", "minimax_coding_openai", "minimax_coding_anthropic", "mimo_openai", "mimo_anthropic", } assert set(payload["moonshot"]) == { "moonshot-v1-8k", "moonshot-v1-32k", "moonshot-v1-128k", "kimi-k2.5*", "kimi-k2.6*", "kimi-k2.7*", "kimi-k2-thinking*", "*", } assert set(payload["anthropic"]) == { "claude-opus-4-6", "claude-sonnet-4-6", "claude-haiku-4-5-20251001", } assert set(payload["openrouter"]) == { "anthropic/claude-opus-4.8", "anthropic/claude-sonnet-4.6", "x-ai/grok-4.3", "stepfun/step-3.5-flash", } # Every packaged row survives normalization — no unknown field names, # no mistyped values (a dropped field would silently weaken a layer). tables = model_catalog_module._normalize_corrections(payload) assert {p: set(t) for p, t in tables.items()} == {p: set(t) for p, t in payload.items()} assert all(fields for table in tables.values() for fields in table.values()) def test_ladder_glob_rows_keep_specific_before_general_file_order() -> None: """FILE ORDER of the transcribed ladder rows is load-bearing. The corrections loader matches globs in file order, each filling only fields still unset — so a more specific prefix must appear before the general one it overlaps (qwen3.5* before qwen3*, doubao-seed-1-6* before *thinking*) and the "*" catch-all must be the LAST key of every ladder provider table. Reordering rows would silently change matching. """ payload = tomllib.loads( resources.files("opensquilla.provider") .joinpath("catalog_overrides.toml") .read_text(encoding="utf-8") ) for provider in ( "dashscope", "moonshot", "volcengine", "byteplus", "gemini", "zhipu", "tencent_tokenhub", "tencent_token_plan", "tokenrhythm", ): keys = list(payload[provider]) assert keys[-1] == "*", provider assert keys.count("*") == 1, provider dashscope = list(payload["dashscope"]) assert dashscope.index("qwen3.5*") < dashscope.index("qwen3*") assert dashscope.index("qwen3.6*") < dashscope.index("qwen3*") volcengine = list(payload["volcengine"]) assert volcengine.index("doubao-seed-1-6*") < volcengine.index("*thinking*") byteplus = list(payload["byteplus"]) assert byteplus.index("kimi-k2-*") < byteplus.index("*thinking*") # deepseek is a single catch-all (reasoning_shape transcription). assert list(payload["deepseek"]) == ["*"] # --------------------------------------------------------------------------- # Packaged corrections data rows (windows/pricing from in-repo static tables) # --------------------------------------------------------------------------- def test_moonshot_v1_windows_resolve_from_packaged_corrections() -> None: # moonshot-v1-* is absent from the snapshot's moonshot table, so without # these rows resolve_entry would fall to the synthesized floor (32k/8k). # The corrections rows are the canonical source of these windows (the # in-repo static fallback table they were copied from has been retired); # the literals below pin the retired table's exact values. catalog = ModelCatalog() expected_windows = { "moonshot-v1-8k": (8_192, 8_192), "moonshot-v1-32k": (32_768, 32_768), "moonshot-v1-128k": (131_072, 131_072), } for model, (max_output, context_window) in expected_windows.items(): entry = catalog.resolve_entry(model, provider="moonshot") assert entry.source == "corrections", model assert entry.context_window == context_window, model assert entry.max_output_tokens == max_output, model # The 8k SKU is 8192/8192, not the synthesized 32k/8k. eight_k = catalog.resolve_entry("moonshot-v1-8k", provider="moonshot") assert (eight_k.max_output_tokens, eight_k.context_window) == (8_192, 8_192) def test_anthropic_listing_models_priced_via_packaged_corrections() -> None: # The corrections rows are the canonical metadata for the SKUs the # Anthropic adapter lists (the adapter's _KNOWN_MODELS table has been # retired; list_models resolves these rows through the shared catalog). # Values are verified against Anthropic's published per-model pricing # page (input/output per Mtok). The opus-4-6 and sonnet-4-6 rows carry # no cache pricing, so their cache_read/write fall through to the # snapshot's vendored models.dev cache rates for the same SKU; the # haiku-4-5 row now carries its own cache rates directly. listing_rows = ( ("claude-opus-4-6", "Claude Opus 4.6", 200_000, 32_000, 5.0, 25.0, 0.5, 6.25), ("claude-sonnet-4-6", "Claude Sonnet 4.6", 200_000, 16_000, 3.0, 15.0, 0.3, 3.75), ("claude-haiku-4-5-20251001", "Claude Haiku 4.5", 200_000, 8_192, 1.0, 5.0, 0.1, 1.25), ) catalog = ModelCatalog() for ( model_id, display_name, context_window, max_output, in_mtok, out_mtok, cr_mtok, cw_mtok, ) in listing_rows: entry = catalog.resolve_entry(model_id, provider="anthropic") assert entry.source == "corrections", model_id assert entry.display_name == display_name, model_id # Windows come from the same rows; corrections beat the snapshot. assert entry.context_window == context_window, model_id assert entry.max_output_tokens == max_output, model_id assert entry.input_cost_per_mtok == pytest.approx(in_mtok) assert entry.output_cost_per_mtok == pytest.approx(out_mtok) # The retired table carried no cache pricing — corrections stays # silent on it, so the snapshot's vendored rate fills the gap. assert entry.cache_read_cost_per_mtok == pytest.approx(cr_mtok) assert entry.cache_write_cost_per_mtok == pytest.approx(cw_mtok) # --------------------------------------------------------------------------- # Live-layer cost adaptation (per-1k cache → per-Mtok canonical) # --------------------------------------------------------------------------- def test_live_costs_adapt_per_1k_to_per_mtok() -> None: entry = _populated_catalog().resolve_entry("vendor/model-x", provider="openrouter") # OpenRouter per-token "0.0000025" → cached 0.0025/1k → canonical 2.5/Mtok. assert entry.input_cost_per_mtok == pytest.approx(2.5) assert entry.output_cost_per_mtok == pytest.approx(10.0) def test_live_zero_price_stays_unknown_not_free() -> None: catalog = ModelCatalog() catalog._populate_from_data([{"id": "vendor/free-model", "context_length": 8_192}]) entry = catalog.resolve_entry("vendor/free-model", provider="openrouter") # The live cache's 0.0 means "free or unknown" — never claim a known $0. assert entry.input_cost_per_mtok is None assert entry.output_cost_per_mtok is None # --------------------------------------------------------------------------- # User overrides — keying and validation # --------------------------------------------------------------------------- def test_user_override_qualified_key_beats_bare_key_per_field() -> None: catalog = ModelCatalog() catalog.set_user_overrides( { "deepseek/some-model": {"context_window": 1_000}, "Some-Model": {"context_window": 2_000, "max_output_tokens": 500}, } ) qualified = catalog.resolve_entry("some-model", provider="deepseek") assert qualified.context_window == 1_000 # qualified key wins the field assert qualified.max_output_tokens == 500 # bare key fills what it left unset bare = catalog.resolve_entry("some-model") assert bare.context_window == 2_000 def test_set_user_overrides_rejects_unknown_fields_and_bad_types() -> None: catalog = ModelCatalog() catalog.set_user_overrides({"kept-model": {"context_window": 4_096}}) with pytest.raises(ValueError, match="bogus_field"): catalog.set_user_overrides({"m": {"bogus_field": 1}}) with pytest.raises(ValueError, match="context_window"): catalog.set_user_overrides({"m": {"context_window": "big"}}) with pytest.raises(ValueError, match="supports_tools"): catalog.set_user_overrides({"m": {"supports_tools": "false"}}) # Identity / derived fields are not overridable. with pytest.raises(ValueError, match="source"): catalog.set_user_overrides({"m": {"source": "user"}}) # A rejected replacement leaves the previous overrides installed. assert catalog.resolve_entry("kept-model").context_window == 4_096 def test_explicit_false_and_zero_overrides_win() -> None: catalog = _populated_catalog() catalog.set_user_overrides( {"vendor/model-x": {"supports_reasoning": False, "max_output_tokens": 0}} ) entry = catalog.resolve_entry("vendor/model-x", provider="openrouter") # Presence in a layer = knowledge: False/0 beat the live layer's values. assert entry.supports_reasoning is False assert entry.max_output_tokens == 0 def test_context_window_override_governs_legacy_resolver_with_source() -> None: # The [models.*] user-override layer is the top layer of the legacy # resolve_context_window chain too, attributed as "override"; models # without an override keep resolving through live/snapshot/corrections # exactly as the parity net below pins. catalog = _populated_catalog() catalog.set_user_overrides({"openrouter/vendor/model-x": {"context_window": 111}}) assert catalog.resolve_context_window_with_source( "vendor/model-x", "openrouter" ) == (111, "override") assert catalog.resolve_context_window("vendor/model-x", "openrouter") == 111 assert catalog.user_context_window_override("vendor/model-x", "openrouter") == 111 # Other models are untouched by the installed override. assert catalog.resolve_context_window_with_source("gpt-5.5", "openai") == ( 1_050_000, "catalog", ) assert catalog.user_context_window_override("gpt-5.5", "openai") is None def test_context_window_override_zero_is_not_a_budgeting_window() -> None: # A 0 override wins per-field in resolve_entry (presence = knowledge) but # cannot serve as a budgeting window, so the legacy chain resolves past it. catalog = _populated_catalog() catalog.set_user_overrides({"openrouter/vendor/model-x": {"context_window": 0}}) assert catalog.user_context_window_override("vendor/model-x", "openrouter") is None assert catalog.resolve_context_window_with_source("vendor/model-x", "openrouter") == ( 200_000, "catalog", ) def test_resolve_effective_context_window_layers() -> None: # Shared implementation of "[models.*] override > global config window > # catalog > default" used by the engine budgeting and RPC paths. catalog = _populated_catalog() catalog.set_user_overrides({"openrouter/vendor/model-x": {"context_window": 111}}) assert resolve_effective_context_window( catalog, "vendor/model-x", provider="openrouter", global_override=999_000 ) == (111, "override") assert resolve_effective_context_window( catalog, "gpt-5.5", provider="openai", global_override=999_000 ) == (999_000, "config") assert resolve_effective_context_window(catalog, "gpt-5.5", provider="openai") == ( 1_050_000, "catalog", ) # Junk/non-positive global values count as unset. assert resolve_effective_context_window( catalog, "gpt-5.5", provider="openai", global_override=-5 ) == (1_050_000, "catalog") def test_resolve_effective_context_window_duck_types_plain_catalogs() -> None: class _PlainCatalog: def resolve_context_window(self, model_id: str, provider: str = "") -> int: return 42_000 # Without *_with_source the value is attributed "catalog" — never # "override" — so the global config value still applies over it. assert resolve_effective_context_window(_PlainCatalog(), "m") == (42_000, "catalog") assert resolve_effective_context_window(_PlainCatalog(), "m", global_override=50_000) == ( 50_000, "config", ) # --------------------------------------------------------------------------- # Snapshot-layer cost emission (compact in/out/cr/cw_mtok keys → per-Mtok # fields). The committed snapshot carries no cost keys yet, so these drive # the layer through a synthetic entry. # --------------------------------------------------------------------------- def test_snapshot_cost_keys_emit_per_mtok_fields(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr( model_catalog_module, "_models_dev_model", lambda provider_id, model_id: { "ctx": 200_000, "out": 32_000, "reasoning": True, "tools": True, "vision": False, "in_mtok": 2.5, "out_mtok": 10, # int leaf — coerced to float "cr_mtok": 0.25, "cw_mtok": 3.125, }, ) entry = ModelCatalog().resolve_entry("priced-model", provider="acme") assert entry.source == "snapshot" assert entry.input_cost_per_mtok == pytest.approx(2.5) assert entry.output_cost_per_mtok == pytest.approx(10.0) assert isinstance(entry.output_cost_per_mtok, float) assert entry.cache_read_cost_per_mtok == pytest.approx(0.25) assert entry.cache_write_cost_per_mtok == pytest.approx(3.125) def test_snapshot_partial_cost_keys_leave_missing_fields_unknown( monkeypatch: pytest.MonkeyPatch, ) -> None: monkeypatch.setattr( model_catalog_module, "_models_dev_model", lambda provider_id, model_id: {"ctx": 8_192, "out": 4_096, "in_mtok": 0.5}, ) entry = ModelCatalog().resolve_entry("partial-model", provider="acme") assert entry.input_cost_per_mtok == pytest.approx(0.5) assert entry.output_cost_per_mtok is None assert entry.cache_read_cost_per_mtok is None assert entry.cache_write_cost_per_mtok is None # --------------------------------------------------------------------------- # PARITY NET — the legacy resolve paths must keep returning today's exact # values (captured as literals from the pre-change tree). get_capabilities # now resolves through resolve_entry + the transcribed ladder rows, and # resolve_max_tokens / resolve_context_window remain independent; any drift # here is an accidental behavior change. The exhaustive provider x model # sweep lives in test_capability_ladder_parity.py. # --------------------------------------------------------------------------- # (model, provider) → (resolve_max_tokens, resolve_context_window) _LEGACY_LIMITS: dict[tuple[str, str], tuple[int, int]] = { ("gpt-5.5", "openai"): (128_000, 1_050_000), ("gpt-5.4-mini", "openai"): (128_000, 400_000), ("deepseek-v4-pro", "deepseek"): (384_000, 1_000_000), ("kimi-k2.5", "moonshot"): (8_192, 262_144), ("glm-5", "zhipu"): (131_072, 204_800), ("glm-5", "zai"): (16_384, 202_752), # models.dev's 2026-07-08 refresh lowered openrouter z-ai/glm-5.2 max # output from 131_072 to 32_768 (the cross-provider min); the window is # unchanged. ("z-ai/glm-5.2", ""): (32_768, 1_000_000), ("gemini-3.5-flash", "gemini"): (65_536, 1_048_576), ("minimax-m2.7", ""): (131_072, 204_800), ("step-3.5-flash", ""): (16_384, 256_000), ("moonshot-v1-32k", "moonshot"): (8_192, 32_768), ("grok-4.3", ""): (16_384, 1_000_000), ("totally-unknown-model", ""): (16_384, 200_000), ("mystery-local", "ollama"): (8_192, 8_192), ("some-cloud-model", "openai"): (16_384, 200_000), } # (model, provider, base_url) → # (supports_reasoning, supports_tools, supports_vision, reasoning_format) _LEGACY_CAPS: dict[tuple[str, str, str], tuple[bool, bool, bool, str]] = { ("claude-opus-4.8", "anthropic", ""): (False, True, False, "none"), ("llama3.2:3b", "ollama", ""): (False, True, False, "none"), ("gpt-5.5", "openai", "https://api.openai.com/v1"): (True, True, False, "openai"), ("deepseek-v4-pro", "deepseek", ""): (True, True, False, "deepseek"), ("glm-5", "zhipu", ""): (True, True, False, "zai"), ("glm-4.6", "zhipu", ""): (False, True, False, "none"), ("qwen3-coder-plus", "dashscope", ""): (True, True, False, "dashscope"), ("kimi-k2.5", "moonshot", ""): (True, True, True, "moonshot"), ("gemini-3.5-flash", "gemini", ""): (True, True, True, "gemini"), ("doubao-seed-1-6-251015", "volcengine", ""): (True, True, True, "volcengine"), ("totally-unknown-model", "openrouter", ""): (False, True, False, "none"), ("gpt-4", "openai", ""): (False, True, False, "none"), } def test_parity_legacy_limits_unchanged() -> None: catalog = ModelCatalog() for (model, provider), (max_tokens, context_window) in _LEGACY_LIMITS.items(): assert catalog.resolve_max_tokens(model, provider=provider) == max_tokens, ( model, provider, ) assert catalog.resolve_context_window(model, provider) == context_window, ( model, provider, ) def test_parity_legacy_capabilities_unchanged() -> None: catalog = ModelCatalog() for (model, provider, base_url), expected in _LEGACY_CAPS.items(): caps = catalog.get_capabilities(model, provider_name=provider, base_url=base_url) observed = ( caps.supports_reasoning, caps.supports_tools, caps.supports_vision, caps.reasoning_format, ) assert observed == expected, (model, provider, base_url) def test_full_entry_literals_snapshot_and_transcribed_ladder() -> None: # Full-literal entry pins (frozen-dataclass equality covers every field, # including the four cost fields — the snapshot now vendors models.dev # cost keys, so these are no longer all None). openai has NO ladder # rows — the api.openai.com branch is host-guarded and stays code — so # its snapshot resolution is byte-identical to the pre-ladder tree: # supports_reasoning is snapshot data but reasoning_format stays "none" # (the snapshot never knows the dialect). deepseek/gemini entries now # layer the transcribed capability-ladder rows (reasoning_format, # ladder-scoped capability booleans) over snapshot windows, so their # source moved to "corrections" — deliberately, as part of the # capability-ladder migration; the corrections rows carry no cost # fields, so cost still comes from the snapshot layer underneath. catalog = ModelCatalog() expected_entries = ( ModelCatalogEntry( provider_id="openai", model_id="gpt-5.5", context_window=1_050_000, max_output_tokens=128_000, supports_reasoning=True, supports_tools=True, supports_vision=True, input_cost_per_mtok=5.0, output_cost_per_mtok=30.0, cache_read_cost_per_mtok=0.5, source="snapshot", ), ModelCatalogEntry( provider_id="deepseek", model_id="deepseek-chat", context_window=1_000_000, max_output_tokens=384_000, # The ladder's deepseek shape: every model of the provider # reasons through the deepseek dialect (snapshot said False). supports_reasoning=True, supports_tools=True, supports_vision=False, reasoning_format="deepseek", input_cost_per_mtok=0.14, output_cost_per_mtok=0.28, cache_read_cost_per_mtok=0.0028, source="corrections", ), ModelCatalogEntry( provider_id="gemini", model_id="gemini-2.5-pro", context_window=1_048_576, max_output_tokens=65_536, supports_reasoning=True, supports_tools=True, supports_vision=True, reasoning_format="gemini", input_cost_per_mtok=1.25, output_cost_per_mtok=10.0, cache_read_cost_per_mtok=0.125, source="corrections", ), ModelCatalogEntry( provider_id="zhipu", model_id="glm-4.6", context_window=204_800, max_output_tokens=131_072, # Ladder semantics win over the snapshot's reasoning=True: # the zai shape reasons only for glm-4.5/glm-4.7/glm-5. supports_reasoning=False, supports_tools=True, supports_vision=False, reasoning_format="none", input_cost_per_mtok=0.6, output_cost_per_mtok=2.2, cache_read_cost_per_mtok=0.11, cache_write_cost_per_mtok=0.0, source="corrections", ), ) for want in expected_entries: got = catalog.resolve_entry(want.model_id, provider=want.provider_id) assert got == want, (want.provider_id, want.model_id)