Skip to content

Instantly share code, notes, and snippets.

@therealkenc
Created May 2, 2026 22:03
Show Gist options
  • Select an option

  • Save therealkenc/e90347f1e298c60492f229349f949124 to your computer and use it in GitHub Desktop.

Select an option

Save therealkenc/e90347f1e298c60492f229349f949124 to your computer and use it in GitHub Desktop.
patch against ggml-org/llama.cpp#21174 to add multimodal tool call responses
diff --git a/tools/server/server-chat.cpp b/tools/server/server-chat.cpp
index 8ef7766fd..1626f70db 100644
--- a/tools/server/server-chat.cpp
+++ b/tools/server/server-chat.cpp
@@ -31,7 +31,7 @@ json server_chat_convert_responses_to_chatcmpl(const json & response_body) {
throw std::invalid_argument("llama.cpp does not support 'previous_response_id'.");
}
- const json input_value = response_body.at("input");
+ const json & input_value = response_body.at("input");
json chatcmpl_body = response_body;
chatcmpl_body.erase("input");
std::vector<json> chatcmpl_messages;
@@ -276,16 +276,47 @@ json server_chat_convert_responses_to_chatcmpl(const json & response_body) {
{"tool_call_id", item.at("call_id")},
});
} else {
- json chatcmpl_outputs = item.at("output");
- for (json & chatcmpl_output : chatcmpl_outputs) {
- if (!chatcmpl_output.contains("type") || chatcmpl_output.at("type") != "input_text") {
- SRV_DBG("responses compat: item_type='%s', action='treat_tool_output_as_text'\n",
- json_value(chatcmpl_output, "type", std::string("missing")).c_str());
+ // Walk output parts. Translate Responses-API content types
+ // into chat-completions content types in place; the
+ // downstream `oaicompat_chat_params_parse` then fetches
+ // any `image_url` parts via `handle_media()` and replaces
+ // them with the model's media marker, which the chat
+ // template renders normally regardless of role.
+ std::vector<json> tool_content;
+ const std::string tool_role = "tool";
+ for (const json & output_part : item.at("output")) {
+ const std::string type = json_value(output_part, "type", std::string());
+ if (type == "input_text" || type == "output_text" || type == "text") {
+ if (!exists_and_is_string(output_part, "text")) {
+ responses_append_recovery_text(tool_content, tool_role, type, type + " item missing text");
+ continue;
+ }
+ tool_content.push_back({
+ {"text", output_part.at("text")},
+ {"type", "text"},
+ });
+ } else if (type == "input_image") {
+ if (!output_part.contains("image_url")) {
+ responses_append_recovery_text(tool_content, tool_role, type, "input_image item missing image_url");
+ continue;
+ }
+ tool_content.push_back({
+ {"image_url", json {
+ {"url", output_part.at("image_url")}
+ }},
+ {"type", "image_url"},
+ });
+ } else {
+ const std::string item_type = type.empty() ? std::string("unknown") : type;
+ responses_append_recovery_text(
+ tool_content,
+ tool_role,
+ item_type,
+ "unsupported tool_output content type " + item_type);
}
- chatcmpl_output["type"] = "text";
}
chatcmpl_messages.push_back(json {
- {"content", chatcmpl_outputs},
+ {"content", tool_content},
{"role", "tool"},
{"tool_call_id", item.at("call_id")},
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment