Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions tests/tools/test_transcription_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -1097,6 +1097,125 @@ def test_is_local_or_private_url(self):
assert not _is_local_or_private_url("")


class TestOpenAIAudioGatewayPreference:
def test_gateway_preference_uses_the_loaded_stt_config(self):
from tools.transcription_tools import _resolve_openai_audio_client_config

managed = MagicMock(
nous_user_token="managed-token",
gateway_origin="https://openai-audio-gateway.nousresearch.com",
)
with patch(
"hermes_cli.config.load_config",
return_value={
"stt": {
"use_gateway": True,
"openai": {"api_key": "direct-config-key"},
}
},
) as config_loader, patch(
"tools.transcription_tools.resolve_openai_audio_api_key",
return_value="direct-env-key",
) as direct_resolver, patch(
"tools.transcription_tools.resolve_managed_tool_gateway",
return_value=managed,
):
api_key, base_url = _resolve_openai_audio_client_config()

assert api_key == "managed-token"
assert base_url == "https://openai-audio-gateway.nousresearch.com/v1"
config_loader.assert_called_once_with()
direct_resolver.assert_not_called()

@pytest.mark.parametrize(
("openai_config", "direct_api_key"),
[
({"api_key": "direct-config-key"}, "direct-env-key"),
({"base_url": "http://localhost:8504/v1"}, ""),
({}, "direct-env-key"),
],
ids=["config-key", "local-endpoint", "environment-key"],
)
def test_gateway_preference_overrides_each_direct_source(
self, openai_config, direct_api_key
):
from tools.transcription_tools import _resolve_openai_audio_client_config

managed = MagicMock(
nous_user_token="managed-token",
gateway_origin="https://openai-audio-gateway.nousresearch.com",
)
with patch(
"tools.transcription_tools._load_stt_config",
return_value={"use_gateway": True, "openai": openai_config},
), patch(
"tools.transcription_tools.resolve_openai_audio_api_key",
return_value=direct_api_key,
) as direct_resolver, patch(
"tools.transcription_tools.resolve_managed_tool_gateway",
return_value=managed,
):
api_key, base_url = _resolve_openai_audio_client_config()

assert api_key == "managed-token"
assert base_url == "https://openai-audio-gateway.nousresearch.com/v1"
direct_resolver.assert_not_called()

def test_gateway_preference_propagates_from_real_config(
self, tmp_path, monkeypatch
):
from tools.transcription_tools import _resolve_openai_audio_client_config

monkeypatch.setenv("HERMES_HOME", str(tmp_path))
monkeypatch.setenv("OPENAI_API_KEY", "direct-env-key")
(tmp_path / "config.yaml").write_text(
"stt:\n"
" provider: openai\n"
" use_gateway: true\n"
" openai:\n"
" api_key: direct-config-key\n",
encoding="utf-8",
)
managed = MagicMock(
nous_user_token="managed-token",
gateway_origin="https://openai-audio-gateway.nousresearch.com",
)
with patch(
"tools.transcription_tools.resolve_managed_tool_gateway",
return_value=managed,
):
api_key, base_url = _resolve_openai_audio_client_config()

assert api_key == "managed-token"
assert base_url == "https://openai-audio-gateway.nousresearch.com/v1"

def test_gateway_preference_does_not_fall_back_to_direct_credentials(self):
from tools.transcription_tools import _resolve_openai_audio_client_config

with patch(
"tools.transcription_tools._load_stt_config",
return_value={"use_gateway": True},
), patch(
"tools.transcription_tools.resolve_openai_audio_api_key",
return_value="direct-env-key",
) as direct_resolver, patch(
"tools.transcription_tools.resolve_managed_tool_gateway",
return_value=None,
), patch(
"tools.transcription_tools.nous_tool_gateway_unavailable_message",
return_value="Managed audio gateway is unavailable",
):
with pytest.raises(
ValueError,
match="stt.use_gateway is enabled, so direct credentials were skipped",
) as excinfo:
_resolve_openai_audio_client_config()

assert "Managed audio gateway is unavailable" in str(excinfo.value)
assert "is set" not in str(excinfo.value)
direct_resolver.assert_not_called()


# =====================================================================


Expand Down
26 changes: 20 additions & 6 deletions tools/transcription_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -2951,25 +2951,39 @@ def transcribe_audio_local_fallback(


def _resolve_openai_audio_client_config() -> tuple[str, str]:
"""Return direct OpenAI audio config or a managed gateway fallback."""
"""Return OpenAI audio config, honoring an explicit gateway preference.

When ``stt.use_gateway`` is set, managed audio is selected before every
direct credential source, matching the other gateway-enabled media tools.
"""
stt_config = _load_stt_config()
openai_cfg = stt_config.get("openai") or {}
cfg_api_key = openai_cfg.get("api_key", "")
cfg_base_url = openai_cfg.get("base_url", "")
if cfg_api_key:
prefer_managed = is_truthy_value(stt_config.get("use_gateway"), default=False)
if cfg_api_key and not prefer_managed:
return cfg_api_key, (cfg_base_url or OPENAI_BASE_URL)
Comment thread
bgrablin marked this conversation as resolved.

# A local OpenAI-compatible server needs no key — send a placeholder so
# the SDK doesn't refuse to construct a client (#25193, credit @nnnet).
if cfg_base_url and _is_local_or_private_url(cfg_base_url):
if cfg_base_url and _is_local_or_private_url(cfg_base_url) and not prefer_managed:
return "not-needed", cfg_base_url

direct_api_key = resolve_openai_audio_api_key()
if direct_api_key:
return direct_api_key, OPENAI_BASE_URL
if not prefer_managed:
direct_api_key = resolve_openai_audio_api_key()
if direct_api_key:
return direct_api_key, OPENAI_BASE_URL

managed_gateway = resolve_managed_tool_gateway("openai-audio")
if managed_gateway is None:
if prefer_managed:
raise ValueError(
"stt.use_gateway is enabled, so direct credentials were skipped. "
+ nous_tool_gateway_unavailable_message(
"managed OpenAI audio for transcription",
)
)

message = "Neither stt.openai.api_key in config nor VOICE_TOOLS_OPENAI_KEY/OPENAI_API_KEY is set"
if managed_nous_tools_enabled():
message += (
Expand Down
Loading