Skip to content

Replay text-encoded files in gr.load_chat history as text, not as images - #13743

Closed
abidlabs wants to merge 1 commit into
mainfrom
fix/issue-11331-default-gr-load-chat-with-large-and-small-inputs
Closed

Replay text-encoded files in gr.load_chat history as text, not as images#13743
abidlabs wants to merge 1 commit into
mainfrom
fix/issue-11331-default-gr-load-chat-with-large-and-small-inputs

Conversation

@abidlabs

Copy link
Copy Markdown
Member

Fixes #11331

Root cause

With the default gr.load_chat(..., file_types="text_encoded"), pasting a large prompt turns it into a text-file attachment. format_conversation() handles that correctly on the turn it is sent — it reads text-encoded files and appends their contents to the prompt as text.

But when it walks the history, it turned every file content part into an image regardless of type:

elif content["type"] == "file":
    new_content.append({"type": "image_url", "image_url": {"url": encode_url_or_file_to_base64(...)}})

So the pasted-text turn goes out fine, and then every message after it re-sends that .txt base64-encoded as an image — the payload literally contains "url": "data:text/plain;base64,...". That is the request vllm serve rejects with "model is not multimodal", which matches the reported sequence exactly: paste long text (works) → type something short (fails).

It also explains why file_types=[] made the error go away: no attachment, so nothing to replay.

Fix

Text-encoded files in the history are inlined as text, the same as when they were first sent. Images still go out as image_url. The extension check and the ## filename\ncontents formatting are now shared between the two paths so they cannot drift again, and the check also excludes http(s) URLs, which Path(...).read_text() could not have handled anyway.

Verification

$ python -m pytest test/test_external.py -k format_conversation -q
1 passed

test_format_conversation_replays_text_files_as_text is new and fails on main:

E  AssertionError: assert [{'type': 'im...Byb21wdA=='}}] == [{'type': 'te...sted prompt'}]

It also pins that a .png in the history is still sent as image_url. Full test/test_external.py: the 3 pre-existing failures are unchanged (test_load_chat_*, which need openai, not installed in my env). TestLoadInterface::test_multiple_spaces_one_private flaked in one batch run and passes on its own — it hits real Spaces over the network.

End-to-end, with the demo below (a local stand-in for vllm serve that rejects image content the way a non-multimodal model does):

before:  payload content types: ['image_url', 'text', 'text']
         BadRequestError: Error code: 400 - ... 'Model Qwen/Qwen3-8B is not multimodal, but multimodal input was provided.'
after:   payload content types: ['text', 'text', 'text']
         response: ok

Minimal demo (not committed)

from pathlib import Path
from gradio.external import format_conversation

pasted = Path("pasted_text.txt")
pasted.write_text("a very long pasted prompt\n" * 50)

# what the Chatbot holds after a turn where a long prompt was pasted
history = [
    {"role": "user", "content": [{"type": "file", "file": {"path": str(pasted)}}]},
    {"role": "assistant", "content": [{"type": "text", "text": "ok"}]},
]

conversation = format_conversation(history, "and now a short one")
print([part["type"] for m in conversation for part in m["content"]])

The full demo file (which also starts a fake OpenAI-compatible server and drives a real gr.load_chat) was kept untracked and is not part of this PR.

🤖 Generated with Claude Code

`format_conversation()` inlines a text-encoded file into the prompt on the turn
it is sent, but turned every `file` content part in the *history* into
`{"type": "image_url", ...}` regardless of its type. So the turn where a large
pasted prompt became a .txt attachment worked, and every message after it sent
that .txt back base64-encoded as an image -- which is why the reporter's next
short message failed with "model is not multimodal" on `vllm serve`.

Text-encoded files in the history are now inlined as text the same way they are
when first sent; images still go out as `image_url`.

Fixes #11331

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gradio-pr-bot

gradio-pr-bot commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

🪼 branch checks and previews

Name Status URL
Spaces ready! Spaces preview
Website ready! Website preview
🦄 Changes detected! Details

Install Gradio from this PR

pip install https://huggingface.co/buckets/gradio/pypi-previews/resolve/607eb88ee96c9fff301565e0a2b01688bac13aaf/gradio-6.23.1-py3-none-any.whl

Install Gradio Python Client from this PR

pip install "gradio-client @ git+https://github.com/gradio-app/gradio@607eb88ee96c9fff301565e0a2b01688bac13aaf#subdirectory=client/python"

Import Gradio JS Client from this PR via CDN

import { Client } from "https://huggingface.co/buckets/gradio/npm-previews/resolve/607eb88ee96c9fff301565e0a2b01688bac13aaf/browser.js";

@gradio-pr-bot

Copy link
Copy Markdown
Collaborator

🦄 change detected

This Pull Request includes changes to the following packages.

Package Version
gradio patch

  • Keep text-encoded files as text when replaying gr.load_chat history

Something isn't right?

  • Maintainers can change the version label to modify the version bump.
  • If the bot has failed to detect any changes, or if this pull request needs to update multiple packages to different versions or requires a more comprehensive changelog entry, maintainers can update the changelog file directly.

@abidlabs

Copy link
Copy Markdown
Member Author

Folded into #13742, which now carries both chat-history fixes (this one and #10823) — the two touch the same area, so they are easier to review together. No content lost: the commit here was cherry-picked onto that branch unchanged.

Closing in favour of #13742.

@abidlabs abidlabs closed this Aug 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Default gr.load_chat(...) with large and small inputs leads to model is not multimodal error in vllm serve

2 participants