mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-01 12:02:05 +00:00
fix(agent): validate OpenRouter provider sort before request dispatch
This commit is contained in:
parent
27322612b4
commit
1b6ebb24c0
4 changed files with 60 additions and 5 deletions
|
|
@ -37,6 +37,7 @@ from tools.terminal_tool import is_persistent_env
|
|||
from utils import base_url_host_matches, base_url_hostname, env_float, env_int
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
_OPENROUTER_PROVIDER_SORT_VALUES = {"throughput", "latency", "price"}
|
||||
|
||||
|
||||
def _ra():
|
||||
|
|
@ -115,6 +116,23 @@ def _is_openai_codex_backend(agent) -> bool:
|
|||
)
|
||||
|
||||
|
||||
def _validated_openrouter_provider_sort(raw_sort: Any) -> Optional[str]:
|
||||
"""Return a normalized OpenRouter provider.sort value or None."""
|
||||
if not isinstance(raw_sort, str):
|
||||
return None
|
||||
sort_value = raw_sort.strip().lower()
|
||||
if not sort_value:
|
||||
return None
|
||||
if sort_value in _OPENROUTER_PROVIDER_SORT_VALUES:
|
||||
return sort_value
|
||||
logger.warning(
|
||||
"Ignoring invalid OpenRouter provider.sort value %r (allowed: %s)",
|
||||
raw_sort,
|
||||
", ".join(sorted(_OPENROUTER_PROVIDER_SORT_VALUES)),
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
def _env_float(name: str, default: float) -> float:
|
||||
try:
|
||||
return float(os.getenv(name, str(default)))
|
||||
|
|
@ -698,8 +716,9 @@ def build_api_kwargs(agent, api_messages: list) -> dict:
|
|||
_prefs["ignore"] = agent.providers_ignored
|
||||
if agent.providers_order:
|
||||
_prefs["order"] = agent.providers_order
|
||||
if agent.provider_sort:
|
||||
_prefs["sort"] = agent.provider_sort
|
||||
_provider_sort = _validated_openrouter_provider_sort(agent.provider_sort)
|
||||
if _provider_sort:
|
||||
_prefs["sort"] = _provider_sort
|
||||
if agent.provider_require_parameters:
|
||||
_prefs["require_parameters"] = True
|
||||
if agent.provider_data_collection:
|
||||
|
|
@ -1476,8 +1495,9 @@ def handle_max_iterations(agent, messages: list, api_call_count: int) -> str:
|
|||
provider_preferences["ignore"] = agent.providers_ignored
|
||||
if agent.providers_order:
|
||||
provider_preferences["order"] = agent.providers_order
|
||||
if agent.provider_sort:
|
||||
provider_preferences["sort"] = agent.provider_sort
|
||||
_provider_sort = _validated_openrouter_provider_sort(agent.provider_sort)
|
||||
if _provider_sort:
|
||||
provider_preferences["sort"] = _provider_sort
|
||||
if provider_preferences and (
|
||||
(agent.provider or "").strip().lower() == "openrouter"
|
||||
or agent._is_openrouter_url()
|
||||
|
|
|
|||
|
|
@ -2335,7 +2335,7 @@ def run_job(job: dict) -> tuple[bool, str, str, Optional[str]]:
|
|||
max_iterations = _cfg.get("agent", {}).get("max_turns") or _cfg.get("max_turns") or 90
|
||||
|
||||
# Provider routing
|
||||
pr = _cfg.get("provider_routing", {})
|
||||
pr = _cfg.get("provider_routing") or {}
|
||||
|
||||
from hermes_cli.runtime_provider import (
|
||||
resolve_runtime_provider,
|
||||
|
|
|
|||
13
tests/agent/test_chat_completion_helpers_provider_sort.py
Normal file
13
tests/agent/test_chat_completion_helpers_provider_sort.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
from agent.chat_completion_helpers import _validated_openrouter_provider_sort
|
||||
|
||||
|
||||
def test_validated_openrouter_provider_sort_accepts_valid_values():
|
||||
assert _validated_openrouter_provider_sort("price") == "price"
|
||||
assert _validated_openrouter_provider_sort(" latency ") == "latency"
|
||||
assert _validated_openrouter_provider_sort("THROUGHPUT") == "throughput"
|
||||
|
||||
|
||||
def test_validated_openrouter_provider_sort_rejects_invalid_values():
|
||||
assert _validated_openrouter_provider_sort("intelligence") is None
|
||||
assert _validated_openrouter_provider_sort("") is None
|
||||
assert _validated_openrouter_provider_sort(None) is None
|
||||
|
|
@ -1676,6 +1676,14 @@ class TestBuildApiKwargs:
|
|||
kwargs = agent._build_api_kwargs(messages)
|
||||
assert kwargs["extra_body"]["provider"]["only"] == ["Anthropic"]
|
||||
|
||||
def test_provider_preferences_drop_invalid_sort(self, agent):
|
||||
agent.provider = "openrouter"
|
||||
agent.base_url = "https://openrouter.ai/api/v1"
|
||||
agent.provider_sort = "intelligence"
|
||||
messages = [{"role": "user", "content": "hi"}]
|
||||
kwargs = agent._build_api_kwargs(messages)
|
||||
assert "sort" not in kwargs.get("extra_body", {}).get("provider", {})
|
||||
|
||||
def test_reasoning_config_default_openrouter(self, agent):
|
||||
"""Default reasoning config for OpenRouter should be medium."""
|
||||
agent.provider = "openrouter"
|
||||
|
|
@ -3492,6 +3500,20 @@ class TestHandleMaxIterations:
|
|||
kwargs = agent.client.chat.completions.create.call_args.kwargs
|
||||
assert kwargs["extra_body"]["provider"]["only"] == ["Anthropic"]
|
||||
|
||||
def test_summary_drops_invalid_provider_sort(self, agent):
|
||||
agent.base_url = "https://openrouter.ai/api/v1"
|
||||
agent._base_url_lower = agent.base_url.lower()
|
||||
agent.provider = "openrouter"
|
||||
agent.provider_sort = "intelligence"
|
||||
agent.client.chat.completions.create.return_value = _mock_response(content="Summary")
|
||||
agent._cached_system_prompt = "You are helpful."
|
||||
|
||||
result = agent._handle_max_iterations([{"role": "user", "content": "do stuff"}], 60)
|
||||
|
||||
assert result == "Summary"
|
||||
kwargs = agent.client.chat.completions.create.call_args.kwargs
|
||||
assert "sort" not in kwargs.get("extra_body", {}).get("provider", {})
|
||||
|
||||
def test_codex_summary_sanitizes_orphan_tool_results(self, agent):
|
||||
agent.api_mode = "codex_responses"
|
||||
agent.provider = "openai-codex"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue