mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
refactor: tie api_mode to provider config instead of env var (#1656)
Remove HERMES_API_MODE env var. api_mode is now configured where the endpoint is defined: - model.api_mode in config.yaml (for the active model config) - custom_providers[].api_mode (for named custom providers) Replace _get_configured_api_mode() with _parse_api_mode() which just validates a value against the whitelist without reading env vars. Both paths (model config and named custom providers) now read api_mode from their respective config entries rather than a global override.
This commit is contained in:
parent
4e66d22151
commit
766f4aae2b
2 changed files with 42 additions and 41 deletions
|
|
@ -33,21 +33,15 @@ def _get_model_config() -> Dict[str, Any]:
|
|||
return {}
|
||||
|
||||
|
||||
def _get_configured_api_mode(model_cfg: Optional[Dict[str, Any]] = None) -> Optional[str]:
|
||||
"""Return an optional API mode override from env or config.
|
||||
_VALID_API_MODES = {"chat_completions", "codex_responses"}
|
||||
|
||||
Allows custom OpenAI-compatible endpoints to opt into codex_responses
|
||||
mode via HERMES_API_MODE env var or model.api_mode in config.yaml,
|
||||
without requiring the OpenAI Codex OAuth provider path.
|
||||
"""
|
||||
candidate = os.getenv("HERMES_API_MODE", "").strip().lower()
|
||||
if not candidate:
|
||||
cfg = model_cfg if isinstance(model_cfg, dict) else _get_model_config()
|
||||
raw = cfg.get("api_mode")
|
||||
if isinstance(raw, str):
|
||||
candidate = raw.strip().lower()
|
||||
if candidate in {"chat_completions", "codex_responses"}:
|
||||
return candidate
|
||||
|
||||
def _parse_api_mode(raw: Any) -> Optional[str]:
|
||||
"""Validate an api_mode value from config. Returns None if invalid."""
|
||||
if isinstance(raw, str):
|
||||
normalized = raw.strip().lower()
|
||||
if normalized in _VALID_API_MODES:
|
||||
return normalized
|
||||
return None
|
||||
|
||||
|
||||
|
|
@ -104,11 +98,15 @@ def _get_named_custom_provider(requested_provider: str) -> Optional[Dict[str, An
|
|||
menu_key = f"custom:{name_norm}"
|
||||
if requested_norm not in {name_norm, menu_key}:
|
||||
continue
|
||||
return {
|
||||
result = {
|
||||
"name": name.strip(),
|
||||
"base_url": base_url.strip(),
|
||||
"api_key": str(entry.get("api_key", "") or "").strip(),
|
||||
}
|
||||
api_mode = _parse_api_mode(entry.get("api_mode"))
|
||||
if api_mode:
|
||||
result["api_mode"] = api_mode
|
||||
return result
|
||||
|
||||
return None
|
||||
|
||||
|
|
@ -139,7 +137,7 @@ def _resolve_named_custom_runtime(
|
|||
|
||||
return {
|
||||
"provider": "openrouter",
|
||||
"api_mode": _get_configured_api_mode() or "chat_completions",
|
||||
"api_mode": custom_provider.get("api_mode", "chat_completions"),
|
||||
"base_url": base_url,
|
||||
"api_key": api_key,
|
||||
"source": f"custom_provider:{custom_provider.get('name', requested_provider)}",
|
||||
|
|
@ -208,11 +206,10 @@ def _resolve_openrouter_runtime(
|
|||
)
|
||||
|
||||
source = "explicit" if (explicit_api_key or explicit_base_url) else "env/config"
|
||||
api_mode = _get_configured_api_mode(model_cfg) or "chat_completions"
|
||||
|
||||
return {
|
||||
"provider": "openrouter",
|
||||
"api_mode": api_mode,
|
||||
"api_mode": _parse_api_mode(model_cfg.get("api_mode")) or "chat_completions",
|
||||
"base_url": base_url,
|
||||
"api_key": api_key,
|
||||
"source": source,
|
||||
|
|
|
|||
|
|
@ -328,10 +328,10 @@ def test_resolve_requested_provider_precedence(monkeypatch):
|
|||
assert rp.resolve_requested_provider() == "auto"
|
||||
|
||||
|
||||
# ── api_mode override tests ─────────────────────────────────────────────
|
||||
# ── api_mode config override tests ──────────────────────────────────────
|
||||
|
||||
|
||||
def test_custom_endpoint_api_mode_from_config(monkeypatch):
|
||||
def test_model_config_api_mode(monkeypatch):
|
||||
"""model.api_mode in config.yaml should override the default chat_completions."""
|
||||
monkeypatch.setattr(rp, "resolve_provider", lambda *a, **k: "openrouter")
|
||||
monkeypatch.setattr(
|
||||
|
|
@ -346,7 +346,6 @@ def test_custom_endpoint_api_mode_from_config(monkeypatch):
|
|||
monkeypatch.setenv("OPENAI_API_KEY", "test-key")
|
||||
monkeypatch.delenv("OPENROUTER_BASE_URL", raising=False)
|
||||
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
|
||||
monkeypatch.delenv("HERMES_API_MODE", raising=False)
|
||||
|
||||
resolved = rp.resolve_runtime_provider(requested="custom")
|
||||
|
||||
|
|
@ -354,28 +353,12 @@ def test_custom_endpoint_api_mode_from_config(monkeypatch):
|
|||
assert resolved["base_url"] == "http://127.0.0.1:9208/v1"
|
||||
|
||||
|
||||
def test_env_api_mode_overrides_config(monkeypatch):
|
||||
"""HERMES_API_MODE env var takes precedence over config."""
|
||||
monkeypatch.setattr(rp, "resolve_provider", lambda *a, **k: "openrouter")
|
||||
monkeypatch.setattr(rp, "_get_model_config", lambda: {"api_mode": "chat_completions"})
|
||||
monkeypatch.setenv("OPENAI_BASE_URL", "http://127.0.0.1:9208/v1")
|
||||
monkeypatch.setenv("OPENAI_API_KEY", "test-key")
|
||||
monkeypatch.setenv("HERMES_API_MODE", "codex_responses")
|
||||
monkeypatch.delenv("OPENROUTER_BASE_URL", raising=False)
|
||||
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
|
||||
|
||||
resolved = rp.resolve_runtime_provider(requested="custom")
|
||||
|
||||
assert resolved["api_mode"] == "codex_responses"
|
||||
|
||||
|
||||
def test_invalid_api_mode_ignored(monkeypatch):
|
||||
"""Invalid api_mode values should fall back to chat_completions."""
|
||||
monkeypatch.setattr(rp, "resolve_provider", lambda *a, **k: "openrouter")
|
||||
monkeypatch.setattr(rp, "_get_model_config", lambda: {"api_mode": "bogus_mode"})
|
||||
monkeypatch.setenv("OPENAI_BASE_URL", "http://127.0.0.1:9208/v1")
|
||||
monkeypatch.setenv("OPENAI_API_KEY", "test-key")
|
||||
monkeypatch.delenv("HERMES_API_MODE", raising=False)
|
||||
monkeypatch.delenv("OPENROUTER_BASE_URL", raising=False)
|
||||
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
|
||||
|
||||
|
|
@ -384,16 +367,37 @@ def test_invalid_api_mode_ignored(monkeypatch):
|
|||
assert resolved["api_mode"] == "chat_completions"
|
||||
|
||||
|
||||
def test_named_custom_provider_respects_api_mode(monkeypatch):
|
||||
"""Named custom providers should also pick up api_mode overrides."""
|
||||
def test_named_custom_provider_api_mode(monkeypatch):
|
||||
"""custom_providers entries with api_mode should use it."""
|
||||
monkeypatch.setattr(rp, "resolve_provider", lambda *a, **k: "my-server")
|
||||
monkeypatch.setattr(
|
||||
rp, "_get_named_custom_provider",
|
||||
lambda p: {"name": "my-server", "base_url": "http://localhost:8000/v1", "api_key": "sk-test"},
|
||||
lambda p: {
|
||||
"name": "my-server",
|
||||
"base_url": "http://localhost:8000/v1",
|
||||
"api_key": "sk-test",
|
||||
"api_mode": "codex_responses",
|
||||
},
|
||||
)
|
||||
monkeypatch.setenv("HERMES_API_MODE", "codex_responses")
|
||||
|
||||
resolved = rp.resolve_runtime_provider(requested="my-server")
|
||||
|
||||
assert resolved["api_mode"] == "codex_responses"
|
||||
assert resolved["base_url"] == "http://localhost:8000/v1"
|
||||
|
||||
|
||||
def test_named_custom_provider_without_api_mode_defaults(monkeypatch):
|
||||
"""custom_providers entries without api_mode should default to chat_completions."""
|
||||
monkeypatch.setattr(rp, "resolve_provider", lambda *a, **k: "my-server")
|
||||
monkeypatch.setattr(
|
||||
rp, "_get_named_custom_provider",
|
||||
lambda p: {
|
||||
"name": "my-server",
|
||||
"base_url": "http://localhost:8000/v1",
|
||||
"api_key": "sk-test",
|
||||
},
|
||||
)
|
||||
|
||||
resolved = rp.resolve_runtime_provider(requested="my-server")
|
||||
|
||||
assert resolved["api_mode"] == "chat_completions"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue