CoolFace
Modelpublic

Felipe97/llama-cpp-compiled

sourceHugging Faceupdated 2d agoView on Hugging Face
0likes1.1kdownloads
json-schema.cpp515 linesDownload Raw Back to common
1#include "json-schema.h"2#include "common.h"3 4#include <cmath>5#include <map>6#include <stdexcept>7#include <string>8#include <unordered_set>9#include <utility>10#include <vector>11 12class common_chat_schema_builder {13    const common_json &      root_;14    common_chat_schema_document & doc_;15 16    // the targets built here, moved into doc_ once the whole schema is built17    std::map<std::string, common_chat_schema_ptr> refs_;18 19    // ref nodes get their target once every $ref is built, a cycle would otherwise need it too early20    std::vector<common_chat_schema_ref *> pending_;21 22    [[noreturn]] static void fail(const std::string & path, const std::string & msg) {23        throw std::runtime_error("JSON schema error at " + path + ": " + msg);24    }25 26    static int get_count(const common_json & schema, const std::string & key, const std::string & path, int def) {27        if (!schema.contains(key)) {28            return def;29        }30        const common_json & value = schema.at(key);31        if (!value.is_number_integer() || value.get<int>() < 0) {32            fail(path, key + " must be a non-negative integer");33        }34        return value.get<int>();35    }36 37    // a fractional bound is rounded inwards, towards the integers it still admits38    static int64_t get_bound(const common_json & schema, const std::string & key, const std::string & path, bool round_up) {39        const common_json & value = schema.at(key);40        if (value.is_number_integer()) {41            return value.get<int64_t>();42        }43        if (!value.is_number()) {44            fail(path, key + " must be a number");45        }46        double d = value.get<double>();47        return (int64_t) (round_up ? std::ceil(d) : std::floor(d));48    }49 50    static common_chat_schema::string_format get_format(const common_json & schema, const std::string & path) {51        if (!schema.contains("format")) {52            return common_chat_schema::FORMAT_NONE;53        }54        const common_json & value = schema.at("format");55        if (!value.is_string()) {56            fail(path, "format must be a string");57        }58        std::string format = value.get<std::string>();59        if (format == "date") {60            return common_chat_schema::FORMAT_DATE;61        }62        if (format == "time") {63            return common_chat_schema::FORMAT_TIME;64        }65        if (format == "date-time") {66            return common_chat_schema::FORMAT_DATE_TIME;67        }68        if (format == "uuid" || (format.size() == 5 && format.compare(0, 4, "uuid") == 0 && format[4] >= '1' && format[4] <= '5')) {69            return common_chat_schema::FORMAT_UUID;70        }71        return common_chat_schema::FORMAT_NONE;72    }73 74    const common_json & resolve_ref(const std::string & ref, const std::string & path) {75        const common_json * target = &root_;76        auto tokens = string_split(ref.substr(1), "/");77        for (size_t i = 1; i < tokens.size(); i++) {78            const std::string & sel = tokens[i];79            if (target->is_object() && target->contains(sel)) {80                target = &target->at(sel);81            } else if (target->is_array()) {82                size_t idx;83                try {84                    idx = std::stoull(sel);85                } catch (const std::logic_error &) {86                    idx = target->size();87                }88                if (idx >= target->size()) {89                    fail(path, "cannot resolve $ref " + ref + ", " + sel + " is out of range");90                }91                target = &target->at(idx);92            } else {93                fail(path, "cannot resolve $ref " + ref + ", " + sel + " not found");94            }95        }96        return *target;97    }98 99    common_chat_schema_ptr build_ref(const common_json & value, const std::string & path) {100        if (!value.is_string()) {101            fail(path, "$ref must be a string");102        }103        std::string ref = value.get<std::string>();104        if (ref.compare(0, 2, "#/") != 0) {105            fail(path, "unsupported $ref " + ref + ", only references into the same document are supported");106        }107        if (refs_.find(ref) == refs_.end()) {108            // reserve the key first, so that a cycle back to this $ref stops here109            refs_[ref] = nullptr;110            refs_[ref] = build_node(resolve_ref(ref, path), ref);111        }112        auto node = std::make_unique<common_chat_schema_ref>(ref);113        pending_.push_back(node.get());114        return node;115    }116 117    template <typename T>118    common_chat_schema_ptr build_alternatives(const common_json & alts, const std::string & path) {119        if (!alts.is_array()) {120            fail(path, "must be an array of schemas");121        }122        if (alts.empty()) {123            fail(path, "must not be empty");124        }125        auto node = std::make_unique<T>();126        size_t i = 0;127        for (const auto & alt : alts) {128            node->children.push_back(build_node(alt, path + "/" + std::to_string(i++)));129        }130        return node;131    }132 133    common_chat_schema_ptr build_object(const common_json & schema, const std::string & path) {134        auto node = std::make_unique<common_chat_schema_object>();135 136        std::unordered_set<std::string> required;137        if (schema.contains("required") && schema.at("required").is_array()) {138            for (const auto & name : schema.at("required")) {139                if (name.is_string()) {140                    required.insert(name.get<std::string>());141                }142            }143        }144 145        if (schema.contains("properties")) {146            const common_json & properties = schema.at("properties");147            if (!properties.is_object()) {148                fail(path, "properties must be an object");149            }150            for (const auto & [name, prop] : properties.items()) {151                node->properties.push_back({name, build_node(prop, path + "/properties/" + name), required.count(name) > 0});152            }153        }154 155        if (schema.contains("additionalProperties")) {156            const common_json & additional = schema.at("additionalProperties");157            if (additional.is_boolean()) {158                if (additional.get<bool>()) {159                    node->additional_properties = std::make_unique<common_chat_schema_any>();160                }161            } else if (additional.is_object()) {162                node->additional_properties = build_node(additional, path + "/additionalProperties");163            } else {164                fail(path, "additionalProperties must be a boolean or a schema");165            }166        } else if (!schema.contains("properties")) {167            // {"type": "object"} on its own accepts any object168            node->additional_properties = std::make_unique<common_chat_schema_any>();169        }170 171        return node;172    }173 174    common_chat_schema_ptr build_array(const common_json & schema, const std::string & path) {175        auto node = std::make_unique<common_chat_schema_array>();176        if (schema.contains("items") || schema.contains("prefixItems")) {177            // "items" wins when both are present; as in the converter, a schema instead of an array is the item schema178            const std::string key = schema.contains("items") ? "items" : "prefixItems";179            const common_json & items = schema.at(key);180            if (items.is_array()) {181                auto tuple = std::make_unique<common_chat_schema_tuple>();182                size_t i = 0;183                for (const auto & item : items) {184                    tuple->items.push_back(build_node(item, path + "/" + key + "/" + std::to_string(i++)));185                }186                return tuple;187            }188            node->items = build_node(items, path + "/" + key);189        } else {190            node->items = std::make_unique<common_chat_schema_any>();191        }192        node->min_items = get_count(schema, "minItems", path, 0);193        node->max_items = get_count(schema, "maxItems", path, -1);194        return node;195    }196 197    common_chat_schema_ptr build_string(const common_json & schema, const std::string & path) {198        auto node = std::make_unique<common_chat_schema_string>();199        if (schema.contains("pattern")) {200            const common_json & pattern = schema.at("pattern");201            if (!pattern.is_string()) {202                fail(path, "pattern must be a string");203            }204            node->pattern = pattern.get<std::string>();205        }206        node->format     = get_format(schema, path);207        node->min_length = get_count(schema, "minLength", path, 0);208        node->max_length = get_count(schema, "maxLength", path, -1);209        return node;210    }211 212    common_chat_schema_ptr build_integer(const common_json & schema, const std::string & path) {213        auto node = std::make_unique<common_chat_schema_integer>();214        if (schema.contains("minimum")) {215            node->minimum = get_bound(schema, "minimum", path, /* round_up */ true);216        } else if (schema.contains("exclusiveMinimum")) {217            node->minimum = get_bound(schema, "exclusiveMinimum", path, /* round_up */ false) + 1;218        }219        if (schema.contains("maximum")) {220            node->maximum = get_bound(schema, "maximum", path, /* round_up */ false);221        } else if (schema.contains("exclusiveMaximum")) {222            node->maximum = get_bound(schema, "exclusiveMaximum", path, /* round_up */ true) - 1;223        }224        return node;225    }226 227    common_chat_schema_ptr build_node(const common_json & schema, const std::string & path) {228        if (!schema.is_object()) {229            fail(path, "schema must be an object");230        }231        if (schema.contains("$ref")) {232            return build_ref(schema.at("$ref"), path);233        }234        if (schema.contains("oneOf") || schema.contains("anyOf")) {235            const std::string key = schema.contains("oneOf") ? "oneOf" : "anyOf";236            return build_alternatives<common_chat_schema_any_of>(schema.at(key), path + "/" + key);237        }238 239        common_json type;240        if (schema.contains("type")) {241            type = schema.at("type");242        }243        if (type.is_array()) {244            // {"type": ["a", "b"], ...} is {"anyOf": [{"type": "a", ...}, {"type": "b", ...}]}245            if (type.empty()) {246                fail(path, "type must not be empty");247            }248            auto node = std::make_unique<common_chat_schema_any_of>();249            size_t i = 0;250            for (const auto & t : type) {251                common_json alt = schema;252                alt["type"] = t;253                node->children.push_back(build_node(alt, path + "/type/" + std::to_string(i++)));254            }255            return node;256        }257        if (schema.contains("const")) {258            return std::make_unique<common_chat_schema_const>(schema.at("const"));259        }260        if (schema.contains("enum")) {261            const common_json & values = schema.at("enum");262            if (!values.is_array() || values.empty()) {263                fail(path, "enum must be a non-empty array");264            }265            auto node = std::make_unique<common_chat_schema_enum>();266            for (const auto & value : values) {267                node->values.push_back(value);268            }269            return node;270        }271        if (!type.is_null() && !type.is_string()) {272            fail(path, "type must be a string or an array of strings");273        }274 275        const std::string type_name = type.is_string() ? type.get<std::string>() : "";276        const bool has_properties = schema.contains("properties") ||277            (schema.contains("additionalProperties") && schema.at("additionalProperties") != true);278 279        if (type_name.empty()) {280            // without a type the structural keywords decide, in the same order as the converter281            if (has_properties) {282                return build_object(schema, path);283            }284            if (schema.contains("allOf")) {285                return build_alternatives<common_chat_schema_all_of>(schema.at("allOf"), path + "/allOf");286            }287            if (schema.contains("items") || schema.contains("prefixItems")) {288                return build_array(schema, path);289            }290            if (schema.contains("pattern") || schema.contains("minLength") || schema.contains("maxLength") || get_format(schema, path) != common_chat_schema::FORMAT_NONE) {291                return build_string(schema, path);292            }293            return std::make_unique<common_chat_schema_any>();294        }295        if (type_name == "object") {296            if (!has_properties && schema.contains("allOf")) {297                return build_alternatives<common_chat_schema_all_of>(schema.at("allOf"), path + "/allOf");298            }299            return build_object(schema, path);300        }301        if (type_name == "string") {302            if (schema.contains("allOf")) {303                return build_alternatives<common_chat_schema_all_of>(schema.at("allOf"), path + "/allOf");304            }305            return build_string(schema, path);306        }307        if (type_name == "array") {308            return build_array(schema, path);309        }310        if (type_name == "integer") {311            return build_integer(schema, path);312        }313        if (type_name == "number") {314            return std::make_unique<common_chat_schema_number>();315        }316        if (type_name == "boolean") {317            return std::make_unique<common_chat_schema_boolean>();318        }319        if (type_name == "null") {320            return std::make_unique<common_chat_schema_null>();321        }322        fail(path, "unrecognized type " + type_name);323    }324 325  public:326    common_chat_schema_builder(const common_json & root, common_chat_schema_document & doc) : root_(root), doc_(doc) {}327 328    common_chat_schema_ptr build() {329        auto node = build_node(root_, "#");330        for (auto & entry : refs_) {331            doc_.refs[entry.first] = std::move(entry.second);332        }333        for (auto * ref : pending_) {334            ref->target = doc_.refs.at(ref->ref).get();335        }336        return node;337    }338};339 340common_chat_schema_document common_chat_schema_from_json(const common_json & schema) {341    common_chat_schema_document doc;342    doc.root = common_chat_schema_builder(schema, doc).build();343    return doc;344}345 346static common_chat_schema::value_type json_type(const common_json & value) {347    if (value.is_null()) {348        return common_chat_schema::TYPE_NULL;349    }350    if (value.is_boolean()) {351        return common_chat_schema::TYPE_BOOLEAN;352    }353    if (value.is_number_integer()) {354        return common_chat_schema::TYPE_INTEGER;355    }356    if (value.is_number()) {357        return common_chat_schema::TYPE_NUMBER;358    }359    if (value.is_string()) {360        return common_chat_schema::TYPE_STRING;361    }362    if (value.is_array()) {363        return common_chat_schema::TYPE_ARRAY;364    }365    return common_chat_schema::TYPE_OBJECT;366}367 368static common_chat_schema::type_set value_types_impl(const common_chat_schema & s, std::unordered_set<const common_chat_schema *> & visited) {369    switch (s.kind()) {370        case common_chat_schema::KIND_ANY:371            return common_chat_schema::type_set::all();372        case common_chat_schema::KIND_NULL:373            return { common_chat_schema::TYPE_NULL };374        case common_chat_schema::KIND_BOOLEAN:375            return { common_chat_schema::TYPE_BOOLEAN };376        case common_chat_schema::KIND_NUMBER:377            return { common_chat_schema::TYPE_NUMBER, common_chat_schema::TYPE_INTEGER };378        case common_chat_schema::KIND_INTEGER:379            return { common_chat_schema::TYPE_INTEGER };380        case common_chat_schema::KIND_STRING:381            return { common_chat_schema::TYPE_STRING };382        case common_chat_schema::KIND_ARRAY:383        case common_chat_schema::KIND_TUPLE:384            return { common_chat_schema::TYPE_ARRAY };385        case common_chat_schema::KIND_OBJECT:386            return { common_chat_schema::TYPE_OBJECT };387        case common_chat_schema::KIND_CONST:388            return { json_type(static_cast<const common_chat_schema_const &>(s).value) };389        case common_chat_schema::KIND_ENUM: {390            common_chat_schema::type_set types;391            for (const auto & value : static_cast<const common_chat_schema_enum &>(s).values) {392                types.add(json_type(value));393            }394            return types;395        }396        case common_chat_schema::KIND_REF: {397            const auto * target = static_cast<const common_chat_schema_ref &>(s).target;398            if (!target || !visited.insert(target).second) {399                // a cycle contributes no type, to be safe400                return {};401            }402            auto types = value_types_impl(*target, visited);403            visited.erase(target);404            return types;405        }406        case common_chat_schema::KIND_ANY_OF: {407            common_chat_schema::type_set types;408            for (const auto & child : static_cast<const common_chat_schema_any_of &>(s).children) {409                types |= value_types_impl(*child, visited);410            }411            return types;412        }413        case common_chat_schema::KIND_ALL_OF: {414            auto types = common_chat_schema::type_set::all();415            for (const auto & child : static_cast<const common_chat_schema_all_of &>(s).children) {416                types &= value_types_impl(*child, visited);417            }418            return types;419        }420    }421    return {};422}423 424common_chat_schema::type_set common_chat_schema::value_types() const {425    std::unordered_set<const common_chat_schema *> visited;426    return value_types_impl(*this, visited);427}428 429static bool may_be_string_impl(const common_chat_schema & s, std::unordered_set<const common_chat_schema *> & visited) {430    switch (s.kind()) {431        case common_chat_schema::KIND_STRING:432            return true;433        case common_chat_schema::KIND_CONST:434            return static_cast<const common_chat_schema_const &>(s).value.is_string();435        case common_chat_schema::KIND_ENUM:436            for (const auto & v : static_cast<const common_chat_schema_enum &>(s).values) {437                if (v.is_string()) {438                    return true;439                }440            }441            return false;442        case common_chat_schema::KIND_REF: {443            // a cycle is taken as not a string, to be safe444            const auto * target = static_cast<const common_chat_schema_ref &>(s).target;445            if (!target || !visited.insert(target).second) {446                return false;447            }448            bool result = may_be_string_impl(*target, visited);449            visited.erase(target);450            return result;451        }452        case common_chat_schema::KIND_ANY_OF:453            for (const auto & child : static_cast<const common_chat_schema_any_of &>(s).children) {454                if (may_be_string_impl(*child, visited)) {455                    return true;456                }457            }458            return false;459        case common_chat_schema::KIND_ALL_OF: {460            // every child must allow a string, an any child constrains nothing461            bool any_string = false;462            for (const auto & child : static_cast<const common_chat_schema_all_of &>(s).children) {463                if (child->kind() == common_chat_schema::KIND_ANY) {464                    continue;465                }466                if (!may_be_string_impl(*child, visited)) {467                    return false;468                }469                any_string = true;470            }471            return any_string;472        }473        default:474            return false;475    }476}477 478bool common_chat_schema::may_be_string() const {479    std::unordered_set<const common_chat_schema *> visited;480    return may_be_string_impl(*this, visited);481}482 483const char * common_chat_schema::kind_name(node_kind kind) {484    switch (kind) {485        case KIND_ANY:     return "any";486        case KIND_REF:     return "ref";487        case KIND_ANY_OF:  return "anyOf";488        case KIND_ALL_OF:  return "allOf";489        case KIND_CONST:   return "const";490        case KIND_ENUM:    return "enum";491        case KIND_NULL:    return "null";492        case KIND_BOOLEAN: return "boolean";493        case KIND_NUMBER:  return "number";494        case KIND_INTEGER: return "integer";495        case KIND_STRING:  return "string";496        case KIND_ARRAY:   return "array";497        case KIND_TUPLE:   return "tuple";498        case KIND_OBJECT:  return "object";499    }500    return "?";501}502 503const char * common_chat_schema::type_name(value_type type) {504    switch (type) {505        case TYPE_NULL:    return "null";506        case TYPE_BOOLEAN: return "boolean";507        case TYPE_NUMBER:  return "number";508        case TYPE_INTEGER: return "integer";509        case TYPE_STRING:  return "string";510        case TYPE_ARRAY:   return "array";511        case TYPE_OBJECT:  return "object";512    }513    return "?";514}515