mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-14 09:03:16 +00:00
fix(vertex_ai): keep main's Gemini adapter and pass a missing Authorization as no key
The Gemini adapter change for later systemInstruction parts goes back to main, since it is not the double-auth fix this PR is for. The Vertex auth dependency now hands a missing Authorization header to the auth check as no key, like main's first auth pass, so custom auth and the 401 text see what they see on main
This commit is contained in:
parent
f418a83f49
commit
02723a28a6
4 changed files with 24 additions and 65 deletions
|
|
@ -249,10 +249,6 @@ def _genai_tool_call(part: object) -> ChatCompletionAssistantToolCall | None:
|
|||
)
|
||||
|
||||
|
||||
def _system_texts(parts: object) -> tuple[str, ...]:
|
||||
return tuple(part["text"] for part in _genai_message_parts(_genai_content_parts(parts)) if part["type"] == "text")
|
||||
|
||||
|
||||
def _genai_content_messages(content: object) -> tuple[AllMessageValues, ...]:
|
||||
fields: Final = _validated(_CONFIG_FIELDS, content)
|
||||
if fields is None:
|
||||
|
|
@ -636,9 +632,11 @@ class GoogleGenAIAdapter:
|
|||
system_instruction: _GenAISystemInstruction | None = None,
|
||||
) -> list[AllMessageValues]:
|
||||
"""Transform Google GenAI contents to OpenAI messages format"""
|
||||
system_texts: Final = _system_texts(system_instruction.get("parts") if system_instruction else None)
|
||||
system_parts: Final = system_instruction.get("parts", ()) if system_instruction else ()
|
||||
system_messages: Final = (
|
||||
(ChatCompletionSystemMessage(role="system", content="\n".join(system_texts)),) if system_texts else ()
|
||||
(ChatCompletionSystemMessage(role="system", content=system_parts[0]["text"]),)
|
||||
if system_parts and "text" in system_parts[0]
|
||||
else ()
|
||||
)
|
||||
return [*system_messages, *chain.from_iterable(_genai_content_messages(content) for content in contents)]
|
||||
|
||||
|
|
|
|||
|
|
@ -3203,7 +3203,9 @@ async def _authenticate_vertex_caller(
|
|||
litellm_api_key: Annotated[str | None, Security(custom_litellm_key_header)],
|
||||
) -> UserAPIKeyAuth:
|
||||
return await user_api_key_auth(
|
||||
request=request, api_key=authorization or "", custom_litellm_key_header=litellm_api_key
|
||||
request=request,
|
||||
api_key=authorization, # pyright: ignore[reportArgumentType] # its own Security default is None when unset
|
||||
custom_litellm_key_header=litellm_api_key,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -194,62 +194,6 @@ def test_native_function_call_preserves_arguments_and_matching_response(args: ob
|
|||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("system_texts", "expected_content"),
|
||||
[
|
||||
(("Answer tersely",), "Answer tersely"),
|
||||
(("Answer tersely", "Use British spelling"), "Answer tersely\nUse British spelling"),
|
||||
],
|
||||
)
|
||||
def test_every_system_instruction_text_part_reaches_the_system_message(
|
||||
system_texts: tuple[str, ...], expected_content: str
|
||||
) -> None:
|
||||
request: Final = GoogleGenAIAdapter().translate_generate_content_to_completion(
|
||||
model="gemini/gemini-3.8-flash",
|
||||
contents=[{"role": "user", "parts": [{"text": "hi"}]}],
|
||||
systemInstruction={"parts": [{"text": text} for text in system_texts]},
|
||||
)
|
||||
assert request["messages"][0] == {"role": "system", "content": expected_content}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("system_parts", "expected_messages"),
|
||||
[
|
||||
pytest.param(
|
||||
[
|
||||
{"text": "Answer tersely"},
|
||||
{"text": None},
|
||||
{"text": 3},
|
||||
{"text": ["x"]},
|
||||
{"text": "Use British spelling"},
|
||||
],
|
||||
[{"role": "system", "content": "Answer tersely\nUse British spelling"}, {"role": "user", "content": "hi"}],
|
||||
id="malformed_parts_skipped_like_contents",
|
||||
),
|
||||
pytest.param(
|
||||
{"text": "Answer tersely"},
|
||||
[{"role": "system", "content": "Answer tersely"}, {"role": "user", "content": "hi"}],
|
||||
id="single_object_parts",
|
||||
),
|
||||
pytest.param(
|
||||
["Answer tersely", {"text": "Use British spelling"}],
|
||||
[{"role": "system", "content": "Answer tersely\nUse British spelling"}, {"role": "user", "content": "hi"}],
|
||||
id="bare_string_part_read_like_contents",
|
||||
),
|
||||
pytest.param(None, [{"role": "user", "content": "hi"}], id="no_parts"),
|
||||
],
|
||||
)
|
||||
def test_system_instruction_parts_follow_the_contents_part_rules(
|
||||
system_parts: object, expected_messages: list[dict[str, str]]
|
||||
) -> None:
|
||||
request: Final = GoogleGenAIAdapter().translate_generate_content_to_completion(
|
||||
model="gemini/gemini-3.8-flash",
|
||||
contents=[{"role": "user", "parts": [{"text": "hi"}]}],
|
||||
systemInstruction={"parts": system_parts},
|
||||
)
|
||||
assert request["messages"] == expected_messages
|
||||
|
||||
|
||||
def test_adapter_import():
|
||||
"""Test that the adapter can be imported successfully"""
|
||||
from litellm.google_genai.adapters.handler import GenerateContentToCompletionHandler
|
||||
|
|
|
|||
|
|
@ -8490,9 +8490,9 @@ class TestAzureSpeechRawBodyThroughRealAuth:
|
|||
class _CountingCustomAuth:
|
||||
def __init__(self, only_accepts: str | None = None) -> None:
|
||||
self.only_accepts: Final = only_accepts
|
||||
self.api_keys_seen: tuple[str, ...] = ()
|
||||
self.api_keys_seen: tuple[str | None, ...] = ()
|
||||
|
||||
async def __call__(self, request: Request, api_key: str) -> UserAPIKeyAuth:
|
||||
async def __call__(self, request: Request, api_key: str | None) -> UserAPIKeyAuth:
|
||||
self.api_keys_seen = (*self.api_keys_seen, api_key)
|
||||
if self.only_accepts is not None and api_key != self.only_accepts:
|
||||
raise ValueError("unknown caller")
|
||||
|
|
@ -8694,6 +8694,21 @@ class TestVertexPassthroughAuthenticatesOncePerRequest:
|
|||
assert not google.called
|
||||
|
||||
|
||||
@pytest.mark.parametrize("path", [f"/vertex_ai/{GENERATE_CONTENT}", f"/vertex_ai/discovery/{DISCOVERY_SEARCH}"])
|
||||
def test_request_without_a_key_hands_custom_auth_none(
|
||||
self, monkeypatch: pytest.MonkeyPatch, path: str
|
||||
) -> None:
|
||||
custom_auth: Final = _CountingCustomAuth(only_accepts=self.VIRTUAL_KEY)
|
||||
with respx.mock(assert_all_called=False) as upstream:
|
||||
google: Final = upstream.route().mock(return_value=httpx.Response(200))
|
||||
|
||||
response, recorder = self._post(monkeypatch, path, {}, custom_auth, ("us-central1", "global"))
|
||||
|
||||
assert response.status_code == 401, response.text
|
||||
assert custom_auth.api_keys_seen == (None,)
|
||||
assert recorder.served_user_ids == ()
|
||||
assert not google.called
|
||||
|
||||
class TestTypeSafePassthroughRoute:
|
||||
@staticmethod
|
||||
def _request(body: object, query_params: Mapping[str, str] | None = None) -> MagicMock:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue