Skip to content

Commit 8847f23

Browse files
Lin NikaidoGWeale
authored andcommitted
feat: raise explicit error for unsupported LiteLlm file attachments
Closes #5546 Change-Id: I18012416f0f6a2a92cc83d24a309319c555faa89
1 parent b5181cf commit 8847f23

2 files changed

Lines changed: 61 additions & 64 deletions

File tree

src/google/adk/models/lite_llm.py

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -410,17 +410,15 @@ def _redact_file_uri_for_log(
410410
return f"{parsed.scheme}://<redacted>"
411411

412412

413-
def _requires_file_uri_fallback(
414-
provider: str, model: str, file_uri: str
415-
) -> bool:
416-
"""Returns True when `file_uri` should not be sent as a file content block."""
413+
def _is_file_uri_supported(provider: str, model: str, file_uri: str) -> bool:
414+
"""Returns True when `file_uri` can be sent as a file content block."""
417415
if provider in _FILE_ID_REQUIRED_PROVIDERS:
418-
return not _looks_like_openai_file_id(file_uri)
416+
return _looks_like_openai_file_id(file_uri)
419417
if provider == "anthropic":
420-
return True
418+
return False
421419
if provider == "vertex_ai" and not _is_litellm_gemini_model(model):
422-
return True
423-
return False
420+
return False
421+
return True
424422

425423

426424
def _decode_inline_text_data(raw_bytes: bytes) -> str:
@@ -1240,21 +1238,15 @@ async def _get_content(
12401238
})
12411239
continue
12421240

1243-
if _requires_file_uri_fallback(provider, model, part.file_data.file_uri):
1244-
logger.debug(
1245-
"File URI %s not supported for provider %s, using text fallback",
1246-
_redact_file_uri_for_log(
1247-
part.file_data.file_uri,
1248-
display_name=part.file_data.display_name,
1249-
),
1250-
provider,
1241+
if not _is_file_uri_supported(provider, model, part.file_data.file_uri):
1242+
redacted_file_uri = _redact_file_uri_for_log(
1243+
part.file_data.file_uri,
1244+
display_name=part.file_data.display_name,
1245+
)
1246+
raise ValueError(
1247+
f"File URI `{redacted_file_uri}` not supported for provider:"
1248+
f" {provider}."
12511249
)
1252-
identifier = part.file_data.display_name or part.file_data.file_uri
1253-
content_objects.append({
1254-
"type": "text",
1255-
"text": f'[File reference: "{identifier}"]',
1256-
})
1257-
continue
12581250

12591251
file_object: ChatCompletionFileUrlObject = {
12601252
"file_id": part.file_data.file_uri,

tests/unittests/models/test_litellm.py

Lines changed: 47 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2910,7 +2910,7 @@ async def test_get_content_file_uri(file_uri, mime_type):
29102910
("azure", "azure/gpt-4"),
29112911
],
29122912
)
2913-
async def test_get_content_file_uri_file_id_required_falls_back_to_text(
2913+
async def test_get_content_file_uri_file_id_required_raises_error(
29142914
provider, model
29152915
):
29162916
parts = [
@@ -2922,10 +2922,11 @@ async def test_get_content_file_uri_file_id_required_falls_back_to_text(
29222922
)
29232923
)
29242924
]
2925-
content = await _get_content(parts, provider=provider, model=model)
2926-
assert content == [
2927-
{"type": "text", "text": '[File reference: "document.pdf"]'}
2928-
]
2925+
with pytest.raises(
2926+
ValueError,
2927+
match=f"File URI `document.pdf` not supported for provider: {provider}",
2928+
):
2929+
_ = await _get_content(parts, provider=provider, model=model)
29292930

29302931

29312932
@pytest.mark.asyncio
@@ -3016,23 +3017,23 @@ async def test_get_content_file_uri_azure_preserves_assistant_file_id():
30163017
("azure", "azure/gpt-4"),
30173018
],
30183019
)
3019-
async def test_get_content_file_uri_http_pdf_file_id_required_falls_back_to_text(
3020+
async def test_get_content_file_uri_http_pdf_file_id_required_raises_error(
30203021
provider, model
30213022
):
3022-
file_uri = "https://example.com/document.pdf"
30233023
parts = [
30243024
types.Part(
30253025
file_data=types.FileData(
3026-
file_uri=file_uri,
3026+
file_uri="https://example.com/document.pdf",
30273027
mime_type="application/pdf",
30283028
display_name="document.pdf",
30293029
)
30303030
)
30313031
]
3032-
content = await _get_content(parts, provider=provider, model=model)
3033-
assert content == [
3034-
{"type": "text", "text": '[File reference: "document.pdf"]'}
3035-
]
3032+
with pytest.raises(
3033+
ValueError,
3034+
match=f"File URI `document.pdf` not supported for provider: {provider}",
3035+
):
3036+
_ = await _get_content(parts, provider=provider, model=model)
30363037

30373038

30383039
@pytest.mark.asyncio
@@ -3056,7 +3057,7 @@ async def test_get_content_file_uri_http_pdf_non_file_id_provider_uses_file():
30563057

30573058

30583059
@pytest.mark.asyncio
3059-
async def test_get_content_file_uri_anthropic_falls_back_to_text():
3060+
async def test_get_content_file_uri_anthropic_raises_error():
30603061
parts = [
30613062
types.Part(
30623063
file_data=types.FileData(
@@ -3066,27 +3067,29 @@ async def test_get_content_file_uri_anthropic_falls_back_to_text():
30663067
)
30673068
)
30683069
]
3069-
content = await _get_content(
3070-
parts, provider="anthropic", model="anthropic/claude-3-5"
3071-
)
3072-
assert content == [
3073-
{"type": "text", "text": '[File reference: "document.pdf"]'}
3074-
]
3070+
with pytest.raises(
3071+
ValueError,
3072+
match="File URI `document.pdf` not supported for provider: anthropic",
3073+
):
3074+
_ = await _get_content(
3075+
parts, provider="anthropic", model="anthropic/claude-3-5"
3076+
)
30753077

30763078

30773079
@pytest.mark.asyncio
3078-
async def test_get_content_file_uri_anthropic_openai_file_id_falls_back_to_text():
3080+
async def test_get_content_file_uri_anthropic_openai_file_id_raises_error():
30793081
parts = [types.Part(file_data=types.FileData(file_uri="file-abc123"))]
3080-
content = await _get_content(
3081-
parts, provider="anthropic", model="anthropic/claude-3-5"
3082-
)
3083-
assert content == [
3084-
{"type": "text", "text": '[File reference: "file-abc123"]'}
3085-
]
3082+
with pytest.raises(
3083+
ValueError,
3084+
match="File URI `file-<redacted>` not supported for provider: anthropic",
3085+
):
3086+
_ = await _get_content(
3087+
parts, provider="anthropic", model="anthropic/claude-3-5"
3088+
)
30863089

30873090

30883091
@pytest.mark.asyncio
3089-
async def test_get_content_file_uri_vertex_ai_non_gemini_falls_back_to_text():
3092+
async def test_get_content_file_uri_vertex_ai_non_gemini_raises_error():
30903093
parts = [
30913094
types.Part(
30923095
file_data=types.FileData(
@@ -3096,12 +3099,13 @@ async def test_get_content_file_uri_vertex_ai_non_gemini_falls_back_to_text():
30963099
)
30973100
)
30983101
]
3099-
content = await _get_content(
3100-
parts, provider="vertex_ai", model="vertex_ai/claude-3-5"
3101-
)
3102-
assert content == [
3103-
{"type": "text", "text": '[File reference: "document.pdf"]'}
3104-
]
3102+
with pytest.raises(
3103+
ValueError,
3104+
match="File URI `document.pdf` not supported for provider: vertex_ai",
3105+
):
3106+
_ = await _get_content(
3107+
parts, provider="vertex_ai", model="vertex_ai/claude-3-5"
3108+
)
31053109

31063110

31073111
@pytest.mark.asyncio
@@ -3265,21 +3269,22 @@ async def test_get_content_audio_inline_data_emits_input_audio(
32653269
("azure", "azure/gpt-4"),
32663270
],
32673271
)
3268-
async def test_get_content_audio_file_uri_http_falls_back_to_text(
3269-
provider, model
3270-
):
3271-
"""Audio HTTP file_uri falls back to a text reference for openai/azure."""
3272+
async def test_get_content_audio_file_uri_http_raises_error(provider, model):
3273+
"""Audio HTTP file_uri raises an error for openai/azure."""
32723274
file_uri = "https://example.com/audio.mp3"
32733275
parts = [
32743276
types.Part(
32753277
file_data=types.FileData(file_uri=file_uri, mime_type="audio/mpeg")
32763278
)
32773279
]
3278-
content = await _get_content(parts, provider=provider, model=model)
3279-
assert content == [{
3280-
"type": "text",
3281-
"text": f'[File reference: "{file_uri}"]',
3282-
}]
3280+
with pytest.raises(
3281+
ValueError,
3282+
match=(
3283+
"File URI `https://<redacted>/audio.mp3` not supported for provider:"
3284+
f" {provider}"
3285+
),
3286+
):
3287+
_ = await _get_content(parts, provider=provider, model=model)
32833288

32843289

32853290
def test_to_litellm_role():

0 commit comments

Comments
 (0)