When all multimodal content parts resolve to text (after media marker
substitution), join them into a single string. Passthrough templates
like `{% for m in messages %}{{m['content']}}{% endfor %}` render a
list of dicts as its Python repr, causing models to see garbled input
and produce empty or degenerate output.
This affects models with simple passthrough chat templates (e.g.,
Baidu Unlimited-OCR / deepseek2-ocr). Templates that iterate over
content parts (e.g., LLaVA-style) are unaffected because the
else-branch preserves the list.
Tested with Unlimited-OCR Q4_K_M + mmproj-F16, MTMDChatHandler.
Before: 0 output tokens (immediate EOS).
After: correct structured OCR with bounding boxes.
Problem
MTMDChatHandler._convert_message_for_template keeps multimodal content as a list of dicts after media marker substitution. For models with passthrough chat templates (e.g., {% for m in messages %}{{m['content']}}{% endfor %}), Jinja renders this list as its Python repr string. The model receives garbled input and produces 0 output tokens (immediate EOS).
Affects any model whose tokenizer.chat_template directly references m['content'] or m.content without iterating over content parts. Confirmed on Baidu Unlimited-OCR (deepseek2-ocr architecture, 4.6K GitHub stars).
Fix
After converting content parts (replacing image_url with the media marker text), check if all parts resolved to text-only dicts. If so, join them into a single concatenated string. Templates that iterate over content parts (e.g., LLaVA-style {% for item in message.content %}) are unaffected because the else-branch preserves the list.
Before / After
Before (0 output tokens):
After (correct structured OCR with bounding boxes):
Test environment