fix(deepseek): remap retired chat/reasoner aliases to v4-flash

DeepSeek cut off deepseek-chat and deepseek-reasoner on 2026-07-24.
Sending those IDs now returns HTTP 400; rewrite them (and fuzzy
reasoner names) to deepseek-v4-flash so saved configs keep working.
This commit is contained in:
HexLab98 2026-07-25 12:33:16 +07:00 committed by Teknium
parent 148497f6d6
commit cc7c418b33
2 changed files with 58 additions and 25 deletions

View file

@ -12,9 +12,11 @@ Different LLM providers expect model identifiers in different formats:
model IDs, but Claude still uses hyphenated native names like
``claude-sonnet-4-6``.
- **OpenCode Go** preserves dots in model names: ``minimax-m2.7``.
- **DeepSeek** accepts ``deepseek-chat`` (V3), ``deepseek-reasoner``
(R1-family), and the first-class V-series IDs (``deepseek-v4-pro``,
``deepseek-v4-flash``, and any future ``deepseek-v<N>-*``). Older
- **DeepSeek** accepts only the first-class V-series IDs
(``deepseek-v4-pro``, ``deepseek-v4-flash``, and any future
``deepseek-v<N>-*``). The legacy aliases ``deepseek-chat`` and
``deepseek-reasoner`` were retired on 2026-07-24 and are remapped to
``deepseek-v4-flash`` (official non-thinking / thinking shims). Older
Hermes revisions folded every non-reasoner input into
``deepseek-chat``, which on aggregators routes to V3 so a user
picking V4 Pro was silently downgraded.
@ -118,8 +120,9 @@ _LOWERCASE_MODEL_PROVIDERS: frozenset[str] = frozenset({
# ---------------------------------------------------------------------------
# DeepSeek special handling
# ---------------------------------------------------------------------------
# DeepSeek's API only recognises exactly two model identifiers. We map
# common aliases and patterns to the canonical names.
# DeepSeek's direct API only accepts first-class V-series IDs after the
# 2026-07-24 cut-off. Legacy aliases and fuzzy names are remapped here so
# saved configs / picker leftovers cannot keep sending retired IDs.
_DEEPSEEK_REASONER_KEYWORDS: frozenset[str] = frozenset({
"reasoner",
@ -129,9 +132,15 @@ _DEEPSEEK_REASONER_KEYWORDS: frozenset[str] = frozenset({
"cot",
})
# Retired on 2026-07-24 15:59 UTC. Official docs: both aliases mapped to
# deepseek-v4-flash (chat = non-thinking, reasoner = thinking). Thinking
# mode itself is controlled by extra_body.thinking on the DeepSeek profile.
_DEEPSEEK_RETIRED_ALIASES: frozenset[str] = frozenset({
"deepseek-chat",
"deepseek-reasoner",
})
_DEEPSEEK_CANONICAL_MODELS: frozenset[str] = frozenset({
"deepseek-chat", # V3 on DeepSeek direct and most aggregators
"deepseek-reasoner", # R1-family reasoning model
"deepseek-v4-pro", # V4 Pro — first-class model ID
"deepseek-v4-flash", # V4 Flash — first-class model ID
})
@ -149,13 +158,15 @@ def _normalize_for_deepseek(model_name: str) -> str:
"""Map a model input to a DeepSeek-accepted identifier.
Rules:
- Already a known canonical (``deepseek-chat``/``deepseek-reasoner``/
``deepseek-v4-pro``/``deepseek-v4-flash``) -> pass through.
- Retired aliases ``deepseek-chat`` / ``deepseek-reasoner`` (cut off
2026-07-24) -> ``deepseek-v4-flash``.
- Already a known canonical (``deepseek-v4-pro``/``deepseek-v4-flash``)
-> pass through.
- Matches the V-series pattern ``deepseek-v<digit>...`` -> pass through
(covers future ``deepseek-v5-*`` and dated variants without a release).
- Contains a reasoner keyword (r1, think, reasoning, cot, reasoner)
-> ``deepseek-reasoner``.
- Everything else -> ``deepseek-chat``.
-> ``deepseek-v4-flash``.
- Everything else -> ``deepseek-v4-flash``.
Args:
model_name: The bare model name (vendor prefix already stripped).
@ -165,6 +176,11 @@ def _normalize_for_deepseek(model_name: str) -> str:
"""
bare = _strip_vendor_prefix(model_name).lower()
# Retired aliases must rewrite — DeepSeek returns HTTP 400 after the
# 2026-07-24 cut-off if these IDs are sent on the wire.
if bare in _DEEPSEEK_RETIRED_ALIASES:
return "deepseek-v4-flash"
if bare in _DEEPSEEK_CANONICAL_MODELS:
return bare
@ -175,9 +191,9 @@ def _normalize_for_deepseek(model_name: str) -> str:
# Check for reasoner-like keywords anywhere in the name
for keyword in _DEEPSEEK_REASONER_KEYWORDS:
if keyword in bare:
return "deepseek-reasoner"
return "deepseek-v4-flash"
return "deepseek-chat"
return "deepseek-v4-flash"
# ---------------------------------------------------------------------------
@ -369,10 +385,13 @@ def normalize_model_for_provider(model_input: str, target_provider: str) -> str:
'minimax-m2.5-free'
>>> normalize_model_for_provider("deepseek-v3", "deepseek")
'deepseek-chat'
'deepseek-v4-flash'
>>> normalize_model_for_provider("deepseek-r1", "deepseek")
'deepseek-reasoner'
'deepseek-v4-flash'
>>> normalize_model_for_provider("deepseek-reasoner", "deepseek")
'deepseek-v4-flash'
>>> normalize_model_for_provider("my-model", "custom")
'my-model'