mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-14 23:21:35 +00:00
fix(openrouter): resolve os.environ api_key + defensive transform strip
Follow-up to #24282 / GH#24234 which landed the native-model guard approach for get_llm_provider. Two small remaining gaps: 1. The OpenRouter early return in get_llm_provider() now fires for both native and non-native IDs, but it runs *before* the generic `api_key = "os.environ/..."` secret resolution. Callers passing `api_key="os.environ/OPENROUTER_API_KEY"` through the OpenRouter path therefore get dynamic_api_key=None. Move the resolution above the early return so the secret is populated in both branches. 2. Add a defensive "openrouter/" strip inside OpenrouterConfig.transform_request for code paths that reach the transform without going through get_llm_provider (e.g. adapter/bridge invocations). Same native-model guard ("/" in remainder) as the merged fix, so openrouter/auto / openrouter/free stay intact. Tests cover both: new TestOpenRouterApiKeyResolution verifies os.environ resolution for native + non-native models, and TestOpenRouterTransformRequestDefensiveStrip covers the transform-layer strip with the native/no-prefix/non-native matrix.
This commit is contained in:
parent
b8f7d61400
commit
037c567096
3 changed files with 88 additions and 0 deletions
|
|
@ -158,6 +158,13 @@ def get_llm_provider( # noqa: PLR0915
|
|||
): # handle scenario where model="azure/*" and custom_llm_provider="azure"
|
||||
model = custom_llm_provider + "/" + model
|
||||
|
||||
# Resolve `os.environ/` api_key placeholders before the OpenRouter
|
||||
# early return below, so callers passing
|
||||
# `api_key="os.environ/OPENROUTER_API_KEY"` still get the secret
|
||||
# resolved when the model routes via the `openrouter/` prefix.
|
||||
if api_key and api_key.startswith("os.environ/"):
|
||||
dynamic_api_key = get_secret_str(api_key)
|
||||
|
||||
# OpenRouter: when the router/proxy already set custom_llm_provider,
|
||||
# the model may still carry LiteLLM's "openrouter/" routing prefix.
|
||||
# Native IDs like "openrouter/auto" must stay intact for the API; IDs
|
||||
|
|
|
|||
|
|
@ -159,6 +159,16 @@ class OpenrouterConfig(OpenAIGPTConfig):
|
|||
Returns:
|
||||
dict: The transformed request. Sent as the body of the API call.
|
||||
"""
|
||||
# Defensive strip of the "openrouter/" prefix for code paths that
|
||||
# reach this transform without going through get_llm_provider (e.g.
|
||||
# some adapter/bridge invocations). Native IDs like
|
||||
# "openrouter/auto" / "openrouter/free" have no "/" after the prefix
|
||||
# and must be kept intact.
|
||||
if model.startswith("openrouter/"):
|
||||
remainder = model[len("openrouter/") :]
|
||||
if "/" in remainder:
|
||||
model = remainder
|
||||
|
||||
if self._supports_cache_control_in_content(model):
|
||||
messages = self._move_cache_control_to_content(messages)
|
||||
|
||||
|
|
|
|||
|
|
@ -100,3 +100,74 @@ class TestOpenRouterNativeModelRouting:
|
|||
)
|
||||
assert provider == "openrouter"
|
||||
assert result_model == "anthropic/claude-3.5-sonnet"
|
||||
|
||||
|
||||
class TestOpenRouterApiKeyResolution:
|
||||
"""api_key="os.environ/..." must be resolved even when the model takes the
|
||||
OpenRouter early-return path (native or pre-resolved custom_llm_provider).
|
||||
Regression introduced when the early return was added above the generic
|
||||
os.environ resolution block.
|
||||
"""
|
||||
|
||||
def test_os_environ_api_key_resolved_for_native_model(self, monkeypatch):
|
||||
monkeypatch.setenv("OPENROUTER_API_KEY_TEST", "sk-test-native-123")
|
||||
|
||||
_, provider, dynamic_api_key, _ = litellm.get_llm_provider(
|
||||
model="openrouter/auto",
|
||||
custom_llm_provider="openrouter",
|
||||
api_key="os.environ/OPENROUTER_API_KEY_TEST",
|
||||
)
|
||||
assert provider == "openrouter"
|
||||
assert dynamic_api_key == "sk-test-native-123"
|
||||
|
||||
def test_os_environ_api_key_resolved_for_non_native_model(self, monkeypatch):
|
||||
monkeypatch.setenv("OPENROUTER_API_KEY_TEST", "sk-test-nonnative-456")
|
||||
|
||||
_, provider, dynamic_api_key, _ = litellm.get_llm_provider(
|
||||
model="openrouter/anthropic/claude-3.5-sonnet",
|
||||
custom_llm_provider="openrouter",
|
||||
api_key="os.environ/OPENROUTER_API_KEY_TEST",
|
||||
)
|
||||
assert provider == "openrouter"
|
||||
assert dynamic_api_key == "sk-test-nonnative-456"
|
||||
|
||||
|
||||
class TestOpenRouterTransformRequestDefensiveStrip:
|
||||
"""OpenrouterConfig.transform_request must strip a doubled "openrouter/"
|
||||
prefix as a safety net for code paths that bypass get_llm_provider, while
|
||||
preserving native single-segment IDs like "openrouter/auto".
|
||||
"""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input_model,expected_model",
|
||||
[
|
||||
# Non-native: prefix must be stripped
|
||||
(
|
||||
"openrouter/anthropic/claude-3.5-sonnet",
|
||||
"anthropic/claude-3.5-sonnet",
|
||||
),
|
||||
(
|
||||
"openrouter/meta-llama/llama-3-70b-instruct",
|
||||
"meta-llama/llama-3-70b-instruct",
|
||||
),
|
||||
# Native: prefix must be preserved
|
||||
("openrouter/auto", "openrouter/auto"),
|
||||
("openrouter/free", "openrouter/free"),
|
||||
# No prefix: unchanged
|
||||
("anthropic/claude-3.5-sonnet", "anthropic/claude-3.5-sonnet"),
|
||||
],
|
||||
)
|
||||
def test_transform_request_strips_non_native_prefix(
|
||||
self, input_model, expected_model
|
||||
):
|
||||
from litellm.llms.openrouter.chat.transformation import OpenrouterConfig
|
||||
|
||||
config = OpenrouterConfig()
|
||||
result = config.transform_request(
|
||||
model=input_model,
|
||||
messages=[{"role": "user", "content": "hi"}],
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
headers={},
|
||||
)
|
||||
assert result["model"] == expected_model
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue