fix(jina_ai): resolve the documented JINA_API_KEY as a fallback (#35992)

The Jina key fallback chain read JINA_AI_API_KEY three times in a row
before falling through to JINA_AI_TOKEN, so two of the four slots were
dead. Jina's own documentation publishes JINA_API_KEY, and litellm's
rerank validate_environment already tells users to set that name, but
nothing ever read it: a user who set only JINA_API_KEY got no key
resolved and Jina answered AUTH_MISSING_API_KEY.

Replace one of the repeats with JINA_API_KEY and drop the other.
JINA_AI_API_KEY stays first so no install that resolves a key today
changes which key it picks.
This commit is contained in:
Yassin Kortam 2026-08-05 14:13:42 -07:00 • committed by GitHub
parent 4562b539d8
commit 309e96c27b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 58 additions and 13 deletions

View file

@ -82,10 +82,7 @@ class JinaAIEmbeddingConfig(BaseEmbeddingConfig):
"""
api_base = api_base or get_secret_str("JINA_AI_API_BASE") or "https://api.jina.ai/v1"
dynamic_api_key: Final = api_key or (
get_secret_str("JINA_AI_API_KEY")
or get_secret_str("JINA_AI_API_KEY")
or get_secret_str("JINA_AI_API_KEY")
or get_secret_str("JINA_AI_TOKEN")
get_secret_str("JINA_AI_API_KEY") or get_secret_str("JINA_API_KEY") or get_secret_str("JINA_AI_TOKEN")
)
return LlmProviders.JINA_AI.value, api_base, dynamic_api_key

View file

@ -2,12 +2,20 @@ import os
import sys
from unittest.mock import MagicMock
sys.path.insert(
0, os.path.abspath("../../../../../..")
) # Adds the parent directory to the system path
import pytest
sys.path.insert(0, os.path.abspath("../../../../../..")) # Adds the parent directory to the system path
from litellm.llms.jina_ai.embedding.transformation import JinaAIEmbeddingConfig
JINA_KEY_ENV_NAMES = ("JINA_AI_API_KEY", "JINA_API_KEY", "JINA_AI_TOKEN")
@pytest.fixture
def no_jina_env(monkeypatch: pytest.MonkeyPatch) -> None:
for name in JINA_KEY_ENV_NAMES:
monkeypatch.delenv(name, raising=False)
class TestJinaAIEmbeddingTransform:
def setup_method(self):
@ -54,9 +62,7 @@ class TestJinaAIEmbeddingTransform:
headers={},
)
expected_input = [
{
"image": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="
}
{"image": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="}
]
expected_result = {
"model": self.model,
@ -64,6 +70,50 @@ class TestJinaAIEmbeddingTransform:
}
assert result == expected_result
@pytest.mark.parametrize("env_name", JINA_KEY_ENV_NAMES)
def test_every_accepted_env_name_resolves_a_key(
self,
env_name: str,
no_jina_env: None,
monkeypatch: pytest.MonkeyPatch,
):
"""Each accepted spelling must be reachable on its own, not shadowed by a repeated read of another name."""
sentinel = f"resolved-via-{env_name.lower()}"
monkeypatch.setenv(env_name, sentinel)
_, _, dynamic_api_key = self.config._get_openai_compatible_provider_info(api_base=None, api_key=None)
assert dynamic_api_key == sentinel
def test_env_name_precedence_is_stable(
self,
no_jina_env: None,
monkeypatch: pytest.MonkeyPatch,
):
"""Earlier names in the chain win, so adding later fallbacks never re-points an already working install."""
for name in JINA_KEY_ENV_NAMES:
monkeypatch.setenv(name, f"resolved-via-{name.lower()}")
for expected_name in JINA_KEY_ENV_NAMES:
_, _, dynamic_api_key = self.config._get_openai_compatible_provider_info(api_base=None, api_key=None)
assert dynamic_api_key == f"resolved-via-{expected_name.lower()}"
monkeypatch.delenv(expected_name)
def test_explicit_api_key_beats_every_env_name(
self,
no_jina_env: None,
monkeypatch: pytest.MonkeyPatch,
):
"""An api_key passed by the caller short-circuits the whole environment chain."""
for name in JINA_KEY_ENV_NAMES:
monkeypatch.setenv(name, f"resolved-via-{name.lower()}")
_, _, dynamic_api_key = self.config._get_openai_compatible_provider_info(
api_base=None, api_key="passed-in-by-caller"
)
assert dynamic_api_key == "passed-in-by-caller"
def test_transform_embedding_request_mixed_input(self):
"""Test transformation of a mixed text and image embedding request"""
# a fake base64 string for testing purposes
@ -77,9 +127,7 @@ class TestJinaAIEmbeddingTransform:
)
expected_input = [
{"text": "hello world"},
{
"image": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="
},
{"image": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="},
]
expected_result = {
"model": self.model,