DeepSeek V4 and Ollama Cloud model ID fixes
NousResearch/hermes-agent · cluster 12988 · agent
Data as of
6 open items. Fix merged in #19881. Check it covers your case.
The short answer
- Known?
- Yes. 17 related items grouped here, 5 issues already closed.
- Fix event
- A fix was merged to main in PR #19881. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
unresolved. That is separate from the fix event. - Still open
- 3 issues and 3 PRs.
Next step: read #19881, then check whether your report is one of the 3 still open.
Read #19881Pick up a Lab taskThese items cover adding DeepSeek V4 model identifiers and pricing, fixing normalization bugs that silently fold V4 IDs to wrong names or break custom endpoints like Volcengine ARK, and addressing Ollama Cloud issues where :cloud/-cloud suffixes cause invalid model IDs.
Open issues3
- Issue#12988[How-to] Volcengine Coding Plan configuration + model_normalize bug with vendor-prefixed model IDs
- Issue#17199deepseek provider: model normalization and base_url override break custom endpoints (e.g. Volcengine ARK)
- Issue#45137ollama-cloud: validate_requested_model rejects bare model IDs like 'deepseek-v4-flash' because normalize_model_for_provider doesn't strip :cloud/-cloud suffix
Open PRs3
Fixes on record
-
#15123 fix(model-normalize): pass DeepSeek V-series IDs through instead of folding to deepseek-chat
-
#19881 fix(models): strip :cloud/-cloud suffix from models.dev Ollama Cloud IDs
files:
hermes_cli/models.py,tests/hermes_cli/test_ollama_cloud_provider.pyShow the diff
-
hermes_cli/models.py@@ -2906,6 +2906,19 @@ def fetch_api_models( _OLLAMA_CLOUD_CACHE_TTL = 3600 # 1 hour +def _strip_ollama_cloud_suffix(model_id: str) -> str: + """Strip :cloud / -cloud suffixes that models.dev appends to Ollama Cloud IDs. + + The live API uses clean IDs (e.g. 'kimi-k2.6') while models.dev sometimes + returns them as 'kimi-k2.6:cloud'. Normalising before the dedup merge + prevents duplicate entries in the merged model list. + """ + for suffix in (":cloud", "-cloud"): + if model_id.endswith(suffix): + return model_id[: -len(suffix)] + return model_id + + def _ollama_cloud_cache_path() -> Path: """Return the path for the Ollama Cloud model cache.""" from hermes_constants import get_hermes_home @@ -3001,9 +3014,10 @@ def fetch_ollama_cloud_models( seen.add(m) merged.append(m) for m in mdev_models: - if m and m not in seen: - seen.add(m) - merged.append(m) + normalized = _strip_ollama_cloud_suffix(m) + if normalized and normalized not in seen: + seen.add(normalized) + merged.append(normalized) if merged: _save_ollama_cloud_cache(merged) return merged -
tests/hermes_cli/test_ollama_cloud_provider.py@@ -401,6 +401,103 @@ def test_get_provider(self): assert pdef.transport == "openai_chat" +# ── Cloud Suffix Stripping ── + +class TestOllamaCloudSuffixStripping: + """models.dev appends :cloud / -cloud suffixes that the live API omits. + + fetch_ollama_cloud_models() must normalise these before the dedup merge so + users never see broken IDs like 'kimi-k2.6:cloud' in the model picker. + """ + + def test_strips_colon_cloud_suffix(self, tmp_path, monkeypatch): + """:cloud suffix from models.dev is stripped before merge.""" + from hermes_cli.models import fetch_ollama_cloud_models + + monkeypatch.setenv("HERMES_HOME", str(tmp_path)) + monkeypatch.delenv("OLLAMA_API_KEY", raising=False) + + mock_mdev = { + "ollama-cloud": { + "models": {"kimi-k2.6:cloud": {"tool_call": True}} + } + } + with patch("agent.models_dev.fetch_models_dev", return_value=mock_mdev): + result = fetch_ollama_cloud_models(force_refresh=True) + + assert "kimi-k2.6" in result + assert "kimi-k2.6:cloud" not in result + + def test_strips_dash_cloud_suffix(self, tmp_path, monkeypatch): + """-cloud suffix from models.dev is stripped before merge.""" + from hermes_cli.models import fetch_ollama_cloud_models + + monkeypatch.setenv("HERMES_HOME", str(tmp_path)) + monkeypatch.delenv("OLLAMA_API_KEY", raising=False) + + mock_mdev = { + "ollama-cloud": { + "models": {"qwen3-coder:480b-cloud": {"tool_call": True}} + } + } + with patch("agent.models_dev.fetch_models_dev", return_value=mock_mdev): + result = fetch_ollama_cloud_models(force_refresh=True) + + assert "qwen3-coder:480b" in result + assert "qwen3-coder:480b-cloud" not in result + + def test_no_duplicate_when_live_clean_and_mdev_suffixed(self, tmp_path, monkeypatch): + """Live API returns clean ID; mdev has :cloud variant — result has exactly one entry.""" + from hermes_cli.models import fetch_ollama_cloud_models + + monkeypatch.setenv("HERMES_HOME", str(tmp_path)) + monkeypatch.setenv("OLLAMA_API_KEY", "test-key") + + mock_mdev = { + "ollama-cloud": { + "models": { + "kimi-k2.6:cloud": {"tool_call": True}, + "glm-5.1:cloud": {"tool_call": True}, + } + } + } + with patch("hermes_cli.models.fetch_api_models", return_value=["kimi-k2.6", "glm-5.1"]), \ + patch("agent.models_dev.fetch_models_dev", return_value=mock_mdev): + result = fetch_ollama_cloud_models(force_refresh=True) + + assert result.count("kimi-k2.6") == 1 + assert result.count("glm-5.1") == 1 + assert "kimi-k2.6:cloud" not in result + assert "glm-5.1:cloud" not in result + + def test_unsuffixed_model_id_unchanged(self, tmp_path, monkeypatch): + """Model IDs without :cloud / -cloud suffix are passed through unchanged.""" + from hermes_cli.models import fetch_ollama_cloud_models + + monkeypatch.setenv("HERMES_HOME", str(tmp_path)) + monkeypatch.delenv("OLLAMA_API_KEY", raising=False) + + mock_mdev = { + "ollama-cloud": { + "models": {"nemotron-3-nano:30b": {"tool_call": True}} + } + } + with patch("agent.models_dev.fetch_models_dev", return_value=mock_mdev): + result = fetch_ollama_cloud_models(force_refresh=True) + + assert "nemotron-3-nano:30b" in result + + def test_strip_suffix_helper(self): + """Unit test for the _strip_ollama_cloud_suffix helper.""" + from hermes_cli.models import _strip_ollama_cloud_suffix + + assert _strip_ollama_cloud_suffix("kimi-k2.6:cloud") == "kimi-k2.6" + assert _strip_ollama_cloud_suffix("glm-5.1:cloud") == "glm-5.1" + assert _strip_ollama_cloud_suffix("qwen3-coder:480b-cloud") == "qwen3-coder:480b" + assert _strip_ollama_cloud_suffix("nemotron-3-nano:30b") == "nemotron-3-nano:30b" + assert _strip_ollama_cloud_suffix("") == "" + + # ── Auxiliary Model ── class TestOllamaCloudAuxiliary:
-
4 PRs closed without merging
From the close comments: superseded by another PR 2 · duplicate 1 · merged via another change 1
-
#14891 feat: add DeepSeek V4 model identifiers · superseded by another PR →
#14946,#14934 -
#14911 feat(models): add deepseek-v4-pro to DeepSeek provider catalog · duplicate →
#14891 -
#16192 fix(models): strip :cloud/-cloud suffix from models.dev Ollama Cloud IDs · merged via another change →
#19881 -
#16196 fix(cli): implement self-healing sanitization for Ollama Cloud model IDs · superseded by another PR →
#19881
Closed issues · 5
- Closed#14902[Feature]: support for deepseek-v4-pro model
- Closed#15936[特点]Native support for DeepSeek V4 series (deepseek-v4-pro / flash)
- Closed#16160deepseek-v4-pro model name gets normalized to deepseek-chat silently
- Closed#16179[Bug]: Ollama Cloud model list shows wrong names with :cloud / -cloud suffix (e.g. kimi-k2.6:cloud), causing 400/404
- Closed#19128[Feature]: add `qwen3.6-flash`, `deepseek-v4-flash`, `deepseek-v4-pro` for Alibaba provider
How this cluster was grouped
-
Link evidence (
partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text. - Members outside that structural core may belong to a different underlying problem.
-
Pairwise link density
0.09: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the Lab