Replace nlohmann/json with native C++20 JSON engine, cut build RAM/time, fix 32-bit build, enable -O2 -march=native
This commit is contained in:
@@ -1,9 +1,477 @@
|
||||
/* json.cpp — request payload building and non-streaming response parsing. */
|
||||
/* json.cpp — native RFC 8259 JSON parser/serializer, request building,
|
||||
* and non-streaming response parsing. */
|
||||
#include "bantam.hpp"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <cerrno>
|
||||
#include <climits>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
using json = nlohmann::json;
|
||||
/* ----------------------------- Json methods --------------------------- */
|
||||
Json::Json() = default;
|
||||
Json::~Json() = default;
|
||||
Json::Json(const Json &) = default;
|
||||
Json::Json(Json &&) noexcept = default;
|
||||
Json &Json::operator=(const Json &) = default;
|
||||
Json &Json::operator=(Json &&) noexcept = default;
|
||||
|
||||
Json::Json(std::nullptr_t) : type(JType::Null) {}
|
||||
Json::Json(bool b) : type(JType::Bool), bval(b) {}
|
||||
Json::Json(int n) : type(JType::Int), ival(n), dval(n) {}
|
||||
Json::Json(int64_t n) : type(JType::Int), ival(n), dval((double)n) {}
|
||||
Json::Json(double d) : type(JType::Float), ival((int64_t)d), dval(d) {}
|
||||
Json::Json(const char *s) : type(JType::Str), sval(s ? s : "") {}
|
||||
Json::Json(std::string s) : type(JType::Str), sval(std::move(s)) {}
|
||||
Json::Json(std::string_view s) : type(JType::Str), sval(s) {}
|
||||
|
||||
Json Json::object() {
|
||||
Json j;
|
||||
j.type = JType::Obj;
|
||||
return j;
|
||||
}
|
||||
Json Json::array() {
|
||||
Json j;
|
||||
j.type = JType::Arr;
|
||||
return j;
|
||||
}
|
||||
|
||||
static const Json kNullJson{};
|
||||
|
||||
bool Json::empty() const {
|
||||
if (type == JType::Arr) return aval.empty();
|
||||
if (type == JType::Obj) return oval.empty();
|
||||
if (type == JType::Str) return sval.empty();
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t Json::size() const {
|
||||
if (type == JType::Arr) return aval.size();
|
||||
if (type == JType::Obj) return oval.size();
|
||||
if (type == JType::Str) return sval.size();
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Json::contains(std::string_view k) const {
|
||||
if (type != JType::Obj) return false;
|
||||
for (auto &kv : oval) {
|
||||
if (kv.first == k) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Json &Json::operator[](std::string_view k) {
|
||||
if (type != JType::Obj) {
|
||||
*this = Json::object();
|
||||
}
|
||||
for (auto &kv : oval) {
|
||||
if (kv.first == k) return kv.second;
|
||||
}
|
||||
oval.emplace_back(std::string(k), Json{});
|
||||
return oval.back().second;
|
||||
}
|
||||
|
||||
const Json &Json::operator[](std::string_view k) const {
|
||||
if (type != JType::Obj) return kNullJson;
|
||||
for (auto &kv : oval) {
|
||||
if (kv.first == k) return kv.second;
|
||||
}
|
||||
return kNullJson;
|
||||
}
|
||||
|
||||
Json &Json::at(size_t i) {
|
||||
return aval[i];
|
||||
}
|
||||
|
||||
const Json &Json::at(size_t i) const {
|
||||
if (type != JType::Arr || i >= aval.size()) return kNullJson;
|
||||
return aval[i];
|
||||
}
|
||||
|
||||
void Json::push_back(Json v) {
|
||||
if (type != JType::Arr) {
|
||||
*this = Json::array();
|
||||
}
|
||||
aval.push_back(std::move(v));
|
||||
}
|
||||
|
||||
std::string Json::str(std::string_view def) const {
|
||||
if (type == JType::Str) return sval;
|
||||
return std::string(def);
|
||||
}
|
||||
|
||||
int64_t Json::as_int64(int64_t def) const {
|
||||
if (type == JType::Int) return ival;
|
||||
return def;
|
||||
}
|
||||
|
||||
int Json::as_int(int def) const {
|
||||
if (type != JType::Int) return def;
|
||||
if (ival > INT_MAX) return INT_MAX;
|
||||
if (ival < INT_MIN) return INT_MIN;
|
||||
return (int)ival;
|
||||
}
|
||||
|
||||
/* ----------------------------- JSON serializer ------------------------ */
|
||||
static void dump_str(std::string &out, std::string_view s) {
|
||||
out += '"';
|
||||
const unsigned char *p = (const unsigned char *)s.data();
|
||||
size_t avail = s.size();
|
||||
while (avail > 0) {
|
||||
unsigned char c = p[0];
|
||||
if (c < 0x80) {
|
||||
p++;
|
||||
avail--;
|
||||
switch (c) {
|
||||
case '"': out += "\\\""; break;
|
||||
case '\\': out += "\\\\"; break;
|
||||
case '\b': out += "\\b"; break;
|
||||
case '\f': out += "\\f"; break;
|
||||
case '\n': out += "\\n"; break;
|
||||
case '\r': out += "\\r"; break;
|
||||
case '\t': out += "\\t"; break;
|
||||
default:
|
||||
if (c < 0x20) str_appendf(out, "\\u%04x", (unsigned)c);
|
||||
else out += (char)c;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
size_t pl = 0;
|
||||
int cp = utf8_decode(p, avail, &pl);
|
||||
if (cp == 0xFFFD && pl == 1) {
|
||||
out += "\xEF\xBF\xBD";
|
||||
p++;
|
||||
avail--;
|
||||
} else {
|
||||
out.append((const char *)p, pl);
|
||||
p += pl;
|
||||
avail -= pl;
|
||||
}
|
||||
}
|
||||
}
|
||||
out += '"';
|
||||
}
|
||||
|
||||
static void dump_rec(std::string &out, const Json &j, int indent, int depth) {
|
||||
switch (j.type) {
|
||||
case JType::Null:
|
||||
out += "null";
|
||||
break;
|
||||
case JType::Bool:
|
||||
out += (j.bval ? "true" : "false");
|
||||
break;
|
||||
case JType::Int:
|
||||
str_appendf(out, "%lld", (long long)j.ival);
|
||||
break;
|
||||
case JType::Float:
|
||||
if (!std::isfinite(j.dval)) out += "null";
|
||||
else str_appendf(out, "%.15g", j.dval);
|
||||
break;
|
||||
case JType::Str:
|
||||
dump_str(out, j.sval);
|
||||
break;
|
||||
case JType::Arr:
|
||||
if (j.aval.empty()) {
|
||||
out += "[]";
|
||||
} else {
|
||||
out += '[';
|
||||
for (size_t i = 0; i < j.aval.size(); i++) {
|
||||
if (i > 0) out += ',';
|
||||
if (indent >= 0) {
|
||||
out += '\n';
|
||||
out.append((size_t)(depth + 1) * (size_t)indent, ' ');
|
||||
}
|
||||
dump_rec(out, j.aval[i], indent, depth + 1);
|
||||
}
|
||||
if (indent >= 0) {
|
||||
out += '\n';
|
||||
out.append((size_t)depth * (size_t)indent, ' ');
|
||||
}
|
||||
out += ']';
|
||||
}
|
||||
break;
|
||||
case JType::Obj:
|
||||
if (j.oval.empty()) {
|
||||
out += "{}";
|
||||
} else {
|
||||
out += '{';
|
||||
for (size_t i = 0; i < j.oval.size(); i++) {
|
||||
if (i > 0) out += ',';
|
||||
if (indent >= 0) {
|
||||
out += '\n';
|
||||
out.append((size_t)(depth + 1) * (size_t)indent, ' ');
|
||||
}
|
||||
dump_str(out, j.oval[i].first);
|
||||
out += (indent >= 0) ? ": " : ":";
|
||||
dump_rec(out, j.oval[i].second, indent, depth + 1);
|
||||
}
|
||||
if (indent >= 0) {
|
||||
out += '\n';
|
||||
out.append((size_t)depth * (size_t)indent, ' ');
|
||||
}
|
||||
out += '}';
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::string Json::dump(int indent) const {
|
||||
std::string out;
|
||||
dump_rec(out, *this, indent, 0);
|
||||
return out;
|
||||
}
|
||||
|
||||
/* ----------------------------- JSON parser ---------------------------- */
|
||||
struct JParser {
|
||||
std::string_view src;
|
||||
size_t pos = 0;
|
||||
|
||||
void skip_ws() {
|
||||
while (pos < src.size()) {
|
||||
char c = src[pos];
|
||||
if (c == ' ' || c == '\t' || c == '\n' || c == '\r') pos++;
|
||||
else break;
|
||||
}
|
||||
}
|
||||
|
||||
static int hex_val(char c) {
|
||||
if (c >= '0' && c <= '9') return c - '0';
|
||||
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
||||
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool parse_hex4(uint32_t &out) {
|
||||
if (pos + 4 > src.size()) return false;
|
||||
uint32_t v = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int h = hex_val(src[pos + (size_t)i]);
|
||||
if (h < 0) return false;
|
||||
v = (v << 4) | (uint32_t)h;
|
||||
}
|
||||
pos += 4;
|
||||
out = v;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void append_utf8(std::string &s, uint32_t cp) {
|
||||
if (cp <= 0x7F) {
|
||||
s += (char)cp;
|
||||
} else if (cp <= 0x7FF) {
|
||||
s += (char)(0xC0 | (cp >> 6));
|
||||
s += (char)(0x80 | (cp & 0x3F));
|
||||
} else if (cp <= 0xFFFF) {
|
||||
s += (char)(0xE0 | (cp >> 12));
|
||||
s += (char)(0x80 | ((cp >> 6) & 0x3F));
|
||||
s += (char)(0x80 | (cp & 0x3F));
|
||||
} else if (cp <= 0x10FFFF) {
|
||||
s += (char)(0xF0 | (cp >> 18));
|
||||
s += (char)(0x80 | ((cp >> 12) & 0x3F));
|
||||
s += (char)(0x80 | ((cp >> 6) & 0x3F));
|
||||
s += (char)(0x80 | (cp & 0x3F));
|
||||
} else {
|
||||
s += "\xEF\xBF\xBD";
|
||||
}
|
||||
}
|
||||
|
||||
bool parse_string(std::string &out) {
|
||||
if (pos >= src.size() || src[pos] != '"') return false;
|
||||
pos++;
|
||||
out.clear();
|
||||
while (pos < src.size()) {
|
||||
unsigned char c = (unsigned char)src[pos++];
|
||||
if (c == '"') return true;
|
||||
if (c < 0x20) return false;
|
||||
if (c != '\\') {
|
||||
out += (char)c;
|
||||
continue;
|
||||
}
|
||||
if (pos >= src.size()) return false;
|
||||
char esc = src[pos++];
|
||||
switch (esc) {
|
||||
case '"': out += '"'; break;
|
||||
case '\\': out += '\\'; break;
|
||||
case '/': out += '/'; break;
|
||||
case 'b': out += '\b'; break;
|
||||
case 'f': out += '\f'; break;
|
||||
case 'n': out += '\n'; break;
|
||||
case 'r': out += '\r'; break;
|
||||
case 't': out += '\t'; break;
|
||||
case 'u': {
|
||||
uint32_t cp = 0;
|
||||
if (!parse_hex4(cp)) return false;
|
||||
if (cp >= 0xD800 && cp <= 0xDBFF) {
|
||||
if (pos + 6 <= src.size() && src[pos] == '\\' && src[pos + 1] == 'u') {
|
||||
size_t saved = pos;
|
||||
pos += 2;
|
||||
uint32_t low = 0;
|
||||
if (parse_hex4(low) && low >= 0xDC00 && low <= 0xDFFF) {
|
||||
cp = 0x10000u + (((cp - 0xD800u) << 10) | (low - 0xDC00u));
|
||||
} else {
|
||||
pos = saved;
|
||||
cp = 0xFFFDu;
|
||||
}
|
||||
} else {
|
||||
cp = 0xFFFDu;
|
||||
}
|
||||
} else if (cp >= 0xDC00 && cp <= 0xDFFF) {
|
||||
cp = 0xFFFDu;
|
||||
}
|
||||
append_utf8(out, cp);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool parse_number(Json &out) {
|
||||
size_t start = pos;
|
||||
if (src[pos] == '-') pos++;
|
||||
if (pos >= src.size()) return false;
|
||||
if (src[pos] == '0') {
|
||||
pos++;
|
||||
if (pos < src.size() && src[pos] >= '0' && src[pos] <= '9') return false;
|
||||
} else if (src[pos] >= '1' && src[pos] <= '9') {
|
||||
while (pos < src.size() && src[pos] >= '0' && src[pos] <= '9') pos++;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
bool is_float = false;
|
||||
if (pos < src.size() && src[pos] == '.') {
|
||||
is_float = true;
|
||||
pos++;
|
||||
if (pos >= src.size() || src[pos] < '0' || src[pos] > '9') return false;
|
||||
while (pos < src.size() && src[pos] >= '0' && src[pos] <= '9') pos++;
|
||||
}
|
||||
if (pos < src.size() && (src[pos] == 'e' || src[pos] == 'E')) {
|
||||
is_float = true;
|
||||
pos++;
|
||||
if (pos < src.size() && (src[pos] == '+' || src[pos] == '-')) pos++;
|
||||
if (pos >= src.size() || src[pos] < '0' || src[pos] > '9') return false;
|
||||
while (pos < src.size() && src[pos] >= '0' && src[pos] <= '9') pos++;
|
||||
}
|
||||
std::string num_str(src.substr(start, pos - start));
|
||||
if (!is_float) {
|
||||
errno = 0;
|
||||
char *end = nullptr;
|
||||
long long iv = std::strtoll(num_str.c_str(), &end, 10);
|
||||
if (errno == 0 && end == num_str.c_str() + num_str.size()) {
|
||||
out = Json((int64_t)iv);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
errno = 0;
|
||||
char *end = nullptr;
|
||||
double dv = std::strtod(num_str.c_str(), &end);
|
||||
if (end != num_str.c_str() + num_str.size() || !std::isfinite(dv)) return false;
|
||||
out = Json(dv);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parse_value(Json &out, int depth) {
|
||||
if (depth > 256) return false;
|
||||
skip_ws();
|
||||
if (pos >= src.size()) return false;
|
||||
char c = src[pos];
|
||||
if (c == 'n') {
|
||||
if (src.substr(pos, 4) != "null") return false;
|
||||
pos += 4;
|
||||
out = Json(nullptr);
|
||||
return true;
|
||||
}
|
||||
if (c == 't') {
|
||||
if (src.substr(pos, 4) != "true") return false;
|
||||
pos += 4;
|
||||
out = Json(true);
|
||||
return true;
|
||||
}
|
||||
if (c == 'f') {
|
||||
if (src.substr(pos, 5) != "false") return false;
|
||||
pos += 5;
|
||||
out = Json(false);
|
||||
return true;
|
||||
}
|
||||
if (c == '"') {
|
||||
std::string s;
|
||||
if (!parse_string(s)) return false;
|
||||
out = Json(std::move(s));
|
||||
return true;
|
||||
}
|
||||
if (c == '[') {
|
||||
pos++;
|
||||
skip_ws();
|
||||
out = Json::array();
|
||||
if (pos < src.size() && src[pos] == ']') {
|
||||
pos++;
|
||||
return true;
|
||||
}
|
||||
while (true) {
|
||||
Json elem;
|
||||
if (!parse_value(elem, depth + 1)) return false;
|
||||
out.aval.push_back(std::move(elem));
|
||||
skip_ws();
|
||||
if (pos < src.size() && src[pos] == ',') {
|
||||
pos++;
|
||||
continue;
|
||||
}
|
||||
if (pos < src.size() && src[pos] == ']') {
|
||||
pos++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (c == '{') {
|
||||
pos++;
|
||||
skip_ws();
|
||||
out = Json::object();
|
||||
if (pos < src.size() && src[pos] == '}') {
|
||||
pos++;
|
||||
return true;
|
||||
}
|
||||
while (true) {
|
||||
skip_ws();
|
||||
std::string key;
|
||||
if (!parse_string(key)) return false;
|
||||
skip_ws();
|
||||
if (pos >= src.size() || src[pos] != ':') return false;
|
||||
pos++;
|
||||
Json val;
|
||||
if (!parse_value(val, depth + 1)) return false;
|
||||
out[key] = std::move(val);
|
||||
skip_ws();
|
||||
if (pos < src.size() && src[pos] == ',') {
|
||||
pos++;
|
||||
continue;
|
||||
}
|
||||
if (pos < src.size() && src[pos] == '}') {
|
||||
pos++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (c == '-' || (c >= '0' && c <= '9')) {
|
||||
return parse_number(out);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
bool Json::parse(std::string_view src, Json &out) {
|
||||
JParser p{src, 0};
|
||||
Json tmp;
|
||||
if (!p.parse_value(tmp, 0)) return false;
|
||||
p.skip_ws();
|
||||
if (p.pos != src.size()) return false;
|
||||
out = std::move(tmp);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ----------------------------- message helpers ------------------------ */
|
||||
/* The tool definitions, identical to the Go reference implementation. */
|
||||
static const char *TOOLS_JSON = R"JSON([
|
||||
{"type":"function","function":{
|
||||
@@ -32,120 +500,134 @@ static const char *TOOLS_JSON = R"JSON([
|
||||
},"required":["filePath","content"]}}}
|
||||
])JSON";
|
||||
|
||||
static void serialize_message(json &obj, const Message &m) {
|
||||
void serialize_message_json(Json &obj, const Message &m) {
|
||||
obj = Json::object();
|
||||
obj["role"] = m.role.empty() ? "" : m.role;
|
||||
if (!m.content.empty()) obj["content"] = m.content;
|
||||
if (!m.reasoning.empty()) obj["reasoning_content"] = m.reasoning;
|
||||
if (!m.calls.empty()) {
|
||||
json arr = json::array();
|
||||
Json arr = Json::array();
|
||||
for (auto &tc : m.calls) {
|
||||
json t;
|
||||
Json t = Json::object();
|
||||
if (!tc.id.empty()) t["id"] = tc.id;
|
||||
t["type"] = "function";
|
||||
json fn;
|
||||
Json fn = Json::object();
|
||||
fn["name"] = tc.name.empty() ? "" : tc.name;
|
||||
fn["arguments"] = tc.arguments.empty() ? "{}" : tc.arguments;
|
||||
t["function"] = fn;
|
||||
arr.push_back(t);
|
||||
t["function"] = std::move(fn);
|
||||
arr.push_back(std::move(t));
|
||||
}
|
||||
obj["tool_calls"] = arr;
|
||||
obj["tool_calls"] = std::move(arr);
|
||||
}
|
||||
if (!m.tool_call_id.empty()) obj["tool_call_id"] = m.tool_call_id;
|
||||
}
|
||||
|
||||
void parse_message_json(const Json &obj, Message &m) {
|
||||
m = Message{};
|
||||
if (!obj.is_object()) return;
|
||||
m.role = obj["role"].str("user");
|
||||
m.content = obj["content"].str();
|
||||
m.reasoning = obj["reasoning_content"].str();
|
||||
m.tool_call_id = obj["tool_call_id"].str();
|
||||
const Json &tcs = obj["tool_calls"];
|
||||
if (tcs.is_array()) {
|
||||
for (auto &tc : tcs.aval) {
|
||||
if (!tc.is_object()) continue;
|
||||
ToolCall t;
|
||||
t.id = tc["id"].str();
|
||||
const Json &fn = tc["function"];
|
||||
if (fn.is_object()) {
|
||||
t.name = fn["name"].str();
|
||||
t.arguments = fn["arguments"].str();
|
||||
}
|
||||
m.calls.push_back(std::move(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------- request / response --------------------- */
|
||||
std::string build_request_json(const Cfg &cfg, const Messages &msgs,
|
||||
bool use_tools, bool stream) {
|
||||
json root = json::object();
|
||||
Json root = Json::object();
|
||||
root["model"] = cfg.model;
|
||||
root["temperature"] = cfg.temperature;
|
||||
json arr = json::array();
|
||||
Json arr = Json::array();
|
||||
for (auto &m : msgs.items) {
|
||||
json mo;
|
||||
serialize_message(mo, m);
|
||||
arr.push_back(mo);
|
||||
Json mo;
|
||||
serialize_message_json(mo, m);
|
||||
arr.push_back(std::move(mo));
|
||||
}
|
||||
root["messages"] = arr;
|
||||
root["messages"] = std::move(arr);
|
||||
root["stream"] = stream;
|
||||
if (use_tools) {
|
||||
try {
|
||||
json tools = json::parse(TOOLS_JSON);
|
||||
root["tools"] = tools;
|
||||
} catch (...) {}
|
||||
Json tools;
|
||||
if (Json::parse(TOOLS_JSON, tools)) {
|
||||
root["tools"] = std::move(tools);
|
||||
}
|
||||
}
|
||||
if (stream) {
|
||||
json so = json::object();
|
||||
Json so = Json::object();
|
||||
so["include_usage"] = true;
|
||||
root["stream_options"] = so;
|
||||
root["stream_options"] = std::move(so);
|
||||
}
|
||||
/* Forward non-internal raw params, parsed as JSON when possible. */
|
||||
for (auto &kv : cfg.raw) {
|
||||
if (internal_key(kv.first)) continue;
|
||||
try {
|
||||
json jv = json::parse(kv.second);
|
||||
root[kv.first] = jv;
|
||||
} catch (...) {
|
||||
Json jv;
|
||||
if (Json::parse(kv.second, jv)) {
|
||||
root[kv.first] = std::move(jv);
|
||||
} else {
|
||||
root[kv.first] = kv.second;
|
||||
}
|
||||
}
|
||||
return root.dump(-1, ' ', false, json::error_handler_t::replace);
|
||||
}
|
||||
|
||||
static std::string json_str(const json &j, const char *k, std::string_view def = "") {
|
||||
if (j.is_object() && j.contains(k) && j[k].is_string())
|
||||
return j[k].get<std::string>();
|
||||
return std::string(def);
|
||||
}
|
||||
|
||||
static int json_int(const json &j, const char *k, int def = 0) {
|
||||
if (j.is_object() && j.contains(k) && j[k].is_number_integer())
|
||||
return j[k].get<int>();
|
||||
return def;
|
||||
return root.dump();
|
||||
}
|
||||
|
||||
bool parse_nonstream_response(std::string_view body, Message &out, Usage &usage) {
|
||||
out = Message{};
|
||||
usage = Usage{};
|
||||
try {
|
||||
auto root = json::parse(body);
|
||||
if (!root.is_object() || !root.contains("choices") ||
|
||||
!root["choices"].is_array() || root["choices"].empty())
|
||||
return false;
|
||||
auto ch = root["choices"][0];
|
||||
if (!ch.is_object() || !ch.contains("message") || !ch["message"].is_object())
|
||||
return false;
|
||||
auto msg = ch["message"];
|
||||
out.role = json_str(msg, "role", "assistant");
|
||||
out.content = json_str(msg, "content");
|
||||
std::string reas = json_str(msg, "reasoning_content");
|
||||
if (reas.empty()) reas = json_str(msg, "reasoning");
|
||||
if (reas.empty()) reas = json_str(msg, "thought");
|
||||
if (!reas.empty()) out.reasoning = std::move(reas);
|
||||
if (msg.contains("tool_calls") && msg["tool_calls"].is_array()) {
|
||||
for (auto &tc : msg["tool_calls"]) {
|
||||
if (!tc.is_object()) continue;
|
||||
ToolCall t;
|
||||
t.id = json_str(tc, "id");
|
||||
if (tc.contains("function") && tc["function"].is_object()) {
|
||||
auto fn = tc["function"];
|
||||
t.name = json_str(fn, "name");
|
||||
t.arguments = json_str(fn, "arguments", "{}");
|
||||
}
|
||||
out.calls.push_back(std::move(t));
|
||||
Json root_mut;
|
||||
if (!Json::parse(body, root_mut) || !root_mut.is_object()) return false;
|
||||
const Json &root = root_mut;
|
||||
const Json &choices = root["choices"];
|
||||
if (!choices.is_array() || choices.empty()) return false;
|
||||
const Json &ch = choices.at(0);
|
||||
if (!ch.is_object()) return false;
|
||||
const Json &msg = ch["message"];
|
||||
if (!msg.is_object()) return false;
|
||||
out.role = msg["role"].str("assistant");
|
||||
out.content = msg["content"].str();
|
||||
std::string reas = msg["reasoning_content"].str();
|
||||
if (reas.empty()) reas = msg["reasoning"].str();
|
||||
if (reas.empty()) reas = msg["thought"].str();
|
||||
if (!reas.empty()) out.reasoning = std::move(reas);
|
||||
const Json &tcs = msg["tool_calls"];
|
||||
if (tcs.is_array()) {
|
||||
for (auto &tc : tcs.aval) {
|
||||
if (!tc.is_object()) continue;
|
||||
ToolCall t;
|
||||
t.id = tc["id"].str();
|
||||
const Json &fn = tc["function"];
|
||||
if (fn.is_object()) {
|
||||
t.name = fn["name"].str();
|
||||
t.arguments = fn["arguments"].str("{}");
|
||||
}
|
||||
out.calls.push_back(std::move(t));
|
||||
}
|
||||
if (root.contains("usage") && root["usage"].is_object()) {
|
||||
auto u = root["usage"];
|
||||
usage.prompt_tokens = json_int(u, "prompt_tokens");
|
||||
usage.completion_tokens = json_int(u, "completion_tokens");
|
||||
usage.total_tokens = json_int(u, "total_tokens");
|
||||
if (u.contains("prompt_tokens_details") && u["prompt_tokens_details"].is_object())
|
||||
usage.cached_tokens = json_int(u["prompt_tokens_details"], "cached_tokens");
|
||||
if (usage.cached_tokens == 0)
|
||||
usage.cached_tokens = json_int(u, "cached_tokens");
|
||||
}
|
||||
usage.model = json_str(root, "model");
|
||||
return true;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
const Json &u = root["usage"];
|
||||
if (u.is_object()) {
|
||||
usage.prompt_tokens = u["prompt_tokens"].as_int();
|
||||
usage.completion_tokens = u["completion_tokens"].as_int();
|
||||
usage.total_tokens = u["total_tokens"].as_int();
|
||||
const Json &ptd = u["prompt_tokens_details"];
|
||||
if (ptd.is_object()) {
|
||||
usage.cached_tokens = ptd["cached_tokens"].as_int();
|
||||
}
|
||||
if (usage.cached_tokens == 0) {
|
||||
usage.cached_tokens = u["cached_tokens"].as_int();
|
||||
}
|
||||
}
|
||||
usage.model = root["model"].str();
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user