fix: send placeholder key to litellm for keyless upstreams on /v1/messages

LiteLLM treats a blank api_key as missing and falls back to
OPENAI_API_KEY, so /v1/messages against a keyless OpenAI-compatible
upstream failed with "The api_key client option must be set", while
/v1/chat/completions on the same upstream worked (it omits auth).
LiteLLM has no option to skip the key, so pass a placeholder instead.
This commit is contained in:
thefux
2026-09-29 08:30:20 +00:00
parent ef40c18d60
commit 3a601ee00e
2 changed files with 35 additions and 1 deletions
+7 -1
View File
@@ -36,6 +36,12 @@ from .reasoning_effort import adapt_messages_body_for_litellm
logger = get_logger(__name__)
# Sent in place of a blank upstream key. LiteLLM treats ``""`` as missing and
# falls back to the provider's env var (e.g. ``OPENAI_API_KEY``), failing with
# an AuthenticationError for keyless upstreams such as self-hosted
# OpenAI-compatible servers, which the chat path reaches without auth.
KEYLESS_UPSTREAM_API_KEY = "no-key"
# Anthropic-Messages-only fields that don't translate to OpenAI
# Chat Completions. ``litellm.drop_params`` only filters *known*
# unsupported params; these newer/extension fields get passed through
@@ -519,7 +525,7 @@ async def dispatch_anthropic_messages(
kwargs: dict = {
"model": litellm_model,
"api_base": base_url,
"api_key": api_key,
"api_key": api_key or KEYLESS_UPSTREAM_API_KEY,
"stream": upstream_stream,
**body,
}
@@ -1755,3 +1755,31 @@ async def test_x_cashu_zero_value_rejected_not_forwarded(
assert body["error"]["code"] == "cashu_token_zero_value"
# Spent-to-zero token must not be echoed back for retry.
assert "X-Cashu" not in response.headers
@pytest.mark.asyncio
async def test_dispatch_passes_placeholder_key_for_keyless_upstream() -> None:
"""A blank upstream key must not reach litellm, which would fall back to
OPENAI_API_KEY and fail with an AuthenticationError."""
provider = BaseUpstreamProvider(base_url="http://localhost:8000/v1", api_key="")
captured_kwargs: dict[str, Any] = {}
async def fake_acreate(**kwargs: Any) -> AsyncIterator[dict]:
captured_kwargs.update(kwargs)
async def no_events() -> AsyncIterator[dict]:
return
yield
return no_events()
with patch(
"litellm.anthropic.messages.acreate",
new=AsyncMock(side_effect=fake_acreate),
):
await provider._dispatch_anthropic_messages(
request_body=_anthropic_request_body(stream=True),
model_obj=_make_model(),
)
assert captured_kwargs["api_key"] == "no-key"