server : merge contiguous Responses input items into a single assistant message (#19773)

* server : merge contiguous input items into a single assistant message

* cont : simplify tool call msg

* cont : reduce and combine content

* cont : fix merging content items
This commit is contained in:
Aldehir Rojas
2026-02-22 14:11:31 +01:00
committed by GitHub
parent e877ad8bd9
commit 34ec1c3f18
+29 -25
View File
@@ -1105,6 +1105,8 @@ json convert_responses_to_chatcmpl(const json & response_body) {
}; };
for (json item : input_value) { for (json item : input_value) {
bool merge_prev = !chatcmpl_messages.empty() && chatcmpl_messages.back().value("role", "") == "assistant";
if (exists_and_is_string(item, "content")) { if (exists_and_is_string(item, "content")) {
// #responses_create-input-input_item_list-input_message-content-text_input // #responses_create-input-input_item_list-input_message-content-text_input
// Only "Input message" contains item["content"]::string // Only "Input message" contains item["content"]::string
@@ -1193,7 +1195,7 @@ json convert_responses_to_chatcmpl(const json & response_body) {
item.at("type") == "message" item.at("type") == "message"
) { ) {
// #responses_create-input-input_item_list-item-output_message // #responses_create-input-input_item_list-item-output_message
std::vector<json> chatcmpl_content; auto chatcmpl_content = json::array();
for (const auto & output_text : item.at("content")) { for (const auto & output_text : item.at("content")) {
const std::string type = json_value(output_text, "type", std::string()); const std::string type = json_value(output_text, "type", std::string());
@@ -1210,10 +1212,19 @@ json convert_responses_to_chatcmpl(const json & response_body) {
}); });
} }
if (merge_prev) {
auto & prev_msg = chatcmpl_messages.back();
if (!exists_and_is_array(prev_msg, "content")) {
prev_msg["content"] = json::array();
}
auto & prev_content = prev_msg["content"];
prev_content.insert(prev_content.end(), chatcmpl_content.begin(), chatcmpl_content.end());
} else {
item.erase("status"); item.erase("status");
item.erase("type"); item.erase("type");
item["content"] = chatcmpl_content; item["content"] = chatcmpl_content;
chatcmpl_messages.push_back(item); chatcmpl_messages.push_back(item);
}
} else if (exists_and_is_string(item, "arguments") && } else if (exists_and_is_string(item, "arguments") &&
exists_and_is_string(item, "call_id") && exists_and_is_string(item, "call_id") &&
exists_and_is_string(item, "name") && exists_and_is_string(item, "name") &&
@@ -1221,24 +1232,27 @@ json convert_responses_to_chatcmpl(const json & response_body) {
item.at("type") == "function_call" item.at("type") == "function_call"
) { ) {
// #responses_create-input-input_item_list-item-function_tool_call // #responses_create-input-input_item_list-item-function_tool_call
json msg = json { json tool_call = {
{"role", "assistant"},
{"tool_calls", json::array({ json {
{"function", json { {"function", json {
{"arguments", item.at("arguments")}, {"arguments", item.at("arguments")},
{"name", item.at("name")}, {"name", item.at("name")},
}}, }},
{"id", item.at("call_id")}, {"id", item.at("call_id")},
{"type", "function"}, {"type", "function"},
}})},
}; };
if (!chatcmpl_messages.empty() && chatcmpl_messages.back().contains("reasoning_content")) { if (merge_prev) {
// Move reasoning content from dummy message to tool call message auto & prev_msg = chatcmpl_messages.back();
msg["reasoning_content"] = chatcmpl_messages.back().at("reasoning_content"); if (!exists_and_is_array(prev_msg, "tool_calls")) {
chatcmpl_messages.pop_back(); prev_msg["tool_calls"] = json::array();
}
prev_msg["tool_calls"].push_back(tool_call);
} else {
chatcmpl_messages.push_back(json {
{"role", "assistant"},
{"tool_calls", json::array({tool_call})}
});
} }
chatcmpl_messages.push_back(msg);
} else if (exists_and_is_string(item, "call_id") && } else if (exists_and_is_string(item, "call_id") &&
(exists_and_is_string(item, "output") || exists_and_is_array(item, "output")) && (exists_and_is_string(item, "output") || exists_and_is_array(item, "output")) &&
exists_and_is_string(item, "type") && exists_and_is_string(item, "type") &&
@@ -1282,12 +1296,16 @@ json convert_responses_to_chatcmpl(const json & response_body) {
throw std::invalid_argument("item['content']['text'] is not a string"); throw std::invalid_argument("item['content']['text'] is not a string");
} }
// Pack reasoning content in dummy message if (merge_prev) {
auto & prev_msg = chatcmpl_messages.back();
prev_msg["reasoning_content"] = item.at("content")[0].at("text");
} else {
chatcmpl_messages.push_back(json { chatcmpl_messages.push_back(json {
{"role", "assistant"}, {"role", "assistant"},
{"content", json::array()}, {"content", json::array()},
{"reasoning_content", item.at("content")[0].at("text")}, {"reasoning_content", item.at("content")[0].at("text")},
}); });
}
} else { } else {
throw std::invalid_argument("Cannot determine type of 'item'"); throw std::invalid_argument("Cannot determine type of 'item'");
} }
@@ -1296,20 +1314,6 @@ json convert_responses_to_chatcmpl(const json & response_body) {
throw std::invalid_argument("'input' must be a string or array of objects"); throw std::invalid_argument("'input' must be a string or array of objects");
} }
// Remove unused dummy message which contains
// reasoning content not followed by tool call
chatcmpl_messages.erase(std::remove_if(
chatcmpl_messages.begin(),
chatcmpl_messages.end(),
[](const json & x){ return x.contains("role") &&
x.at("role") == "assistant" &&
x.contains("content") &&
x.at("content") == json::array() &&
x.contains("reasoning_content");
}),
chatcmpl_messages.end()
);
chatcmpl_body["messages"] = chatcmpl_messages; chatcmpl_body["messages"] = chatcmpl_messages;
if (response_body.contains("tools")) { if (response_body.contains("tools")) {