From d6f59f140cf81e5d3c48cd9f1967a247184cbc92 Mon Sep 17 00:00:00 2001 From: Michal Kulakowski Date: Wed, 5 Aug 2026 14:32:30 +0200 Subject: [PATCH] Fix responses funtion call output --- src/llm/apis/openai_responses.cpp | 50 +++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/src/llm/apis/openai_responses.cpp b/src/llm/apis/openai_responses.cpp index f5f0ab779c..4c604ef3dc 100644 --- a/src/llm/apis/openai_responses.cpp +++ b/src/llm/apis/openai_responses.cpp @@ -167,6 +167,47 @@ static FunctionCallFields readFunctionCallFields(const rapidjson::Value& item) { return out; } +static absl::StatusOr normalizeFunctionCallOutput(const rapidjson::Value& outputValue) { + if (outputValue.IsString()) { + return std::string(outputValue.GetString()); + } + if (!outputValue.IsArray()) { + return absl::InvalidArgumentError("function_call_output.output must be a string or array"); + } + + std::string textOnlyOutput; + bool hasAnyText = false; + for (const auto& contentItem : outputValue.GetArray()) { + if (!contentItem.IsObject()) { + continue; + } + auto contentObj = contentItem.GetObject(); + auto typeIt = contentObj.FindMember("type"); + auto textIt = contentObj.FindMember("text"); + if (typeIt == contentObj.MemberEnd() || !typeIt->value.IsString() || + textIt == contentObj.MemberEnd() || !textIt->value.IsString()) { + continue; + } + const std::string contentType = typeIt->value.GetString(); + if (contentType != "input_text" && contentType != "output_text") { + continue; + } + if (hasAnyText) { + textOnlyOutput += "\n"; + } + textOnlyOutput += textIt->value.GetString(); + hasAnyText = true; + } + if (hasAnyText) { + return textOnlyOutput; + } + + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + outputValue.Accept(writer); + return std::string(buffer.GetString()); +} + // Reject function_call items that would translate to a syntactically valid but // semantically broken assistant.tool_calls entry (missing identifier, name, or // arguments). The call_id/id mismatch is also what breaks tool_call_id linkage @@ -321,10 +362,13 @@ class ResponsesInputBuilder { auto callIdIt = itemObj.FindMember("call_id"); if (callIdIt != itemObj.MemberEnd() && callIdIt->value.IsString()) callId = callIdIt->value.GetString(); - std::string output; auto outputIt = itemObj.FindMember("output"); - if (outputIt != itemObj.MemberEnd() && outputIt->value.IsString()) - output = outputIt->value.GetString(); + if (outputIt == itemObj.MemberEnd()) + return absl::InvalidArgumentError("function_call_output item is missing required output field"); + auto outputStatus = normalizeFunctionCallOutput(outputIt->value); + if (!outputStatus.ok()) + return outputStatus.status(); + std::string output = outputStatus.value(); sink.emitToolMessage(callId, output); return absl::OkStatus(); }