fix(auxiliary): propagate explicit_api_key to _try_openrouter()
When resolve_provider_client() passes explicit_api_key for OpenRouter auxiliary tasks, _try_openrouter() now accepts and honors this parameter instead of silently ignoring it and falling back to OPENROUTER_API_KEY env var. Root cause: _try_openrouter() had no explicit_api_key parameter, so even when callers wanted to pass a runtime credential pool key, it could not be used. Fix: - Add explicit_api_key: str = None parameter to _try_openrouter() - Prioritize explicit_api_key over pool key and env var - Update resolve_provider_client() call site to pass explicit_api_key Regression coverage: - Test that explicit_api_key is passed to OpenAI client when provided - Test that fallback to OPENROUTER_API_KEY still works when explicit_api_key is None Closes #18338
This commit is contained in:
parent
73bcd83dba
commit
af98122793
@ -1149,10 +1149,10 @@ def _resolve_api_key_provider() -> Tuple[Optional[OpenAI], Optional[str]]:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _try_openrouter() -> Tuple[Optional[OpenAI], Optional[str]]:
|
def _try_openrouter(explicit_api_key: str = None) -> Tuple[Optional[OpenAI], Optional[str]]:
|
||||||
pool_present, entry = _select_pool_entry("openrouter")
|
pool_present, entry = _select_pool_entry("openrouter")
|
||||||
if pool_present:
|
if pool_present:
|
||||||
or_key = _pool_runtime_api_key(entry)
|
or_key = explicit_api_key or _pool_runtime_api_key(entry)
|
||||||
if not or_key:
|
if not or_key:
|
||||||
return None, None
|
return None, None
|
||||||
base_url = _pool_runtime_base_url(entry, OPENROUTER_BASE_URL) or OPENROUTER_BASE_URL
|
base_url = _pool_runtime_base_url(entry, OPENROUTER_BASE_URL) or OPENROUTER_BASE_URL
|
||||||
@ -1160,7 +1160,7 @@ def _try_openrouter() -> Tuple[Optional[OpenAI], Optional[str]]:
|
|||||||
return OpenAI(api_key=or_key, base_url=base_url,
|
return OpenAI(api_key=or_key, base_url=base_url,
|
||||||
default_headers=_OR_HEADERS), _OPENROUTER_MODEL
|
default_headers=_OR_HEADERS), _OPENROUTER_MODEL
|
||||||
|
|
||||||
or_key = os.getenv("OPENROUTER_API_KEY")
|
or_key = explicit_api_key or os.getenv("OPENROUTER_API_KEY")
|
||||||
if not or_key:
|
if not or_key:
|
||||||
return None, None
|
return None, None
|
||||||
logger.debug("Auxiliary client: OpenRouter")
|
logger.debug("Auxiliary client: OpenRouter")
|
||||||
@ -2053,9 +2053,9 @@ def resolve_provider_client(
|
|||||||
return (_to_async_client(client, final_model, is_vision=is_vision) if async_mode
|
return (_to_async_client(client, final_model, is_vision=is_vision) if async_mode
|
||||||
else (client, final_model))
|
else (client, final_model))
|
||||||
|
|
||||||
# ── OpenRouter ───────────────────────────────────────────────────
|
# ── OpenRouter ───────────────────────────────────────────
|
||||||
if provider == "openrouter":
|
if provider == "openrouter":
|
||||||
client, default = _try_openrouter()
|
client, default = _try_openrouter(explicit_api_key=explicit_api_key)
|
||||||
if client is None:
|
if client is None:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"resolve_provider_client: openrouter requested but %s",
|
"resolve_provider_client: openrouter requested but %s",
|
||||||
|
|||||||
@ -1818,3 +1818,78 @@ class TestBuildCallKwargsToolDedup:
|
|||||||
provider="openai", model="gpt-4o", messages=[], tools=None,
|
provider="openai", model="gpt-4o", messages=[], tools=None,
|
||||||
)
|
)
|
||||||
assert "tools" not in kwargs
|
assert "tools" not in kwargs
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def _clean_env(monkeypatch):
|
||||||
|
"""Strip provider env vars so each test starts clean."""
|
||||||
|
for key in (
|
||||||
|
"OPENROUTER_API_KEY", "OPENAI_BASE_URL", "OPENAI_API_KEY",
|
||||||
|
):
|
||||||
|
monkeypatch.delenv(key, raising=False)
|
||||||
|
|
||||||
|
|
||||||
|
class TestOpenRouterExplicitApiKey:
|
||||||
|
"""Test that explicit_api_key is correctly propagated to _try_openrouter()."""
|
||||||
|
|
||||||
|
def test_resolve_provider_client_passes_explicit_api_key_to_openrouter(
|
||||||
|
self, monkeypatch
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
When resolve_provider_client() is called with explicit_api_key for OpenRouter,
|
||||||
|
the explicit key should be passed to the OpenAI client instead of falling back
|
||||||
|
to OPENROUTER_API_KEY env var.
|
||||||
|
"""
|
||||||
|
# Set up env var as fallback (should NOT be used when explicit_api_key is provided)
|
||||||
|
monkeypatch.setenv("OPENROUTER_API_KEY", "env-fallback-key")
|
||||||
|
|
||||||
|
# Mock OpenAI to capture the api_key used
|
||||||
|
mock_openai = MagicMock()
|
||||||
|
mock_openai.return_value = MagicMock(name="openrouter-client")
|
||||||
|
|
||||||
|
with patch("agent.auxiliary_client.OpenAI", mock_openai):
|
||||||
|
client, model = resolve_provider_client(
|
||||||
|
provider="openrouter",
|
||||||
|
explicit_api_key="explicit-pool-key",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify a client was created
|
||||||
|
assert client is not None
|
||||||
|
# Verify the explicit key was used, not the env var fallback
|
||||||
|
mock_openai.assert_called_once()
|
||||||
|
call_kwargs = mock_openai.call_args[1]
|
||||||
|
assert call_kwargs["api_key"] == "explicit-pool-key", (
|
||||||
|
f"Expected explicit_api_key to be passed, got: {call_kwargs['api_key']}"
|
||||||
|
)
|
||||||
|
assert call_kwargs["api_key"] != "env-fallback-key", (
|
||||||
|
"Should NOT fall back to OPENROUTER_API_KEY when explicit_api_key is provided"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_resolve_provider_client_without_explicit_api_key_falls_back_to_env(
|
||||||
|
self, monkeypatch
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
When resolve_provider_client() is called WITHOUT explicit_api_key for OpenRouter,
|
||||||
|
it should fall back to OPENROUTER_API_KEY env var.
|
||||||
|
"""
|
||||||
|
# Set up env var as fallback (should be used when explicit_api_key is NOT provided)
|
||||||
|
monkeypatch.setenv("OPENROUTER_API_KEY", "env-fallback-key")
|
||||||
|
|
||||||
|
# Mock OpenAI to capture the api_key used
|
||||||
|
mock_openai = MagicMock()
|
||||||
|
mock_openai.return_value = MagicMock(name="openrouter-client")
|
||||||
|
|
||||||
|
with patch("agent.auxiliary_client.OpenAI", mock_openai):
|
||||||
|
client, model = resolve_provider_client(
|
||||||
|
provider="openrouter",
|
||||||
|
explicit_api_key=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify a client was created
|
||||||
|
assert client is not None
|
||||||
|
# Verify the env var fallback was used
|
||||||
|
mock_openai.assert_called_once()
|
||||||
|
call_kwargs = mock_openai.call_args[1]
|
||||||
|
assert call_kwargs["api_key"] == "env-fallback-key", (
|
||||||
|
f"Expected env fallback key to be used when explicit_api_key is None, got: {call_kwargs['api_key']}"
|
||||||
|
)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user