mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-29 15:31:38 +08:00
Compare commits
1 Commits
skill/gith
...
hermes/her
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd15c04016 |
@@ -537,8 +537,11 @@ def switch_model(
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# --- Step c: On aggregator, convert vendor:model to vendor/model ---
|
# --- Step c: On aggregator, convert vendor:model to vendor/model ---
|
||||||
|
# Only convert when there's no slash — a slash means the name
|
||||||
|
# is already in vendor/model format and the colon is a variant
|
||||||
|
# tag (:free, :extended, :fast) that must be preserved.
|
||||||
colon_pos = raw_input.find(":")
|
colon_pos = raw_input.find(":")
|
||||||
if colon_pos > 0 and is_aggregator(current_provider):
|
if colon_pos > 0 and "/" not in raw_input and is_aggregator(current_provider):
|
||||||
left = raw_input[:colon_pos].strip().lower()
|
left = raw_input[:colon_pos].strip().lower()
|
||||||
right = raw_input[colon_pos + 1:].strip()
|
right = raw_input[colon_pos + 1:].strip()
|
||||||
if left and right:
|
if left and right:
|
||||||
|
|||||||
70
tests/hermes_cli/test_model_switch_variant_tags.py
Normal file
70
tests/hermes_cli/test_model_switch_variant_tags.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
"""Tests for OpenRouter variant tag preservation in model switching.
|
||||||
|
|
||||||
|
Regression test for GitHub PR #6088 / Discord report: OpenRouter model IDs
|
||||||
|
with variant suffixes like ``:free``, ``:extended``, ``:fast`` were being
|
||||||
|
mangled by the colon-to-slash conversion in model_switch.py Step c.
|
||||||
|
|
||||||
|
The fix: Step c now skips colon→slash conversion when the model name already
|
||||||
|
contains a forward slash (i.e. is already in ``vendor/model`` format), since
|
||||||
|
the colon is a variant tag, not a vendor separator.
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from hermes_cli.model_switch import switch_model
|
||||||
|
|
||||||
|
|
||||||
|
# Shared mock context — skip network calls, credential resolution, catalog lookups
|
||||||
|
_MOCK_VALIDATION = {"accepted": True, "persist": True, "recognized": True, "message": None}
|
||||||
|
|
||||||
|
|
||||||
|
def _run_switch(raw_input: str, current_provider: str = "openrouter") -> str:
|
||||||
|
"""Run switch_model with mocked dependencies, return the resolved model name."""
|
||||||
|
with patch("hermes_cli.model_switch.resolve_alias", return_value=None), \
|
||||||
|
patch("hermes_cli.model_switch.list_provider_models", return_value=[]), \
|
||||||
|
patch("hermes_cli.runtime_provider.resolve_runtime_provider",
|
||||||
|
return_value={"api_key": "test", "base_url": "", "api_mode": "chat_completions"}), \
|
||||||
|
patch("hermes_cli.models.validate_requested_model", return_value=_MOCK_VALIDATION), \
|
||||||
|
patch("hermes_cli.model_switch.get_model_info", return_value=None), \
|
||||||
|
patch("hermes_cli.model_switch.get_model_capabilities", return_value=None), \
|
||||||
|
patch("hermes_cli.models.detect_provider_for_model", return_value=None):
|
||||||
|
result = switch_model(
|
||||||
|
raw_input=raw_input,
|
||||||
|
current_provider=current_provider,
|
||||||
|
current_model="anthropic/claude-sonnet-4.6",
|
||||||
|
)
|
||||||
|
assert result.success, f"switch_model failed: {result.error_message}"
|
||||||
|
return result.new_model
|
||||||
|
|
||||||
|
|
||||||
|
class TestVariantTagPreservation:
|
||||||
|
"""OpenRouter variant tags (:free, :extended, :fast) must survive model switching."""
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("model,expected", [
|
||||||
|
("nvidia/nemotron-3-super-120b-a12b:free", "nvidia/nemotron-3-super-120b-a12b:free"),
|
||||||
|
("anthropic/claude-sonnet-4.6:extended", "anthropic/claude-sonnet-4.6:extended"),
|
||||||
|
("meta-llama/llama-4-maverick:fast", "meta-llama/llama-4-maverick:fast"),
|
||||||
|
])
|
||||||
|
def test_slash_format_preserves_variant_tag(self, model, expected):
|
||||||
|
"""Models already in vendor/model:tag format must not have their tag mangled."""
|
||||||
|
assert _run_switch(model) == expected
|
||||||
|
|
||||||
|
def test_legacy_colon_format_converts_to_slash(self):
|
||||||
|
"""Legacy vendor:model (no slash) should still be converted to vendor/model."""
|
||||||
|
result = _run_switch("nvidia:nemotron-3-super-120b-a12b")
|
||||||
|
assert result == "nvidia/nemotron-3-super-120b-a12b"
|
||||||
|
|
||||||
|
def test_legacy_colon_format_with_tag_converts_first_colon_only(self):
|
||||||
|
"""vendor:model:free (no slash) → vendor/model:free — first colon becomes slash."""
|
||||||
|
result = _run_switch("nvidia:nemotron-3-super-120b-a12b:free")
|
||||||
|
assert result == "nvidia/nemotron-3-super-120b-a12b:free"
|
||||||
|
|
||||||
|
def test_bare_model_name_unaffected(self):
|
||||||
|
"""Bare model names without colons or slashes should work normally."""
|
||||||
|
result = _run_switch("claude-sonnet-4.6")
|
||||||
|
assert result == "anthropic/claude-sonnet-4.6"
|
||||||
|
|
||||||
|
def test_already_correct_slug_no_tag(self):
|
||||||
|
"""Standard vendor/model slugs without tags pass through unchanged."""
|
||||||
|
result = _run_switch("anthropic/claude-sonnet-4.6")
|
||||||
|
assert result == "anthropic/claude-sonnet-4.6"
|
||||||
Reference in New Issue
Block a user