Repository navigation
[BUG] LiteLLM (OpenAI/gpt-4o-mini): load_artifacts PDF attachment fails with “Invalid file data: 'file_id' … expected application/pdf, got None” #4174
Description
Describe the bug
When a user uploads a PDF and asks “what do you see in this PDF?”, ADK SaveFilesAsArtifactsPlugin saves the file as an artifact and adds an artifact placeholder. Then the agent calls the load_artifacts tool to inline/attach the artifact content. This flow works on Gemini/Vertex models, but fails on OpenAI GPT models via LiteLLM (reproduced with openai/gpt-4o-mini) with:
litellm.BadRequestError: OpenAIException - Invalid file data: 'file_id'. Expected a file with an application/pdf MIME type, but got unsupported MIME type 'None'.
This suggests the file attachment forwarded to OpenAI has missing/None MIME type for a PDF.
To Reproduce
Install dependencies:
pip install google-adk litellm openai
Run an ADK app/agent configured to use LiteLLM with model openai/gpt-4o-mini, with:
Plugin enabled: SaveFilesAsArtifactsPlugin()
Tool enabled: load_artifacts
Upload a PDF.
Send: what do you see in this pdf?
Observe that after the agent calls load_artifacts and tries to inline/attach the artifact, the request fails with:
Invalid file data: 'file_id' ... expected application/pdf ... got None
Note: The same steps succeed with Gemini and Claude models.
Expected behavior
The agent should successfully load/attach the uploaded PDF artifact and answer based on the PDF content, without failing due to missing MIME type.
Desktop (please complete the following information):
- OS: Linux / MacOs
- Python version(python -V): 3.10
- ADK version(pip show google-adk): 1.22 This commit installed: Commit 43b484f
Model Information:
- Are you using LiteLLM: Yes
- Which model is being used: gpt-4o-mini
Notes
- This issue was previously reported and closed as resolved, pointing to commit
89bed43, but the problem still occurs for PDF attachments when using LiteLLM + OpenAI models. - The failure appears to be in the artifact → file attachment step where the MIME type is lost or not set when constructing the file upload forwarded to OpenAI.
- This only affects PDF inputs — images and other media do not reproduce the error in my tests.
Related commits
89bed43(mentioned in prior responses) — please check if this commit is related to the regression.
Additional context
Full traceback:
POST Request Sent from LiteLLM:
curl -X POST \
https://api.openai.com/v1 \
-d '{'model': 'gpt-4o-mini', 'messages': [{'role': 'system', 'content': 'You are Nova, a general-purpose root AI assistant whose primary responsibility is orchestration: detect user intent and deleral-purpose root AI assistant whose primary responsibility is orchestration: detect user integths. Your role is routing, not to produce full domain answers yourself when a suitable sub-agent existsnt and deleral-purpose root AI assistant whose primary responsibility is orchestration: detecain(s), task type, required expertise, format, and constraints (tone, length, citations, step-by-step, pt user integths. Your role is routing, not to produce full domain answers yourself when a suie step-by-step", "generate social post").\n2. Agent selection: Match the request to the single best-fit table sub-agent existsnt and deleral-purpose root AI assistant whose primary responsibility i Prefer specialized agents over yourself for domain tasks (e.g., a Botanist agent for plant ID, a High-As orchestration: detecain(s), task type, required expertise, format, and constraints (tone, l multi-faceted (e.g., research + write + fact-check), split it into sub-tasks and assign each sub-task tength, citations, step-by-step, pt user integths. Your role is routing, not to produce full d in parallel as needed.\n4. Handoff content: When delegating, send each sub-agent a concise, self-contaiomain answers yourself when a suie step-by-step", "generate social post").\n2. Agent selectioe)\n - relevant conversation context and user preferences\n - required output format (structure, lenn: Match the request to the single best-fit table sub-agent existsnt and delegate tasks to th\n - explicit success criteria (what counts as a usable answer)\n5. Response assembly: After sub-agente most appropriate subordinate agent(s) base Prefer specialized agents over yourself for domansistency and obvious errors\n - If multiple agents provided conflicting answers, run a reconciliationin tasks (e.g., a Botanist agent for plant ID, a High-Ad on their descriptionain(s), task typ- Compose a coherent, single reply to the user that indicates which sub-agent(s) provided which parts (ee, required expertise, format, and constraints (tone, l multi-faceted (e.g., research + write asked not to reveal routing.\n\nQuality, safety, and transparency\n1. Accuracy first: Ensure sub-agents + fact-check), split it into sub-tasks and assign each sub-task tength, citations, step-by-s clearly.\n2. No hallucinations: If a sub-agent output appears unsupported or speculative, request sourctep, ps and strengths. Your role is routing, not to produce full d in parallel as needed.\n4.s.\n3. Privacy & policy: Enforce user privacy and safety constraints during delegation; do not share pri Handoff content: When delegating, send each sub-agent a concise, self-contaiomain answers yo, briefly explain to the user why specific sub-agent(s) were chosen and what each will (or did) handle.\urself when a suie step-by-step", "generate social post").\n2. Agent selectioe)\n - relevanempt one of:\n - ask another suitable sub-agent,\n - decompose the task differently,\n - or providt conversation context and user preferences\n - required output format (structure, lenn: Manswer myself or route this to a human expert”).\n6. Efficiency & user preference: Respect user preferenctch the request to the single best-fit table sub-agent exists.\n\nRouting & delegation r\n is; if they ask for "detailed" or "step-by-step", orchestrate deeper specialist pipelines.\n\nFormatting- explicit success criteria (what counts as a usable answer)\n5. Response assembly: After subt when delegating.\n- Use structured messages for sub-agent instructions (title, user message, context, -agentules\n1. Domain detection: Parse the user\'s Prefer specialized agents over yourself foss the user requests anonymity).\n- Maintain conversational continuity: you are the orchestrator and finr domansistency and obvious errors\n - If multiple agents provided conflicting answers, runwith a sub-agent.\n\nGoal\nAct as a reliable, domain-aware orchestrator: accurately detect intent, route a reconciliationin tasks (e.g., a Botanist agent for plant ID, a High-A request to detect do and appropriately formatted response to the user. Your default behavior is delegation — answering direcmain(s), task typ- Compose a coherent, single reply to the user that indicates which sub-agenYou are an agent. Your internal name is "NovaAgent". The description about you is "A Nova AI assistant tt(s) provided which parts (ee, required expertise, format, and constraints (tone, l multi-facate, contextually relevant, and helpful responses.".\n\nYou have a list of artifacts:\n ["artifact_e-aeeted (e.g., research + write asked not to reveal routing.\n\nQuality, safety, and transparencifacts, you should call the\n `load_artifacts` function to load the artifact. Always call load_artifacty\n1. Accuracy first: Ensure sub-agents + fact-check), split it into sub-tasks and assign eaccts have been loaded before. Do not depend on prior answers about the\n artifacts.\n '}, {'role': 'useh sub-task tength, citations, step-by-s clearly.\n2. No hallucinations: If a sub-agent outputt': '[Uploaded Artifact: "artifact_e-ae5bf877-5f86-45a8-983a-8c6257b4b62c_1"]'}, {'type': 'text', 'text' appears unsupported or speculative, request sourctep, privacy needs). Prefer explicit signaltant', 'tool_calls': [{'type': 'function', 'id': 'call_iC88a1ehmgvvztqY2vf8oQKd', 'function': {'name': 's in the user message ( in parallel as needed.\n4.s.\n3. Privacy & policy: Enforce user privab4b62c_1"]}'}}]}, {'role': 'tool', 'tool_call_id': 'call_iC88a1ehmgvvztqY2vf8oQKd', 'content': '{"artifacy and safety constraints during delegation; do not share pri Handoff content: When delegatins temporarily inserted and removed. to access these artifacts, call load_artifacts tool again."}'}, {'rog, send each sub-agent a concise, self-contaie.g., "analyze t, briefly explain to the user wha-8c6257b4b62c_1 is:'}, {'type': 'file', 'file': {'file_id': 'file-5S5XJKzYVRFSgcRRemqbCv'}}]}], 'tempery specific sub-agent(s) were chosen and what each will (or did) handle.\his paper", "solve ston': 'Loads artifacts into the session for this request.\n\nNOTE: Call when you need access to artifactsep-by-step", "generate social post").\n2. Agent selectioe)\n - relevanempt one of:\n - as: {'artifact_names': {'items': {'type': 'string'}, 'type': 'array'}}}}}, {'type': 'function', 'function'k another suitable sub-agent,\n - decompose the task differently,\n - or providt conversaP.\n \n Args:\n tool_context: Tool context with state and artifact access\n prompt: tion context and user preferences\n - required output format (structure, lenn: Manswer mysepe_4_3")\n num_images: Number of images to generate (default: 1)\n \n Returns:\n diclf or route this to a human expert”).\n6. Efficiency & user preference: Respect user preferen'prompt': {'type': 'string'}, 'image_size': {'default': 'landscape_4_3', 'type': 'string'}, 'num_images'ctch the request to the single best-fit sub-agent by comparing the user\'s needs to each \n True, 'stream_options': {'include_usage': True}}'
21:33:26 - LiteLLM:DEBUG: utils.py:465 - Logging Details LiteLLM-Async Success Call, cache_hit=None
Give Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new
LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.
21:33:26 - LiteLLM:DEBUG: exception_mapping_utils.py:2375 - Logging Details: logger_fn - None | callable(logger_fn) - False
21:33:26 - LiteLLM:DEBUG: litellm_logging.py:2733 - Logging Details LiteLLM-Failure Call: []
2026-01-12T18:33:27.027958Z [error ] Error in adk_events error=litellm.BadRequestError: OpenAIException - Invalid file data: 'file_id'. Expected a file with an application/pdf MIME tystError: OpenAIException - Invalid file data: 'file_id'. Expected a file with an application/
Activity
kursatdinc commentedon Jan 16, 2026
Related to #4125
kursatdinc commentedon Jan 16, 2026
Related commit: 89bed43
AlejandroBlanco2001 commentedon Jan 20, 2026
Hey @kursatdinc, I was able to experience the same issues as you with the
adk run webllalitkumarrr commentedon Jan 28, 2026
Hello,
We would like to go through your code and try to reproduce this issue. Could you please share your code snippet to us reproduce this issue?
kursatdinc commentedon Jan 28, 2026
When a user uploads a PDF and asks “what do you see in this PDF?”, ADK SaveFilesAsArtifactsPlugin saves the file as an artifact and adds an artifact placeholder. Then the agent calls the load_artifacts tool to inline/attach the artifact content. This flow works on Gemini/Vertex models, but fails on OpenAI GPT models via LiteLLM (reproduced with openai/gpt-4o-mini) with:
litellm.BadRequestError: OpenAIException - Invalid file data: 'file_id'. Expected a file with an application/pdf MIME type, but got unsupported MIME type 'None'.
This suggests the file attachment forwarded to OpenAI has missing/None MIME type for a PDF.
alejandro-sotoca-bts commentedon Feb 24, 2026
Root cause and proposed fix
I was able to reproduce this issue with
adk web(not only viaload_artifacts) on ADK v1.25.0 usingopenai/gpt-4oandazure/...models.Root cause
In
src/google/adk/models/lite_llm.py, theacreate_filecall for OpenAI/Azure providers passes only raw bytes as thefileargument:LiteLLM (and the underlying OpenAI API) expects a multipart upload with a
(filename, bytes, content_type)tuple so it can set theContent-Typeheader correctly. Passing only raw bytes causes the stored file to have MIME typeNone, which the OpenAI chat completions API then rejects.This code path is only reached for
_FILE_ID_REQUIRED_PROVIDERS = frozenset({"openai", "azure"}). Other providers (Gemini, Anthropic) send files inline as data URIs and are not affected.Proposed fix
Pass the full tuple
(filename, bytes, mime_type)toacreate_file, using the file'sdisplay_nameor a sensible default derived from the MIME type:This fix has been validated locally and resolves the error for PDF and other document attachments with OpenAI and Azure providers. I am happy to open a PR with this change.
fix(models): pass (filename, bytes, mime_type) tuple to acreate_file …
fix(models): pass (filename, bytes, mime_type) tuple to acreate_file …
rohityan commentedon Feb 26, 2026
Hi @alejandro-sotoca-bts , Your root cause analysis is spot on. Passing only raw bytes strips the metadata during the upload, which causes OpenAI to reject it with a
NoneMIME typeFor the fix, instead of maintaining a hardcoded dictionary for MIME types, you can make it more robust by using Python's built-in
mimetypeslibrary along with the existing_normalize_mime_type helper.adk-bot commentedon Mar 6, 2026
This issue has been automatically marked as stale because it has not had recent activity for 7 days after a maintainer requested clarification. It will be closed if no further activity occurs within 7 days.
wes-evans commentedon Mar 6, 2026
I'm commenting because I'm currently experiencing this issue as well, and want to remove the
stalelabel.I see #4603 is in-progress, which resolves this issue. Thanks!
llalitkumarrr commentedon Apr 8, 2026
Hello @wes-evans ,
The PR associated with this issue is currently under review; we request your patience.
fix: preserve non-image file MIME type in LiteLlm content conversion
fix: preserve non-image file MIME type in LiteLlm content conversion