Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 47 additions & 3 deletions src/llm/apis/openai_responses.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,47 @@ static FunctionCallFields readFunctionCallFields(const rapidjson::Value& item) {
return out;
}

static absl::StatusOr<std::string> 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");
}
Comment on lines +170 to +176

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<rapidjson::StringBuffer> 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
Expand Down Expand Up @@ -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();
}
Expand Down