diff --git a/examples/cybergym/pyproject.toml b/examples/cybergym/pyproject.toml index 3f146451b..a3ff2827f 100644 --- a/examples/cybergym/pyproject.toml +++ b/examples/cybergym/pyproject.toml @@ -6,7 +6,7 @@ readme = "README.md" requires-python = ">=3.12,<3.14" dependencies = [ "nooa[tracing] @ git+https://github.com/NVIDIA-NeMo/labs-OO-Agents.git@8229922d7274628c9be83f745589b40852680d60", - "pydantic>=2.5.0", + "pydantic>=2.11.0", ] [project.optional-dependencies] diff --git a/packages/nooa-memory/pyproject.toml b/packages/nooa-memory/pyproject.toml index 04b182d10..2881da60c 100644 --- a/packages/nooa-memory/pyproject.toml +++ b/packages/nooa-memory/pyproject.toml @@ -8,7 +8,7 @@ readme = "README.md" requires-python = ">=3.12,<3.14" dependencies = [ "nooa", - "pydantic>=2.5.0", + "pydantic>=2.11.0", "numpy>=1.24.0", ] diff --git a/pyproject.toml b/pyproject.toml index 24cff7de8..3ad3fb5aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ classifiers = [ "Topic :: Scientific/Engineering :: Artificial Intelligence", ] dependencies = [ - "pydantic>=2.5.0", + "pydantic>=2.11.0", # context_blocks + unifiedllm now ship as nooa.context_blocks / # nooa.unifiedllm subpackages — pull their formerly-external deps in. # >=1.97.0: routes GPT-5.4 tool calls through Responses and replays reasoning_items; diff --git a/src/nooa/unifiedllm/__init__.py b/src/nooa/unifiedllm/__init__.py index 321742c0e..e457479e3 100644 --- a/src/nooa/unifiedllm/__init__.py +++ b/src/nooa/unifiedllm/__init__.py @@ -1,5 +1,22 @@ # SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 +from nooa.unifiedllm.contracts import ( + OPAQUE_REPLAY_KEY_VERSION, + ModelCompatGroup, + NormalizedModel, + ProviderIdentity, + ReasoningCapabilities, + ReasoningKind, + ReasoningRecord, + ReasoningReplayMode, + UnknownProviderIdentityError, + compat_group_for, + derive_opaque_replay_key, + get_reasoning_capabilities, + parse_model_string, + register_compat_group, + register_reasoning_capabilities, +) from nooa.unifiedllm.fake import FakeLLMClient from nooa.unifiedllm.http_config import HttpConfig from nooa.unifiedllm.registry import ( @@ -30,6 +47,22 @@ ) __all__ = [ + # Provider contracts (identity and capability types) + "ModelCompatGroup", + "NormalizedModel", + "OPAQUE_REPLAY_KEY_VERSION", + "ProviderIdentity", + "ReasoningCapabilities", + "ReasoningKind", + "ReasoningRecord", + "ReasoningReplayMode", + "UnknownProviderIdentityError", + "compat_group_for", + "derive_opaque_replay_key", + "get_reasoning_capabilities", + "parse_model_string", + "register_compat_group", + "register_reasoning_capabilities", # Core classes "UnifiedLLM", "CompletionClient", diff --git a/src/nooa/unifiedllm/contracts.py b/src/nooa/unifiedllm/contracts.py new file mode 100644 index 000000000..5129a2a28 --- /dev/null +++ b/src/nooa/unifiedllm/contracts.py @@ -0,0 +1,631 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Transport-neutral provider identity and capability contracts. + +Models reach NOOA through routing strings (``openai/nvidia/zai-org/glm-5.3``, +``openai/azure/openai/gpt-5.6-sol``, ``kimi-k3:free``) that mix gateway +routes, vendor namespaces, tier suffixes, and provider aliases into one +identifier. Inferring semantic identity from those substrings is how a +routing prefix like ``openai/`` gets mistaken for the OpenAI provider. This +module resolves a routing string to a logical provider exactly once, at the +adapter edge, so no other code has to guess. + +It also types two things providers disagree about: + +- **reasoning artifacts** (encrypted items, plain text, checkpoints) as a + kind + payload + provenance record, so they can be stored and compared + without holding provider SDK objects; and +- **capabilities** (what a provider can capture/replay, which effort levels + it supports), as explicit declarations where ``None`` means "unsupported" + and unknown providers are never promoted to "supported". + +Fail-closed rules encoded here: + +- Gateway/routing prefixes never determine the logical provider; a model + string that cannot be resolved raises UnknownProviderIdentityError + instead of guessing. +- Opaque replay compatibility requires a *declared* model compat group; + undeclared models derive no key rather than a speculative one. +- Unknown capability lookups return None. + +The module is intentionally additive: nothing here is consumed by existing +clients yet, and importing it does not import LiteLLM or any provider SDK. +""" + +from __future__ import annotations + +import hashlib +import json +from collections.abc import Mapping +from enum import StrEnum +from typing import Literal + +from pydantic import BaseModel + +__all__ = [ + "ModelCompatGroup", + "NormalizedModel", + "OPAQUE_REPLAY_KEY_VERSION", + "ProviderIdentity", + "ReasoningCapabilities", + "ReasoningKind", + "ReasoningRecord", + "ReasoningReplayMode", + "RedactionClass", + "UnknownProviderIdentityError", + "compat_group_for", + "derive_opaque_replay_key", + "get_reasoning_capabilities", + "parse_model_string", + "register_compat_group", + "register_reasoning_capabilities", +] + +#: Recursive JSON value. Provider objects are converted to this shape at +#: the adapter edge so no SDK object ever persists or crosses the boundary. +#: +#: Defined here because neither Python nor Pydantic ships one: ``json`` is a +#: (de)serializer, not a type; ``typing.Any`` validates nothing; and the +#: old-style recursive alias (``JsonValue = list["JsonValue"]``) recurses +#: infinitely under Pydantic 2.x schema generation. The PEP 695 ``type`` +#: statement is the one recursive form Pydantic supports as a field +#: annotation, which is why this module requires pydantic>=2.11. +type JsonValue = None | bool | int | float | str | list["JsonValue"] | dict[str, "JsonValue"] + +#: Version tag mixed into every opaque replay key: when the normalization +#: rules change the key must change with them, so rollout is a new version. +OPAQUE_REPLAY_KEY_VERSION = "nooa.opaque-replay-key.v1" + + +class ReasoningKind(StrEnum): + """Kind of retained reasoning artifact.""" + + OPAQUE = "opaque" # encrypted/signed/redacted provider state + TEXT = "text" # provider-exposed plain reasoning + CHECKPOINT = "checkpoint" # compaction/continuation checkpoint + + +class ReasoningReplayMode(StrEnum): + """Replay policy modes; AUTO is the default.""" + + OFF = "off" + AUTO = "auto" + NATIVE_ONLY = "native_only" + TEXT_CONTEXT = "text_context" # force plain text to labeled context + + +#: Redaction class is attached independently of the payload kind. +RedactionClass = Literal["opaque", "plain_reasoning"] + + +class ProviderIdentity(BaseModel): + """Logical provider identity, independent of transport routing strings. + + Owned by NOOA rather than borrowed from a transport library (litellm or a + successor) because the two answer different questions. litellm parses a + routing string to pick *where to send the request*; that parsing is + transport-specific, mutable across library versions, and confuses gateway + routes with providers — an ``openai/`` prefix in a routed id is not + evidence the model is served by OpenAI. This identity answers *who + produced a stored artifact and who may receive it back*: it is stamped + once at the adapter edge, persisted alongside captured reasoning, and + compared long after the original request. Making that durable, + provider-independent, and testable in isolation is why it lives here. + + endpoint_id and account_scope are non-secret fingerprints (never raw + credentials). transport is *not* part of replay compatibility, so it is + excluded from derive_opaque_replay_key. + """ + + provider: str # openai, anthropic, moonshot, zai, ... + api_style: str # responses, chat-completions, messages, ... + model: str + endpoint_id: str | None = None # stable non-secret endpoint fingerprint + account_scope: str | None = None # non-secret hash if replay-bound + transport: str # litellm, anyllm, direct, fake + opaque_replay_key: str | None = None # adapter-declared compat boundary + + @classmethod + def from_model_string( + cls, + model_string: str, + *, + api_style: str, + transport: str, + endpoint_id: str | None = None, + account_scope: str | None = None, + compat_groups: Mapping[str, ModelCompatGroup] | None = None, + ) -> ProviderIdentity: + """Build an identity from a (legacy) LiteLLM-style model string. + + This is the adapter-edge parser: routing and + namespace prefixes, provider aliases, and tier suffixes are stripped + here, once, so no runtime or formatter ever infers semantic identity + from model substrings. Unresolvable strings fail closed with + UnknownProviderIdentityError; adapters with private model knowledge + may construct the model directly instead. + """ + parsed = parse_model_string(model_string) + if parsed.provider is None: + raise UnknownProviderIdentityError(model_string) + return cls( + provider=parsed.provider, + api_style=api_style, + model=parsed.model, + endpoint_id=endpoint_id, + account_scope=account_scope, + transport=transport, + opaque_replay_key=derive_opaque_replay_key( + provider=parsed.provider, + api_style=api_style, + model=parsed.model, + endpoint_id=endpoint_id, + account_scope=account_scope, + compat_groups=compat_groups, + ), + ) + + +class ReasoningRecord(BaseModel): + """One retained reasoning artifact with provenance. + + payload is NOOA-owned JSON only; provider SDK objects are converted at + the adapter edge and never persist. + """ + + version: int = 1 + kind: ReasoningKind + payload: JsonValue + provenance: ProviderIdentity + provider_item_type: str | None = None + sequence: int # exact order within provider output + provider_token_count: int | None = None + replayable: bool = True + redaction_class: RedactionClass + + +class ReasoningCapabilities(BaseModel): + """Reasoning capability profile for a provider. + + All fields are explicit declarations — unknowns are never promoted to + supported. effort_map maps neutral effort levels to the provider wire + value, with None meaning "unsupported". + """ + + capture_kinds: frozenset[ReasoningKind] + native_replay_kinds: frozenset[ReasoningKind] + effort_map: dict[str, str | None] # null means unsupported + supports_reasoning_when_disabled: bool | None = None + requires_tool_turn_reasoning_replay: bool | None = None + replay_field: str | None = None + signature_prefix_sensitive: bool | None = None + terminal_backfill: bool = False + + +class ModelCompatGroup(BaseModel): + """Explicitly declared model compatibility group. + + Group membership is hand-registered only after live verification; model-id + equality stays out of the replay hot path. A group is scoped to one + logical provider, so the same model string can never silently grant + compatibility across providers. + """ + + name: str + provider: str + models: frozenset[str] + + +class NormalizedModel(BaseModel): + """Result of normalizing a routing-style model string.""" + + provider: str | None # None = unknown, callers must fail closed + model: str # normalized model id (prefixes/tier stripped) + namespace: str | None = None # org-style prefix, e.g. "zai-org" + tier: str | None = None # stripped tier suffix, e.g. "free", "cloud" + + +class UnknownProviderIdentityError(ValueError): + """A model string could not be resolved to a logical provider. + + This is the fail-closed path: never guess a provider + from a gateway prefix. Adapters with private knowledge can construct + ProviderIdentity directly with an explicit provider. + """ + + +# --- Identity normalization ------------------------------------------------- + +#: Gateway/routing prefixes stripped from the left of a model string. These +#: are routing hints, never logical providers. +_ROUTING_PREFIXES: frozenset[str] = frozenset( + {"openai", "nvidia", "azure", "aws", "bedrock", "vertex_ai", "nvidia_nim", "huggingface"} +) + +#: Free-tier / capacity / routing tier suffixes stripped before any comparison +#: or hash (e.g. "kimi-k3:free", "deepseek-v4-flash:cloud", "o3:batch"). +_TIER_SUFFIXES: frozenset[str] = frozenset({"free", "cloud", "batch"}) + +#: Provider aliases: a leading segment that names a vendor but is not the +#: canonical logical provider. Canonical names map to themselves. +_PROVIDER_ALIASES: dict[str, str] = { + "claude": "anthropic", + "anthropic": "anthropic", + "z-ai": "glm", + "zai": "glm", + "zai-org": "glm", + "moonshot": "kimi", + "moonshotai": "kimi", + "meta-llama": "meta", + "meta": "meta", + "mistralai": "mistral", + "mistral": "mistral", + "x-ai": "xai", + "deepseek-ai": "deepseek", + "google": "google", + "minimaxai": "minimax", + "minimax": "minimax", + "microsoft": "microsoft", + "cohere": "cohere", + "amazon": "amazon", + "ai21": "ai21", + "qwen": "qwen", +} + +#: Model-family prefixes -> logical provider. Matched on the final path +#: segment, at token boundaries only (so "glm" never matches "glmx"). +#: Prefixes are checked in order; the first boundary-respecting match wins, +#: so "llama" is checked after "nemotron" to keep NVIDIA's llama-based +#: nemotron models attributed to nvidia. +_MODEL_FAMILIES: tuple[tuple[str, str], ...] = ( + ("gpt-oss", "openai"), + ("gpt-audio", "openai"), + ("gpt-chat", "openai"), + ("gpt-image", "openai"), + ("gpt-realtime", "openai"), + ("gpt-", "openai"), + ("o1", "openai"), + ("o3", "openai"), + ("o4", "openai"), + ("chatgpt-", "openai"), + ("glm", "glm"), + ("kimi", "kimi"), + ("deepseek", "deepseek"), + ("qwen", "qwen"), + ("claude", "anthropic"), + ("llama", "meta"), + ("muse-spark", "meta"), + ("muse-glimmer", "meta"), + ("mistral", "mistral"), + ("ministral", "mistral"), + ("mixtral", "mistral"), + ("codestral", "mistral"), + ("devstral", "mistral"), + ("voxtral", "mistral"), + ("gemini", "google"), + ("gemma", "google"), + ("lyria", "google"), + ("grok", "xai"), + ("phi-", "microsoft"), + ("phi4", "microsoft"), + ("command", "cohere"), + ("jamba", "ai21"), + ("codex", "openai"), + ("dall-e", "openai"), + ("sora", "openai"), + ("text-embedding", "openai"), + ("tts-", "openai"), + ("whisper", "openai"), + ("imagen", "google"), + ("chirp", "google"), + ("imagegeneration", "google"), + ("veo", "google"), +) +_FAMILY_BOUNDARY = "-_.0123456789" + +#: Distinctive mid-id markers -> logical provider, checked before prefix +#: matching. Vendors derive model lines from other families' bases +#: ("llama-3.1-nemotron-ultra-..." is NVIDIA's post-trained Llama), so no +#: prefix rule can attribute them; the marker is unambiguous in practice. +_FAMILY_MARKERS: tuple[tuple[str, str], ...] = ( + ("nemotron", "nvidia"), + ("nemoguard", "nvidia"), + ("nemoretriever", "nvidia"), + ("nemosmith", "nvidia"), + ("nv-embedqa", "nvidia"), + ("nv-rerankqa", "nvidia"), + ("nv-embed", "nvidia"), + ("nv-rerank", "nvidia"), +) + + +def _resolve_family(segment: str) -> str | None: + """Return the logical provider for a final model-id segment, if known.""" + lowered = segment.lower() + for marker, provider in _FAMILY_MARKERS: + if marker in lowered: + return provider + # Bedrock-style ids embed the vendor: "anthropic.claude-3-5-sonnet". + if "." in lowered: + head = lowered.split(".", 1)[0] + canonical = _PROVIDER_ALIASES.get(head) + if canonical is not None: + return canonical + for prefix, provider in _MODEL_FAMILIES: + if lowered.startswith(prefix) and ( + len(lowered) == len(prefix) or lowered[len(prefix)] in _FAMILY_BOUNDARY + ): + return provider + return None + + +def parse_model_string(model: str) -> NormalizedModel: + """Normalize a routing-style model string into logical identity parts. + + Strips gateway/routing prefixes (openai/, nvidia/, azure/, ...), org-style + namespace prefixes (zai-org/, moonshotai/, meta-llama/), and tier suffixes + (:free, :cloud), then resolves the logical provider from the model family + or a provider alias. provider is None when the identity is unknown; + callers must fail closed on that. + """ + raw = model.strip() + # Variant markers like OpenRouter's "~anthropic/claude-..." prefix. + raw = raw.lstrip("~") + raw = raw.strip() + if not raw: + return NormalizedModel(provider=None, model="", namespace=None, tier=None) + + segments = [s for s in raw.split("/") if s] + if not segments: + return NormalizedModel(provider=None, model=raw, namespace=None, tier=None) + + # Strip routing prefixes from the left; a lone segment is never stripped. + i = 0 + while i < len(segments) - 1 and segments[i].lower() in _ROUTING_PREFIXES: + i += 1 + segments = segments[i:] + + # Strip a known tier suffix from the final segment. + tier: str | None = None + last = segments[-1] + if ":" in last: + stem, _, suffix = last.rpartition(":") + if suffix.lower() in _TIER_SUFFIXES: + tier = suffix.lower() + last = stem + segments[-1] = last + # Vertex-style version suffixes: "codestral@latest", "claude-3-5-sonnet@20240620". + if "@" in last: + last = last.split("@", 1)[0] + segments[-1] = last + + namespace = "/".join(segments[:-1]) or None + + provider = _resolve_family(last) + if provider is not None: + # Bedrock-style dotted ids ("anthropic.claude-sonnet-4-5") embed the + # vendor: the provider resolves from the head, and the model is the + # remainder, so both spellings of one identity normalize identically + # and never diverge in key derivation. + if "." in last: + head, _, rest = last.partition(".") + # Strip the vendor head only when it is a declared vendor name + # ("moonshotai.kimi-k2-thinking", "qwen.qwen3-coder-next"), not a + # model family — "jamba-1.5-mini" must not lose its "jamba-1" head. + if rest and _PROVIDER_ALIASES.get(head.lower()) is not None: + last = rest + return NormalizedModel(provider=provider, model=last, namespace=namespace, tier=tier) + + # Unknown model family: a leading vendor segment may still name the + # logical provider (e.g. "claude/foo"). Otherwise fail closed (None). + if len(segments) > 1: + alias = _PROVIDER_ALIASES.get(segments[0].lower()) + if alias is not None: + return NormalizedModel( + provider=alias, model="/".join(segments[1:]), namespace=None, tier=tier + ) + return NormalizedModel(provider=None, model=last, namespace=namespace, tier=tier) + + +# --- Model compatibility groups --------------------------------------------- + +#: Conservative default declarations for families known to share opaque replay +#: compatibility. Adapters register further groups explicitly, only after +#: live verification. +_DEFAULT_COMPAT_GROUPS: tuple[ModelCompatGroup, ...] = ( + ModelCompatGroup( + name="openai-gpt-5", + provider="openai", + models=frozenset({"gpt-5.5", "gpt-5.6", "gpt-5.6-sol", "gpt-5-mini"}), + ), + ModelCompatGroup( + name="anthropic-claude-4-5", + provider="anthropic", + models=frozenset( + {"claude-sonnet-4-5", "claude-sonnet-4-5-v1", "claude-haiku-4-5-v1", "claude-opus-4-5"} + ), + ), +) + +_COMPAT_GROUPS: dict[str, ModelCompatGroup] = { + group.name: group for group in _DEFAULT_COMPAT_GROUPS +} + + +def register_compat_group(group: ModelCompatGroup) -> None: + """Register (or explicitly override) a model compatibility group. + + Explicit registration wins over generated/default data. + The group is stored case-normalized (model ids lowercased) so mixed-case + registrations are reachable by the case-insensitive lookup below. + """ + normalized = group.model_copy(update={"models": frozenset(m.lower() for m in group.models)}) + _COMPAT_GROUPS[group.name] = normalized + + +def compat_group_for( + provider: str, + model: str, + *, + groups: Mapping[str, ModelCompatGroup] | None = None, +) -> ModelCompatGroup | None: + """Return the declared compat group for a provider + model, or None. + + Membership must be declared; a similar-but-undeclared model id fails + closed. Groups are scoped per logical provider. + """ + registry = _COMPAT_GROUPS if groups is None else groups + provider_key = provider.lower() + model_key = model.lower() + # Members are lowercased at comparison time so injected mappings match + # regardless of how they were constructed (register_compat_group also + # normalizes on write; this covers callers passing their own mapping). + matches = sorted( + ( + group + for group in registry.values() + if group.provider.lower() == provider_key + and model_key in {m.lower() for m in group.models} + ), + key=lambda group: group.name, + ) + return matches[0] if matches else None + + +# --- Opaque replay key derivation ------------------------------------------- + + +def derive_opaque_replay_key( + *, + provider: str, + api_style: str, + model: str, + endpoint_id: str | None = None, + account_scope: str | None = None, + compat_groups: Mapping[str, ModelCompatGroup] | None = None, +) -> str | None: + """Derive the non-secret opaque replay compatibility digest. + + The key covers issuer/provider, API style, endpoint/account scope, and + the *declared* model compat group. The compat group is the model + dimension of the key — raw model ids are deliberately NOT mixed in, so + two models in the same hand-verified group are replay-compatible and + model-id churn never breaks replay. Identity inputs are + normalized inside this function (tier suffixes, aliases, prefixes), so + normalization is versioned with the key itself. + + Fail-closed: returns None (never a speculative key) when the provider + is empty or the model has no declared compat group. transport is + deliberately excluded — replay compatibility is transport-neutral. + """ + if not provider or not model: + return None + parsed = parse_model_string(model) + if not parsed.model: + return None + # Canonicalize the caller-supplied provider through the alias map so a + # direct caller passing "zai" or "claude" derives the same key scope as the + # canonical "glm" / "anthropic" (the docstring promises alias stripping + # happens here). from_model_string already supplies canonical providers; + # this guards direct callers. + provider_key = _PROVIDER_ALIASES.get(provider.lower(), provider.lower()) + group = compat_group_for(provider_key, parsed.model, groups=compat_groups) + if group is None: + return None + payload = { + "key_version": OPAQUE_REPLAY_KEY_VERSION, + "provider": provider_key, + "api_style": api_style.lower(), + "endpoint_id": endpoint_id, + "account_scope": account_scope, + "compat_group": group.name, + } + canonical = json.dumps(payload, sort_keys=True, separators=(",", ":")) + return "sha256:" + hashlib.sha256(canonical.encode("utf-8")).hexdigest() + + +# --- Reasoning capability registry ------------------------------------------- + +_NEUTRAL_EFFORT_LEVELS = ("minimal", "low", "medium", "high") + + +def _chat_reasoning_capabilities() -> ReasoningCapabilities: + """OpenAI-compatible reasoning-content providers (GLM/Kimi/DeepSeek/...). + + Their reasoning is plain text on reasoning_content; effort levels are + declared unsupported (None) until an adapter verifies a mapping. + """ + return ReasoningCapabilities( + capture_kinds=frozenset({ReasoningKind.TEXT}), + native_replay_kinds=frozenset({ReasoningKind.TEXT}), + effort_map=dict.fromkeys(_NEUTRAL_EFFORT_LEVELS), + supports_reasoning_when_disabled=True, + requires_tool_turn_reasoning_replay=False, + replay_field="reasoning_content", + signature_prefix_sensitive=False, + terminal_backfill=False, + ) + + +#: Declared default capability catalog. Unknown providers are absent by +#: design — lookups for them return None (fail closed, never promoted). +DEFAULT_REASONING_CAPABILITIES: dict[str, ReasoningCapabilities] = { + "openai": ReasoningCapabilities( + capture_kinds=frozenset( + {ReasoningKind.OPAQUE, ReasoningKind.TEXT, ReasoningKind.CHECKPOINT} + ), + native_replay_kinds=frozenset({ReasoningKind.OPAQUE, ReasoningKind.CHECKPOINT}), + effort_map={ + "minimal": "minimal", + "low": "low", + "medium": "medium", + "high": "high", + }, + supports_reasoning_when_disabled=True, + requires_tool_turn_reasoning_replay=True, + replay_field=None, # Responses reasoning items, not a message field + signature_prefix_sensitive=False, + terminal_backfill=True, # Azure-style terminal-only encrypted content + ), + "anthropic": ReasoningCapabilities( + capture_kinds=frozenset({ReasoningKind.OPAQUE, ReasoningKind.TEXT}), + native_replay_kinds=frozenset({ReasoningKind.OPAQUE}), + # Effort levels are declared unsupported until a verified + # budget_tokens-vs-effort mapping exists for these models. + effort_map=dict.fromkeys(_NEUTRAL_EFFORT_LEVELS), + supports_reasoning_when_disabled=True, + requires_tool_turn_reasoning_replay=True, + replay_field="thinking", + signature_prefix_sensitive=True, + terminal_backfill=False, + ), +} +for _chat_provider in ("glm", "kimi", "deepseek", "qwen", "nvidia"): + DEFAULT_REASONING_CAPABILITIES[_chat_provider] = _chat_reasoning_capabilities() + +_REASONING_CAPABILITIES: dict[str, ReasoningCapabilities] = dict(DEFAULT_REASONING_CAPABILITIES) + + +def register_reasoning_capabilities(provider: str, capabilities: ReasoningCapabilities) -> None: + """Register (or explicitly override) capabilities for a provider. + + Explicit overrides take precedence over the generated/default catalog. + Stored under the lowercased provider key so + mixed-case registrations stay reachable by the case-insensitive lookup. + """ + _REASONING_CAPABILITIES[provider.lower()] = capabilities + + +def get_reasoning_capabilities( + provider: str, + *, + catalog: Mapping[str, ReasoningCapabilities] | None = None, +) -> ReasoningCapabilities | None: + """Return declared capabilities for a provider, or None if unknown. + + None is the fail-closed answer (unknowns are not silently promoted to + supported); callers must treat it as "no native + replay, no effort mapping" rather than inventing defaults. + """ + registry = _REASONING_CAPABILITIES if catalog is None else catalog + return registry.get(provider.lower()) diff --git a/tests/unifiedllm/fixtures/contracts_golden.json b/tests/unifiedllm/fixtures/contracts_golden.json new file mode 100644 index 000000000..61c639674 --- /dev/null +++ b/tests/unifiedllm/fixtures/contracts_golden.json @@ -0,0 +1,500 @@ +{ + "enums": { + "ReasoningKind": [ + "opaque", + "text", + "checkpoint" + ], + "ReasoningReplayMode": [ + "off", + "auto", + "native_only", + "text_context" + ] + }, + "examples": { + "provider_identity": { + "provider": "openai", + "api_style": "responses", + "model": "gpt-5.6-sol", + "endpoint_id": "ep-123", + "account_scope": "acct-abc", + "transport": "litellm", + "opaque_replay_key": "sha256:0e57868347184b39df61aaf0dc917e5b3d7ad2deb231ad7a5456075393690934" + }, + "reasoning_record": { + "version": 1, + "kind": "opaque", + "payload": { + "encrypted_content": "opaque-blob" + }, + "provenance": { + "provider": "openai", + "api_style": "responses", + "model": "gpt-5.6-sol", + "endpoint_id": null, + "account_scope": null, + "transport": "litellm", + "opaque_replay_key": null + }, + "provider_item_type": "reasoning", + "sequence": 0, + "provider_token_count": 128, + "replayable": true, + "redaction_class": "opaque" + }, + "capabilities_openai": { + "capture_kinds": [ + "opaque", + "checkpoint", + "text" + ], + "native_replay_kinds": [ + "opaque", + "checkpoint" + ], + "effort_map": { + "minimal": "minimal", + "low": "low", + "medium": "medium", + "high": "high" + }, + "supports_reasoning_when_disabled": true, + "requires_tool_turn_reasoning_replay": true, + "replay_field": null, + "signature_prefix_sensitive": false, + "terminal_backfill": true + } + }, + "opaque_replay_key_version": "nooa.opaque-replay-key.v1", + "schema_version": 1, + "schemas": { + "ProviderIdentity": { + "description": "Logical provider identity, independent of transport routing strings.\n\nOwned by NOOA rather than borrowed from a transport library (litellm or a\nsuccessor) because the two answer different questions. litellm parses a\nrouting string to pick *where to send the request*; that parsing is\ntransport-specific, mutable across library versions, and confuses gateway\nroutes with providers \u2014 an ``openai/`` prefix in a routed id is not\nevidence the model is served by OpenAI. This identity answers *who\nproduced a stored artifact and who may receive it back*: it is stamped\nonce at the adapter edge, persisted alongside captured reasoning, and\ncompared long after the original request. Making that durable,\nprovider-independent, and testable in isolation is why it lives here.\n\nendpoint_id and account_scope are non-secret fingerprints (never raw\ncredentials). transport is *not* part of replay compatibility, so it is\nexcluded from derive_opaque_replay_key.", + "properties": { + "provider": { + "title": "Provider", + "type": "string" + }, + "api_style": { + "title": "Api Style", + "type": "string" + }, + "model": { + "title": "Model", + "type": "string" + }, + "endpoint_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Endpoint Id" + }, + "account_scope": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Account Scope" + }, + "transport": { + "title": "Transport", + "type": "string" + }, + "opaque_replay_key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Opaque Replay Key" + } + }, + "required": [ + "provider", + "api_style", + "model", + "transport" + ], + "title": "ProviderIdentity", + "type": "object" + }, + "ReasoningRecord": { + "$defs": { + "JsonValue": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "integer" + }, + { + "type": "number" + }, + { + "type": "string" + }, + { + "items": { + "$ref": "#/$defs/JsonValue" + }, + "type": "array" + }, + { + "additionalProperties": { + "$ref": "#/$defs/JsonValue" + }, + "type": "object" + }, + { + "type": "null" + } + ] + }, + "ProviderIdentity": { + "description": "Logical provider identity, independent of transport routing strings.\n\nOwned by NOOA rather than borrowed from a transport library (litellm or a\nsuccessor) because the two answer different questions. litellm parses a\nrouting string to pick *where to send the request*; that parsing is\ntransport-specific, mutable across library versions, and confuses gateway\nroutes with providers \u2014 an ``openai/`` prefix in a routed id is not\nevidence the model is served by OpenAI. This identity answers *who\nproduced a stored artifact and who may receive it back*: it is stamped\nonce at the adapter edge, persisted alongside captured reasoning, and\ncompared long after the original request. Making that durable,\nprovider-independent, and testable in isolation is why it lives here.\n\nendpoint_id and account_scope are non-secret fingerprints (never raw\ncredentials). transport is *not* part of replay compatibility, so it is\nexcluded from derive_opaque_replay_key.", + "properties": { + "provider": { + "title": "Provider", + "type": "string" + }, + "api_style": { + "title": "Api Style", + "type": "string" + }, + "model": { + "title": "Model", + "type": "string" + }, + "endpoint_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Endpoint Id" + }, + "account_scope": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Account Scope" + }, + "transport": { + "title": "Transport", + "type": "string" + }, + "opaque_replay_key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Opaque Replay Key" + } + }, + "required": [ + "provider", + "api_style", + "model", + "transport" + ], + "title": "ProviderIdentity", + "type": "object" + }, + "ReasoningKind": { + "description": "Kind of retained reasoning artifact.", + "enum": [ + "opaque", + "text", + "checkpoint" + ], + "title": "ReasoningKind", + "type": "string" + } + }, + "description": "One retained reasoning artifact with provenance.\n\npayload is NOOA-owned JSON only; provider SDK objects are converted at\nthe adapter edge and never persist.", + "properties": { + "version": { + "default": 1, + "title": "Version", + "type": "integer" + }, + "kind": { + "$ref": "#/$defs/ReasoningKind" + }, + "payload": { + "$ref": "#/$defs/JsonValue" + }, + "provenance": { + "$ref": "#/$defs/ProviderIdentity" + }, + "provider_item_type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Provider Item Type" + }, + "sequence": { + "title": "Sequence", + "type": "integer" + }, + "provider_token_count": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Provider Token Count" + }, + "replayable": { + "default": true, + "title": "Replayable", + "type": "boolean" + }, + "redaction_class": { + "enum": [ + "opaque", + "plain_reasoning" + ], + "title": "Redaction Class", + "type": "string" + } + }, + "required": [ + "kind", + "payload", + "provenance", + "sequence", + "redaction_class" + ], + "title": "ReasoningRecord", + "type": "object" + }, + "ReasoningCapabilities": { + "$defs": { + "ReasoningKind": { + "description": "Kind of retained reasoning artifact.", + "enum": [ + "opaque", + "text", + "checkpoint" + ], + "title": "ReasoningKind", + "type": "string" + } + }, + "description": "Reasoning capability profile for a provider.\n\nAll fields are explicit declarations \u2014 unknowns are never promoted to\nsupported. effort_map maps neutral effort levels to the provider wire\nvalue, with None meaning \"unsupported\".", + "properties": { + "capture_kinds": { + "items": { + "$ref": "#/$defs/ReasoningKind" + }, + "title": "Capture Kinds", + "type": "array", + "uniqueItems": true + }, + "native_replay_kinds": { + "items": { + "$ref": "#/$defs/ReasoningKind" + }, + "title": "Native Replay Kinds", + "type": "array", + "uniqueItems": true + }, + "effort_map": { + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "title": "Effort Map", + "type": "object" + }, + "supports_reasoning_when_disabled": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Supports Reasoning When Disabled" + }, + "requires_tool_turn_reasoning_replay": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Requires Tool Turn Reasoning Replay" + }, + "replay_field": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Replay Field" + }, + "signature_prefix_sensitive": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Signature Prefix Sensitive" + }, + "terminal_backfill": { + "default": false, + "title": "Terminal Backfill", + "type": "boolean" + } + }, + "required": [ + "capture_kinds", + "native_replay_kinds", + "effort_map" + ], + "title": "ReasoningCapabilities", + "type": "object" + }, + "ModelCompatGroup": { + "description": "Explicitly declared model compatibility group.\n\nGroup membership is hand-registered only after live verification; model-id\nequality stays out of the replay hot path. A group is scoped to one\nlogical provider, so the same model string can never silently grant\ncompatibility across providers.", + "properties": { + "name": { + "title": "Name", + "type": "string" + }, + "provider": { + "title": "Provider", + "type": "string" + }, + "models": { + "items": { + "type": "string" + }, + "title": "Models", + "type": "array", + "uniqueItems": true + } + }, + "required": [ + "name", + "provider", + "models" + ], + "title": "ModelCompatGroup", + "type": "object" + }, + "NormalizedModel": { + "description": "Result of normalizing a routing-style model string.", + "properties": { + "provider": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Provider" + }, + "model": { + "title": "Model", + "type": "string" + }, + "namespace": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Namespace" + }, + "tier": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Tier" + } + }, + "required": [ + "provider", + "model" + ], + "title": "NormalizedModel", + "type": "object" + } + } +} diff --git a/tests/unifiedllm/fixtures/model_id_corpora.json b/tests/unifiedllm/fixtures/model_id_corpora.json new file mode 100644 index 000000000..413458a01 --- /dev/null +++ b/tests/unifiedllm/fixtures/model_id_corpora.json @@ -0,0 +1,4705 @@ +{ + "_comment": "Model-id corpora for parse_model_string conformance. openrouter: 430 ids from the public OpenRouter catalog (fetched 2026-09-07); ground truth = leading id segment. nvidia_gateway: 241 ids from the NVIDIA inference gateway /v1/models; ground truth = middle vendor segment for nvidia// spellings. azure/vertex_ai/bedrock: deployment-prefixed ids from the catalog bundled with the transport library; ground truth = the logical provider of the served model (bare-id provider, or the Bedrock vendor.model head). Transport labels (azure/bedrock/...) are never treated as logical providers. vendor=null means the catalog cannot verify the logical provider.", + "openrouter": [ + { + "id": "openai/gpt-6-astra", + "vendor": "openai" + }, + { + "id": "openai/gpt-6-astra:batch", + "vendor": "openai" + }, + { + "id": "openai/gpt-6-astra-pro", + "vendor": "openai" + }, + { + "id": "openai/gpt-6-astra-pro:batch", + "vendor": "openai" + }, + { + "id": "inclusionai/ling-3.0-flash-sante:free", + "vendor": "inclusionai" + }, + { + "id": "qwen/qwen3.8-max-0902", + "vendor": "qwen" + }, + { + "id": "meta/muse-spark-1.3-contributor", + "vendor": "meta" + }, + { + "id": "meta/muse-spark-1.3", + "vendor": "meta" + }, + { + "id": "google/gemini-3.8-flash", + "vendor": "google" + }, + { + "id": "google/gemini-3.8-flash:batch", + "vendor": "google" + }, + { + "id": "anthropic/claude-fable-5.1", + "vendor": "anthropic" + }, + { + "id": "anthropic/claude-fable-5.1:batch", + "vendor": "anthropic" + }, + { + "id": "inception/mercury-2.5-preview", + "vendor": "inception" + }, + { + "id": "ibm-granite/granite-4.2-8b", + "vendor": "ibm-granite" + }, + { + "id": "tencent/hy4-preview", + "vendor": "tencent" + }, + { + "id": "inclusionai/ling-3.0-flash-fin", + "vendor": "inclusionai" + }, + { + "id": "inclusionai/ling-3.0-flash-fin:free", + "vendor": "inclusionai" + }, + { + "id": "~z-ai/glm-flash-latest", + "vendor": "z-ai" + }, + { + "id": "qwen/qwen3.8-flash", + "vendor": "qwen" + }, + { + "id": "z-ai/glm-5.3-flash", + "vendor": "z-ai" + }, + { + "id": "z-ai/glm-5.3-flash:batch", + "vendor": "z-ai" + }, + { + "id": "meta/muse-spark-1.2-contributor", + "vendor": "meta" + }, + { + "id": "deepseek/deepseek-v4-flash-vision-exp", + "vendor": "deepseek" + }, + { + "id": "tencent/hy-mt2-1.8b", + "vendor": "tencent" + }, + { + "id": "tencent/hy-mt2-30b-a3b", + "vendor": "tencent" + }, + { + "id": "~z-ai/glm-latest", + "vendor": "z-ai" + }, + { + "id": "tencent/hy-mt2-7b", + "vendor": "tencent" + }, + { + "id": "z-ai/glm-5.3", + "vendor": "z-ai" + }, + { + "id": "qwen/qwen3.8-27b", + "vendor": "qwen" + }, + { + "id": "dots-studio/dots-3-note-preview:free", + "vendor": "dots-studio" + }, + { + "id": "google/gemini-3.7-flash", + "vendor": "google" + }, + { + "id": "google/gemini-3.7-flash:batch", + "vendor": "google" + }, + { + "id": "bytedance-seed/seed-2-1-turbo", + "vendor": "bytedance-seed" + }, + { + "id": "qwen/qwen3.8-2.4t-a95b", + "vendor": "qwen" + }, + { + "id": "qwen/qwen3.8-2.4t-a95b:batch", + "vendor": "qwen" + }, + { + "id": "bytedance-seed/seed-2.0-code", + "vendor": "bytedance-seed" + }, + { + "id": "deepseek/deepseek-v4-pro-0813", + "vendor": "deepseek" + }, + { + "id": "deepseek/deepseek-v4-pro-0813:batch", + "vendor": "deepseek" + }, + { + "id": "x-ai/grok-4.6", + "vendor": "x-ai" + }, + { + "id": "liquid/lfm-2.5-2.6b:free", + "vendor": "liquid" + }, + { + "id": "nvidia/nemotron-3.5-lightning", + "vendor": "nvidia" + }, + { + "id": "nvidia/nemotron-3.5-lightning:free", + "vendor": "nvidia" + }, + { + "id": "sakana/sakana-namazu", + "vendor": "sakana" + }, + { + "id": "upstage/solar-pro4", + "vendor": "upstage" + }, + { + "id": "meta/muse-glimmer-30b", + "vendor": "meta" + }, + { + "id": "meta/muse-glimmer-30b:batch", + "vendor": "meta" + }, + { + "id": "meta/muse-spark-1.2", + "vendor": "meta" + }, + { + "id": "~deepseek/deepseek-v4-flash-latest", + "vendor": "deepseek" + }, + { + "id": "deepseek/deepseek-v4-flash-0731", + "vendor": "deepseek" + }, + { + "id": "deepseek/deepseek-v4-flash-0731:batch", + "vendor": "deepseek" + }, + { + "id": "thinkingmachines/inkling-small", + "vendor": "thinkingmachines" + }, + { + "id": "thinkingmachines/inkling-small:batch", + "vendor": "thinkingmachines" + }, + { + "id": "thinkingmachines/inkling-small:free", + "vendor": "thinkingmachines" + }, + { + "id": "qwen/qwen3.7-flash", + "vendor": "qwen" + }, + { + "id": "anthropic/claude-opus-5", + "vendor": "anthropic" + }, + { + "id": "anthropic/claude-opus-5:batch", + "vendor": "anthropic" + }, + { + "id": "inclusionai/ling-3.0-flash", + "vendor": "inclusionai" + }, + { + "id": "poolside/laguna-s-2.1", + "vendor": "poolside" + }, + { + "id": "poolside/laguna-s-2.1:free", + "vendor": "poolside" + }, + { + "id": "google/gemini-3.6-flash", + "vendor": "google" + }, + { + "id": "google/gemini-3.6-flash:batch", + "vendor": "google" + }, + { + "id": "google/gemini-3.5-flash-lite", + "vendor": "google" + }, + { + "id": "google/gemini-3.5-flash-lite:batch", + "vendor": "google" + }, + { + "id": "meituan/longcat-2.0", + "vendor": "meituan" + }, + { + "id": "thinkingmachines/inkling", + "vendor": "thinkingmachines" + }, + { + "id": "thinkingmachines/inkling:batch", + "vendor": "thinkingmachines" + }, + { + "id": "thinkingmachines/inkling:free", + "vendor": "thinkingmachines" + }, + { + "id": "openrouter/auto-beta", + "vendor": "openrouter" + }, + { + "id": "moonshotai/kimi-k3", + "vendor": "moonshotai" + }, + { + "id": "moonshotai/kimi-k3:batch", + "vendor": "moonshotai" + }, + { + "id": "meta/muse-spark-1.1", + "vendor": "meta" + }, + { + "id": "kwaipilot/kat-coder-pro-v2.5", + "vendor": "kwaipilot" + }, + { + "id": "openai/gpt-5.6-luna-pro", + "vendor": "openai" + }, + { + "id": "openai/gpt-5.6-luna-pro:batch", + "vendor": "openai" + }, + { + "id": "openai/gpt-5.6-luna", + "vendor": "openai" + }, + { + "id": "openai/gpt-5.6-luna:batch", + "vendor": "openai" + }, + { + "id": "openai/gpt-5.6-terra-pro", + "vendor": "openai" + }, + { + "id": "openai/gpt-5.6-terra-pro:batch", + "vendor": "openai" + }, + { + "id": "openai/gpt-5.6-terra", + "vendor": "openai" + }, + { + "id": "openai/gpt-5.6-terra:batch", + "vendor": "openai" + }, + { + "id": "openai/gpt-5.6-sol-pro", + "vendor": "openai" + }, + { + "id": "openai/gpt-5.6-sol-pro:batch", + "vendor": "openai" + }, + { + "id": "openai/gpt-5.6-sol", + "vendor": "openai" + }, + { + "id": "openai/gpt-5.6-sol:batch", + "vendor": "openai" + }, + { + "id": "x-ai/grok-4.5", + "vendor": "x-ai" + }, + { + "id": "~x-ai/grok-latest", + "vendor": "x-ai" + }, + { + "id": "aion-labs/aion-3.0-mini", + "vendor": "aion-labs" + }, + { + "id": "aion-labs/aion-3.0", + "vendor": "aion-labs" + }, + { + "id": "tencent/hy3", + "vendor": "tencent" + }, + { + "id": "poolside/laguna-xs-2.1", + "vendor": "poolside" + }, + { + "id": "poolside/laguna-xs-2.1:free", + "vendor": "poolside" + }, + { + "id": "anthropic/claude-sonnet-5", + "vendor": "anthropic" + }, + { + "id": "anthropic/claude-sonnet-5:batch", + "vendor": "anthropic" + }, + { + "id": "google/gemini-3.1-flash-lite-image", + "vendor": "google" + }, + { + "id": "nex-agi/nex-n2-mini", + "vendor": "nex-agi" + }, + { + "id": "sakana/fugu-ultra", + "vendor": "sakana" + }, + { + "id": "google/gemini-3.1-flash-image", + "vendor": "google" + }, + { + "id": "google/gemini-3-pro-image", + "vendor": "google" + }, + { + "id": "cohere/north-mini-code:free", + "vendor": "cohere" + }, + { + "id": "z-ai/glm-5.2", + "vendor": "z-ai" + }, + { + "id": "openrouter/fusion", + "vendor": "openrouter" + }, + { + "id": "moonshotai/kimi-k2.7-code", + "vendor": "moonshotai" + }, + { + "id": "~anthropic/claude-fable-latest", + "vendor": "anthropic" + }, + { + "id": "anthropic/claude-fable-5", + "vendor": "anthropic" + }, + { + "id": "anthropic/claude-fable-5:batch", + "vendor": "anthropic" + }, + { + "id": "nex-agi/nex-n2-pro", + "vendor": "nex-agi" + }, + { + "id": "nvidia/nemotron-3.5-content-safety", + "vendor": "nvidia" + }, + { + "id": "nvidia/nemotron-3.5-content-safety:free", + "vendor": "nvidia" + }, + { + "id": "nvidia/nemotron-3-ultra-550b-a55b", + "vendor": "nvidia" + }, + { + "id": "nvidia/nemotron-3-ultra-550b-a55b:free", + "vendor": "nvidia" + }, + { + "id": "qwen/qwen3.7-plus", + "vendor": "qwen" + }, + { + "id": "minimax/minimax-m3", + "vendor": "minimax" + }, + { + "id": "minimax/minimax-m3:batch", + "vendor": "minimax" + }, + { + "id": "minimax/minimax-m3:free", + "vendor": "minimax" + }, + { + "id": "stepfun/step-3.7-flash", + "vendor": "stepfun" + }, + { + "id": "anthropic/claude-opus-4.8", + "vendor": "anthropic" + }, + { + "id": "anthropic/claude-opus-4.8:batch", + "vendor": "anthropic" + }, + { + "id": "qwen/qwen3.7-max", + "vendor": "qwen" + }, + { + "id": "x-ai/grok-build-0.1", + "vendor": "x-ai" + }, + { + "id": "google/gemini-3.5-flash", + "vendor": "google" + }, + { + "id": "google/gemini-3.5-flash:batch", + "vendor": "google" + }, + { + "id": "perceptron/perceptron-mk1", + "vendor": "perceptron" + }, + { + "id": "google/gemini-3.1-flash-lite", + "vendor": "google" + }, + { + "id": "google/gemini-3.1-flash-lite:batch", + "vendor": "google" + }, + { + "id": "openai/gpt-chat-latest", + "vendor": "openai" + }, + { + "id": "x-ai/grok-4.3", + "vendor": "x-ai" + }, + { + "id": "x-ai/grok-4.3:batch", + "vendor": "x-ai" + }, + { + "id": "mistralai/mistral-medium-3-5", + "vendor": "mistralai" + }, + { + "id": "mistralai/mistral-medium-3-5:batch", + "vendor": "mistralai" + }, + { + "id": "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free", + "vendor": "nvidia" + }, + { + "id": "~anthropic/claude-haiku-latest", + "vendor": "anthropic" + }, + { + "id": "~openai/gpt-mini-latest", + "vendor": "openai" + }, + { + "id": "~google/gemini-pro-latest", + "vendor": "google" + }, + { + "id": "~moonshotai/kimi-latest", + "vendor": "moonshotai" + }, + { + "id": "~google/gemini-flash-latest", + "vendor": "google" + }, + { + "id": "~anthropic/claude-sonnet-latest", + "vendor": "anthropic" + }, + { + "id": "~openai/gpt-latest", + "vendor": "openai" + }, + { + "id": "qwen/qwen3.5-plus-20260420", + "vendor": "qwen" + }, + { + "id": "qwen/qwen3.6-flash", + "vendor": "qwen" + }, + { + "id": "qwen/qwen3.6-35b-a3b", + "vendor": "qwen" + }, + { + "id": "qwen/qwen3.6-max-preview", + "vendor": "qwen" + }, + { + "id": "qwen/qwen3.6-27b", + "vendor": "qwen" + }, + { + "id": "openai/gpt-5.5-pro", + "vendor": "openai" + }, + { + "id": "openai/gpt-5.5-pro:batch", + "vendor": "openai" + }, + { + "id": "openai/gpt-5.5", + "vendor": "openai" + }, + { + "id": "openai/gpt-5.5:batch", + "vendor": "openai" + }, + { + "id": "deepseek/deepseek-v4-pro", + "vendor": "deepseek" + }, + { + "id": "deepseek/deepseek-v4-flash", + "vendor": "deepseek" + }, + { + "id": "tencent/hy3-preview", + "vendor": "tencent" + }, + { + "id": "xiaomi/mimo-v2.5-pro", + "vendor": "xiaomi" + }, + { + "id": "xiaomi/mimo-v2.5", + "vendor": "xiaomi" + }, + { + "id": "openai/gpt-5.4-image-2", + "vendor": "openai" + }, + { + "id": "~anthropic/claude-opus-latest", + "vendor": "anthropic" + }, + { + "id": "openrouter/pareto-code", + "vendor": "openrouter" + }, + { + "id": "moonshotai/kimi-k2.6", + "vendor": "moonshotai" + }, + { + "id": "anthropic/claude-opus-4.7", + "vendor": "anthropic" + }, + { + "id": "anthropic/claude-opus-4.7:batch", + "vendor": "anthropic" + }, + { + "id": "z-ai/glm-5.1", + "vendor": "z-ai" + }, + { + "id": "google/gemma-4-26b-a4b-it", + "vendor": "google" + }, + { + "id": "google/gemma-4-26b-a4b-it:free", + "vendor": "google" + }, + { + "id": "google/gemma-4-31b-it", + "vendor": "google" + }, + { + "id": "google/gemma-4-31b-it:batch", + "vendor": "google" + }, + { + "id": "google/gemma-4-31b-it:free", + "vendor": "google" + }, + { + "id": "qwen/qwen3.6-plus", + "vendor": "qwen" + }, + { + "id": "z-ai/glm-5v-turbo", + "vendor": "z-ai" + }, + { + "id": "arcee-ai/trinity-large-thinking", + "vendor": "arcee-ai" + }, + { + "id": "x-ai/grok-4.20-multi-agent", + "vendor": "x-ai" + }, + { + "id": "x-ai/grok-4.20", + "vendor": "x-ai" + }, + { + "id": "google/lyria-3-pro-preview", + "vendor": "google" + }, + { + "id": "google/lyria-3-clip-preview", + "vendor": "google" + }, + { + "id": "kwaipilot/kat-coder-pro-v2", + "vendor": "kwaipilot" + }, + { + "id": "rekaai/reka-edge", + "vendor": "rekaai" + }, + { + "id": "minimax/minimax-m2.7", + "vendor": "minimax" + }, + { + "id": "minimax/minimax-m2.7:free", + "vendor": "minimax" + }, + { + "id": "openai/gpt-5.4-nano", + "vendor": "openai" + }, + { + "id": "openai/gpt-5.4-nano:batch", + "vendor": "openai" + }, + { + "id": "openai/gpt-5.4-mini", + "vendor": "openai" + }, + { + "id": "openai/gpt-5.4-mini:batch", + "vendor": "openai" + }, + { + "id": "mistralai/mistral-small-2603", + "vendor": "mistralai" + }, + { + "id": "z-ai/glm-5-turbo", + "vendor": "z-ai" + }, + { + "id": "nvidia/nemotron-3-super-120b-a12b", + "vendor": "nvidia" + }, + { + "id": "nvidia/nemotron-3-super-120b-a12b:free", + "vendor": "nvidia" + }, + { + "id": "bytedance-seed/seed-2.0-lite", + "vendor": "bytedance-seed" + }, + { + "id": "qwen/qwen3.5-9b", + "vendor": "qwen" + }, + { + "id": "qwen/qwen3.5-9b:batch", + "vendor": "qwen" + }, + { + "id": "openai/gpt-5.4-pro", + "vendor": "openai" + }, + { + "id": "openai/gpt-5.4-pro:batch", + "vendor": "openai" + }, + { + "id": "openai/gpt-5.4", + "vendor": "openai" + }, + { + "id": "openai/gpt-5.4:batch", + "vendor": "openai" + }, + { + "id": "inception/mercury-2", + "vendor": "inception" + }, + { + "id": "google/gemini-3.1-flash-lite-preview", + "vendor": "google" + }, + { + "id": "bytedance-seed/seed-2.0-mini", + "vendor": "bytedance-seed" + }, + { + "id": "google/gemini-3.1-flash-image-preview", + "vendor": "google" + }, + { + "id": "qwen/qwen3.5-35b-a3b", + "vendor": "qwen" + }, + { + "id": "qwen/qwen3.5-27b", + "vendor": "qwen" + }, + { + "id": "qwen/qwen3.5-122b-a10b", + "vendor": "qwen" + }, + { + "id": "qwen/qwen3.5-flash-02-23", + "vendor": "qwen" + }, + { + "id": "google/gemini-3.1-pro-preview-customtools", + "vendor": "google" + }, + { + "id": "openai/gpt-5.3-codex", + "vendor": "openai" + }, + { + "id": "aion-labs/aion-2.0", + "vendor": "aion-labs" + }, + { + "id": "google/gemini-3.1-pro-preview", + "vendor": "google" + }, + { + "id": "google/gemini-3.1-pro-preview:batch", + "vendor": "google" + }, + { + "id": "anthropic/claude-sonnet-4.6", + "vendor": "anthropic" + }, + { + "id": "anthropic/claude-sonnet-4.6:batch", + "vendor": "anthropic" + }, + { + "id": "qwen/qwen3.5-plus-02-15", + "vendor": "qwen" + }, + { + "id": "qwen/qwen3.5-397b-a17b", + "vendor": "qwen" + }, + { + "id": "minimax/minimax-m2.5", + "vendor": "minimax" + }, + { + "id": "z-ai/glm-5", + "vendor": "z-ai" + }, + { + "id": "qwen/qwen3-max-thinking", + "vendor": "qwen" + }, + { + "id": "anthropic/claude-opus-4.6", + "vendor": "anthropic" + }, + { + "id": "anthropic/claude-opus-4.6:batch", + "vendor": "anthropic" + }, + { + "id": "qwen/qwen3-coder-next", + "vendor": "qwen" + }, + { + "id": "openrouter/free", + "vendor": "openrouter" + }, + { + "id": "stepfun/step-3.5-flash", + "vendor": "stepfun" + }, + { + "id": "moonshotai/kimi-k2.5", + "vendor": "moonshotai" + }, + { + "id": "upstage/solar-pro-3", + "vendor": "upstage" + }, + { + "id": "minimax/minimax-m2-her", + "vendor": "minimax" + }, + { + "id": "writer/palmyra-x5", + "vendor": "writer" + }, + { + "id": "openai/gpt-audio", + "vendor": "openai" + }, + { + "id": "openai/gpt-audio-mini", + "vendor": "openai" + }, + { + "id": "z-ai/glm-4.7-flash", + "vendor": "z-ai" + }, + { + "id": "openai/gpt-5.2-codex", + "vendor": "openai" + }, + { + "id": "bytedance-seed/seed-1.6-flash", + "vendor": "bytedance-seed" + }, + { + "id": "bytedance-seed/seed-1.6", + "vendor": "bytedance-seed" + }, + { + "id": "minimax/minimax-m2.1", + "vendor": "minimax" + }, + { + "id": "z-ai/glm-4.7", + "vendor": "z-ai" + }, + { + "id": "google/gemini-3-flash-preview", + "vendor": "google" + }, + { + "id": "google/gemini-3-flash-preview:batch", + "vendor": "google" + }, + { + "id": "nvidia/nemotron-3-nano-30b-a3b", + "vendor": "nvidia" + }, + { + "id": "openai/gpt-5.2-chat", + "vendor": "openai" + }, + { + "id": "openai/gpt-5.2-pro", + "vendor": "openai" + }, + { + "id": "openai/gpt-5.2-pro:batch", + "vendor": "openai" + }, + { + "id": "openai/gpt-5.2", + "vendor": "openai" + }, + { + "id": "openai/gpt-5.2:batch", + "vendor": "openai" + }, + { + "id": "mistralai/devstral-2512", + "vendor": "mistralai" + }, + { + "id": "relace/relace-search", + "vendor": "relace" + }, + { + "id": "z-ai/glm-4.6v", + "vendor": "z-ai" + }, + { + "id": "openrouter/bodybuilder", + "vendor": "openrouter" + }, + { + "id": "openai/gpt-5.1-codex-max", + "vendor": "openai" + }, + { + "id": "amazon/nova-2-lite-v1", + "vendor": "amazon" + }, + { + "id": "mistralai/ministral-14b-2512", + "vendor": "mistralai" + }, + { + "id": "mistralai/ministral-8b-2512", + "vendor": "mistralai" + }, + { + "id": "mistralai/ministral-3b-2512", + "vendor": "mistralai" + }, + { + "id": "mistralai/mistral-large-2512", + "vendor": "mistralai" + }, + { + "id": "deepseek/deepseek-v3.2", + "vendor": "deepseek" + }, + { + "id": "anthropic/claude-opus-4.5", + "vendor": "anthropic" + }, + { + "id": "anthropic/claude-opus-4.5:batch", + "vendor": "anthropic" + }, + { + "id": "google/gemini-3-pro-image-preview", + "vendor": "google" + }, + { + "id": "openai/gpt-5.1", + "vendor": "openai" + }, + { + "id": "openai/gpt-5.1:batch", + "vendor": "openai" + }, + { + "id": "openai/gpt-5.1-codex", + "vendor": "openai" + }, + { + "id": "openai/gpt-5.1-codex-mini", + "vendor": "openai" + }, + { + "id": "moonshotai/kimi-k2-thinking", + "vendor": "moonshotai" + }, + { + "id": "amazon/nova-premier-v1", + "vendor": "amazon" + }, + { + "id": "perplexity/sonar-pro-search", + "vendor": "perplexity" + }, + { + "id": "mistralai/voxtral-small-24b-2507", + "vendor": "mistralai" + }, + { + "id": "openai/gpt-oss-safeguard-20b", + "vendor": "openai" + }, + { + "id": "minimax/minimax-m2", + "vendor": "minimax" + }, + { + "id": "qwen/qwen3-vl-32b-instruct", + "vendor": "qwen" + }, + { + "id": "ibm-granite/granite-4.0-h-micro", + "vendor": "ibm-granite" + }, + { + "id": "openai/gpt-5-image-mini", + "vendor": "openai" + }, + { + "id": "anthropic/claude-haiku-4.5", + "vendor": "anthropic" + }, + { + "id": "anthropic/claude-haiku-4.5:batch", + "vendor": "anthropic" + }, + { + "id": "qwen/qwen3-vl-8b-thinking", + "vendor": "qwen" + }, + { + "id": "qwen/qwen3-vl-8b-instruct", + "vendor": "qwen" + }, + { + "id": "openai/gpt-5-image", + "vendor": "openai" + }, + { + "id": "google/gemini-2.5-flash-image", + "vendor": "google" + }, + { + "id": "qwen/qwen3-vl-30b-a3b-thinking", + "vendor": "qwen" + }, + { + "id": "qwen/qwen3-vl-30b-a3b-instruct", + "vendor": "qwen" + }, + { + "id": "openai/gpt-5-pro", + "vendor": "openai" + }, + { + "id": "openai/gpt-5-pro:batch", + "vendor": "openai" + }, + { + "id": "z-ai/glm-4.6", + "vendor": "z-ai" + }, + { + "id": "anthropic/claude-sonnet-4.5", + "vendor": "anthropic" + }, + { + "id": "anthropic/claude-sonnet-4.5:batch", + "vendor": "anthropic" + }, + { + "id": "deepseek/deepseek-v3.2-exp", + "vendor": "deepseek" + }, + { + "id": "thedrummer/cydonia-24b-v4.1", + "vendor": "thedrummer" + }, + { + "id": "relace/relace-apply-3", + "vendor": "relace" + }, + { + "id": "qwen/qwen3-vl-235b-a22b-thinking", + "vendor": "qwen" + }, + { + "id": "qwen/qwen3-vl-235b-a22b-instruct", + "vendor": "qwen" + }, + { + "id": "qwen/qwen3-max", + "vendor": "qwen" + }, + { + "id": "qwen/qwen3-coder-plus", + "vendor": "qwen" + }, + { + "id": "deepseek/deepseek-v3.1-terminus", + "vendor": "deepseek" + }, + { + "id": "qwen/qwen3-coder-flash", + "vendor": "qwen" + }, + { + "id": "qwen/qwen3-next-80b-a3b-thinking", + "vendor": "qwen" + }, + { + "id": "qwen/qwen3-next-80b-a3b-instruct", + "vendor": "qwen" + }, + { + "id": "qwen/qwen-plus-2025-07-28", + "vendor": "qwen" + }, + { + "id": "moonshotai/kimi-k2-0905", + "vendor": "moonshotai" + }, + { + "id": "qwen/qwen3-30b-a3b-thinking-2507", + "vendor": "qwen" + }, + { + "id": "nousresearch/hermes-4-70b", + "vendor": "nousresearch" + }, + { + "id": "nousresearch/hermes-4-405b", + "vendor": "nousresearch" + }, + { + "id": "deepseek/deepseek-chat-v3.1", + "vendor": "deepseek" + }, + { + "id": "mistralai/mistral-medium-3.1", + "vendor": "mistralai" + }, + { + "id": "z-ai/glm-4.5v", + "vendor": "z-ai" + }, + { + "id": "openai/gpt-5", + "vendor": "openai" + }, + { + "id": "openai/gpt-5:batch", + "vendor": "openai" + }, + { + "id": "openai/gpt-5-mini", + "vendor": "openai" + }, + { + "id": "openai/gpt-5-mini:batch", + "vendor": "openai" + }, + { + "id": "openai/gpt-5-nano", + "vendor": "openai" + }, + { + "id": "openai/gpt-5-nano:batch", + "vendor": "openai" + }, + { + "id": "openai/gpt-oss-120b", + "vendor": "openai" + }, + { + "id": "openai/gpt-oss-120b:batch", + "vendor": "openai" + }, + { + "id": "openai/gpt-oss-20b", + "vendor": "openai" + }, + { + "id": "openai/gpt-oss-20b:batch", + "vendor": "openai" + }, + { + "id": "anthropic/claude-opus-4.1", + "vendor": "anthropic" + }, + { + "id": "anthropic/claude-opus-4.1:batch", + "vendor": "anthropic" + }, + { + "id": "mistralai/codestral-2508", + "vendor": "mistralai" + }, + { + "id": "qwen/qwen3-coder-30b-a3b-instruct", + "vendor": "qwen" + }, + { + "id": "qwen/qwen3-30b-a3b-instruct-2507", + "vendor": "qwen" + }, + { + "id": "z-ai/glm-4.5", + "vendor": "z-ai" + }, + { + "id": "z-ai/glm-4.5-air", + "vendor": "z-ai" + }, + { + "id": "qwen/qwen3-235b-a22b-thinking-2507", + "vendor": "qwen" + }, + { + "id": "qwen/qwen3-coder", + "vendor": "qwen" + }, + { + "id": "bytedance/ui-tars-1.5-7b", + "vendor": "bytedance" + }, + { + "id": "google/gemini-2.5-flash-lite", + "vendor": "google" + }, + { + "id": "google/gemini-2.5-flash-lite:batch", + "vendor": "google" + }, + { + "id": "qwen/qwen3-235b-a22b-2507", + "vendor": "qwen" + }, + { + "id": "moonshotai/kimi-k2", + "vendor": "moonshotai" + }, + { + "id": "cognitivecomputations/dolphin-mistral-24b-venice-edition", + "vendor": "cognitivecomputations" + }, + { + "id": "tencent/hunyuan-a13b-instruct", + "vendor": "tencent" + }, + { + "id": "morph/morph-v3-large", + "vendor": "morph" + }, + { + "id": "morph/morph-v3-fast", + "vendor": "morph" + }, + { + "id": "baidu/ernie-4.5-vl-424b-a47b", + "vendor": "baidu" + }, + { + "id": "mistralai/mistral-small-3.2-24b-instruct", + "vendor": "mistralai" + }, + { + "id": "minimax/minimax-m1", + "vendor": "minimax" + }, + { + "id": "google/gemini-2.5-flash", + "vendor": "google" + }, + { + "id": "google/gemini-2.5-flash:batch", + "vendor": "google" + }, + { + "id": "google/gemini-2.5-pro", + "vendor": "google" + }, + { + "id": "google/gemini-2.5-pro:batch", + "vendor": "google" + }, + { + "id": "openai/o3-pro", + "vendor": "openai" + }, + { + "id": "google/gemini-2.5-pro-preview", + "vendor": "google" + }, + { + "id": "deepseek/deepseek-r1-0528", + "vendor": "deepseek" + }, + { + "id": "anthropic/claude-opus-4", + "vendor": "anthropic" + }, + { + "id": "anthropic/claude-sonnet-4", + "vendor": "anthropic" + }, + { + "id": "mistralai/mistral-medium-3", + "vendor": "mistralai" + }, + { + "id": "google/gemini-2.5-pro-preview-05-06", + "vendor": "google" + }, + { + "id": "meta-llama/llama-guard-4-12b", + "vendor": "meta-llama" + }, + { + "id": "qwen/qwen3-30b-a3b", + "vendor": "qwen" + }, + { + "id": "qwen/qwen3-8b", + "vendor": "qwen" + }, + { + "id": "qwen/qwen3-14b", + "vendor": "qwen" + }, + { + "id": "qwen/qwen3-32b", + "vendor": "qwen" + }, + { + "id": "qwen/qwen3-235b-a22b", + "vendor": "qwen" + }, + { + "id": "openai/o4-mini-high", + "vendor": "openai" + }, + { + "id": "openai/o3", + "vendor": "openai" + }, + { + "id": "openai/o3:batch", + "vendor": "openai" + }, + { + "id": "openai/o4-mini", + "vendor": "openai" + }, + { + "id": "openai/o4-mini:batch", + "vendor": "openai" + }, + { + "id": "openai/gpt-4.1", + "vendor": "openai" + }, + { + "id": "openai/gpt-4.1:batch", + "vendor": "openai" + }, + { + "id": "openai/gpt-4.1-mini", + "vendor": "openai" + }, + { + "id": "openai/gpt-4.1-mini:batch", + "vendor": "openai" + }, + { + "id": "openai/gpt-4.1-nano", + "vendor": "openai" + }, + { + "id": "openai/gpt-4.1-nano:batch", + "vendor": "openai" + }, + { + "id": "meta-llama/llama-4-maverick", + "vendor": "meta-llama" + }, + { + "id": "meta-llama/llama-4-scout", + "vendor": "meta-llama" + }, + { + "id": "deepseek/deepseek-chat-v3-0324", + "vendor": "deepseek" + }, + { + "id": "openai/o1-pro", + "vendor": "openai" + }, + { + "id": "mistralai/mistral-small-3.1-24b-instruct", + "vendor": "mistralai" + }, + { + "id": "google/gemma-3-4b-it", + "vendor": "google" + }, + { + "id": "google/gemma-3-12b-it", + "vendor": "google" + }, + { + "id": "cohere/command-a", + "vendor": "cohere" + }, + { + "id": "rekaai/reka-flash-3", + "vendor": "rekaai" + }, + { + "id": "google/gemma-3-27b-it", + "vendor": "google" + }, + { + "id": "thedrummer/skyfall-36b-v2", + "vendor": "thedrummer" + }, + { + "id": "perplexity/sonar-reasoning-pro", + "vendor": "perplexity" + }, + { + "id": "perplexity/sonar-pro", + "vendor": "perplexity" + }, + { + "id": "perplexity/sonar-deep-research", + "vendor": "perplexity" + }, + { + "id": "mistralai/mistral-saba", + "vendor": "mistralai" + }, + { + "id": "openai/o3-mini-high", + "vendor": "openai" + }, + { + "id": "aion-labs/aion-rp-llama-3.1-8b", + "vendor": "aion-labs" + }, + { + "id": "qwen/qwen2.5-vl-72b-instruct", + "vendor": "qwen" + }, + { + "id": "qwen/qwen-plus", + "vendor": "qwen" + }, + { + "id": "openai/o3-mini", + "vendor": "openai" + }, + { + "id": "openai/o3-mini:batch", + "vendor": "openai" + }, + { + "id": "mistralai/mistral-small-24b-instruct-2501", + "vendor": "mistralai" + }, + { + "id": "perplexity/sonar", + "vendor": "perplexity" + }, + { + "id": "deepseek/deepseek-r1-distill-llama-70b", + "vendor": "deepseek" + }, + { + "id": "deepseek/deepseek-r1", + "vendor": "deepseek" + }, + { + "id": "minimax/minimax-01", + "vendor": "minimax" + }, + { + "id": "microsoft/phi-4", + "vendor": "microsoft" + }, + { + "id": "deepseek/deepseek-chat", + "vendor": "deepseek" + }, + { + "id": "sao10k/l3.3-euryale-70b", + "vendor": "sao10k" + }, + { + "id": "openai/o1", + "vendor": "openai" + }, + { + "id": "cohere/command-r7b-12-2024", + "vendor": "cohere" + }, + { + "id": "meta-llama/llama-3.3-70b-instruct", + "vendor": "meta-llama" + }, + { + "id": "amazon/nova-lite-v1", + "vendor": "amazon" + }, + { + "id": "amazon/nova-micro-v1", + "vendor": "amazon" + }, + { + "id": "amazon/nova-pro-v1", + "vendor": "amazon" + }, + { + "id": "openai/gpt-4o-2024-11-20", + "vendor": "openai" + }, + { + "id": "mistralai/mistral-large-2407", + "vendor": "mistralai" + }, + { + "id": "qwen/qwen-2.5-coder-32b-instruct", + "vendor": "qwen" + }, + { + "id": "thedrummer/unslopnemo-12b", + "vendor": "thedrummer" + }, + { + "id": "anthracite-org/magnum-v4-72b", + "vendor": "anthracite-org" + }, + { + "id": "qwen/qwen-2.5-7b-instruct", + "vendor": "qwen" + }, + { + "id": "meta-llama/llama-3.2-1b-instruct", + "vendor": "meta-llama" + }, + { + "id": "meta-llama/llama-3.2-3b-instruct", + "vendor": "meta-llama" + }, + { + "id": "qwen/qwen-2.5-72b-instruct", + "vendor": "qwen" + }, + { + "id": "cohere/command-r-08-2024", + "vendor": "cohere" + }, + { + "id": "cohere/command-r-plus-08-2024", + "vendor": "cohere" + }, + { + "id": "sao10k/l3.1-euryale-70b", + "vendor": "sao10k" + }, + { + "id": "nousresearch/hermes-3-llama-3.1-70b", + "vendor": "nousresearch" + }, + { + "id": "nousresearch/hermes-3-llama-3.1-405b", + "vendor": "nousresearch" + }, + { + "id": "sao10k/l3-lunaris-8b", + "vendor": "sao10k" + }, + { + "id": "openai/gpt-4o-2024-08-06", + "vendor": "openai" + }, + { + "id": "meta-llama/llama-3.1-70b-instruct", + "vendor": "meta-llama" + }, + { + "id": "meta-llama/llama-3.1-8b-instruct", + "vendor": "meta-llama" + }, + { + "id": "mistralai/mistral-nemo", + "vendor": "mistralai" + }, + { + "id": "openai/gpt-4o-mini", + "vendor": "openai" + }, + { + "id": "openai/gpt-4o-mini-2024-07-18", + "vendor": "openai" + }, + { + "id": "openai/gpt-4o-mini:batch", + "vendor": "openai" + }, + { + "id": "google/gemma-2-27b-it", + "vendor": "google" + }, + { + "id": "openai/gpt-4o", + "vendor": "openai" + }, + { + "id": "openai/gpt-4o-2024-05-13", + "vendor": "openai" + }, + { + "id": "openai/gpt-4o:batch", + "vendor": "openai" + }, + { + "id": "mistralai/mixtral-8x22b-instruct", + "vendor": "mistralai" + }, + { + "id": "microsoft/wizardlm-2-8x22b", + "vendor": "microsoft" + }, + { + "id": "openai/gpt-4-turbo", + "vendor": "openai" + }, + { + "id": "openai/gpt-4-turbo:batch", + "vendor": "openai" + }, + { + "id": "anthropic/claude-3-haiku", + "vendor": "anthropic" + }, + { + "id": "mistralai/mistral-large", + "vendor": "mistralai" + }, + { + "id": "openai/gpt-3.5-turbo-0613", + "vendor": "openai" + }, + { + "id": "openai/gpt-4-turbo-preview", + "vendor": "openai" + }, + { + "id": "openrouter/auto", + "vendor": "openrouter" + }, + { + "id": "openai/gpt-3.5-turbo-instruct", + "vendor": "openai" + }, + { + "id": "openai/gpt-3.5-turbo-16k", + "vendor": "openai" + }, + { + "id": "mancer/weaver", + "vendor": "mancer" + }, + { + "id": "undi95/remm-slerp-l2-13b", + "vendor": "undi95" + }, + { + "id": "gryphe/mythomax-l2-13b", + "vendor": "gryphe" + }, + { + "id": "openai/gpt-3.5-turbo", + "vendor": "openai" + }, + { + "id": "openai/gpt-3.5-turbo:batch", + "vendor": "openai" + }, + { + "id": "openai/gpt-4", + "vendor": "openai" + } + ], + "nvidia_gateway": [ + { + "id": "gcp/google/gemini-omni-flash-preview", + "vendor": null + }, + { + "id": "test/nvidia/load-test", + "vendor": null + }, + { + "id": "nvidia/nvidia/cosmos3-nano-reasoner", + "vendor": "nvidia" + }, + { + "id": "nvidia/nvidia/dfw-llama-3.2-nv-embedqa-1b", + "vendor": "nvidia" + }, + { + "id": "nvidia/google/gemma-2-9b-it", + "vendor": "google" + }, + { + "id": "nvidia/google/gemma-4-31b-it", + "vendor": "google" + }, + { + "id": "nvidia/openai/gpt-oss-20b", + "vendor": "openai" + }, + { + "id": "nvidia/meta/llama-3.1-8b-instruct", + "vendor": "meta" + }, + { + "id": "nvidia/nvidia/llama-3.1-nemoguard-8b-content-safety", + "vendor": "nvidia" + }, + { + "id": "nvidia/nvidia/llama-3.1-nemotron-ultra-253b-v1", + "vendor": "nvidia" + }, + { + "id": "nvidia/meta/llama-3.2-11b-vision-instruct", + "vendor": "meta" + }, + { + "id": "nvidia/meta/llama-3.2-1b-instruct", + "vendor": "meta" + }, + { + "id": "nvcf/meta/llama-3.2-1b-instruct", + "vendor": null + }, + { + "id": "nvidia/meta/llama-3.2-90b-vision-instruct", + "vendor": "meta" + }, + { + "id": "nvidia/nvidia/llama-3.2-nemoretriever-300m-embed-v2", + "vendor": "nvidia" + }, + { + "id": "nvidia/nvidia/llama-3.2-nemoretriever-500m-rerank-v2", + "vendor": "nvidia" + }, + { + "id": "nvidia/nvidia/llama-3.2-nv-embedqa-1b-v2", + "vendor": "nvidia" + }, + { + "id": "nvcf/nvidia/llama-3.2-nv-embedqa-1b-v2", + "vendor": null + }, + { + "id": "nvidia/nvidia/llama-3.2-nv-rerankqa-1b-v2", + "vendor": "nvidia" + }, + { + "id": "nvcf/nvidia/llama-3.2-nv-rerankqa-1b-v2", + "vendor": null + }, + { + "id": "nvidia/meta/llama-3.3-70b-instruct", + "vendor": "meta" + }, + { + "id": "nvcf/meta/llama-3.3-70b-instruct", + "vendor": null + }, + { + "id": "nvcf/meta/llama-3.1-70b-instruct", + "vendor": null + }, + { + "id": "nvidia/meta/llama-3.1-70b-instruct", + "vendor": "meta" + }, + { + "id": "nvidia/nvidia/llama-3.3-nemotron-super-49b-v1", + "vendor": "nvidia" + }, + { + "id": "nvcf/nvidia/llama-3.3-nemotron-super-49b-v1", + "vendor": null + }, + { + "id": "nvidia/nvidia/llama-embed-nemotron-8b", + "vendor": "nvidia" + }, + { + "id": "nvidia/nvidia/llama-nemotron-embed-vl-1b-v2", + "vendor": "nvidia" + }, + { + "id": "nvcf/nvidia/llama-nemotron-embed-vl-1b-v2", + "vendor": null + }, + { + "id": "nvidia/nvidia/llama-nemotron-rerank-vl-1b-v2", + "vendor": "nvidia" + }, + { + "id": "nvidia/nvidia/magpie-tts-multilingual-357m", + "vendor": "nvidia" + }, + { + "id": "nvidia/mistralai/mistral-7b-instruct-v0.3", + "vendor": "mistralai" + }, + { + "id": "nvidia/mistralai/mixtral-8x22b-instruct-v01", + "vendor": "mistralai" + }, + { + "id": "nvidia/nvidia/nemosmith-4b-instruct", + "vendor": "nvidia" + }, + { + "id": "nvidia/nvidia/nemotron-3-embed-1b", + "vendor": "nvidia" + }, + { + "id": "nvidia/nvidia/nemotron-3-nano-omni-30b-a3b-reasoning", + "vendor": "nvidia" + }, + { + "id": "nvidia/nvidia/nemotron-3.5-lightning", + "vendor": "nvidia" + }, + { + "id": "nvidia/nvidia/nemotron-nano-3.5-preview", + "vendor": "nvidia" + }, + { + "id": "nvidia/nvidia/nemotron-nano-12b-v2-vl", + "vendor": "nvidia" + }, + { + "id": "nvidia/nvidia/nemotron-nano-9b-v2", + "vendor": "nvidia" + }, + { + "id": "nvidia/nvidia/Nemotron-3-Nano-30B-A3B", + "vendor": "nvidia" + }, + { + "id": "nvidia/nvidia/nemotron-3-nano-30b-a3b", + "vendor": "nvidia" + }, + { + "id": "nvidia/nvidia/nemotron-nano-31b-v3", + "vendor": "nvidia" + }, + { + "id": "nvcf/nvidia/nemotron-nano-31b-v3", + "vendor": null + }, + { + "id": "nvidia/nvidia/nemotron-ocr-v2-english", + "vendor": "nvidia" + }, + { + "id": "nvidia/nvidia/nemotron-ocr-v2-multilingual", + "vendor": "nvidia" + }, + { + "id": "nvidia/nvidia/nemotron-page-elements-v3", + "vendor": "nvidia" + }, + { + "id": "nvidia/nvidia/nemotron-table-structure-v1", + "vendor": "nvidia" + }, + { + "id": "nvidia/nvidia/nv-embed-qa-v4", + "vendor": "nvidia" + }, + { + "id": "nvidia/nvidia/nv-embedqa-e5-v5", + "vendor": "nvidia" + }, + { + "id": "nvidia/nvidia/nv-rerankqa-mistral-4b-v3", + "vendor": "nvidia" + }, + { + "id": "nvidia/baidu/paddleocr-vl", + "vendor": "baidu" + }, + { + "id": "nvidia/microsoft/phi-4-mini-instruct", + "vendor": "microsoft" + }, + { + "id": "nvidia/qwen/qwen3-32b", + "vendor": "qwen" + }, + { + "id": "nvidia/qwen/qwen3-embedding-0.6b", + "vendor": "qwen" + }, + { + "id": "nvidia/qwen/qwen3-next-80b-a3b-instruct", + "vendor": "qwen" + }, + { + "id": "nvidia/qwen/qwen3-reranker-0.6b", + "vendor": "qwen" + }, + { + "id": "nvidia/qwen/qwen3-reranker-8b", + "vendor": "qwen" + }, + { + "id": "nvidia/qwen/qwen3.5-0.8b", + "vendor": "qwen" + }, + { + "id": "nvidia/qwen/qwen3.5-122b-a10b", + "vendor": "qwen" + }, + { + "id": "nvidia/qwen/qwen3.5-9b", + "vendor": "qwen" + }, + { + "id": "nvidia/qwen/qwen3.6-27b", + "vendor": "qwen" + }, + { + "id": "nvidia/qwen/qwen3.6-35b-a3b", + "vendor": "qwen" + }, + { + "id": "nvidia/qwen/qwen3.5-35b-a3b", + "vendor": "qwen" + }, + { + "id": "nvidia/qwen/qwen3.8-27b", + "vendor": "qwen" + }, + { + "id": "nvidia/nvidia/riva-translate-4b-instruct-v1.1", + "vendor": "nvidia" + }, + { + "id": "gcp/google/gemini-3.1-flash-lite", + "vendor": null + }, + { + "id": "gcp/google/gemini-3.1-flash-lite-image", + "vendor": null + }, + { + "id": "gcp/google/gemini-3.1-pro-preview", + "vendor": null + }, + { + "id": "gcp/google/gemini-3.5-flash", + "vendor": null + }, + { + "id": "gcp/google/gemini-3.5-flash-lite", + "vendor": null + }, + { + "id": "gcp/google/gemini-3.6-flash", + "vendor": null + }, + { + "id": "gcp/google/gemini-3.7-flash", + "vendor": null + }, + { + "id": "gcp/google/gemini-3.8-flash", + "vendor": null + }, + { + "id": "gcp/google/gemini-2.5-pro", + "vendor": null + }, + { + "id": "gcp/google/gemini-2.5-flash", + "vendor": null + }, + { + "id": "gcp/google/gemini-2.5-flash-lite", + "vendor": null + }, + { + "id": "gcp/google/multimodalembedding", + "vendor": null + }, + { + "id": "gcp/google/gemini-embedding-001", + "vendor": null + }, + { + "id": "gcp/google/gemini-embedding-2", + "vendor": null + }, + { + "id": "gcp/google/veo-3.1-generate-001", + "vendor": null + }, + { + "id": "gcp/google/veo-3.0-generate-001", + "vendor": null + }, + { + "id": "gcp/google/gemini-3-flash-preview", + "vendor": null + }, + { + "id": "gcp/google/gemini-3-pro-image", + "vendor": null + }, + { + "id": "gcp/google/gemini-3.1-flash-image", + "vendor": null + }, + { + "id": "gemini-omni-flash-preview", + "vendor": null + }, + { + "id": "fake-openai-endpoint", + "vendor": null + }, + { + "id": "fusion-fake-llm", + "vendor": null + }, + { + "id": "azure/anthropic/claude-sonnet-4-5", + "vendor": null + }, + { + "id": "azure/anthropic/claude-haiku-4-5", + "vendor": null + }, + { + "id": "azure/anthropic/claude-opus-4-5", + "vendor": null + }, + { + "id": "azure/anthropic/claude-opus-4-6", + "vendor": null + }, + { + "id": "azure/anthropic/claude-opus-4-7", + "vendor": null + }, + { + "id": "azure/anthropic/claude-opus-4-8", + "vendor": null + }, + { + "id": "azure/anthropic/claude-opus-5", + "vendor": null + }, + { + "id": "azure/anthropic/claude-sonnet-4-6", + "vendor": null + }, + { + "id": "azure/anthropic/claude-sonnet-5", + "vendor": null + }, + { + "id": "azure/openai/gpt-5.4-nano", + "vendor": null + }, + { + "id": "azure/openai/gpt-5.4-mini", + "vendor": null + }, + { + "id": "azure/openai/gpt-5.6-terra", + "vendor": null + }, + { + "id": "azure/openai/gpt-5.6-luna", + "vendor": null + }, + { + "id": "azure/openai/gpt-5.6-sol", + "vendor": null + }, + { + "id": "azure/openai/gpt-5.3-chat", + "vendor": null + }, + { + "id": "azure/openai/gpt-5.3-codex", + "vendor": null + }, + { + "id": "azure/openai/gpt-5.2-codex", + "vendor": null + }, + { + "id": "azure/openai/gpt-5.2", + "vendor": null + }, + { + "id": "azure/openai/gpt-5.2-chat", + "vendor": null + }, + { + "id": "azure/openai/gpt-5-chat", + "vendor": null + }, + { + "id": "azure/openai/gpt-5-mini", + "vendor": null + }, + { + "id": "azure/openai/gpt-5", + "vendor": null + }, + { + "id": "azure/openai/gpt-5-nano", + "vendor": null + }, + { + "id": "azure/openai/gpt-5.1", + "vendor": null + }, + { + "id": "azure/openai/gpt-5.1-chat", + "vendor": null + }, + { + "id": "azure/openai/gpt-5.1-codex", + "vendor": null + }, + { + "id": "azure/openai/gpt-5.1-codex-mini", + "vendor": null + }, + { + "id": "azure/openai/gpt-4.1", + "vendor": null + }, + { + "id": "azure/openai/gpt-4o", + "vendor": null + }, + { + "id": "azure/openai/o1", + "vendor": null + }, + { + "id": "azure/openai/o3-mini", + "vendor": null + }, + { + "id": "azure/openai/o4-mini", + "vendor": null + }, + { + "id": "azure/openai/text-embedding-3-small", + "vendor": null + }, + { + "id": "azure/openai/text-embedding-3-large", + "vendor": null + }, + { + "id": "azure/openai/o3", + "vendor": null + }, + { + "id": "azure/openai/gpt-4.1-mini", + "vendor": null + }, + { + "id": "azure/openai/gpt-4o-mini", + "vendor": null + }, + { + "id": "azure/openai/gpt-5.1-codex-max", + "vendor": null + }, + { + "id": "azure/openai/text-embedding-ada-002", + "vendor": null + }, + { + "id": "azure/openai/gpt-5.4", + "vendor": null + }, + { + "id": "azure/openai/gpt-5.5", + "vendor": null + }, + { + "id": "azure/openai/gpt-image-2", + "vendor": null + }, + { + "id": "us/azure/openai/gpt-5.3-codex", + "vendor": null + }, + { + "id": "us/azure/openai/gpt-5.4", + "vendor": null + }, + { + "id": "us/azure/openai/gpt-5.2", + "vendor": null + }, + { + "id": "us/azure/openai/gpt-5.1", + "vendor": null + }, + { + "id": "us/azure/openai/gpt-5-mini", + "vendor": null + }, + { + "id": "us/azure/openai/gpt-5", + "vendor": null + }, + { + "id": "us/azure/openai/o3-mini", + "vendor": null + }, + { + "id": "us/azure/openai/o1", + "vendor": null + }, + { + "id": "us/azure/openai/gpt-4.1", + "vendor": null + }, + { + "id": "us/azure/openai/text-embedding-ada-002", + "vendor": null + }, + { + "id": "us/azure/openai/text-embedding-3-small", + "vendor": null + }, + { + "id": "us/azure/openai/text-embedding-3-large", + "vendor": null + }, + { + "id": "us/azure/openai/gpt-5-nano", + "vendor": null + }, + { + "id": "us/azure/openai/gpt-4o-mini", + "vendor": null + }, + { + "id": "us/azure/openai/gpt-4.1-mini", + "vendor": null + }, + { + "id": "us/azure/openai/o4-mini", + "vendor": null + }, + { + "id": "us/azure/openai/gpt-4.1-nano", + "vendor": null + }, + { + "id": "aws/anthropic/bedrock-claude-opus-4-7", + "vendor": null + }, + { + "id": "aws/anthropic/bedrock-claude-opus-4-8", + "vendor": null + }, + { + "id": "aws/anthropic/bedrock-claude-opus-5", + "vendor": null + }, + { + "id": "aws/anthropic/bedrock-claude-sonnet-4-6", + "vendor": null + }, + { + "id": "aws/anthropic/bedrock-claude-opus-4-6", + "vendor": null + }, + { + "id": "aws/anthropic/claude-opus-4-5", + "vendor": null + }, + { + "id": "aws/anthropic/bedrock-claude-sonnet-4-5-v1", + "vendor": null + }, + { + "id": "aws/anthropic/bedrock-claude-sonnet-5", + "vendor": null + }, + { + "id": "aws/anthropic/claude-haiku-4-5-v1", + "vendor": null + }, + { + "id": "openai/openai/gpt-5.5", + "vendor": null + }, + { + "id": "openai/openai/gpt-5.5-batch", + "vendor": null + }, + { + "id": "openai/gpt-realtime-2.1", + "vendor": null + }, + { + "id": "openai/gpt-realtime-2.1-mini", + "vendor": null + }, + { + "id": "openai/openai/gpt-5.6-luna", + "vendor": null + }, + { + "id": "openai/openai/gpt-5.6-sol", + "vendor": null + }, + { + "id": "openai/openai/gpt-5.6-sol-batch", + "vendor": null + }, + { + "id": "openai/openai/gpt-5.6-terra", + "vendor": null + }, + { + "id": "openai/openai/gpt-5.4-mini", + "vendor": null + }, + { + "id": "openai/openai/gpt-5.4-nano", + "vendor": null + }, + { + "id": "openai/openai/gpt-5.4-pro", + "vendor": null + }, + { + "id": "openai/openai/gpt-5.4", + "vendor": null + }, + { + "id": "openai/openai/gpt-5.4-batch", + "vendor": null + }, + { + "id": "openai/openai/gpt-5.3-codex", + "vendor": null + }, + { + "id": "openai/openai/gpt-5.2", + "vendor": null + }, + { + "id": "openai/openai/gpt-5-mini", + "vendor": null + }, + { + "id": "openai/openai/gpt-5-nano", + "vendor": null + }, + { + "id": "openai/openai/gpt-5.1", + "vendor": null + }, + { + "id": "openai/openai/gpt-3.5-turbo", + "vendor": null + }, + { + "id": "openai/openai/gpt-image-2", + "vendor": null + }, + { + "id": "openai/openai/sora-2", + "vendor": null + }, + { + "id": "openai/openai/sora-2-pro", + "vendor": null + }, + { + "id": "openai/openai/gpt-4o-mini", + "vendor": null + }, + { + "id": "openai/openai/gpt-4o-mini-tts", + "vendor": null + }, + { + "id": "openai/openai/gpt-4o-mini-transcribe", + "vendor": null + }, + { + "id": "openai/openai/gpt-transcribe", + "vendor": null + }, + { + "id": "openai/openai/text-embedding-3-large", + "vendor": null + }, + { + "id": "openai/openai/text-embedding-3-small", + "vendor": null + }, + { + "id": "openai/openai/text-embedding-ada-002", + "vendor": null + }, + { + "id": "cognition/nvidia/nemotron-3-ultra-evals", + "vendor": null + }, + { + "id": "nvidia/nvidia/nemotron-3-ultra", + "vendor": "nvidia" + }, + { + "id": "nvidia/deepseek-ai/deepseek-v4-flash", + "vendor": "deepseek-ai" + }, + { + "id": "nvidia/deepseek-ai/deepseek-v4-pro", + "vendor": "deepseek-ai" + }, + { + "id": "nvidia/moonshotai/kimi-k2.7", + "vendor": "moonshotai" + }, + { + "id": "nvidia/moonshotai/kimi-k2.6", + "vendor": "moonshotai" + }, + { + "id": "nvidia/moonshotai/kimi-k2.5", + "vendor": "moonshotai" + }, + { + "id": "cognition/zai-org/glm-5.3-flash", + "vendor": null + }, + { + "id": "nvidia/zai-org/glm-5.3-flash", + "vendor": "zai-org" + }, + { + "id": "nvidia/zai-org/glm-5.2", + "vendor": "zai-org" + }, + { + "id": "nvidia/zai-org/glm-5.1", + "vendor": "zai-org" + }, + { + "id": "nvidia/moonshotai/kimi-k3", + "vendor": "moonshotai" + }, + { + "id": "xai/xai/grok-4.6", + "vendor": null + }, + { + "id": "xai/grok-voice-latest", + "vendor": null + }, + { + "id": "perplexity/perplexity/sonar", + "vendor": null + }, + { + "id": "perplexity/perplexity/sonar-pro", + "vendor": null + }, + { + "id": "perplexity/perplexity/sonar-reasoning-pro", + "vendor": null + }, + { + "id": "perplexity/perplexity/sonar-deep-research", + "vendor": null + }, + { + "id": "nvidia/openai/gpt-oss-120b", + "vendor": "openai" + }, + { + "id": "nvcf/openai/gpt-oss-120b", + "vendor": null + }, + { + "id": "nvidia/nvidia/nemotron-3.5-super-text-preview", + "vendor": "nvidia" + }, + { + "id": "nvidia/qwen/qwen-235b", + "vendor": "qwen" + }, + { + "id": "nvidia/qwen/qwen3.8-flash-next", + "vendor": "qwen" + }, + { + "id": "nvidia/nvidia/nemotron-3.5-super-vl-preview", + "vendor": "nvidia" + }, + { + "id": "nvidia/nvidia/super-sft-charxiv-babyvision-blend", + "vendor": "nvidia" + }, + { + "id": "nvidia/nvidia/nemotron-3-super-v3", + "vendor": "nvidia" + }, + { + "id": "nvidia/nvidia/nemotron-3-super-preview", + "vendor": "nvidia" + }, + { + "id": "nvidia/nvidia/nemotron-3-super-rc-nim", + "vendor": "nvidia" + }, + { + "id": "nvidia/nvidia/cosmos3-super-reasoner", + "vendor": "nvidia" + }, + { + "id": "nvidia/nvidia/nemotron-3-ultra-nvfp4", + "vendor": "nvidia" + }, + { + "id": "nvidia/nvidia/evals-nemotron-3-nano", + "vendor": "nvidia" + }, + { + "id": "nvidia/nvidia/evals-nemotron-3-30b-a3b", + "vendor": "nvidia" + }, + { + "id": "nvidia/nvidia/nemotron-3-super-120b-long-ctx", + "vendor": "nvidia" + }, + { + "id": "nvidia_dynamo/deepseek-ai/deepseek-v4-flash-nvfp4-elb", + "vendor": null + }, + { + "id": "nvidia_dynamo/moonshotai/kimi-k2.6-nvfp4-elb", + "vendor": null + }, + { + "id": "nvidia/nvidia/llama-3.3-nemotron-super-49b-v1.5", + "vendor": "nvidia" + }, + { + "id": "nvidia/qwen/qwen3-5-397b-a17b", + "vendor": "qwen" + }, + { + "id": "nvidia/minimaxai/minimax-m2.7", + "vendor": "minimaxai" + }, + { + "id": "nvidia/minimaxai/minimax-m3", + "vendor": "minimaxai" + }, + { + "id": "nvidia/meta/evals-muse-glimmer-30b", + "vendor": "meta" + }, + { + "id": "nvidia/google/evals-gemma-4-31b", + "vendor": "google" + }, + { + "id": "nvidia/google/evals-gemma-4-26b-a4b", + "vendor": "google" + }, + { + "id": "nvidia/zai-org/glm-5.3", + "vendor": "zai-org" + }, + { + "id": "nvidia/thinkingmachines/inkling", + "vendor": "thinkingmachines" + }, + { + "id": "nvidia/thinkingmachines/inkling-small", + "vendor": "thinkingmachines" + }, + { + "id": "switchyard/openai/gpt-5.6-luna", + "vendor": null + }, + { + "id": "switchyard/openai/gpt-5.6-sol", + "vendor": null + }, + { + "id": "switchyard/openai/gpt-5.6-terra", + "vendor": null + }, + { + "id": "switchyard/openai/gpt-5.5", + "vendor": null + }, + { + "id": "switchyard/openai/gpt-5.4", + "vendor": null + }, + { + "id": "switchyard/openai/gpt-5.2", + "vendor": null + }, + { + "id": "switchyard/openai/gpt-5.1", + "vendor": null + }, + { + "id": "switchyard/openai/gpt-5.3-codex", + "vendor": null + }, + { + "id": "switchyard/openai/gpt-5-mini", + "vendor": null + }, + { + "id": "switchyard/openai/gpt-5-nano", + "vendor": null + }, + { + "id": "switchyard/openai/gpt-4o-mini", + "vendor": null + } + ], + "azure": [ + { + "id": "azure/ada", + "vendor": null + }, + { + "id": "azure/codex-mini", + "vendor": null + }, + { + "id": "azure/command-r-plus", + "vendor": "cohere" + }, + { + "id": "azure/computer-use-preview", + "vendor": null + }, + { + "id": "azure/container", + "vendor": null + }, + { + "id": "azure/eu/gpt-4o-2024-08-06", + "vendor": "openai" + }, + { + "id": "azure/eu/gpt-4o-2024-11-20", + "vendor": "openai" + }, + { + "id": "azure/eu/gpt-4o-mini-2024-07-18", + "vendor": "openai" + }, + { + "id": "azure/eu/gpt-4o-mini-realtime-preview-2024-12-17", + "vendor": "openai" + }, + { + "id": "azure/eu/gpt-4o-realtime-preview-2024-10-01", + "vendor": null + }, + { + "id": "azure/eu/gpt-4o-realtime-preview-2024-12-17", + "vendor": "openai" + }, + { + "id": "azure/eu/gpt-5-2025-08-07", + "vendor": "openai" + }, + { + "id": "azure/eu/gpt-5-mini-2025-08-07", + "vendor": "openai" + }, + { + "id": "azure/eu/gpt-5-nano-2025-08-07", + "vendor": "openai" + }, + { + "id": "azure/eu/gpt-5.1", + "vendor": "openai" + }, + { + "id": "azure/eu/gpt-5.1-chat", + "vendor": null + }, + { + "id": "azure/eu/gpt-5.1-codex", + "vendor": "openai" + }, + { + "id": "azure/eu/gpt-5.1-codex-mini", + "vendor": "openai" + }, + { + "id": "azure/eu/gpt-5.4", + "vendor": "openai" + }, + { + "id": "azure/eu/gpt-5.4-2026-03-05", + "vendor": "openai" + }, + { + "id": "azure/eu/gpt-5.5", + "vendor": "openai" + }, + { + "id": "azure/eu/gpt-5.5-2026-04-23", + "vendor": "openai" + }, + { + "id": "azure/eu/gpt-5.6", + "vendor": "openai" + }, + { + "id": "azure/eu/gpt-5.6-luna", + "vendor": "openai" + }, + { + "id": "azure/eu/gpt-5.6-sol", + "vendor": "openai" + }, + { + "id": "azure/eu/gpt-5.6-terra", + "vendor": "openai" + }, + { + "id": "azure/eu/o1-2024-12-17", + "vendor": "openai" + }, + { + "id": "azure/eu/o1-mini-2024-09-12", + "vendor": null + }, + { + "id": "azure/eu/o1-preview-2024-09-12", + "vendor": null + }, + { + "id": "azure/eu/o3-mini-2025-01-31", + "vendor": "openai" + }, + { + "id": "azure/global-standard/gpt-4o-2024-08-06", + "vendor": "openai" + }, + { + "id": "azure/global-standard/gpt-4o-2024-11-20", + "vendor": "openai" + }, + { + "id": "azure/global-standard/gpt-4o-mini", + "vendor": "openai" + }, + { + "id": "azure/global/gpt-4o-2024-08-06", + "vendor": "openai" + }, + { + "id": "azure/global/gpt-4o-2024-11-20", + "vendor": "openai" + }, + { + "id": "azure/global/gpt-5.1", + "vendor": "openai" + }, + { + "id": "azure/global/gpt-5.1-chat", + "vendor": null + }, + { + "id": "azure/global/gpt-5.1-codex", + "vendor": "openai" + }, + { + "id": "azure/global/gpt-5.1-codex-mini", + "vendor": "openai" + }, + { + "id": "azure/gpt-3.5-turbo", + "vendor": "openai" + }, + { + "id": "azure/gpt-3.5-turbo-0125", + "vendor": "openai" + }, + { + "id": "azure/gpt-3.5-turbo-instruct-0914", + "vendor": "openai" + }, + { + "id": "azure/gpt-35-turbo", + "vendor": null + }, + { + "id": "azure/gpt-35-turbo-0125", + "vendor": null + }, + { + "id": "azure/gpt-35-turbo-1106", + "vendor": null + }, + { + "id": "azure/gpt-35-turbo-16k", + "vendor": null + }, + { + "id": "azure/gpt-35-turbo-16k-0613", + "vendor": null + }, + { + "id": "azure/gpt-35-turbo-instruct", + "vendor": null + }, + { + "id": "azure/gpt-35-turbo-instruct-0914", + "vendor": null + }, + { + "id": "azure/gpt-4", + "vendor": "openai" + }, + { + "id": "azure/gpt-4-0125-preview", + "vendor": "openai" + }, + { + "id": "azure/gpt-4-0613", + "vendor": "openai" + }, + { + "id": "azure/gpt-4-1106-preview", + "vendor": "openai" + }, + { + "id": "azure/gpt-4-32k", + "vendor": null + }, + { + "id": "azure/gpt-4-32k-0613", + "vendor": null + }, + { + "id": "azure/gpt-4-turbo", + "vendor": "openai" + }, + { + "id": "azure/gpt-4-turbo-2024-04-09", + "vendor": "openai" + }, + { + "id": "azure/gpt-4-turbo-vision-preview", + "vendor": null + }, + { + "id": "azure/gpt-4.1", + "vendor": "openai" + }, + { + "id": "azure/gpt-4.1-2025-04-14", + "vendor": "openai" + }, + { + "id": "azure/gpt-4.1-mini", + "vendor": "openai" + }, + { + "id": "azure/gpt-4.1-mini-2025-04-14", + "vendor": "openai" + }, + { + "id": "azure/gpt-4.1-nano", + "vendor": "openai" + }, + { + "id": "azure/gpt-4.1-nano-2025-04-14", + "vendor": "openai" + }, + { + "id": "azure/gpt-4.5-preview", + "vendor": null + }, + { + "id": "azure/gpt-4o", + "vendor": "openai" + }, + { + "id": "azure/gpt-4o-2024-05-13", + "vendor": "openai" + }, + { + "id": "azure/gpt-4o-2024-08-06", + "vendor": "openai" + }, + { + "id": "azure/gpt-4o-2024-11-20", + "vendor": "openai" + }, + { + "id": "azure/gpt-4o-audio-preview-2024-12-17", + "vendor": "openai" + }, + { + "id": "azure/gpt-4o-mini", + "vendor": "openai" + }, + { + "id": "azure/gpt-4o-mini-2024-07-18", + "vendor": "openai" + }, + { + "id": "azure/gpt-4o-mini-audio-preview-2024-12-17", + "vendor": "openai" + }, + { + "id": "azure/gpt-4o-mini-realtime-preview-2024-12-17", + "vendor": "openai" + }, + { + "id": "azure/gpt-4o-mini-transcribe", + "vendor": "openai" + }, + { + "id": "azure/gpt-4o-mini-tts", + "vendor": "openai" + }, + { + "id": "azure/gpt-4o-realtime-preview-2024-10-01", + "vendor": null + }, + { + "id": "azure/gpt-4o-realtime-preview-2024-12-17", + "vendor": "openai" + }, + { + "id": "azure/gpt-4o-transcribe", + "vendor": "openai" + }, + { + "id": "azure/gpt-4o-transcribe-diarize", + "vendor": "openai" + }, + { + "id": "azure/gpt-5", + "vendor": "openai" + }, + { + "id": "azure/gpt-5-2025-08-07", + "vendor": "openai" + }, + { + "id": "azure/gpt-5-chat", + "vendor": "openai" + }, + { + "id": "azure/gpt-5-chat-latest", + "vendor": "openai" + }, + { + "id": "azure/gpt-5-codex", + "vendor": "openai" + }, + { + "id": "azure/gpt-5-mini", + "vendor": "openai" + }, + { + "id": "azure/gpt-5-mini-2025-08-07", + "vendor": "openai" + }, + { + "id": "azure/gpt-5-nano", + "vendor": "openai" + }, + { + "id": "azure/gpt-5-nano-2025-08-07", + "vendor": "openai" + }, + { + "id": "azure/gpt-5-pro", + "vendor": "openai" + }, + { + "id": "azure/gpt-5.1", + "vendor": "openai" + }, + { + "id": "azure/gpt-5.1-2025-11-13", + "vendor": "openai" + }, + { + "id": "azure/gpt-5.1-chat", + "vendor": null + }, + { + "id": "azure/gpt-5.1-chat-2025-11-13", + "vendor": null + }, + { + "id": "azure/gpt-5.1-codex", + "vendor": "openai" + }, + { + "id": "azure/gpt-5.1-codex-2025-11-13", + "vendor": null + }, + { + "id": "azure/gpt-5.1-codex-max", + "vendor": "openai" + }, + { + "id": "azure/gpt-5.1-codex-mini", + "vendor": "openai" + }, + { + "id": "azure/gpt-5.1-codex-mini-2025-11-13", + "vendor": null + }, + { + "id": "azure/gpt-5.2", + "vendor": "openai" + }, + { + "id": "azure/gpt-5.2-2025-12-11", + "vendor": "openai" + }, + { + "id": "azure/gpt-5.2-chat", + "vendor": null + }, + { + "id": "azure/gpt-5.2-chat-2025-12-11", + "vendor": null + }, + { + "id": "azure/gpt-5.2-codex", + "vendor": "openai" + }, + { + "id": "azure/gpt-5.2-pro", + "vendor": "openai" + }, + { + "id": "azure/gpt-5.2-pro-2025-12-11", + "vendor": "openai" + }, + { + "id": "azure/gpt-5.3-chat", + "vendor": null + }, + { + "id": "azure/gpt-5.3-codex", + "vendor": "openai" + }, + { + "id": "azure/gpt-5.4", + "vendor": "openai" + }, + { + "id": "azure/gpt-5.4-2026-03-05", + "vendor": "openai" + }, + { + "id": "azure/gpt-5.4-mini", + "vendor": "openai" + }, + { + "id": "azure/gpt-5.4-mini-2026-03-17", + "vendor": "openai" + }, + { + "id": "azure/gpt-5.4-nano", + "vendor": "openai" + }, + { + "id": "azure/gpt-5.4-nano-2026-03-17", + "vendor": "openai" + }, + { + "id": "azure/gpt-5.4-pro", + "vendor": "openai" + }, + { + "id": "azure/gpt-5.4-pro-2026-03-05", + "vendor": "openai" + }, + { + "id": "azure/gpt-5.5", + "vendor": "openai" + }, + { + "id": "azure/gpt-5.5-2026-04-23", + "vendor": "openai" + }, + { + "id": "azure/gpt-5.5-pro", + "vendor": "openai" + }, + { + "id": "azure/gpt-5.5-pro-2026-04-23", + "vendor": "openai" + }, + { + "id": "azure/gpt-5.6", + "vendor": "openai" + }, + { + "id": "azure/gpt-5.6-luna", + "vendor": "openai" + }, + { + "id": "azure/gpt-5.6-sol", + "vendor": "openai" + }, + { + "id": "azure/gpt-5.6-terra", + "vendor": "openai" + }, + { + "id": "azure/gpt-audio-1.5-2026-02-23", + "vendor": null + }, + { + "id": "azure/gpt-audio-2025-08-28", + "vendor": "openai" + }, + { + "id": "azure/gpt-audio-mini-2025-10-06", + "vendor": "openai" + }, + { + "id": "azure/gpt-image-1", + "vendor": "openai" + }, + { + "id": "azure/gpt-image-1-mini", + "vendor": "openai" + }, + { + "id": "azure/gpt-image-1.5", + "vendor": "openai" + }, + { + "id": "azure/gpt-image-1.5-2025-12-16", + "vendor": "openai" + }, + { + "id": "azure/gpt-image-2", + "vendor": "openai" + }, + { + "id": "azure/gpt-image-2-2026-04-21", + "vendor": "openai" + }, + { + "id": "azure/gpt-realtime-1.5-2026-02-23", + "vendor": null + }, + { + "id": "azure/gpt-realtime-2025-08-28", + "vendor": "openai" + }, + { + "id": "azure/gpt-realtime-mini-2025-10-06", + "vendor": "openai" + }, + { + "id": "azure/gpt-realtime-whisper", + "vendor": "openai" + }, + { + "id": "azure/hd/1024-x-1024/dall-e-3", + "vendor": "openai" + }, + { + "id": "azure/hd/1024-x-1792/dall-e-3", + "vendor": "openai" + }, + { + "id": "azure/hd/1792-x-1024/dall-e-3", + "vendor": "openai" + }, + { + "id": "azure/high/1024-x-1024/gpt-image-1", + "vendor": "openai" + }, + { + "id": "azure/high/1024-x-1024/gpt-image-1-mini", + "vendor": "openai" + }, + { + "id": "azure/high/1024-x-1536/gpt-image-1", + "vendor": "openai" + }, + { + "id": "azure/high/1024-x-1536/gpt-image-1-mini", + "vendor": "openai" + }, + { + "id": "azure/high/1536-x-1024/gpt-image-1", + "vendor": "openai" + }, + { + "id": "azure/high/1536-x-1024/gpt-image-1-mini", + "vendor": "openai" + }, + { + "id": "azure/low/1024-x-1024/gpt-image-1", + "vendor": "openai" + }, + { + "id": "azure/low/1024-x-1024/gpt-image-1-mini", + "vendor": "openai" + }, + { + "id": "azure/low/1024-x-1536/gpt-image-1", + "vendor": "openai" + }, + { + "id": "azure/low/1024-x-1536/gpt-image-1-mini", + "vendor": "openai" + }, + { + "id": "azure/low/1536-x-1024/gpt-image-1", + "vendor": "openai" + }, + { + "id": "azure/low/1536-x-1024/gpt-image-1-mini", + "vendor": "openai" + }, + { + "id": "azure/medium/1024-x-1024/gpt-image-1", + "vendor": "openai" + }, + { + "id": "azure/medium/1024-x-1024/gpt-image-1-mini", + "vendor": "openai" + }, + { + "id": "azure/medium/1024-x-1536/gpt-image-1", + "vendor": "openai" + }, + { + "id": "azure/medium/1024-x-1536/gpt-image-1-mini", + "vendor": "openai" + }, + { + "id": "azure/medium/1536-x-1024/gpt-image-1", + "vendor": "openai" + }, + { + "id": "azure/medium/1536-x-1024/gpt-image-1-mini", + "vendor": "openai" + }, + { + "id": "azure/mistral-large-2402", + "vendor": null + }, + { + "id": "azure/mistral-large-latest", + "vendor": null + }, + { + "id": "azure/o1", + "vendor": "openai" + }, + { + "id": "azure/o1-2024-12-17", + "vendor": "openai" + }, + { + "id": "azure/o1-mini", + "vendor": null + }, + { + "id": "azure/o1-mini-2024-09-12", + "vendor": null + }, + { + "id": "azure/o1-preview", + "vendor": null + }, + { + "id": "azure/o1-preview-2024-09-12", + "vendor": null + }, + { + "id": "azure/o3", + "vendor": "openai" + }, + { + "id": "azure/o3-2025-04-16", + "vendor": "openai" + }, + { + "id": "azure/o3-deep-research", + "vendor": "openai" + }, + { + "id": "azure/o3-mini", + "vendor": "openai" + }, + { + "id": "azure/o3-mini-2025-01-31", + "vendor": "openai" + }, + { + "id": "azure/o3-pro", + "vendor": "openai" + }, + { + "id": "azure/o3-pro-2025-06-10", + "vendor": "openai" + }, + { + "id": "azure/o4-mini", + "vendor": "openai" + }, + { + "id": "azure/o4-mini-2025-04-16", + "vendor": "openai" + }, + { + "id": "azure/sora-2", + "vendor": "openai" + }, + { + "id": "azure/sora-2-pro", + "vendor": "openai" + }, + { + "id": "azure/sora-2-pro-high-res", + "vendor": "openai" + }, + { + "id": "azure/speech/azure-stt", + "vendor": null + }, + { + "id": "azure/speech/azure-tts", + "vendor": null + }, + { + "id": "azure/speech/azure-tts-hd", + "vendor": null + }, + { + "id": "azure/standard/1024-x-1024/dall-e-2", + "vendor": "openai" + }, + { + "id": "azure/standard/1024-x-1024/dall-e-3", + "vendor": "openai" + }, + { + "id": "azure/standard/1024-x-1792/dall-e-3", + "vendor": "openai" + }, + { + "id": "azure/standard/1792-x-1024/dall-e-3", + "vendor": "openai" + }, + { + "id": "azure/text-embedding-3-large", + "vendor": "openai" + }, + { + "id": "azure/text-embedding-3-small", + "vendor": "openai" + }, + { + "id": "azure/text-embedding-ada-002", + "vendor": "openai" + }, + { + "id": "azure/tts-1", + "vendor": "openai" + }, + { + "id": "azure/tts-1-hd", + "vendor": "openai" + }, + { + "id": "azure/us/gpt-4.1-2025-04-14", + "vendor": "openai" + }, + { + "id": "azure/us/gpt-4.1-mini-2025-04-14", + "vendor": "openai" + }, + { + "id": "azure/us/gpt-4.1-nano-2025-04-14", + "vendor": "openai" + }, + { + "id": "azure/us/gpt-4o-2024-08-06", + "vendor": "openai" + }, + { + "id": "azure/us/gpt-4o-2024-11-20", + "vendor": "openai" + }, + { + "id": "azure/us/gpt-4o-mini-2024-07-18", + "vendor": "openai" + }, + { + "id": "azure/us/gpt-4o-mini-realtime-preview-2024-12-17", + "vendor": "openai" + }, + { + "id": "azure/us/gpt-4o-realtime-preview-2024-10-01", + "vendor": null + }, + { + "id": "azure/us/gpt-4o-realtime-preview-2024-12-17", + "vendor": "openai" + }, + { + "id": "azure/us/gpt-5-2025-08-07", + "vendor": "openai" + }, + { + "id": "azure/us/gpt-5-mini-2025-08-07", + "vendor": "openai" + }, + { + "id": "azure/us/gpt-5-nano-2025-08-07", + "vendor": "openai" + }, + { + "id": "azure/us/gpt-5.1", + "vendor": "openai" + }, + { + "id": "azure/us/gpt-5.1-chat", + "vendor": null + }, + { + "id": "azure/us/gpt-5.1-codex", + "vendor": "openai" + }, + { + "id": "azure/us/gpt-5.1-codex-mini", + "vendor": "openai" + }, + { + "id": "azure/us/gpt-5.4", + "vendor": "openai" + }, + { + "id": "azure/us/gpt-5.4-2026-03-05", + "vendor": "openai" + }, + { + "id": "azure/us/gpt-5.5", + "vendor": "openai" + }, + { + "id": "azure/us/gpt-5.5-2026-04-23", + "vendor": "openai" + }, + { + "id": "azure/us/gpt-5.6", + "vendor": "openai" + }, + { + "id": "azure/us/gpt-5.6-luna", + "vendor": "openai" + }, + { + "id": "azure/us/gpt-5.6-sol", + "vendor": "openai" + }, + { + "id": "azure/us/gpt-5.6-terra", + "vendor": "openai" + }, + { + "id": "azure/us/o1-2024-12-17", + "vendor": "openai" + }, + { + "id": "azure/us/o1-mini-2024-09-12", + "vendor": null + }, + { + "id": "azure/us/o1-preview-2024-09-12", + "vendor": null + }, + { + "id": "azure/us/o3-2025-04-16", + "vendor": "openai" + }, + { + "id": "azure/us/o3-mini-2025-01-31", + "vendor": "openai" + }, + { + "id": "azure/us/o4-mini-2025-04-16", + "vendor": "openai" + }, + { + "id": "azure/whisper-1", + "vendor": "openai" + } + ], + "vertex_ai": [ + { + "id": "vertex_ai/chirp", + "vendor": null + }, + { + "id": "vertex_ai/chirp_3", + "vendor": null + }, + { + "id": "vertex_ai/claude-3-5-haiku", + "vendor": null + }, + { + "id": "vertex_ai/claude-3-5-haiku@20241022", + "vendor": null + }, + { + "id": "vertex_ai/claude-3-5-sonnet", + "vendor": null + }, + { + "id": "vertex_ai/claude-3-5-sonnet@20240620", + "vendor": null + }, + { + "id": "vertex_ai/claude-3-7-sonnet@20250219", + "vendor": null + }, + { + "id": "vertex_ai/claude-3-haiku", + "vendor": null + }, + { + "id": "vertex_ai/claude-3-haiku@20240307", + "vendor": null + }, + { + "id": "vertex_ai/claude-3-opus", + "vendor": null + }, + { + "id": "vertex_ai/claude-3-opus@20240229", + "vendor": null + }, + { + "id": "vertex_ai/claude-3-sonnet", + "vendor": null + }, + { + "id": "vertex_ai/claude-3-sonnet@20240229", + "vendor": null + }, + { + "id": "vertex_ai/claude-fable-5", + "vendor": "anthropic" + }, + { + "id": "vertex_ai/claude-fable-5@default", + "vendor": "anthropic" + }, + { + "id": "vertex_ai/claude-haiku-4-5", + "vendor": "anthropic" + }, + { + "id": "vertex_ai/claude-haiku-4-5@20251001", + "vendor": "anthropic" + }, + { + "id": "vertex_ai/claude-opus-4", + "vendor": null + }, + { + "id": "vertex_ai/claude-opus-4-1", + "vendor": "anthropic" + }, + { + "id": "vertex_ai/claude-opus-4-1@20250805", + "vendor": "anthropic" + }, + { + "id": "vertex_ai/claude-opus-4-5", + "vendor": "anthropic" + }, + { + "id": "vertex_ai/claude-opus-4-5@20251101", + "vendor": "anthropic" + }, + { + "id": "vertex_ai/claude-opus-4-6", + "vendor": "anthropic" + }, + { + "id": "vertex_ai/claude-opus-4-6@default", + "vendor": "anthropic" + }, + { + "id": "vertex_ai/claude-opus-4-7", + "vendor": "anthropic" + }, + { + "id": "vertex_ai/claude-opus-4-7@default", + "vendor": "anthropic" + }, + { + "id": "vertex_ai/claude-opus-4-8", + "vendor": "anthropic" + }, + { + "id": "vertex_ai/claude-opus-4-8@default", + "vendor": "anthropic" + }, + { + "id": "vertex_ai/claude-opus-4@20250514", + "vendor": null + }, + { + "id": "vertex_ai/claude-opus-5", + "vendor": "anthropic" + }, + { + "id": "vertex_ai/claude-opus-5@default", + "vendor": "anthropic" + }, + { + "id": "vertex_ai/claude-sonnet-4", + "vendor": null + }, + { + "id": "vertex_ai/claude-sonnet-4-5", + "vendor": "anthropic" + }, + { + "id": "vertex_ai/claude-sonnet-4-5@20250929", + "vendor": "anthropic" + }, + { + "id": "vertex_ai/claude-sonnet-4-6", + "vendor": "anthropic" + }, + { + "id": "vertex_ai/claude-sonnet-4-6@default", + "vendor": "anthropic" + }, + { + "id": "vertex_ai/claude-sonnet-4@20250514", + "vendor": null + }, + { + "id": "vertex_ai/claude-sonnet-5", + "vendor": "anthropic" + }, + { + "id": "vertex_ai/claude-sonnet-5@default", + "vendor": "anthropic" + }, + { + "id": "vertex_ai/codestral-2", + "vendor": null + }, + { + "id": "vertex_ai/codestral-2501", + "vendor": null + }, + { + "id": "vertex_ai/codestral-2@001", + "vendor": null + }, + { + "id": "vertex_ai/codestral@2405", + "vendor": null + }, + { + "id": "vertex_ai/codestral@latest", + "vendor": null + }, + { + "id": "vertex_ai/deep-research-pro-preview-12-2025", + "vendor": null + }, + { + "id": "vertex_ai/deepseek-ai/deepseek-ocr-maas", + "vendor": null + }, + { + "id": "vertex_ai/deepseek-ai/deepseek-r1-0528-maas", + "vendor": null + }, + { + "id": "vertex_ai/deepseek-ai/deepseek-v3.1-maas", + "vendor": null + }, + { + "id": "vertex_ai/deepseek-ai/deepseek-v3.2-maas", + "vendor": null + }, + { + "id": "vertex_ai/gemini-2.5-flash-image", + "vendor": null + }, + { + "id": "vertex_ai/gemini-3-flash-preview", + "vendor": null + }, + { + "id": "vertex_ai/gemini-3-pro-image", + "vendor": null + }, + { + "id": "vertex_ai/gemini-3-pro-image-preview", + "vendor": null + }, + { + "id": "vertex_ai/gemini-3-pro-preview", + "vendor": null + }, + { + "id": "vertex_ai/gemini-3.1-flash-image", + "vendor": null + }, + { + "id": "vertex_ai/gemini-3.1-flash-image-preview", + "vendor": null + }, + { + "id": "vertex_ai/gemini-3.1-flash-lite", + "vendor": null + }, + { + "id": "vertex_ai/gemini-3.1-flash-lite-preview", + "vendor": null + }, + { + "id": "vertex_ai/gemini-3.1-pro-preview", + "vendor": null + }, + { + "id": "vertex_ai/gemini-3.1-pro-preview-customtools", + "vendor": null + }, + { + "id": "vertex_ai/gemini-3.5-flash", + "vendor": null + }, + { + "id": "vertex_ai/gemini-3.5-flash-lite", + "vendor": null + }, + { + "id": "vertex_ai/gemini-3.6-flash", + "vendor": null + }, + { + "id": "vertex_ai/gemini-embedding-2", + "vendor": "google" + }, + { + "id": "vertex_ai/gemini-embedding-2-preview", + "vendor": "google" + }, + { + "id": "vertex_ai/google/gemma-4-26b-a4b-it-maas", + "vendor": null + }, + { + "id": "vertex_ai/imagegeneration@006", + "vendor": null + }, + { + "id": "vertex_ai/imagen-3.0-capability-001", + "vendor": null + }, + { + "id": "vertex_ai/imagen-3.0-fast-generate-001", + "vendor": null + }, + { + "id": "vertex_ai/imagen-3.0-generate-001", + "vendor": null + }, + { + "id": "vertex_ai/imagen-3.0-generate-002", + "vendor": null + }, + { + "id": "vertex_ai/imagen-4.0-fast-generate-001", + "vendor": null + }, + { + "id": "vertex_ai/imagen-4.0-generate-001", + "vendor": null + }, + { + "id": "vertex_ai/imagen-4.0-ultra-generate-001", + "vendor": null + }, + { + "id": "vertex_ai/jamba-1.5", + "vendor": "ai21" + }, + { + "id": "vertex_ai/jamba-1.5-large", + "vendor": "ai21" + }, + { + "id": "vertex_ai/jamba-1.5-large@001", + "vendor": "ai21" + }, + { + "id": "vertex_ai/jamba-1.5-mini", + "vendor": "ai21" + }, + { + "id": "vertex_ai/jamba-1.5-mini@001", + "vendor": "ai21" + }, + { + "id": "vertex_ai/meta/llama-3.1-405b-instruct-maas", + "vendor": null + }, + { + "id": "vertex_ai/meta/llama-3.1-70b-instruct-maas", + "vendor": null + }, + { + "id": "vertex_ai/meta/llama-3.1-8b-instruct-maas", + "vendor": null + }, + { + "id": "vertex_ai/meta/llama-3.2-90b-vision-instruct-maas", + "vendor": null + }, + { + "id": "vertex_ai/meta/llama-4-maverick-17b-128e-instruct-maas", + "vendor": null + }, + { + "id": "vertex_ai/meta/llama-4-maverick-17b-16e-instruct-maas", + "vendor": null + }, + { + "id": "vertex_ai/meta/llama-4-scout-17b-128e-instruct-maas", + "vendor": null + }, + { + "id": "vertex_ai/meta/llama-4-scout-17b-16e-instruct-maas", + "vendor": null + }, + { + "id": "vertex_ai/meta/llama3-405b-instruct-maas", + "vendor": null + }, + { + "id": "vertex_ai/meta/llama3-70b-instruct-maas", + "vendor": null + }, + { + "id": "vertex_ai/meta/llama3-8b-instruct-maas", + "vendor": null + }, + { + "id": "vertex_ai/minimaxai/minimax-m2-maas", + "vendor": null + }, + { + "id": "vertex_ai/mistral-large-2411", + "vendor": null + }, + { + "id": "vertex_ai/mistral-large@2407", + "vendor": null + }, + { + "id": "vertex_ai/mistral-large@2411-001", + "vendor": null + }, + { + "id": "vertex_ai/mistral-large@latest", + "vendor": null + }, + { + "id": "vertex_ai/mistral-medium-3", + "vendor": null + }, + { + "id": "vertex_ai/mistral-medium-3@001", + "vendor": null + }, + { + "id": "vertex_ai/mistral-nemo@2407", + "vendor": null + }, + { + "id": "vertex_ai/mistral-nemo@latest", + "vendor": null + }, + { + "id": "vertex_ai/mistral-ocr-2505", + "vendor": null + }, + { + "id": "vertex_ai/mistral-small-2503", + "vendor": null + }, + { + "id": "vertex_ai/mistral-small-2503@001", + "vendor": null + }, + { + "id": "vertex_ai/mistralai/codestral-2", + "vendor": null + }, + { + "id": "vertex_ai/mistralai/codestral-2@001", + "vendor": null + }, + { + "id": "vertex_ai/mistralai/mistral-medium-3", + "vendor": null + }, + { + "id": "vertex_ai/mistralai/mistral-medium-3@001", + "vendor": null + }, + { + "id": "vertex_ai/moonshotai/kimi-k2-thinking-maas", + "vendor": null + }, + { + "id": "vertex_ai/openai/gpt-oss-120b-maas", + "vendor": null + }, + { + "id": "vertex_ai/openai/gpt-oss-20b-maas", + "vendor": null + }, + { + "id": "vertex_ai/qwen/qwen3-235b-a22b-instruct-2507-maas", + "vendor": null + }, + { + "id": "vertex_ai/qwen/qwen3-coder-480b-a35b-instruct-maas", + "vendor": null + }, + { + "id": "vertex_ai/qwen/qwen3-next-80b-a3b-instruct-maas", + "vendor": null + }, + { + "id": "vertex_ai/qwen/qwen3-next-80b-a3b-thinking-maas", + "vendor": null + }, + { + "id": "vertex_ai/search_api", + "vendor": null + }, + { + "id": "vertex_ai/veo-2.0-generate-001", + "vendor": null + }, + { + "id": "vertex_ai/veo-3.0-fast-generate-001", + "vendor": null + }, + { + "id": "vertex_ai/veo-3.0-generate-001", + "vendor": null + }, + { + "id": "vertex_ai/veo-3.1-fast-generate-001", + "vendor": null + }, + { + "id": "vertex_ai/veo-3.1-fast-generate-preview", + "vendor": null + }, + { + "id": "vertex_ai/veo-3.1-generate-001", + "vendor": null + }, + { + "id": "vertex_ai/veo-3.1-generate-preview", + "vendor": null + }, + { + "id": "vertex_ai/xai/grok-4.1-fast-non-reasoning", + "vendor": null + }, + { + "id": "vertex_ai/xai/grok-4.1-fast-reasoning", + "vendor": null + }, + { + "id": "vertex_ai/xai/grok-4.20-non-reasoning", + "vendor": null + }, + { + "id": "vertex_ai/xai/grok-4.20-reasoning", + "vendor": null + }, + { + "id": "vertex_ai/zai-org/glm-4.7-maas", + "vendor": null + }, + { + "id": "vertex_ai/zai-org/glm-5-maas", + "vendor": null + } + ], + "bedrock": [ + { + "id": "bedrock/*/1-month-commitment/cohere.command-light-text-v14", + "vendor": "cohere" + }, + { + "id": "bedrock/*/1-month-commitment/cohere.command-text-v14", + "vendor": "cohere" + }, + { + "id": "bedrock/*/6-month-commitment/cohere.command-light-text-v14", + "vendor": "cohere" + }, + { + "id": "bedrock/*/6-month-commitment/cohere.command-text-v14", + "vendor": "cohere" + }, + { + "id": "bedrock/ap-northeast-1/1-month-commitment/anthropic.claude-instant-v1", + "vendor": "anthropic" + }, + { + "id": "bedrock/ap-northeast-1/1-month-commitment/anthropic.claude-v1", + "vendor": "anthropic" + }, + { + "id": "bedrock/ap-northeast-1/1-month-commitment/anthropic.claude-v2:1", + "vendor": "anthropic" + }, + { + "id": "bedrock/ap-northeast-1/6-month-commitment/anthropic.claude-instant-v1", + "vendor": "anthropic" + }, + { + "id": "bedrock/ap-northeast-1/6-month-commitment/anthropic.claude-v1", + "vendor": "anthropic" + }, + { + "id": "bedrock/ap-northeast-1/6-month-commitment/anthropic.claude-v2:1", + "vendor": "anthropic" + }, + { + "id": "bedrock/ap-northeast-1/anthropic.claude-instant-v1", + "vendor": "anthropic" + }, + { + "id": "bedrock/ap-northeast-1/anthropic.claude-v1", + "vendor": "anthropic" + }, + { + "id": "bedrock/ap-northeast-1/anthropic.claude-v2:1", + "vendor": "anthropic" + }, + { + "id": "bedrock/ap-northeast-1/deepseek.v3.2", + "vendor": "deepseek" + }, + { + "id": "bedrock/ap-northeast-1/minimax.minimax-m2.1", + "vendor": "minimax" + }, + { + "id": "bedrock/ap-northeast-1/minimax.minimax-m2.5", + "vendor": "minimax" + }, + { + "id": "bedrock/ap-northeast-1/moonshotai.kimi-k2-thinking", + "vendor": "kimi" + }, + { + "id": "bedrock/ap-northeast-1/moonshotai.kimi-k2.5", + "vendor": "kimi" + }, + { + "id": "bedrock/ap-northeast-1/qwen.qwen3-coder-next", + "vendor": "qwen" + }, + { + "id": "bedrock/ap-south-1/deepseek.v3.2", + "vendor": "deepseek" + }, + { + "id": "bedrock/ap-south-1/meta.llama3-70b-instruct-v1:0", + "vendor": "meta" + }, + { + "id": "bedrock/ap-south-1/meta.llama3-8b-instruct-v1:0", + "vendor": "meta" + }, + { + "id": "bedrock/ap-south-1/minimax.minimax-m2.1", + "vendor": "minimax" + }, + { + "id": "bedrock/ap-south-1/minimax.minimax-m2.5", + "vendor": "minimax" + }, + { + "id": "bedrock/ap-south-1/moonshotai.kimi-k2-thinking", + "vendor": "kimi" + }, + { + "id": "bedrock/ap-south-1/moonshotai.kimi-k2.5", + "vendor": "kimi" + }, + { + "id": "bedrock/ap-south-1/qwen.qwen3-coder-next", + "vendor": "qwen" + }, + { + "id": "bedrock/ap-southeast-2/minimax.minimax-m2.5", + "vendor": "minimax" + }, + { + "id": "bedrock/ap-southeast-3/deepseek.v3.2", + "vendor": "deepseek" + }, + { + "id": "bedrock/ap-southeast-3/minimax.minimax-m2.1", + "vendor": "minimax" + }, + { + "id": "bedrock/ap-southeast-3/minimax.minimax-m2.5", + "vendor": "minimax" + }, + { + "id": "bedrock/ap-southeast-3/moonshotai.kimi-k2.5", + "vendor": "kimi" + }, + { + "id": "bedrock/ap-southeast-3/qwen.qwen3-coder-next", + "vendor": "qwen" + }, + { + "id": "bedrock/ca-central-1/meta.llama3-70b-instruct-v1:0", + "vendor": "meta" + }, + { + "id": "bedrock/ca-central-1/meta.llama3-8b-instruct-v1:0", + "vendor": "meta" + }, + { + "id": "bedrock/eu-central-1/1-month-commitment/anthropic.claude-instant-v1", + "vendor": "anthropic" + }, + { + "id": "bedrock/eu-central-1/1-month-commitment/anthropic.claude-v1", + "vendor": "anthropic" + }, + { + "id": "bedrock/eu-central-1/1-month-commitment/anthropic.claude-v2:1", + "vendor": "anthropic" + }, + { + "id": "bedrock/eu-central-1/6-month-commitment/anthropic.claude-instant-v1", + "vendor": "anthropic" + }, + { + "id": "bedrock/eu-central-1/6-month-commitment/anthropic.claude-v1", + "vendor": "anthropic" + }, + { + "id": "bedrock/eu-central-1/6-month-commitment/anthropic.claude-v2:1", + "vendor": "anthropic" + }, + { + "id": "bedrock/eu-central-1/anthropic.claude-instant-v1", + "vendor": "anthropic" + }, + { + "id": "bedrock/eu-central-1/anthropic.claude-v1", + "vendor": "anthropic" + }, + { + "id": "bedrock/eu-central-1/anthropic.claude-v2:1", + "vendor": "anthropic" + }, + { + "id": "bedrock/eu-central-1/minimax.minimax-m2.1", + "vendor": "minimax" + }, + { + "id": "bedrock/eu-central-1/minimax.minimax-m2.5", + "vendor": "minimax" + }, + { + "id": "bedrock/eu-central-1/qwen.qwen3-coder-next", + "vendor": "qwen" + }, + { + "id": "bedrock/eu-north-1/deepseek.v3.2", + "vendor": "deepseek" + }, + { + "id": "bedrock/eu-north-1/minimax.minimax-m2.1", + "vendor": "minimax" + }, + { + "id": "bedrock/eu-north-1/minimax.minimax-m2.5", + "vendor": "minimax" + }, + { + "id": "bedrock/eu-north-1/moonshotai.kimi-k2.5", + "vendor": "kimi" + }, + { + "id": "bedrock/eu-south-1/minimax.minimax-m2.1", + "vendor": "minimax" + }, + { + "id": "bedrock/eu-south-1/minimax.minimax-m2.5", + "vendor": "minimax" + }, + { + "id": "bedrock/eu-south-1/qwen.qwen3-coder-next", + "vendor": "qwen" + }, + { + "id": "bedrock/eu-west-1/meta.llama3-70b-instruct-v1:0", + "vendor": "meta" + }, + { + "id": "bedrock/eu-west-1/meta.llama3-8b-instruct-v1:0", + "vendor": "meta" + }, + { + "id": "bedrock/eu-west-1/minimax.minimax-m2.1", + "vendor": "minimax" + }, + { + "id": "bedrock/eu-west-1/minimax.minimax-m2.5", + "vendor": "minimax" + }, + { + "id": "bedrock/eu-west-1/qwen.qwen3-coder-next", + "vendor": "qwen" + }, + { + "id": "bedrock/eu-west-2/meta.llama3-70b-instruct-v1:0", + "vendor": "meta" + }, + { + "id": "bedrock/eu-west-2/meta.llama3-8b-instruct-v1:0", + "vendor": "meta" + }, + { + "id": "bedrock/eu-west-2/minimax.minimax-m2.1", + "vendor": "minimax" + }, + { + "id": "bedrock/eu-west-2/minimax.minimax-m2.5", + "vendor": "minimax" + }, + { + "id": "bedrock/eu-west-2/qwen.qwen3-coder-next", + "vendor": "qwen" + }, + { + "id": "bedrock/eu-west-3/mistral.mistral-7b-instruct-v0:2", + "vendor": "mistral" + }, + { + "id": "bedrock/eu-west-3/mistral.mistral-large-2402-v1:0", + "vendor": "mistral" + }, + { + "id": "bedrock/eu-west-3/mistral.mixtral-8x7b-instruct-v0:1", + "vendor": "mistral" + }, + { + "id": "bedrock/invoke/anthropic.claude-3-5-sonnet-20240620-v1:0", + "vendor": "anthropic" + }, + { + "id": "bedrock/moonshotai.kimi-k2-thinking", + "vendor": "kimi" + }, + { + "id": "bedrock/moonshotai.kimi-k2.5", + "vendor": "kimi" + }, + { + "id": "bedrock/sa-east-1/deepseek.v3.2", + "vendor": "deepseek" + }, + { + "id": "bedrock/sa-east-1/meta.llama3-70b-instruct-v1:0", + "vendor": "meta" + }, + { + "id": "bedrock/sa-east-1/meta.llama3-8b-instruct-v1:0", + "vendor": "meta" + }, + { + "id": "bedrock/sa-east-1/minimax.minimax-m2.1", + "vendor": "minimax" + }, + { + "id": "bedrock/sa-east-1/minimax.minimax-m2.5", + "vendor": "minimax" + }, + { + "id": "bedrock/sa-east-1/moonshotai.kimi-k2-thinking", + "vendor": "kimi" + }, + { + "id": "bedrock/sa-east-1/moonshotai.kimi-k2.5", + "vendor": "kimi" + }, + { + "id": "bedrock/sa-east-1/qwen.qwen3-coder-next", + "vendor": "qwen" + }, + { + "id": "bedrock/us-east-1/1-month-commitment/anthropic.claude-instant-v1", + "vendor": "anthropic" + }, + { + "id": "bedrock/us-east-1/1-month-commitment/anthropic.claude-v1", + "vendor": "anthropic" + }, + { + "id": "bedrock/us-east-1/1-month-commitment/anthropic.claude-v2:1", + "vendor": "anthropic" + }, + { + "id": "bedrock/us-east-1/6-month-commitment/anthropic.claude-instant-v1", + "vendor": "anthropic" + }, + { + "id": "bedrock/us-east-1/6-month-commitment/anthropic.claude-v1", + "vendor": "anthropic" + }, + { + "id": "bedrock/us-east-1/6-month-commitment/anthropic.claude-v2:1", + "vendor": "anthropic" + }, + { + "id": "bedrock/us-east-1/anthropic.claude-instant-v1", + "vendor": "anthropic" + }, + { + "id": "bedrock/us-east-1/anthropic.claude-v1", + "vendor": "anthropic" + }, + { + "id": "bedrock/us-east-1/anthropic.claude-v2:1", + "vendor": "anthropic" + }, + { + "id": "bedrock/us-east-1/deepseek.v3.2", + "vendor": "deepseek" + }, + { + "id": "bedrock/us-east-1/meta.llama3-70b-instruct-v1:0", + "vendor": "meta" + }, + { + "id": "bedrock/us-east-1/meta.llama3-8b-instruct-v1:0", + "vendor": "meta" + }, + { + "id": "bedrock/us-east-1/minimax.minimax-m2.1", + "vendor": "minimax" + }, + { + "id": "bedrock/us-east-1/minimax.minimax-m2.5", + "vendor": "minimax" + }, + { + "id": "bedrock/us-east-1/mistral.mistral-7b-instruct-v0:2", + "vendor": "mistral" + }, + { + "id": "bedrock/us-east-1/mistral.mistral-large-2402-v1:0", + "vendor": "mistral" + }, + { + "id": "bedrock/us-east-1/mistral.mixtral-8x7b-instruct-v0:1", + "vendor": "mistral" + }, + { + "id": "bedrock/us-east-1/moonshotai.kimi-k2-thinking", + "vendor": "kimi" + }, + { + "id": "bedrock/us-east-1/moonshotai.kimi-k2.5", + "vendor": "kimi" + }, + { + "id": "bedrock/us-east-1/qwen.qwen3-coder-next", + "vendor": "qwen" + }, + { + "id": "bedrock/us-east-1/zai.glm-5", + "vendor": "glm" + }, + { + "id": "bedrock/us-east-2/deepseek.v3.2", + "vendor": "deepseek" + }, + { + "id": "bedrock/us-east-2/minimax.minimax-m2.1", + "vendor": "minimax" + }, + { + "id": "bedrock/us-east-2/minimax.minimax-m2.5", + "vendor": "minimax" + }, + { + "id": "bedrock/us-east-2/moonshotai.kimi-k2-thinking", + "vendor": "kimi" + }, + { + "id": "bedrock/us-east-2/moonshotai.kimi-k2.5", + "vendor": "kimi" + }, + { + "id": "bedrock/us-east-2/qwen.qwen3-coder-next", + "vendor": "qwen" + }, + { + "id": "bedrock/us-gov-east-1/amazon.nova-pro-v1:0", + "vendor": "amazon" + }, + { + "id": "bedrock/us-gov-east-1/amazon.titan-embed-text-v1", + "vendor": "amazon" + }, + { + "id": "bedrock/us-gov-east-1/amazon.titan-embed-text-v2:0", + "vendor": "amazon" + }, + { + "id": "bedrock/us-gov-east-1/amazon.titan-text-express-v1", + "vendor": "amazon" + }, + { + "id": "bedrock/us-gov-east-1/amazon.titan-text-lite-v1", + "vendor": "amazon" + }, + { + "id": "bedrock/us-gov-east-1/amazon.titan-text-premier-v1:0", + "vendor": "amazon" + }, + { + "id": "bedrock/us-gov-east-1/anthropic.claude-3-5-sonnet-20240620-v1:0", + "vendor": "anthropic" + }, + { + "id": "bedrock/us-gov-east-1/anthropic.claude-3-haiku-20240307-v1:0", + "vendor": "anthropic" + }, + { + "id": "bedrock/us-gov-east-1/anthropic.claude-haiku-4-5-20251001-v1:0", + "vendor": "anthropic" + }, + { + "id": "bedrock/us-gov-east-1/anthropic.claude-sonnet-4-5-20250929-v1:0", + "vendor": "anthropic" + }, + { + "id": "bedrock/us-gov-east-1/claude-sonnet-4-5-20250929-v1:0", + "vendor": null + }, + { + "id": "bedrock/us-gov-east-1/meta.llama3-70b-instruct-v1:0", + "vendor": "meta" + }, + { + "id": "bedrock/us-gov-east-1/meta.llama3-8b-instruct-v1:0", + "vendor": "meta" + }, + { + "id": "bedrock/us-gov-west-1/amazon.nova-pro-v1:0", + "vendor": "amazon" + }, + { + "id": "bedrock/us-gov-west-1/amazon.titan-embed-text-v1", + "vendor": "amazon" + }, + { + "id": "bedrock/us-gov-west-1/amazon.titan-embed-text-v2:0", + "vendor": "amazon" + }, + { + "id": "bedrock/us-gov-west-1/amazon.titan-text-express-v1", + "vendor": "amazon" + }, + { + "id": "bedrock/us-gov-west-1/amazon.titan-text-lite-v1", + "vendor": "amazon" + }, + { + "id": "bedrock/us-gov-west-1/amazon.titan-text-premier-v1:0", + "vendor": "amazon" + }, + { + "id": "bedrock/us-gov-west-1/anthropic.claude-3-5-sonnet-20240620-v1:0", + "vendor": "anthropic" + }, + { + "id": "bedrock/us-gov-west-1/anthropic.claude-3-7-sonnet-20250219-v1:0", + "vendor": "anthropic" + }, + { + "id": "bedrock/us-gov-west-1/anthropic.claude-3-haiku-20240307-v1:0", + "vendor": "anthropic" + }, + { + "id": "bedrock/us-gov-west-1/anthropic.claude-haiku-4-5-20251001-v1:0", + "vendor": "anthropic" + }, + { + "id": "bedrock/us-gov-west-1/anthropic.claude-sonnet-4-5-20250929-v1:0", + "vendor": "anthropic" + }, + { + "id": "bedrock/us-gov-west-1/claude-sonnet-4-5-20250929-v1:0", + "vendor": null + }, + { + "id": "bedrock/us-gov-west-1/meta.llama3-70b-instruct-v1:0", + "vendor": "meta" + }, + { + "id": "bedrock/us-gov-west-1/meta.llama3-8b-instruct-v1:0", + "vendor": "meta" + }, + { + "id": "bedrock/us-west-1/meta.llama3-70b-instruct-v1:0", + "vendor": "meta" + }, + { + "id": "bedrock/us-west-1/meta.llama3-8b-instruct-v1:0", + "vendor": "meta" + }, + { + "id": "bedrock/us-west-2/1-month-commitment/anthropic.claude-instant-v1", + "vendor": "anthropic" + }, + { + "id": "bedrock/us-west-2/1-month-commitment/anthropic.claude-v1", + "vendor": "anthropic" + }, + { + "id": "bedrock/us-west-2/1-month-commitment/anthropic.claude-v2:1", + "vendor": "anthropic" + }, + { + "id": "bedrock/us-west-2/6-month-commitment/anthropic.claude-instant-v1", + "vendor": "anthropic" + }, + { + "id": "bedrock/us-west-2/6-month-commitment/anthropic.claude-v1", + "vendor": "anthropic" + }, + { + "id": "bedrock/us-west-2/6-month-commitment/anthropic.claude-v2:1", + "vendor": "anthropic" + }, + { + "id": "bedrock/us-west-2/anthropic.claude-instant-v1", + "vendor": "anthropic" + }, + { + "id": "bedrock/us-west-2/anthropic.claude-v1", + "vendor": "anthropic" + }, + { + "id": "bedrock/us-west-2/anthropic.claude-v2:1", + "vendor": "anthropic" + }, + { + "id": "bedrock/us-west-2/deepseek.v3.2", + "vendor": "deepseek" + }, + { + "id": "bedrock/us-west-2/minimax.minimax-m2.1", + "vendor": "minimax" + }, + { + "id": "bedrock/us-west-2/minimax.minimax-m2.5", + "vendor": "minimax" + }, + { + "id": "bedrock/us-west-2/mistral.mistral-7b-instruct-v0:2", + "vendor": "mistral" + }, + { + "id": "bedrock/us-west-2/mistral.mistral-large-2402-v1:0", + "vendor": "mistral" + }, + { + "id": "bedrock/us-west-2/mistral.mixtral-8x7b-instruct-v0:1", + "vendor": "mistral" + }, + { + "id": "bedrock/us-west-2/moonshotai.kimi-k2-thinking", + "vendor": "kimi" + }, + { + "id": "bedrock/us-west-2/moonshotai.kimi-k2.5", + "vendor": "kimi" + }, + { + "id": "bedrock/us-west-2/qwen.qwen3-coder-next", + "vendor": "qwen" + }, + { + "id": "bedrock/us-west-2/zai.glm-5", + "vendor": "glm" + }, + { + "id": "bedrock/us.anthropic.claude-3-5-haiku-20241022-v1:0", + "vendor": null + } + ] +} diff --git a/tests/unifiedllm/test_contracts.py b/tests/unifiedllm/test_contracts.py new file mode 100644 index 000000000..7df7d64cc --- /dev/null +++ b/tests/unifiedllm/test_contracts.py @@ -0,0 +1,391 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Provider identity and capability contract tests. + +Freezes the public schema of the provider-contract types (golden +fixtures) and pins the normalization, key-derivation, and fail-closed +behavior. +""" + +from __future__ import annotations + +import json +from pathlib import Path + +import pytest + +from nooa.unifiedllm.contracts import ( + DEFAULT_REASONING_CAPABILITIES, + OPAQUE_REPLAY_KEY_VERSION, + ModelCompatGroup, + NormalizedModel, + ProviderIdentity, + ReasoningCapabilities, + ReasoningKind, + ReasoningRecord, + ReasoningReplayMode, + UnknownProviderIdentityError, + compat_group_for, + derive_opaque_replay_key, + get_reasoning_capabilities, + parse_model_string, + register_compat_group, + register_reasoning_capabilities, +) + +FIXTURES = Path(__file__).parent / "fixtures" +GOLDEN = FIXTURES / "contracts_golden.json" + + +# --- Golden schema fixtures ------------------------------------------------ + + +def test_golden_fixture_enums_frozen() -> None: + golden = json.loads(GOLDEN.read_text()) + assert golden["enums"]["ReasoningKind"] == ["opaque", "text", "checkpoint"] + assert golden["enums"]["ReasoningReplayMode"] == [ + "off", + "auto", + "native_only", + "text_context", + ] + assert ReasoningKind("opaque") is ReasoningKind.OPAQUE + assert ReasoningReplayMode("text_context") is ReasoningReplayMode.TEXT_CONTEXT + + +def test_golden_fixture_schemas_frozen() -> None: + golden = json.loads(GOLDEN.read_text()) + schemas = golden["schemas"] + assert schemas == { + "ProviderIdentity": ProviderIdentity.model_json_schema(), + "ReasoningRecord": ReasoningRecord.model_json_schema(), + "ReasoningCapabilities": ReasoningCapabilities.model_json_schema(), + "ModelCompatGroup": ModelCompatGroup.model_json_schema(), + "NormalizedModel": NormalizedModel.model_json_schema(), + } + + +def test_golden_fixture_examples_round_trip() -> None: + golden = json.loads(GOLDEN.read_text()) + examples = golden["examples"] + identity = ProviderIdentity.model_validate(examples["provider_identity"]) + assert identity.provider == "openai" + assert identity.opaque_replay_key is not None + # The frozen digest must still be reproducible from the frozen identity: + # if a normalization rule changes without bumping OPAQUE_REPLAY_KEY_VERSION, + # this catches the drift instead of the presence-only assertion staying green. + assert identity.opaque_replay_key == derive_opaque_replay_key( + provider=identity.provider, + api_style=identity.api_style, + model=identity.model, + endpoint_id=identity.endpoint_id, + account_scope=identity.account_scope, + ) + record = ReasoningRecord.model_validate(examples["reasoning_record"]) + assert record.kind is ReasoningKind.OPAQUE + assert record.redaction_class == "opaque" + # Round-trip through JSON again: no SDK objects, plain NOOA-owned JSON. + assert json.loads(record.model_dump_json()) == examples["reasoning_record"] + caps = ReasoningCapabilities.model_validate(examples["capabilities_openai"]) + assert caps.replay_field is None + assert caps.terminal_backfill is True + + +def test_opaque_replay_key_version_recorded_in_fixture() -> None: + golden = json.loads(GOLDEN.read_text()) + assert golden["opaque_replay_key_version"] == OPAQUE_REPLAY_KEY_VERSION + assert OPAQUE_REPLAY_KEY_VERSION == "nooa.opaque-replay-key.v1" + + +# --- Gateway alias fixtures (NVIDIA gateway) --------------------------------- + + +@pytest.mark.parametrize( + ("model_string", "provider", "model"), + [ + ("openai/nvidia/zai-org/glm-5.3", "glm", "glm-5.3"), + ("openai/nvidia/moonshotai/kimi-k2.6", "kimi", "kimi-k2.6"), + ("openai/azure/openai/gpt-5.6-sol", "openai", "gpt-5.6-sol"), + ("anthropic/claude-sonnet-4-5", "anthropic", "claude-sonnet-4-5"), + ("claude-3-5-sonnet", "anthropic", "claude-3-5-sonnet"), + ("openai/nvidia/nemotron-3-super-v3", "nvidia", "nemotron-3-super-v3"), + ("openai/nvidia/deepseek-r1", "deepseek", "deepseek-r1"), + ("openai/nvidia/qwen3.5-35b-a3b", "qwen", "qwen3.5-35b-a3b"), + ], +) +def test_gateway_aliases_resolve_to_logical_provider( + model_string: str, provider: str, model: str +) -> None: + parsed = parse_model_string(model_string) + assert parsed.provider == provider + assert parsed.model == model + + +def test_gateway_prefixes_never_determine_provider() -> None: + # Routing prefixes are stripped regardless of stacking; the logical + # provider comes from the model family, not the gateway brand. + assert parse_model_string("openai/nvidia/zai-org/glm-5.3").provider == "glm" + assert parse_model_string("nvidia/zai-org/glm-5.3").provider == "glm" + # The routing prefix alone (no known family) must fail closed. + assert parse_model_string("openai/someprivate-internal-model").provider is None + + +# --- Tier and alias normalization ---------------------------------------------- + + +@pytest.mark.parametrize( + ("model_string", "model", "tier"), + [ + ("kimi-k3:free", "kimi-k3", "free"), + ("deepseek-v4-flash:cloud", "deepseek-v4-flash", "cloud"), + ("openai/nvidia/moonshotai/kimi-k3:free", "kimi-k3", "free"), + ("qwen3.5-35b-a3b", "qwen3.5-35b-a3b", None), + ], +) +def test_tier_suffixes_stripped_before_comparison( + model_string: str, model: str, tier: str | None +) -> None: + parsed = parse_model_string(model_string) + assert parsed.model == model + assert parsed.tier == tier + + +def test_provider_aliases_normalized() -> None: + assert parse_model_string("claude/foo").provider == "anthropic" + assert parse_model_string("zai/glm-5.3").provider == "glm" + assert parse_model_string("moonshot/kimi-k2.6").provider == "kimi" + # Bedrock-style embedded vendor ids also normalize. + assert parse_model_string("anthropic.claude-3-5-sonnet").provider == "anthropic" + + +def test_empty_model_string_fails_closed() -> None: + parsed = parse_model_string("") + assert parsed.provider is None + assert parsed.model == "" + + +# --- Key derivation ------------------------------------------------------------ + + +def _openai_key(**overrides: object) -> str | None: + kwargs: dict[str, object] = { + "provider": "openai", + "api_style": "responses", + "model": "gpt-5.6-sol", + "endpoint_id": "ep-123", + "account_scope": "acct-abc", + } + kwargs.update(overrides) + return derive_opaque_replay_key(**kwargs) # type: ignore[arg-type] + + +def test_same_identity_derives_same_key() -> None: + assert _openai_key() == _openai_key() + # Normalization happens inside derivation: prefix and tier variants of the + # same identity derive the same key. + assert _openai_key() == _openai_key(model="openai/azure/openai/gpt-5.6-sol") + + +def test_key_differs_across_identity_parts() -> None: + base = _openai_key() + assert base is not None + assert _openai_key(api_style="chat-completions") != base + assert _openai_key(endpoint_id="ep-other") != base + assert _openai_key(account_scope="acct-other") != base + assert _openai_key(endpoint_id=None, account_scope=None) != base + + +def test_key_undeclared_model_fails_closed() -> None: + # gpt-4o is a real OpenAI model but has no declared compat group: no key. + assert _openai_key(model="gpt-4o") is None + # No provider / empty model: never a speculative key. + assert _openai_key(model="") is None + assert derive_opaque_replay_key(provider="", api_style="responses", model="gpt-5.6-sol") is None + + +def test_key_transport_neutral() -> None: + # transport is deliberately excluded from compatibility. + litellm_key = derive_opaque_replay_key( + provider="openai", api_style="responses", model="gpt-5.6-sol" + ) + identity = ProviderIdentity.from_model_string( + "openai/gpt-5.6-sol", api_style="responses", transport="anyllm" + ) + assert identity.opaque_replay_key == litellm_key + + +def test_same_compat_group_members_derive_equal_keys() -> None: + """The compat group is the model dimension of the key. + + Two models in the same hand-verified group must be replay-compatible: + raw model ids are NOT mixed into the key payload, so model-id churn + never changes the key. + """ + k55 = derive_opaque_replay_key(provider="openai", api_style="responses", model="gpt-5.5") + k56 = derive_opaque_replay_key(provider="openai", api_style="responses", model="gpt-5.6-sol") + assert k55 is not None and k56 is not None + assert k55 == k56 + # Different groups / providers still diverge. + assert k56 != derive_opaque_replay_key( + provider="anthropic", api_style="messages", model="claude-sonnet-4-5" + ) + + +def test_bedrock_dotted_id_derives_same_key_as_plain_form() -> None: + """Two spellings of one identity must not diverge in key derivation.""" + dotted = derive_opaque_replay_key( + provider="anthropic", api_style="messages", model="anthropic.claude-sonnet-4-5" + ) + plain = derive_opaque_replay_key( + provider="anthropic", api_style="messages", model="claude-sonnet-4-5" + ) + assert dotted is not None + assert dotted == plain + + +def test_key_is_prefixed_non_secret_digest() -> None: + key = _openai_key() + assert key is not None + assert key.startswith("sha256:") + assert len(key) == len("sha256:") + 64 + assert "gpt-5.6-sol" not in key # digest, not a model echo + + +# --- Identity construction ---------------------------------------------------- + + +def test_identity_from_model_string_uses_normalized_parts() -> None: + identity = ProviderIdentity.from_model_string( + "openai/nvidia/zai-org/glm-5.3", + api_style="chat-completions", + transport="litellm", + ) + assert identity.provider == "glm" + assert identity.model == "glm-5.3" + + +def test_identity_unknown_model_string_fails_closed() -> None: + with pytest.raises(UnknownProviderIdentityError): + ProviderIdentity.from_model_string( + "totally-unknown-vendor/model-x", api_style="responses", transport="litellm" + ) + + +def test_identity_round_trips_through_json() -> None: + identity = ProviderIdentity.from_model_string( + "openai/gpt-5.6-sol", api_style="responses", transport="litellm" + ) + restored = ProviderIdentity.model_validate(json.loads(identity.model_dump_json())) + assert restored == identity + + +# --- Model compat groups -------------------------------------------------------- + + +def test_compat_group_requires_declared_membership() -> None: + assert compat_group_for("openai", "gpt-5.6-sol") is not None + # Similar but undeclared model ids fail closed. + assert compat_group_for("openai", "gpt-5.6-sonnet") is None + assert compat_group_for("openai", "gpt-5.6-sol-mini") is None + # Groups are scoped per logical provider. + assert compat_group_for("glm", "gpt-5.6-sol") is None + assert compat_group_for("openai", "claude-sonnet-4-5") is None + + +def test_injected_compat_groups_are_case_normalized() -> None: + """Callers passing their own groups mapping get the same case handling + as register_compat_group: mixed-case members must still match.""" + injected = { + "custom-openai": ModelCompatGroup( + name="custom-openai", + provider="OpenAI", + models=frozenset({"GPT-5.6-SOL"}), + ) + } + # Mixed-case provider and model both resolve through the injected mapping. + group = compat_group_for("openai", "gpt-5.6-sol", groups=injected) + assert group is not None and group.name == "custom-openai" + # And key derivation honors the injected (mixed-case) declaration. + key = derive_opaque_replay_key( + provider="openai", + api_style="responses", + model="gpt-5.6-sol", + compat_groups=injected, + ) + assert key is not None + + +def test_register_compat_group_overrides_default() -> None: + group = ModelCompatGroup( + name="openai-gpt-5", + provider="openai", + models=frozenset({"gpt-5.6-sol", "gpt-5.6-sol-preview"}), + ) + register_compat_group(group) + try: + assert "gpt-5.6-sol-preview" in compat_group_for("openai", "gpt-5.6-sol").models # type: ignore[union-attr] + # Newly declared members derive a key; nothing else changes. + assert _openai_key(model="gpt-5.6-sol-preview") is not None + assert _openai_key(model="gpt-5.5") is None + finally: + # Restore the default group so other tests are unaffected. + from nooa.unifiedllm import contracts as c + + c._COMPAT_GROUPS.clear() + c._COMPAT_GROUPS.update({group.name: group for group in c._DEFAULT_COMPAT_GROUPS}) + + +# --- Capability profile ----------------------------------------------------------- + + +def test_unknown_capability_fails_closed() -> None: + assert get_reasoning_capabilities("some-unknown-provider") is None + + +def test_declared_capabilities_present() -> None: + for provider in ("openai", "anthropic", "glm", "kimi", "deepseek", "qwen", "nvidia"): + caps = get_reasoning_capabilities(provider) + assert caps is not None, provider + + +def test_effort_map_null_means_unsupported() -> None: + # OpenAI maps effort levels; the chat-family providers and Anthropic + # declare None (unsupported) until a verified mapping exists. + assert DEFAULT_REASONING_CAPABILITIES["openai"].effort_map["medium"] == "medium" + for provider in ("glm", "kimi", "deepseek", "qwen", "nvidia", "anthropic"): + assert provider in DEFAULT_REASONING_CAPABILITIES + assert DEFAULT_REASONING_CAPABILITIES[provider].effort_map["medium"] is None + + +def test_register_reasoning_capabilities_overrides_catalog() -> None: + custom = ReasoningCapabilities( + capture_kinds=frozenset({ReasoningKind.TEXT}), + native_replay_kinds=frozenset(), + effort_map={"medium": "banana"}, + replay_field="thoughts", + ) + register_reasoning_capabilities("bananaai", custom) + try: + assert get_reasoning_capabilities("bananaai") is custom + assert get_reasoning_capabilities("BANANAai") is custom # case-insensitive + # Mixed-case registrations stay reachable (stored lowercased). + assert get_reasoning_capabilities("BananaAI") is custom + finally: + # Restore the catalog so other tests are unaffected. + from nooa.unifiedllm import contracts as c + + c._REASONING_CAPABILITIES.clear() + c._REASONING_CAPABILITIES.update(dict(c.DEFAULT_REASONING_CAPABILITIES)) + + +# --- No behavior change / additive surface -------------------------------------- + + +def test_contracts_module_has_no_provider_sdk_imports() -> None: + import nooa.unifiedllm.contracts as contracts + + source = Path(contracts.__file__).read_text() + assert "import litellm" not in source + assert "from litellm" not in source + assert "from openai" not in source + assert "from anthropic" not in source diff --git a/tests/unifiedllm/test_model_id_corpora.py b/tests/unifiedllm/test_model_id_corpora.py new file mode 100644 index 000000000..9b0957a50 --- /dev/null +++ b/tests/unifiedllm/test_model_id_corpora.py @@ -0,0 +1,182 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Model-id corpus conformance tests. + +The corpora are real catalog ids (OpenRouter public catalog, NVIDIA inference +gateway). Ground truth is the vendor segment each catalog itself attaches to +the id — the same signal an adapter would trust at request time. The invariant +under test: the parser never *mis-attributes* an id; ids it cannot resolve +fail closed (provider=None) rather than guessing. +""" + +from __future__ import annotations + +import json +from pathlib import Path + +import pytest + +from nooa.unifiedllm.contracts import parse_model_string + +FIXTURES = Path(__file__).parent / "fixtures" +CORPORA = json.loads((FIXTURES / "model_id_corpora.json").read_text()) + +#: Catalog vendor label -> canonical logical provider. These are spelling +#: variants of the SAME logical provider, not misattributions: the catalogs +#: spell the vendor differently than the parser's canonical name +#: ("meta-llama" vs "meta", "mistralai" vs "mistral", "~anthropic" is +#: OpenRouter's variant marker on the vendor segment). +_CANON = { + "openai": "openai", + "anthropic": "anthropic", + "~anthropic": "anthropic", + "google": "google", + "meta": "meta", + "meta-llama": "meta", + "mistral": "mistral", + "mistralai": "mistral", + "x-ai": "xai", + "~x-ai": "xai", + "xai": "xai", + "nvidia": "nvidia", + "deepseek": "deepseek", + "deepseek-ai": "deepseek", + "~deepseek": "deepseek", + "qwen": "qwen", + "z-ai": "glm", + "~z-ai": "glm", + "zai": "glm", + "zai-org": "glm", + "moonshot": "kimi", + "moonshotai": "kimi", + "minimaxai": "minimax", + "microsoft": "microsoft", +} + + +def _canon(label: str | None) -> str | None: + if not label: + return None + return _CANON.get(label.lower().strip(), label.lower().strip()) + + +def _corpus_ids(name: str) -> list[tuple[str, str | None]]: + entries = CORPORA[name] + return [(e["id"], _canon(e.get("vendor"))) for e in entries if e.get("id")] + + +@pytest.mark.parametrize("name", ["openrouter", "nvidia_gateway", "azure", "vertex_ai", "bedrock"]) +def test_no_misattribution_in_corpora(name: str) -> None: + """Every id the parser resolves must match the catalog's own vendor. + + Fail-closed (provider=None) is acceptable for genuinely-unknown model + families — it is the safe default, not a bug. Resolving to the WRONG + provider is a bug; there are zero such ids in either corpus. + """ + misattributed = [] + for model_id, vendor in _corpus_ids(name): + if vendor is None: + continue # no ground truth for this spelling + parsed = parse_model_string(model_id) + if parsed.provider is not None and parsed.provider != vendor: + misattributed.append((model_id, vendor, parsed.provider)) + assert not misattributed, misattributed[:20] + + +@pytest.mark.parametrize("name", ["openrouter", "nvidia_gateway", "azure", "vertex_ai", "bedrock"]) +def test_corpora_resolution_rate(name: str) -> None: + """Guard the measured resolution rate against regressions. + + The rates below were measured after the corpus-driven fixes (2026-09-07). + Fail-closed remainder is dominated by boutique/one-off vendors not worth + a declaration; the rate guards that a table edit does not silently drop + a previously-resolving family. + """ + ids_with_truth = [x for x in _corpus_ids(name) if x[1] is not None] + resolved = [x for x in ids_with_truth if parse_model_string(x[0]).provider is not None] + rate = len(resolved) / len(ids_with_truth) + # Measured at the time of writing: OpenRouter 0.74, NVIDIA gateway 0.91, + # azure 0.97, vertex_ai 0.94, bedrock 0.98 (of verifiable ids). + floors = { + "openrouter": 0.70, + "nvidia_gateway": 0.85, + "azure": 0.90, + "vertex_ai": 0.90, + "bedrock": 0.95, + } + assert rate >= floors[name], f"{name}: resolution rate {rate:.3f} dropped" + + +def test_deployment_spellings_resolve() -> None: + """Routing-prefixed deployment spellings collapse to the same identity. + + These are the spellings that actually arrive through gateways and + deployment-specific clients, drawn from each provider's deployment docs. + """ + cases = [ + # Azure OpenAI: azure/, incl. region segment. + ("azure/gpt-4o", "openai", "gpt-4o"), + ("azure/eu/gpt-4o-2024-08-06", "openai", "gpt-4o-2024-08-06"), + # Vertex AI: vertex_ai/gemini-*. + ("vertex_ai/gemini-2.5-pro", "google", "gemini-2.5-pro"), + # Bedrock: bedrock//.../vendor.model, and vendor.model alone. + ( + "bedrock/us-east-1/1-month-commitment/anthropic.claude-3-5-sonnet", + "anthropic", + "claude-3-5-sonnet", + ), + ("anthropic.claude-3-5-sonnet", "anthropic", "claude-3-5-sonnet"), + # OpenRouter tier/variant markers. + ("kimi-k3:free", "kimi", "kimi-k3"), + ("o3:batch", "openai", "o3"), + ("~anthropic/claude-fable-latest", "anthropic", "claude-fable-latest"), + # NVIDIA gateway: nvidia//. + ("nvidia/zai-org/glm-5.3", "glm", "glm-5.3"), + ( + "nvidia/nvidia/llama-3.1-nemotron-ultra-253b-v1", + "nvidia", + "llama-3.1-nemotron-ultra-253b-v1", + ), + ("nvidia/google/gemma-4-31b-it", "google", "gemma-4-31b-it"), + # o-series with effort suffix. + ("o4-mini-high", "openai", "o4-mini-high"), + # Vertex @version suffixes; Bedrock region+commitment paths with + # vendor.model ids and deployment segments in the middle. + ("vertex_ai/claude-3-5-sonnet@20240620", "anthropic", "claude-3-5-sonnet"), + ("vertex_ai/codestral@latest", "mistral", "codestral"), + ("bedrock/ap-northeast-1/moonshotai.kimi-k2-thinking", "kimi", "kimi-k2-thinking"), + ("bedrock/eu-central-1/qwen.qwen3-coder-next", "qwen", "qwen3-coder-next"), + ( + "bedrock/*/1-month-commitment/cohere.command-light-text-v14", + "cohere", + "command-light-text-v14", + ), + ("bedrock/us-east-1/amazon.nova-pro-v1", "amazon", "nova-pro-v1"), + # Azure media/aux model families. + ("azure/gpt-image-1", "openai", "gpt-image-1"), + ("azure/eu/gpt-realtime-mini-2025-10-06", "openai", "gpt-realtime-mini-2025-10-06"), + ("azure/text-embedding-3-large", "openai", "text-embedding-3-large"), + ("azure/tts-1-hd", "openai", "tts-1-hd"), + ("azure/command-r-plus", "cohere", "command-r-plus"), + ("azure/eu/gpt-5.6", "openai", "gpt-5.6"), + # Vertex media families. + ("vertex_ai/gemini-embedding-2", "google", "gemini-embedding-2"), + ("vertex_ai/imagen-3.0-generate-001", "google", "imagen-3.0-generate-001"), + ("vertex_ai/jamba-1.5-mini@001", "ai21", "jamba-1.5-mini"), + ] + for model_string, provider, model in cases: + parsed = parse_model_string(model_string) + assert parsed.provider == provider, (model_string, parsed.provider, provider) + assert parsed.model == model, (model_string, parsed.model, model) + + +def test_unknown_boutique_vendors_fail_closed() -> None: + """Undeclared model families resolve to None — never a guess.""" + for model_id in ( + "aion-labs/aion-3.0", + "baidu/ernie-4.5-vl-424b-a47b", + "thinkingmachines/inkling", + "some-unknown-vendor/model-x", + ): + parsed = parse_model_string(model_id) + assert parsed.provider is None, (model_id, parsed.provider) diff --git a/uv.lock b/uv.lock index 8f0897d95..bd7874aca 100644 --- a/uv.lock +++ b/uv.lock @@ -1467,7 +1467,7 @@ requires-dist = [ { name = "opentelemetry-api", marker = "extra == 'tracing'", specifier = ">=1.20.0" }, { name = "opentelemetry-sdk", marker = "extra == 'tracing'", specifier = ">=1.20.0" }, { name = "pillow", marker = "extra == 'arc'", specifier = ">=10.0" }, - { name = "pydantic", specifier = ">=2.5.0" }, + { name = "pydantic", specifier = ">=2.11.0" }, { name = "python-dotenv", marker = "extra == 'viewer'", specifier = ">=1.0.0" }, { name = "python-multipart", marker = "extra == 'viewer'", specifier = ">=0.0.5" }, { name = "rich", marker = "extra == 'arc'", specifier = ">=13.0" }, @@ -1605,7 +1605,7 @@ dependencies = [ requires-dist = [ { name = "nooa", editable = "." }, { name = "numpy", specifier = ">=1.24.0" }, - { name = "pydantic", specifier = ">=2.5.0" }, + { name = "pydantic", specifier = ">=2.11.0" }, ] [[package]]