Repository navigation
server : fix image blocks in tool_result being dropped during Anthropic→OpenAI conversion - #22536
Conversation
|
Hi @quei4r, thanks for your contribution! Per our contribution guidelines, the automated PR checker found the following issue(s) that need your attention:
Please note that maintainers reserve the right to make final decisions on PRs. If you believe there is a mistake, please comment below. |
|
can you add a test case in test_compat_anthropic.py ? |
Okay.I want to understand the test framework properly before writing the test, so I'll add it tomorrow. |
Test added and rebased to latest master. The new test_anthropic_tool_result_with_image verifies image blocks inside tool_result are converted to OpenAI image_url format. |
quei4r
left a comment
There was a problem hiding this comment.
Waiting for review. Thanks!
|
Cool fix, I think std::ostringstream is overkill here, simple string concatenation would do. Also, the two-pass scan (detect images, then build) could be collapsed into a single pass building content_parts directly and deciding the final shape at the end. |
…ic→OpenAI conversion server_chat_convert_anthropic_to_oai() silently discarded image blocks inside Anthropic tool_result content. This broke multimodal tool outputs (e.g. a tool that returns an image) because the model never received the image. When tool_result contains image blocks, convert them to OpenAI multimodal content parts (text + image_url array). Plain-text results remain simple strings for backwards compatibility.
|
@ServeurpersoCom Thanks for the suggestions. Updated in the latest push: replaced std::ostringstream with string concatenation and collapsed the two-pass scan into a single pass. Let me know if there's anything else. |
ServeurpersoCom
left a comment
There was a problem hiding this comment.
Nice, the single-pass version reads much better. LGTM.
|
@ngxson Could you take a look when you have a moment. Let me know if you see any issues or have suggestions for improvement. Thanks. |
Hello, do you have time to resolve this issue: https://github.com/ggml-org/llama.cpp/issues/23524。 |
|
@lc1234567484848 If your issue is that Claude Code's read tool returns an image but the model keeps saying it doesn't see anything, that sounds like the same bug this PR fixes — the image block in tool_result gets silently dropped during conversion.You can try applying the changes from server-chat.cpp in this PR as a temporary workaround to see if it resolves your issue. |
Thank you for your answer, I will give it a try. The model will automatically execute: Of course, the ideal situation would still be to directly tell Claude code a local path to achieve what I want. My approach is just a polite, roundabout solution. |
|
It's messed up if model sends 2 or more image reads in 1 tool call. Order is unpredictable |
|
Hi, Just encounter the same issue. This will be nice to be merged. It looks like just one more approve needed @ngxson Thanks! |
|
Confirmed this fix independently on a different model/stack: Qwen3.6-35B-A3B-Unsloth (Unsloth quant) + mmproj, built from b9957 with this patch applied, tested via the exact Claude Code Read-tool → tool_result flow this PR targets. Before: input_tokens: 121, model responds as if no image was provided (fabricates content based on the filename). Also related to #20663, which reports the same symptom. Would be great to get this merged — @ngxson, is there anything blocking beyond a final review pass? |
…ic OpenAI conversion (ggml-org#22536) * server : fix image blocks in tool_result being dropped during Anthropic→OpenAI conversion server_chat_convert_anthropic_to_oai() silently discarded image blocks inside Anthropic tool_result content. This broke multimodal tool outputs (e.g. a tool that returns an image) because the model never received the image. When tool_result contains image blocks, convert them to OpenAI multimodal content parts (text + image_url array). Plain-text results remain simple strings for backwards compatibility. * server : add test for image blocks in Anthropic tool_result conversion
The Responses API's function_call_output only accepts input_text, so returning an image from a tool gives a 400. This breaks Codex when it uses the vision capability of a local model. Convert input_image to image_url and keep it inside the tool message. This works because oaicompat_chat_params_parse() handles image_url the same way for every message role - it extracts the media into out_files and replaces it with a media marker, so the chat template never sees the media itself. Same approach as ggml-org#22536 for the Anthropic path.
…ic OpenAI conversion (ggml-org#22536) * server : fix image blocks in tool_result being dropped during Anthropic→OpenAI conversion server_chat_convert_anthropic_to_oai() silently discarded image blocks inside Anthropic tool_result content. This broke multimodal tool outputs (e.g. a tool that returns an image) because the model never received the image. When tool_result contains image blocks, convert them to OpenAI multimodal content parts (text + image_url array). Plain-text results remain simple strings for backwards compatibility. * server : add test for image blocks in Anthropic tool_result conversion
Overview
server_chat_convert_anthropic_to_oai()silently drops image blocks when converting Anthropictool_resultcontent to OpenAI format. This breaks clients that expect tools to return images (e.g. Claude Code) — the model behaves as if no image was provided at all.When
tool_resultcontains image blocks, convert the content to OpenAI multimodal content parts (text + image array). Plain text results remain plain strings for maximum backward compatibility.This change is safe because
oaicompat_chat_params_parse()already uniformly handlesimage_urlacross all message roles — extracting media toout_filesand replacing it withmedia_markerplaceholders for the chat template.Key Changes
In
server_chat_convert_anthropic_to_oai():tool_resultcontent blocks for those withtype == "image".{"type": "text", "text": "..."}source.type == "base64"→ Build a data URI (data:<media_type>;base64,<data>) and emit{"type": "image_url", "image_url": {"url": "..."}}source.type == "url"→ Pass the URL directly as{"type": "image_url", "image_url": {"url": "..."}}Testing
Test script
Before
After
Tested locally with Qwen3.6-27B Q8 + Claude Code read tool.
Requirements