Skip to content

Commit ecec4f0

Browse files
fix(models): pass (filename, bytes, mime_type) tuple to acreate_file for OpenAI/Azure providers
When uploading non-image file attachments (e.g. PDFs) via the LiteLLM integration, the `acreate_file` call for OpenAI and Azure providers was passing only raw bytes as the `file` argument. LiteLLM and the OpenAI Files API expect a multipart upload with a `(filename, bytes, content_type)` tuple so the Content-Type header is set correctly. Passing raw bytes caused the stored file to have MIME type `None`, which the chat completions API then rejected with: Invalid file data: 'file_id'. Expected a file with an application/pdf MIME type, but got unsupported MIME type 'None'. Fix: pass `(display_name, data, mime_type)` to `acreate_file`, using the part's `display_name` when available, or a sensible default filename derived from the MIME type via the new `_filename_for_mime` helper. Fixes #4174 Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent dab80e4 commit ecec4f0

File tree

1 file changed

+28
-2
lines changed

1 file changed

+28
-2
lines changed

src/google/adk/models/lite_llm.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,24 @@
138138
# Providers that require file_id instead of inline file_data
139139
_FILE_ID_REQUIRED_PROVIDERS = frozenset({"openai", "azure"})
140140

141+
# Default filenames for file uploads when display_name is missing.
142+
# OpenAI derives the MIME type from the filename extension during upload,
143+
# so passing a proper name ensures the stored file gets the right content-type.
144+
_MIME_TO_FILENAME = {
145+
"application/pdf": "document.pdf",
146+
"application/json": "document.json",
147+
"application/msword": "document.doc",
148+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": "document.docx",
149+
"application/vnd.openxmlformats-officedocument.presentationml.presentation": "document.pptx",
150+
"application/x-sh": "script.sh",
151+
}
152+
153+
154+
def _filename_for_mime(mime_type: str) -> str:
155+
"""Return a default filename for a MIME type so uploads get the right content-type."""
156+
return _MIME_TO_FILENAME.get(mime_type, "document.bin")
157+
158+
141159
_MISSING_TOOL_RESULT_MESSAGE = (
142160
"Error: Missing tool result (tool execution may have been interrupted "
143161
"before a response was recorded)."
@@ -840,10 +858,18 @@ async def _get_content(
840858
url_content_type: {"url": data_uri},
841859
})
842860
elif mime_type in _SUPPORTED_FILE_CONTENT_MIME_TYPES:
843-
# OpenAI/Azure require file_id from uploaded file, not inline data
861+
# OpenAI/Azure require file_id from uploaded file, not inline data.
862+
# Pass (filename, content, content_type) so the upload gets the right MIME type.
844863
if provider in _FILE_ID_REQUIRED_PROVIDERS:
864+
display_name = getattr(
865+
part.inline_data, "display_name", None
866+
) or _filename_for_mime(part.inline_data.mime_type)
845867
file_response = await litellm.acreate_file(
846-
file=part.inline_data.data,
868+
file=(
869+
display_name,
870+
part.inline_data.data,
871+
part.inline_data.mime_type,
872+
),
847873
purpose="assistants",
848874
custom_llm_provider=provider,
849875
)

0 commit comments

Comments
 (0)