fix(agent): honor explicit free OpenRouter models

This commit is contained in:
fangliquanflq
2026-08-13 21:46:03 +08:00
committed by Teknium
parent ab80a38514
commit a65ad15636
2 changed files with 59 additions and 5 deletions

View File

@@ -2943,7 +2943,6 @@ def _try_openrouter(explicit_api_key: str = None, model: str = None) -> Tuple[Op
"auxiliary.free_only.",
or_model,
)
_mark_provider_unhealthy("openrouter", ttl=60)
return None, None
if not _is_free_model(or_model):
_warn_paid_lane_once(or_model)
@@ -2969,8 +2968,15 @@ def _try_openrouter(explicit_api_key: str = None, model: str = None) -> Tuple[Op
default_headers=build_or_headers()), or_model
def _describe_openrouter_unavailable() -> str:
"""Return a more precise OpenRouter auth failure reason for logs."""
def _describe_openrouter_unavailable(model: str = None) -> str:
"""Return the policy or credential reason OpenRouter was unavailable."""
free_only, cfg_model = _aux_openrouter_settings()
or_model = model or cfg_model
if free_only and not _is_free_model(or_model):
return (
f"auxiliary.free_only rejected non-free model {or_model!r}; "
"the request was skipped before provider availability checks"
)
pool_present, entry = _select_pool_entry("openrouter")
if pool_present:
if entry is None:
@@ -6489,11 +6495,14 @@ def resolve_provider_client(
# ── OpenRouter ───────────────────────────────────────────
if provider == "openrouter":
client, default = _try_openrouter(explicit_api_key=explicit_api_key)
client, default = _try_openrouter(
explicit_api_key=explicit_api_key,
model=model,
)
if client is None:
logger.warning(
"resolve_provider_client: openrouter requested but %s",
_describe_openrouter_unavailable(),
_describe_openrouter_unavailable(model=model),
)
return None, None
final_model = _normalize_resolved_model(model or default, provider)

View File

@@ -1042,6 +1042,51 @@ class TestOpenRouterPaidLaneGuard:
assert model is None
mock_openai.assert_not_called()
def test_resolver_forwards_explicit_free_model_to_gate(self, monkeypatch):
"""The concrete OpenRouter route gates the caller's model, not its default."""
monkeypatch.setenv("OPENROUTER_API_KEY", "or-key")
with patch("agent.auxiliary_client._select_pool_entry", return_value=(False, None)), \
patch("hermes_cli.config.load_config_readonly",
return_value={"auxiliary": {"free_only": True}}), \
patch("agent.auxiliary_client.OpenAI") as mock_openai:
mock_client = MagicMock(name="openrouter_client")
mock_openai.return_value = mock_client
client, model = resolve_provider_client(
"openrouter", model="nvidia/nemotron-3-ultra-550b-a55b:free"
)
assert client is mock_client
assert model == "nvidia/nemotron-3-ultra-550b-a55b:free"
def test_free_only_gate_does_not_mark_openrouter_unhealthy(self, monkeypatch):
monkeypatch.setenv("OPENROUTER_API_KEY", "or-key")
with patch("agent.auxiliary_client._select_pool_entry", return_value=(False, None)), \
patch("hermes_cli.config.load_config_readonly",
return_value={"auxiliary": {"free_only": True}}), \
patch("agent.auxiliary_client._mark_provider_unhealthy") as mark_unhealthy:
client, model = resolve_provider_client(
"openrouter", model="google/gemini-3.6-flash"
)
assert client is None
assert model is None
mark_unhealthy.assert_not_called()
def test_free_only_gate_reports_policy_not_credentials(self, monkeypatch, caplog):
import logging
monkeypatch.setenv("OPENROUTER_API_KEY", "or-key")
with patch("agent.auxiliary_client._select_pool_entry", return_value=(False, None)), \
patch("hermes_cli.config.load_config_readonly",
return_value={"auxiliary": {"free_only": True}}), \
caplog.at_level(logging.WARNING, logger="agent.auxiliary_client"):
resolve_provider_client("openrouter", model="google/gemini-3.6-flash")
messages = [record.getMessage() for record in caplog.records]
assert any("free_only" in message and "google/gemini-3.6-flash" in message
for message in messages)
assert not any("credentials" in message for message in messages)
def test_paid_lane_warns_once(self, monkeypatch, caplog):
"""Engaging the default paid model logs a WARNING (once per model)."""
import logging