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 07:11:31 -06:00 committed by GitHub
parent e877ad8bd9
commit 34ec1c3f18
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 44 additions and 40 deletions

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) {
}); });
} }
item.erase("status"); if (merge_prev) {
item.erase("type"); auto & prev_msg = chatcmpl_messages.back();
item["content"] = chatcmpl_content; if (!exists_and_is_array(prev_msg, "content")) {
chatcmpl_messages.push_back(item); 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("type");
item["content"] = chatcmpl_content;
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"}, {"function", json {
{"tool_calls", json::array({ json { {"arguments", item.at("arguments")},
{"function", json { {"name", item.at("name")},
{"arguments", item.at("arguments")}, }},
{"name", item.at("name")}, {"id", item.at("call_id")},
}}, {"type", "function"},
{"id", item.at("call_id")},
{"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) {
chatcmpl_messages.push_back(json { auto & prev_msg = chatcmpl_messages.back();
{"role", "assistant"}, prev_msg["reasoning_content"] = item.at("content")[0].at("text");
{"content", json::array()}, } else {
{"reasoning_content", item.at("content")[0].at("text")}, chatcmpl_messages.push_back(json {
}); {"role", "assistant"},
{"content", json::array()},
{"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")) {