mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-27 17:58:07 +00:00
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:
parent
148497f6d6
commit
cc7c418b33
2 changed files with 58 additions and 25 deletions
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -232,19 +232,33 @@ class TestDeepseekVSeriesPassThrough:
|
|||
assert result == "deepseek-v4-flash"
|
||||
|
||||
|
||||
# ── DeepSeek regressions (existing behaviour still holds) ──────────────
|
||||
# ── DeepSeek post-2026-07-24 alias remapping ───────────────────────────
|
||||
|
||||
class TestDeepseekCanonicalAndReasonerMapping:
|
||||
"""Canonical pass-through and reasoner-keyword folding stay intact."""
|
||||
"""Retired aliases and fuzzy names rewrite to deepseek-v4-flash.
|
||||
|
||||
DeepSeek cut off ``deepseek-chat`` / ``deepseek-reasoner`` on
|
||||
2026-07-24; sending them on the wire returns HTTP 400.
|
||||
"""
|
||||
|
||||
@pytest.mark.parametrize("model,expected", [
|
||||
("deepseek-chat", "deepseek-chat"),
|
||||
("deepseek-reasoner", "deepseek-reasoner"),
|
||||
("DEEPSEEK-CHAT", "deepseek-chat"),
|
||||
("deepseek-chat", "deepseek-v4-flash"),
|
||||
("deepseek-reasoner", "deepseek-v4-flash"),
|
||||
("DEEPSEEK-CHAT", "deepseek-v4-flash"),
|
||||
("DEEPSEEK-REASONER", "deepseek-v4-flash"),
|
||||
("deepseek/deepseek-reasoner", "deepseek-v4-flash"),
|
||||
("deepseek-v4-pro", "deepseek-v4-pro"),
|
||||
("deepseek-v4-flash", "deepseek-v4-flash"),
|
||||
])
|
||||
def test_canonical_models_pass_through(self, model, expected):
|
||||
def test_retired_aliases_and_canonicals(self, model, expected):
|
||||
assert _normalize_for_deepseek(model) == expected
|
||||
|
||||
def test_provider_path_rewrites_reasoner(self):
|
||||
assert (
|
||||
normalize_model_for_provider("deepseek-reasoner", "deepseek")
|
||||
== "deepseek-v4-flash"
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize("model", [
|
||||
"deepseek-r1",
|
||||
"deepseek-r1-0528",
|
||||
|
|
@ -252,8 +266,8 @@ class TestDeepseekCanonicalAndReasonerMapping:
|
|||
"deepseek-reasoning-preview",
|
||||
"deepseek-cot-experimental",
|
||||
])
|
||||
def test_reasoner_keywords_map_to_reasoner(self, model):
|
||||
assert _normalize_for_deepseek(model) == "deepseek-reasoner"
|
||||
def test_reasoner_keywords_map_to_v4_flash(self, model):
|
||||
assert _normalize_for_deepseek(model) == "deepseek-v4-flash"
|
||||
|
||||
@pytest.mark.parametrize("model", [
|
||||
"deepseek-chat-v3.1", # 'chat' prefix, not V-series pattern
|
||||
|
|
@ -261,5 +275,5 @@ class TestDeepseekCanonicalAndReasonerMapping:
|
|||
"something-random",
|
||||
"gpt-5", # non-DeepSeek names still fall through
|
||||
])
|
||||
def test_unknown_names_fall_back_to_chat(self, model):
|
||||
assert _normalize_for_deepseek(model) == "deepseek-chat"
|
||||
def test_unknown_names_fall_back_to_v4_flash(self, model):
|
||||
assert _normalize_for_deepseek(model) == "deepseek-v4-flash"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue