Model Normalization¶
normalize_model() resolves whatever you typed into a fully-qualified LiteLLM
wire ID. It runs before generation, strategy detection and pricing lookup.
User-facing model names use lowercase provider/creator/model, with creator
aliases folded to one spelling. The wire ID preserves the provider's exact
spelling and capitalization for requests.
from basemode.detect import normalize_model
normalize_model("claude-sonnet-4-6") # "anthropic/claude-sonnet-4-6"
normalize_model("gpt-4o-mini") # "openai/gpt-4o-mini"
normalize_model("kimi-k2") # "moonshot/kimi-k2-0905-preview"
normalize_model("deepinfra/zai/glm-5.3-flash") # "deepinfra/zai-org/GLM-5.3-Flash"
This lets the CLI accept familiar short names while the rest of the pipeline uses provider-qualified IDs.
Resolution order¶
- Exact alias. A lookup table of shorthand and of models newer than
LiteLLM's baked-in list.
kimi-k2resolves to a specific dated snapshot (moonshot/kimi-k2-0905-preview) because the bare name is not routable. - Explicit provider prefix. Anything containing
/is taken as provider-qualified, with Anthropic name fixes applied to the part after the slash. - Provider inference from name fragments.
claude,opus,sonnetandhaikuimplyanthropic;geminiandgemmaimplygemini;gpt,o1,o3,o4implyopenai; alsoglm(zai),grok(xai),command(cohere),kimi(moonshot),deepseek. - LiteLLM's own resolution, as a last resort.
The resulting name is matched against the local catalog: exact wire IDs take
precedence, followed by case-insensitive wire IDs and canonical identities.
Known names resolve to the provider's spelling; ambiguous canonical identities
require an exact wire ID. Unknown names pass through for custom endpoints and
models newer than the catalog. basemode default additionally rejects unknown
names unless --force is given.
This resolution is shared by CLI model arguments and the Python streaming APIs, including continuation, branching and chat. It makes no provider requests. The catalog index is cached for the life of the process.
basemode models, including --live, displays canonical names. Use
--wire-ids to inspect exact provider IDs. JSON picker entries expose the
canonical name as model, display and canonical_id, and the exact provider
ID as wire_id. Snapshot IDs and picker selections use canonical names too.
Nested model paths are preserved: novita/deepseek/deepseek-r1/community
and novita/deepseek/deepseek-v3/community remain distinct identities.
Local aliases are checked before LiteLLM. Some LiteLLM resolution failures print provider guidance to stdout, which would corrupt machine-readable CLI output.
Anthropic-specific handling¶
Anthropic IDs use dashes between version digits; dotted versions are accepted:
Anthropic names also expand by unique substring match, so you can skip the
claude- prefix and the date suffix:
normalize_model("sonnet-4-5") # "anthropic/claude-sonnet-4-5-20250929"
normalize_model("opus-4-7") # "anthropic/claude-opus-4-7"
Expansion requires exactly one known match. Ambiguous or unknown fragments pass through unchanged and may be rejected by the provider.
Checking what you got¶
basemode info shows the canonical name and wire ID alongside the strategy it selected:
If a model unexpectedly returns 404, check whether normalization left an ambiguous fragment unchanged.
Adding an alias¶
Aliases live in _MODEL_ALIASES and provider fragments in _PREFIX_MAP, both
in src/basemode/detect.py. Add a regression in tests/ alongside. See
Agent Quickstart.