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