char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
67672dc5b
char/aliceai-t5-moe
1from __future__import annotations 2 3import inspect 4import json 5import re 6from copy import copy 7from enum import Enum 8from inspect import getdoc ,isclass 9from typing import TYPE_CHECKING ,Any ,Callable ,Optional ,Union ,get_args ,get_origin ,get_type_hints 10 11from docstring_parser import parse 12from pydantic import BaseModel ,create_model 13 14if TYPE_CHECKING : 15from types import GenericAlias 16else : 17# python 3.8 compat 18from typing import _GenericAlias as GenericAlias 19 20# TODO: fix this 21# pyright: reportAttributeAccessIssue=information 22 23 24class PydanticDataType (Enum ): 25""" 26Defines the data types supported by the grammar_generator. 27 28Attributes: 29STRING (str): Represents a string data type. 30BOOLEAN (str): Represents a boolean data type. 31INTEGER (str): Represents an integer data type. 32FLOAT (str): Represents a float data type. 33OBJECT (str): Represents an object data type. 34ARRAY (str): Represents an array data type. 35ENUM (str): Represents an enum data type. 36CUSTOM_CLASS (str): Represents a custom class data type. 37""" 38 39STRING = "string" 40TRIPLE_QUOTED_STRING = "triple_quoted_string" 41MARKDOWN_CODE_BLOCK = "markdown_code_block" 42BOOLEAN = "boolean" 43INTEGER = "integer" 44FLOAT = "float" 45OBJECT = "object" 46ARRAY = "array" 47ENUM = "enum" 48ANY = "any" 49NULL = "null" 50CUSTOM_CLASS = "custom-class" 51CUSTOM_DICT = "custom-dict" 52SET = "set" 53 54 55def map_pydantic_type_to_gbnf (pydantic_type :type [Any ])-> str : 56origin_type = get_origin (pydantic_type ) 57origin_type = pydantic_type if origin_type is None else origin_type 58 59if isclass (origin_type )and issubclass (origin_type ,str ): 60return PydanticDataType .STRING .value 61elif isclass (origin_type )and issubclass (origin_type ,bool ): 62return PydanticDataType .BOOLEAN .value 63elif isclass (origin_type )and issubclass (origin_type ,int ): 64return PydanticDataType .INTEGER .value 65elif isclass (origin_type )and issubclass (origin_type ,float ): 66return PydanticDataType .FLOAT .value 67elif isclass (origin_type )and issubclass (origin_type ,Enum ): 68return PydanticDataType .ENUM .value 69 70elif isclass (origin_type )and issubclass (origin_type ,BaseModel ): 71return format_model_and_field_name (origin_type .__name__ ) 72elif origin_type is list : 73element_type = get_args (pydantic_type )[0 ] 74return f" { map_pydantic_type_to_gbnf ( element_type ) } -list" 75elif origin_type is set : 76element_type = get_args (pydantic_type )[0 ] 77return f" { map_pydantic_type_to_gbnf ( element_type ) } -set" 78elif origin_type is Union : 79union_types = get_args (pydantic_type ) 80union_rules = [map_pydantic_type_to_gbnf (ut )for ut in union_types ] 81return f"union- { '-or-' . join ( union_rules ) } " 82elif origin_type is Optional : 83element_type = get_args (pydantic_type )[0 ] 84return f"optional- { map_pydantic_type_to_gbnf ( element_type ) } " 85elif isclass (origin_type ): 86return f" { PydanticDataType . CUSTOM_CLASS . value } - { format_model_and_field_name ( origin_type . __name__ ) } " 87elif origin_type is dict : 88key_type ,value_type = get_args (pydantic_type ) 89return f"custom-dict-key-type- { format_model_and_field_name ( map_pydantic_type_to_gbnf ( key_type )) } -value-type- { format_model_and_field_name ( map_pydantic_type_to_gbnf ( value_type )) } " 90else : 91return "unknown" 92 93 94def format_model_and_field_name (model_name :str )-> str : 95parts = re .findall ("[A-Z][^A-Z]*" ,model_name ) 96if not parts :# Check if the list is empty 97return model_name .lower ().replace ("_" ,"-" ) 98return "-" .join (part .lower ().replace ("_" ,"-" )for part in parts ) 99 100 101def generate_list_rule (element_type ): 102""" 103Generate a GBNF rule for a list of a given element type. 104 105:param element_type: The type of the elements in the list (e.g., 'string'). 106:return: A string representing the GBNF rule for a list of the given type. 107""" 108rule_name = f" { map_pydantic_type_to_gbnf ( element_type ) } -list" 109element_rule = map_pydantic_type_to_gbnf (element_type ) 110list_rule = rf' { rule_name } ::= "[" { element_rule } ("," { element_rule } )* "]"' 111return list_rule 112 113 114def get_members_structure (cls ,rule_name ): 115if issubclass (cls ,Enum ): 116# Handle Enum types 117members = [f'"\\" { member . value } \\""' for name ,member in cls .__members__ .items ()] 118return f" { cls . __name__ . lower () } ::= " + " | " .join (members ) 119if cls .__annotations__ and cls .__annotations__ != {}: 120result = f' { rule_name } ::= "{{"' 121# Modify this comprehension 122members = [ 123f' "\\" { name } \\"" ":" { map_pydantic_type_to_gbnf ( param_type ) } ' 124for name ,param_type in get_type_hints (cls ).items () 125if name != "self" 126 ] 127 128result += '"," ' .join (members ) 129result += ' "}"' 130return result 131if rule_name == "custom-class-any" : 132result = f" { rule_name } ::= " 133result += "value" 134return result 135 136init_signature = inspect .signature (cls .__init__ ) 137parameters = init_signature .parameters 138result = f' { rule_name } ::= "{{"' 139# Modify this comprehension too 140members = [ 141f' "\\" { name } \\"" ":" { map_pydantic_type_to_gbnf ( param . annotation ) } ' 142for name ,param in parameters .items () 143if name != "self" and param .annotation != inspect .Parameter .empty 144 ] 145 146result += '", "' .join (members ) 147result += ' "}"' 148return result 149 150 151def regex_to_gbnf (regex_pattern :str )-> str : 152""" 153Translate a basic regex pattern to a GBNF rule. 154Note: This function handles only a subset of simple regex patterns. 155""" 156gbnf_rule = regex_pattern 157 158# Translate common regex components to GBNF 159gbnf_rule = gbnf_rule .replace ("\\d" ,"[0-9]" ) 160gbnf_rule = gbnf_rule .replace ("\\s" ,"[ \t\n]" ) 161 162# Handle quantifiers and other regex syntax that is similar in GBNF 163# (e.g., '*', '+', '?', character classes) 164 165return gbnf_rule 166 167 168def generate_gbnf_integer_rules (max_digit = None ,min_digit = None ): 169""" 170 171Generate GBNF Integer Rules 172 173Generates GBNF (Generalized Backus-Naur Form) rules for integers based on the given maximum and minimum digits. 174 175Parameters: 176max_digit (int): The maximum number of digits for the integer. Default is None. 177min_digit (int): The minimum number of digits for the integer. Default is None. 178 179Returns: 180integer_rule (str): The identifier for the integer rule generated. 181additional_rules (list): A list of additional rules generated based on the given maximum and minimum digits. 182 183""" 184additional_rules = [] 185 186# Define the rule identifier based on max_digit and min_digit 187integer_rule = "integer-part" 188if max_digit is not None : 189integer_rule += f"-max { max_digit } " 190if min_digit is not None : 191integer_rule += f"-min { min_digit } " 192 193# Handling Integer Rules 194if max_digit is not None or min_digit is not None : 195# Start with an empty rule part 196integer_rule_part = "" 197 198# Add mandatory digits as per min_digit 199if min_digit is not None : 200integer_rule_part += "[0-9] " * min_digit 201 202# Add optional digits up to max_digit 203if max_digit is not None : 204optional_digits = max_digit - (min_digit if min_digit is not None else 0 ) 205integer_rule_part += "" .join (["[0-9]? " for _ in range (optional_digits )]) 206 207# Trim the rule part and append it to additional rules 208integer_rule_part = integer_rule_part .strip () 209if integer_rule_part : 210additional_rules .append (f" { integer_rule } ::= { integer_rule_part } " ) 211 212return integer_rule ,additional_rules 213 214 215def generate_gbnf_float_rules (max_digit = None ,min_digit = None ,max_precision = None ,min_precision = None ): 216""" 217Generate GBNF float rules based on the given constraints. 218 219:param max_digit: Maximum number of digits in the integer part (default: None) 220:param min_digit: Minimum number of digits in the integer part (default: None) 221:param max_precision: Maximum number of digits in the fractional part (default: None) 222:param min_precision: Minimum number of digits in the fractional part (default: None) 223:return: A tuple containing the float rule and additional rules as a list 224 225Example Usage: 226max_digit = 3 227min_digit = 1 228max_precision = 2 229min_precision = 1 230generate_gbnf_float_rules(max_digit, min_digit, max_precision, min_precision) 231 232Output: 233('float-3-1-2-1', ['integer-part-max3-min1 ::= [0-9] [0-9] [0-9]?', 'fractional-part-max2-min1 ::= [0-9] [0-9]?', 'float-3-1-2-1 ::= integer-part-max3-min1 "." fractional-part-max2-min 234*1']) 235 236Note: 237GBNF stands for Generalized Backus-Naur Form, which is a notation technique to specify the syntax of programming languages or other formal grammars. 238""" 239additional_rules = [] 240 241# Define the integer part rule 242integer_part_rule = ( 243"integer-part" 244+ (f"-max { max_digit } " if max_digit is not None else "" ) 245+ (f"-min { min_digit } " if min_digit is not None else "" ) 246 ) 247 248# Define the fractional part rule based on precision constraints 249fractional_part_rule = "fractional-part" 250fractional_rule_part = "" 251if max_precision is not None or min_precision is not None : 252fractional_part_rule += (f"-max { max_precision } " if max_precision is not None else "" )+ ( 253f"-min { min_precision } " if min_precision is not None else "" 254 ) 255# Minimum number of digits 256fractional_rule_part = "[0-9]" * (min_precision if min_precision is not None else 1 ) 257# Optional additional digits 258fractional_rule_part += "" .join ( 259 [" [0-9]?" ]* ((max_precision - ( 260min_precision if min_precision is not None else 1 ))if max_precision is not None else 0 ) 261 ) 262additional_rules .append (f" { fractional_part_rule } ::= { fractional_rule_part } " ) 263 264# Define the float rule 265float_rule = f"float- { max_digit if max_digit is not None else 'X' } - { min_digit if min_digit is not None else 'X' } - { max_precision if max_precision is not None else 'X' } - { min_precision if min_precision is not None else 'X' } " 266additional_rules .append (f' { float_rule } ::= { integer_part_rule } "." { fractional_part_rule } ' ) 267 268# Generating the integer part rule definition, if necessary 269if max_digit is not None or min_digit is not None : 270integer_rule_part = "[0-9]" 271if min_digit is not None and min_digit > 1 : 272integer_rule_part += " [0-9]" * (min_digit - 1 ) 273if max_digit is not None : 274integer_rule_part += "" .join ([" [0-9]?" ]* (max_digit - (min_digit if min_digit is not None else 1 ))) 275additional_rules .append (f" { integer_part_rule } ::= { integer_rule_part . strip () } " ) 276 277return float_rule ,additional_rules 278 279 280def generate_gbnf_rule_for_type ( 281model_name ,field_name ,field_type ,is_optional ,processed_models ,created_rules ,field_info = None 282)-> tuple [str ,list [str ]]: 283""" 284Generate GBNF rule for a given field type. 285 286:param model_name: Name of the model. 287 288:param field_name: Name of the field. 289:param field_type: Type of the field. 290:param is_optional: Whether the field is optional. 291:param processed_models: List of processed models. 292:param created_rules: List of created rules. 293:param field_info: Additional information about the field (optional). 294 295:return: Tuple containing the GBNF type and a list of additional rules. 296:rtype: tuple[str, list] 297""" 298rules = [] 299 300field_name = format_model_and_field_name (field_name ) 301gbnf_type = map_pydantic_type_to_gbnf (field_type ) 302 303origin_type = get_origin (field_type ) 304origin_type = field_type if origin_type is None else origin_type 305 306if isclass (origin_type )and issubclass (origin_type ,BaseModel ): 307nested_model_name = format_model_and_field_name (field_type .__name__ ) 308nested_model_rules ,_ = generate_gbnf_grammar (field_type ,processed_models ,created_rules ) 309rules .extend (nested_model_rules ) 310gbnf_type ,rules = nested_model_name ,rules 311elif isclass (origin_type )and issubclass (origin_type ,Enum ): 312enum_values = [f'"\\" { e . value } \\""' for e in field_type ]# Adding escaped quotes 313enum_rule = f" { model_name } - { field_name } ::= { ' | ' . join ( enum_values ) } " 314rules .append (enum_rule ) 315gbnf_type ,rules = model_name + "-" + field_name ,rules 316elif origin_type is list :# Array 317element_type = get_args (field_type )[0 ] 318element_rule_name ,additional_rules = generate_gbnf_rule_for_type ( 319model_name ,f" { field_name } -element" ,element_type ,is_optional ,processed_models ,created_rules 320 ) 321rules .extend (additional_rules ) 322array_rule = f""" { model_name } - { field_name } ::= "[" ws { element_rule_name } ("," ws { element_rule_name } )* "]" """ 323rules .append (array_rule ) 324gbnf_type ,rules = model_name + "-" + field_name ,rules 325 326elif origin_type is set :# Array 327element_type = get_args (field_type )[0 ] 328element_rule_name ,additional_rules = generate_gbnf_rule_for_type ( 329model_name ,f" { field_name } -element" ,element_type ,is_optional ,processed_models ,created_rules 330 ) 331rules .extend (additional_rules ) 332array_rule = f""" { model_name } - { field_name } ::= "[" ws { element_rule_name } ("," ws { element_rule_name } )* "]" """ 333rules .append (array_rule ) 334gbnf_type ,rules = model_name + "-" + field_name ,rules 335 336elif gbnf_type .startswith ("custom-class-" ): 337rules .append (get_members_structure (field_type ,gbnf_type )) 338elif gbnf_type .startswith ("custom-dict-" ): 339key_type ,value_type = get_args (field_type ) 340 341additional_key_type ,additional_key_rules = generate_gbnf_rule_for_type ( 342model_name ,f" { field_name } -key-type" ,key_type ,is_optional ,processed_models ,created_rules 343 ) 344additional_value_type ,additional_value_rules = generate_gbnf_rule_for_type ( 345model_name ,f" { field_name } -value-type" ,value_type ,is_optional ,processed_models ,created_rules 346 ) 347gbnf_type = rf' { gbnf_type } ::= "{{" ( { additional_key_type } ": " { additional_value_type } ("," "\n" ws { additional_key_type } ":" { additional_value_type } )* )? "}}" ' 348 349rules .extend (additional_key_rules ) 350rules .extend (additional_value_rules ) 351elif gbnf_type .startswith ("union-" ): 352union_types = get_args (field_type ) 353union_rules = [] 354 355for union_type in union_types : 356if isinstance (union_type ,GenericAlias ): 357union_gbnf_type ,union_rules_list = generate_gbnf_rule_for_type ( 358model_name ,field_name ,union_type ,False ,processed_models ,created_rules 359 ) 360union_rules .append (union_gbnf_type ) 361rules .extend (union_rules_list ) 362 363elif not issubclass (union_type ,type (None )): 364union_gbnf_type ,union_rules_list = generate_gbnf_rule_for_type ( 365model_name ,field_name ,union_type ,False ,processed_models ,created_rules 366 ) 367union_rules .append (union_gbnf_type ) 368rules .extend (union_rules_list ) 369 370# Defining the union grammar rule separately 371if len (union_rules )== 1 : 372union_grammar_rule = f" { model_name } - { field_name } -optional ::= { ' | ' . join ( union_rules ) } | null" 373else : 374union_grammar_rule = f" { model_name } - { field_name } -union ::= { ' | ' . join ( union_rules ) } " 375rules .append (union_grammar_rule ) 376if len (union_rules )== 1 : 377gbnf_type = f" { model_name } - { field_name } -optional" 378else : 379gbnf_type = f" { model_name } - { field_name } -union" 380elif isclass (origin_type )and issubclass (origin_type ,str ): 381if field_info and hasattr (field_info ,"json_schema_extra" )and field_info .json_schema_extra is not None : 382triple_quoted_string = field_info .json_schema_extra .get ("triple_quoted_string" ,False ) 383markdown_string = field_info .json_schema_extra .get ("markdown_code_block" ,False ) 384 385gbnf_type = PydanticDataType .TRIPLE_QUOTED_STRING .value if triple_quoted_string else PydanticDataType .STRING .value 386gbnf_type = PydanticDataType .MARKDOWN_CODE_BLOCK .value if markdown_string else gbnf_type 387 388elif field_info and hasattr (field_info ,"pattern" ): 389# Convert regex pattern to grammar rule 390regex_pattern = field_info .regex .pattern 391gbnf_type = f"pattern- { field_name } ::= { regex_to_gbnf ( regex_pattern ) } " 392else : 393gbnf_type = PydanticDataType .STRING .value 394 395elif ( 396isclass (origin_type ) 397and issubclass (origin_type ,float ) 398and field_info 399and hasattr (field_info ,"json_schema_extra" ) 400and field_info .json_schema_extra is not None 401 ): 402# Retrieve precision attributes for floats 403max_precision = ( 404field_info .json_schema_extra .get ("max_precision" )if field_info and hasattr (field_info , 405"json_schema_extra" )else None 406 ) 407min_precision = ( 408field_info .json_schema_extra .get ("min_precision" )if field_info and hasattr (field_info , 409"json_schema_extra" )else None 410 ) 411max_digits = field_info .json_schema_extra .get ("max_digit" )if field_info and hasattr (field_info , 412"json_schema_extra" )else None 413min_digits = field_info .json_schema_extra .get ("min_digit" )if field_info and hasattr (field_info , 414"json_schema_extra" )else None 415 416# Generate GBNF rule for float with given attributes 417gbnf_type ,rules = generate_gbnf_float_rules ( 418max_digit = max_digits ,min_digit = min_digits ,max_precision = max_precision ,min_precision = min_precision 419 ) 420 421elif ( 422isclass (origin_type ) 423and issubclass (origin_type ,int ) 424and field_info 425and hasattr (field_info ,"json_schema_extra" ) 426and field_info .json_schema_extra is not None 427 ): 428# Retrieve digit attributes for integers 429max_digits = field_info .json_schema_extra .get ("max_digit" )if field_info and hasattr (field_info , 430"json_schema_extra" )else None 431min_digits = field_info .json_schema_extra .get ("min_digit" )if field_info and hasattr (field_info , 432"json_schema_extra" )else None 433 434# Generate GBNF rule for integer with given attributes 435gbnf_type ,rules = generate_gbnf_integer_rules (max_digit = max_digits ,min_digit = min_digits ) 436else : 437gbnf_type ,rules = gbnf_type , [] 438 439return gbnf_type ,rules 440 441 442def generate_gbnf_grammar (model :type [BaseModel ],processed_models :set [type [BaseModel ]],created_rules :dict [str ,list [str ]])-> tuple [list [str ],bool ]: 443""" 444 445Generate GBnF Grammar 446 447Generates a GBnF grammar for a given model. 448 449:param model: A Pydantic model class to generate the grammar for. Must be a subclass of BaseModel. 450:param processed_models: A set of already processed models to prevent infinite recursion. 451:param created_rules: A dict containing already created rules to prevent duplicates. 452:return: A list of GBnF grammar rules in string format. And two booleans indicating if an extra markdown or triple quoted string is in the grammar. 453Example Usage: 454``` 455model = MyModel 456processed_models = set() 457created_rules = dict() 458 459gbnf_grammar = generate_gbnf_grammar(model, processed_models, created_rules) 460``` 461""" 462if model in processed_models : 463return [],False 464 465processed_models .add (model ) 466model_name = format_model_and_field_name (model .__name__ ) 467 468if not issubclass (model ,BaseModel ): 469# For non-Pydantic classes, generate model_fields from __annotations__ or __init__ 470if hasattr (model ,"__annotations__" )and model .__annotations__ : 471model_fields = {name : (typ , ...)for name ,typ in get_type_hints (model ).items ()} 472else : 473init_signature = inspect .signature (model .__init__ ) 474parameters = init_signature .parameters 475model_fields = {name : (param .annotation ,param .default )for name ,param in parameters .items ()if 476name != "self" } 477else : 478# For Pydantic models, use model_fields and check for ellipsis (required fields) 479model_fields = get_type_hints (model ) 480 481model_rule_parts = [] 482nested_rules = [] 483has_markdown_code_block = False 484has_triple_quoted_string = False 485look_for_markdown_code_block = False 486look_for_triple_quoted_string = False 487for field_name ,field_info in model_fields .items (): 488if not issubclass (model ,BaseModel ): 489field_type ,default_value = field_info 490# Check if the field is optional (not required) 491is_optional = (default_value is not inspect .Parameter .empty )and (default_value is not Ellipsis ) 492else : 493field_type = field_info 494field_info = model .model_fields [field_name ] 495is_optional = field_info .is_required is False and get_origin (field_type )is Optional 496rule_name ,additional_rules = generate_gbnf_rule_for_type ( 497model_name ,format_model_and_field_name (field_name ),field_type ,is_optional ,processed_models , 498created_rules ,field_info 499 ) 500look_for_markdown_code_block = True if rule_name == "markdown_code_block" else False 501look_for_triple_quoted_string = True if rule_name == "triple_quoted_string" else False 502if not look_for_markdown_code_block and not look_for_triple_quoted_string : 503if rule_name not in created_rules : 504created_rules [rule_name ]= additional_rules 505model_rule_parts .append (f' ws "\\" { field_name } \\"" ":" ws { rule_name } ' )# Adding escaped quotes 506nested_rules .extend (additional_rules ) 507else : 508has_triple_quoted_string = look_for_triple_quoted_string 509has_markdown_code_block = look_for_markdown_code_block 510 511fields_joined = r' "," "\n" ' .join (model_rule_parts ) 512model_rule = rf' { model_name } ::= "{{" "\n" { fields_joined } "\n" ws "}}"' 513 514has_special_string = False 515if has_triple_quoted_string : 516model_rule += '"\\n" ws "}"' 517model_rule += '"\\n" triple-quoted-string' 518has_special_string = True 519if has_markdown_code_block : 520model_rule += '"\\n" ws "}"' 521model_rule += '"\\n" markdown-code-block' 522has_special_string = True 523all_rules = [model_rule ]+ nested_rules 524 525return all_rules ,has_special_string 526 527 528def generate_gbnf_grammar_from_pydantic_models ( 529models :list [type [BaseModel ]],outer_object_name :str | None = None ,outer_object_content :str | None = None , 530list_of_outputs :bool = False 531)-> str : 532""" 533Generate GBNF Grammar from Pydantic Models. 534 535This method takes a list of Pydantic models and uses them to generate a GBNF grammar string. The generated grammar string can be used for parsing and validating data using the generated 536* grammar. 537 538Args: 539models (list[type[BaseModel]]): A list of Pydantic models to generate the grammar from. 540outer_object_name (str): Outer object name for the GBNF grammar. If None, no outer object will be generated. Eg. "function" for function calling. 541outer_object_content (str): Content for the outer rule in the GBNF grammar. Eg. "function_parameters" or "params" for function calling. 542list_of_outputs (str, optional): Allows a list of output objects 543Returns: 544str: The generated GBNF grammar string. 545 546Examples: 547models = [UserModel, PostModel] 548grammar = generate_gbnf_grammar_from_pydantic(models) 549print(grammar) 550# Output: 551# root ::= UserModel | PostModel 552# ... 553""" 554processed_models :set [type [BaseModel ]]= set () 555all_rules = [] 556created_rules :dict [str ,list [str ]]= {} 557if outer_object_name is None : 558for model in models : 559model_rules ,_ = generate_gbnf_grammar (model ,processed_models ,created_rules ) 560all_rules .extend (model_rules ) 561 562if list_of_outputs : 563root_rule = r'root ::= (" "| "\n") "[" ws grammar-models ("," ws grammar-models)* ws "]"' + "\n" 564else : 565root_rule = r'root ::= (" "| "\n") grammar-models' + "\n" 566root_rule += "grammar-models ::= " + " | " .join ( 567 [format_model_and_field_name (model .__name__ )for model in models ]) 568all_rules .insert (0 ,root_rule ) 569return "\n" .join (all_rules ) 570elif outer_object_name is not None : 571if list_of_outputs : 572root_rule = ( 573rf'root ::= (" "| "\n") "[" ws { format_model_and_field_name ( outer_object_name ) } ("," ws { format_model_and_field_name ( outer_object_name ) } )* ws "]"' 574+ "\n" 575 ) 576else : 577root_rule = f"root ::= { format_model_and_field_name ( outer_object_name ) } \n" 578 579model_rule = ( 580rf' { format_model_and_field_name ( outer_object_name ) } ::= (" "| "\n") "{{" ws "\" { outer_object_name } \"" ":" ws grammar-models' 581 ) 582 583fields_joined = " | " .join ( 584 [rf" { format_model_and_field_name ( model . __name__ ) } -grammar-model" for model in models ]) 585 586grammar_model_rules = f"\ngrammar-models ::= { fields_joined } " 587mod_rules = [] 588for model in models : 589mod_rule = rf" { format_model_and_field_name ( model . __name__ ) } -grammar-model ::= " 590mod_rule += ( 591rf'"\" { model . __name__ } \"" "," ws "\" { outer_object_content } \"" ":" ws { format_model_and_field_name ( model . __name__ ) } ' + "\n" 592 ) 593mod_rules .append (mod_rule ) 594grammar_model_rules += "\n" + "\n" .join (mod_rules ) 595 596for model in models : 597model_rules ,has_special_string = generate_gbnf_grammar (model ,processed_models , 598created_rules ) 599 600if not has_special_string : 601model_rules [0 ]+= r'"\n" ws "}"' 602 603all_rules .extend (model_rules ) 604 605all_rules .insert (0 ,root_rule + model_rule + grammar_model_rules ) 606return "\n" .join (all_rules ) 607 608 609def get_primitive_grammar (grammar ): 610""" 611Returns the needed GBNF primitive grammar for a given GBNF grammar string. 612 613Args: 614grammar (str): The string containing the GBNF grammar. 615 616Returns: 617str: GBNF primitive grammar string. 618""" 619type_list :list [type [object ]]= [] 620if "string-list" in grammar : 621type_list .append (str ) 622if "boolean-list" in grammar : 623type_list .append (bool ) 624if "integer-list" in grammar : 625type_list .append (int ) 626if "float-list" in grammar : 627type_list .append (float ) 628additional_grammar = [generate_list_rule (t )for t in type_list ] 629primitive_grammar = r""" 630boolean ::= "true" | "false" 631null ::= "null" 632string ::= "\"" ( 633[^"\\] | 634"\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) 635)* "\"" ws 636ws ::= ([ \t\n] ws)? 637float ::= ("-"? ([0] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? ws 638 639integer ::= [0-9]+""" 640 641any_block = "" 642if "custom-class-any" in grammar : 643any_block = """ 644value ::= object | array | string | number | boolean | null 645 646object ::= 647"{" ws ( 648string ":" ws value 649("," ws string ":" ws value)* 650)? "}" ws 651 652array ::= 653"[" ws ( 654value 655("," ws value)* 656)? "]" ws 657 658number ::= integer | float""" 659 660markdown_code_block_grammar = "" 661if "markdown-code-block" in grammar : 662markdown_code_block_grammar = r''' 663markdown-code-block ::= opening-triple-ticks markdown-code-block-content closing-triple-ticks 664markdown-code-block-content ::= ( [^`] | "`" [^`] | "`" "`" [^`] )* 665opening-triple-ticks ::= "```" "python" "\n" | "```" "c" "\n" | "```" "cpp" "\n" | "```" "txt" "\n" | "```" "text" "\n" | "```" "json" "\n" | "```" "javascript" "\n" | "```" "css" "\n" | "```" "html" "\n" | "```" "markdown" "\n" 666closing-triple-ticks ::= "```" "\n"''' 667 668if "triple-quoted-string" in grammar : 669markdown_code_block_grammar = r""" 670triple-quoted-string ::= triple-quotes triple-quoted-string-content triple-quotes 671triple-quoted-string-content ::= ( [^'] | "'" [^'] | "'" "'" [^'] )* 672triple-quotes ::= "'''" """ 673return "\n" + "\n" .join (additional_grammar )+ any_block + primitive_grammar + markdown_code_block_grammar 674 675 676def generate_markdown_documentation ( 677pydantic_models :list [type [BaseModel ]],model_prefix = "Model" ,fields_prefix = "Fields" , 678documentation_with_field_description = True 679)-> str : 680""" 681Generate markdown documentation for a list of Pydantic models. 682 683Args: 684pydantic_models (list[type[BaseModel]]): list of Pydantic model classes. 685model_prefix (str): Prefix for the model section. 686fields_prefix (str): Prefix for the fields section. 687documentation_with_field_description (bool): Include field descriptions in the documentation. 688 689Returns: 690str: Generated text documentation. 691""" 692documentation = "" 693pyd_models :list [tuple [type [BaseModel ],bool ]]= [(model ,True )for model in pydantic_models ] 694for model ,add_prefix in pyd_models : 695if add_prefix : 696documentation += f" { model_prefix } : { model . __name__ } \n" 697else : 698documentation += f"Model: { model . __name__ } \n" 699 700# Handling multi-line model description with proper indentation 701 702class_doc = getdoc (model ) 703base_class_doc = getdoc (BaseModel ) 704class_description = class_doc if class_doc and class_doc != base_class_doc else "" 705if class_description != "" : 706documentation += " Description: " 707documentation += format_multiline_description (class_description ,0 )+ "\n" 708 709if add_prefix : 710# Indenting the fields section 711documentation += f" { fields_prefix } :\n" 712else : 713documentation += f" Fields:\n" # noqa: F541 714if isclass (model )and issubclass (model ,BaseModel ): 715for name ,field_type in get_type_hints (model ).items (): 716# if name == "markdown_code_block": 717# continue 718if get_origin (field_type )== list : 719element_type = get_args (field_type )[0 ] 720if isclass (element_type )and issubclass (element_type ,BaseModel ): 721pyd_models .append ((element_type ,False )) 722if get_origin (field_type )== Union : 723element_types = get_args (field_type ) 724for element_type in element_types : 725if isclass (element_type )and issubclass (element_type ,BaseModel ): 726pyd_models .append ((element_type ,False )) 727documentation += generate_field_markdown ( 728name ,field_type ,model ,documentation_with_field_description = documentation_with_field_description 729 ) 730documentation += "\n" 731 732if hasattr (model ,"Config" )and hasattr (model .Config , 733"json_schema_extra" )and "example" in model .Config .json_schema_extra : 734documentation += f" Expected Example Output for { format_model_and_field_name ( model . __name__ ) } :\n" 735json_example = json .dumps (model .Config .json_schema_extra ["example" ]) 736documentation += format_multiline_description (json_example ,2 )+ "\n" 737 738return documentation 739 740 741def generate_field_markdown ( 742field_name :str ,field_type :type [Any ],model :type [BaseModel ],depth = 1 , 743documentation_with_field_description = True 744)-> str : 745""" 746Generate markdown documentation for a Pydantic model field. 747 748Args: 749field_name (str): Name of the field. 750field_type (type[Any]): Type of the field. 751model (type[BaseModel]): Pydantic model class. 752depth (int): Indentation depth in the documentation. 753documentation_with_field_description (bool): Include field descriptions in the documentation. 754 755Returns: 756str: Generated text documentation for the field. 757""" 758indent = " " * depth 759 760field_info = model .model_fields .get (field_name ) 761field_description = field_info .description if field_info and field_info .description else "" 762 763origin_type = get_origin (field_type ) 764origin_type = field_type if origin_type is None else origin_type 765 766if origin_type == list : 767element_type = get_args (field_type )[0 ] 768field_text = f" { indent } { field_name } ( { format_model_and_field_name ( field_type . __name__ ) } of { format_model_and_field_name ( element_type . __name__ ) } )" 769if field_description != "" : 770field_text += ":\n" 771else : 772field_text += "\n" 773elif origin_type == Union : 774element_types = get_args (field_type ) 775types = [] 776for element_type in element_types : 777types .append (format_model_and_field_name (element_type .__name__ )) 778field_text = f" { indent } { field_name } ( { ' or ' . join ( types ) } )" 779if field_description != "" : 780field_text += ":\n" 781else : 782field_text += "\n" 783else : 784field_text = f" { indent } { field_name } ( { format_model_and_field_name ( field_type . __name__ ) } )" 785if field_description != "" : 786field_text += ":\n" 787else : 788field_text += "\n" 789 790if not documentation_with_field_description : 791return field_text 792 793if field_description != "" : 794field_text += f" Description: { field_description } \n" 795 796# Check for and include field-specific examples if available 797if hasattr (model ,"Config" )and hasattr (model .Config , 798"json_schema_extra" )and "example" in model .Config .json_schema_extra : 799field_example = model .Config .json_schema_extra ["example" ].get (field_name ) 800if field_example is not None : 801example_text = f"' { field_example } '" if isinstance (field_example ,str )else field_example 802field_text += f" { indent } Example: { example_text } \n" 803 804if isclass (origin_type )and issubclass (origin_type ,BaseModel ): 805field_text += f" { indent } Details:\n" 806for name ,type_ in get_type_hints (field_type ).items (): 807field_text += generate_field_markdown (name ,type_ ,field_type ,depth + 2 ) 808 809return field_text 810 811 812def format_json_example (example :dict [str ,Any ],depth :int )-> str : 813""" 814Format a JSON example into a readable string with indentation. 815 816Args: 817example (dict): JSON example to be formatted. 818depth (int): Indentation depth. 819 820Returns: 821str: Formatted JSON example string. 822""" 823indent = " " * depth 824formatted_example = "{\n" 825for key ,value in example .items (): 826value_text = f"' { value } '" if isinstance (value ,str )else value 827formatted_example += f" { indent } { key } : { value_text } ,\n" 828formatted_example = formatted_example .rstrip (",\n" )+ "\n" + indent + "}" 829return formatted_example 830 831 832def generate_text_documentation ( 833pydantic_models :list [type [BaseModel ]],model_prefix = "Model" ,fields_prefix = "Fields" , 834documentation_with_field_description = True 835)-> str : 836""" 837Generate text documentation for a list of Pydantic models. 838 839Args: 840pydantic_models (list[type[BaseModel]]): List of Pydantic model classes. 841model_prefix (str): Prefix for the model section. 842fields_prefix (str): Prefix for the fields section. 843documentation_with_field_description (bool): Include field descriptions in the documentation. 844 845Returns: 846str: Generated text documentation. 847""" 848documentation = "" 849pyd_models :list [tuple [type [BaseModel ],bool ]]= [(model ,True )for model in pydantic_models ] 850for model ,add_prefix in pyd_models : 851if add_prefix : 852documentation += f" { model_prefix } : { model . __name__ } \n" 853else : 854documentation += f"Model: { model . __name__ } \n" 855 856# Handling multi-line model description with proper indentation 857 858class_doc = getdoc (model ) 859base_class_doc = getdoc (BaseModel ) 860class_description = class_doc if class_doc and class_doc != base_class_doc else "" 861if class_description != "" : 862documentation += " Description: " 863documentation += "\n" + format_multiline_description (class_description ,2 )+ "\n" 864 865if isclass (model )and issubclass (model ,BaseModel ): 866documentation_fields = "" 867for name ,field_type in get_type_hints (model ).items (): 868# if name == "markdown_code_block": 869# continue 870if get_origin (field_type )== list : 871element_type = get_args (field_type )[0 ] 872if isclass (element_type )and issubclass (element_type ,BaseModel ): 873pyd_models .append ((element_type ,False )) 874if get_origin (field_type )== Union : 875element_types = get_args (field_type ) 876for element_type in element_types : 877if isclass (element_type )and issubclass (element_type ,BaseModel ): 878pyd_models .append ((element_type ,False )) 879documentation_fields += generate_field_text ( 880name ,field_type ,model ,documentation_with_field_description = documentation_with_field_description 881 ) 882if documentation_fields != "" : 883if add_prefix : 884documentation += f" { fields_prefix } :\n { documentation_fields } " 885else : 886documentation += f" Fields:\n { documentation_fields } " 887documentation += "\n" 888 889if hasattr (model ,"Config" )and hasattr (model .Config , 890"json_schema_extra" )and "example" in model .Config .json_schema_extra : 891documentation += f" Expected Example Output for { format_model_and_field_name ( model . __name__ ) } :\n" 892json_example = json .dumps (model .Config .json_schema_extra ["example" ]) 893documentation += format_multiline_description (json_example ,2 )+ "\n" 894 895return documentation 896 897 898def generate_field_text ( 899field_name :str ,field_type :type [Any ],model :type [BaseModel ],depth = 1 , 900documentation_with_field_description = True 901)-> str : 902""" 903Generate text documentation for a Pydantic model field. 904 905Args: 906field_name (str): Name of the field. 907field_type (type[Any]): Type of the field. 908model (type[BaseModel]): Pydantic model class. 909depth (int): Indentation depth in the documentation. 910documentation_with_field_description (bool): Include field descriptions in the documentation. 911 912Returns: 913str: Generated text documentation for the field. 914""" 915indent = " " * depth 916 917field_info = model .model_fields .get (field_name ) 918field_description = field_info .description if field_info and field_info .description else "" 919 920if get_origin (field_type )== list : 921element_type = get_args (field_type )[0 ] 922field_text = f" { indent } { field_name } ( { format_model_and_field_name ( field_type . __name__ ) } of { format_model_and_field_name ( element_type . __name__ ) } )" 923if field_description != "" : 924field_text += ":\n" 925else : 926field_text += "\n" 927elif get_origin (field_type )== Union : 928element_types = get_args (field_type ) 929types = [] 930for element_type in element_types : 931types .append (format_model_and_field_name (element_type .__name__ )) 932field_text = f" { indent } { field_name } ( { ' or ' . join ( types ) } )" 933if field_description != "" : 934field_text += ":\n" 935else : 936field_text += "\n" 937else : 938field_text = f" { indent } { field_name } ( { format_model_and_field_name ( field_type . __name__ ) } )" 939if field_description != "" : 940field_text += ":\n" 941else : 942field_text += "\n" 943 944if not documentation_with_field_description : 945return field_text 946 947if field_description != "" : 948field_text += f" { indent } Description: " + field_description + "\n" 949 950# Check for and include field-specific examples if available 951if hasattr (model ,"Config" )and hasattr (model .Config , 952"json_schema_extra" )and "example" in model .Config .json_schema_extra : 953field_example = model .Config .json_schema_extra ["example" ].get (field_name ) 954if field_example is not None : 955example_text = f"' { field_example } '" if isinstance (field_example ,str )else field_example 956field_text += f" { indent } Example: { example_text } \n" 957 958if isclass (field_type )and issubclass (field_type ,BaseModel ): 959field_text += f" { indent } Details:\n" 960for name ,type_ in get_type_hints (field_type ).items (): 961field_text += generate_field_text (name ,type_ ,field_type ,depth + 2 ) 962 963return field_text 964 965 966def format_multiline_description (description :str ,indent_level :int )-> str : 967""" 968Format a multiline description with proper indentation. 969 970Args: 971description (str): Multiline description. 972indent_level (int): Indentation level. 973 974Returns: 975str: Formatted multiline description. 976""" 977indent = " " * indent_level 978return indent + description .replace ("\n" ,"\n" + indent ) 979 980 981def save_gbnf_grammar_and_documentation ( 982grammar ,documentation ,grammar_file_path = "./grammar.gbnf" ,documentation_file_path = "./grammar_documentation.md" 983): 984""" 985Save GBNF grammar and documentation to specified files. 986 987Args: 988grammar (str): GBNF grammar string. 989documentation (str): Documentation string. 990grammar_file_path (str): File path to save the GBNF grammar. 991documentation_file_path (str): File path to save the documentation. 992 993Returns: 994None 995""" 996try : 997with open (grammar_file_path ,"w" )as file : 998file .write (grammar + get_primitive_grammar (grammar )) 999f"Grammar successfully saved to { grammar_file_path } " ) 1000except IOError as e : 1001f"An error occurred while saving the grammar file: { e } " ) 1002 1003try : 1004with open (documentation_file_path ,"w" )as file : 1005file .write (documentation ) 1006f"Documentation successfully saved to { documentation_file_path } " ) 1007except IOError as e : 1008f"An error occurred while saving the documentation file: { e } " ) 1009 1010 1011def remove_empty_lines (string ): 1012""" 1013Remove empty lines from a string. 1014 1015Args: 1016string (str): Input string. 1017 1018Returns: 1019str: String with empty lines removed. 1020""" 1021lines = string .splitlines () 1022non_empty_lines = [line for line in lines if line .strip ()!= "" ] 1023string_no_empty_lines = "\n" .join (non_empty_lines ) 1024return string_no_empty_lines 1025 1026 1027def generate_and_save_gbnf_grammar_and_documentation ( 1028pydantic_model_list , 1029grammar_file_path = "./generated_grammar.gbnf" , 1030documentation_file_path = "./generated_grammar_documentation.md" , 1031outer_object_name :str | None = None , 1032outer_object_content :str | None = None , 1033model_prefix :str = "Output Model" , 1034fields_prefix :str = "Output Fields" , 1035list_of_outputs :bool = False , 1036documentation_with_field_description = True , 1037): 1038""" 1039Generate GBNF grammar and documentation, and save them to specified files. 1040 1041Args: 1042pydantic_model_list: List of Pydantic model classes. 1043grammar_file_path (str): File path to save the generated GBNF grammar. 1044documentation_file_path (str): File path to save the generated documentation. 1045outer_object_name (str): Outer object name for the GBNF grammar. If None, no outer object will be generated. Eg. "function" for function calling. 1046outer_object_content (str): Content for the outer rule in the GBNF grammar. Eg. "function_parameters" or "params" for function calling. 1047model_prefix (str): Prefix for the model section in the documentation. 1048fields_prefix (str): Prefix for the fields section in the documentation. 1049list_of_outputs (bool): Whether the output is a list of items. 1050documentation_with_field_description (bool): Include field descriptions in the documentation. 1051 1052Returns: 1053None 1054""" 1055documentation = generate_markdown_documentation ( 1056pydantic_model_list ,model_prefix ,fields_prefix , 1057documentation_with_field_description = documentation_with_field_description 1058 ) 1059grammar = generate_gbnf_grammar_from_pydantic_models (pydantic_model_list ,outer_object_name ,outer_object_content , 1060list_of_outputs ) 1061grammar = remove_empty_lines (grammar ) 1062save_gbnf_grammar_and_documentation (grammar ,documentation ,grammar_file_path ,documentation_file_path ) 1063 1064 1065def generate_gbnf_grammar_and_documentation ( 1066pydantic_model_list , 1067outer_object_name :str | None = None , 1068outer_object_content :str | None = None , 1069model_prefix :str = "Output Model" , 1070fields_prefix :str = "Output Fields" , 1071list_of_outputs :bool = False , 1072documentation_with_field_description = True , 1073): 1074""" 1075Generate GBNF grammar and documentation for a list of Pydantic models. 1076 1077Args: 1078pydantic_model_list: List of Pydantic model classes. 1079outer_object_name (str): Outer object name for the GBNF grammar. If None, no outer object will be generated. Eg. "function" for function calling. 1080outer_object_content (str): Content for the outer rule in the GBNF grammar. Eg. "function_parameters" or "params" for function calling. 1081model_prefix (str): Prefix for the model section in the documentation. 1082fields_prefix (str): Prefix for the fields section in the documentation. 1083list_of_outputs (bool): Whether the output is a list of items. 1084documentation_with_field_description (bool): Include field descriptions in the documentation. 1085 1086Returns: 1087tuple: GBNF grammar string, documentation string. 1088""" 1089documentation = generate_markdown_documentation ( 1090copy (pydantic_model_list ),model_prefix ,fields_prefix , 1091documentation_with_field_description = documentation_with_field_description 1092 ) 1093grammar = generate_gbnf_grammar_from_pydantic_models (pydantic_model_list ,outer_object_name ,outer_object_content , 1094list_of_outputs ) 1095grammar = remove_empty_lines (grammar + get_primitive_grammar (grammar )) 1096return grammar ,documentation 1097 1098 1099def generate_gbnf_grammar_and_documentation_from_dictionaries ( 1100dictionaries :list [dict [str ,Any ]], 1101outer_object_name :str | None = None , 1102outer_object_content :str | None = None , 1103model_prefix :str = "Output Model" , 1104fields_prefix :str = "Output Fields" , 1105list_of_outputs :bool = False , 1106documentation_with_field_description = True , 1107): 1108""" 1109Generate GBNF grammar and documentation from a list of dictionaries. 1110 1111Args: 1112dictionaries (list[dict]): List of dictionaries representing Pydantic models. 1113outer_object_name (str): Outer object name for the GBNF grammar. If None, no outer object will be generated. Eg. "function" for function calling. 1114outer_object_content (str): Content for the outer rule in the GBNF grammar. Eg. "function_parameters" or "params" for function calling. 1115model_prefix (str): Prefix for the model section in the documentation. 1116fields_prefix (str): Prefix for the fields section in the documentation. 1117list_of_outputs (bool): Whether the output is a list of items. 1118documentation_with_field_description (bool): Include field descriptions in the documentation. 1119 1120Returns: 1121tuple: GBNF grammar string, documentation string. 1122""" 1123pydantic_model_list = create_dynamic_models_from_dictionaries (dictionaries ) 1124documentation = generate_markdown_documentation ( 1125copy (pydantic_model_list ),model_prefix ,fields_prefix , 1126documentation_with_field_description = documentation_with_field_description 1127 ) 1128grammar = generate_gbnf_grammar_from_pydantic_models (pydantic_model_list ,outer_object_name ,outer_object_content , 1129list_of_outputs ) 1130grammar = remove_empty_lines (grammar + get_primitive_grammar (grammar )) 1131return grammar ,documentation 1132 1133 1134def create_dynamic_model_from_function (func :Callable [...,Any ]): 1135""" 1136Creates a dynamic Pydantic model from a given function's type hints and adds the function as a 'run' method. 1137 1138Args: 1139func (Callable): A function with type hints from which to create the model. 1140 1141Returns: 1142A dynamic Pydantic model class with the provided function as a 'run' method. 1143""" 1144 1145# Get the signature of the function 1146sig = inspect .signature (func ) 1147 1148# Parse the docstring 1149assert func .__doc__ is not None 1150docstring = parse (func .__doc__ ) 1151 1152dynamic_fields = {} 1153param_docs = [] 1154for param in sig .parameters .values (): 1155# Exclude 'self' parameter 1156if param .name == "self" : 1157continue 1158 1159# Assert that the parameter has a type annotation 1160if param .annotation == inspect .Parameter .empty : 1161raise TypeError (f"""Parameter ' { param . name } ' in function ' { getattr ( func , "__name__" , "" ) } ' lacks a type annotation""" ) 1162 1163# Find the parameter's description in the docstring 1164param_doc = next ((d for d in docstring .params if d .arg_name == param .name ),None ) 1165 1166# Assert that the parameter has a description 1167if not param_doc or not param_doc .description : 1168raise ValueError ( 1169f"""Parameter ' { param . name } ' in function ' { getattr ( func , "__name__" , "" ) } ' lacks a description in the docstring""" ) 1170 1171# Add parameter details to the schema 1172param_docs .append ((param .name ,param_doc )) 1173if param .default == inspect .Parameter .empty : 1174default_value = ... 1175else : 1176default_value = param .default 1177dynamic_fields [param .name ]= ( 1178param .annotation if param .annotation != inspect .Parameter .empty else str ,default_value ) 1179# Creating the dynamic model 1180dynamic_model = create_model (f" { getattr ( func , '__name__' ) } " ,** dynamic_fields )# ty: ignore[no-matching-overload] 1181 1182for name ,param_doc in param_docs : 1183dynamic_model .model_fields [name ].description = param_doc .description 1184 1185dynamic_model .__doc__ = docstring .short_description 1186 1187def run_method_wrapper (self ): 1188func_args = {name :getattr (self ,name )for name ,_ in dynamic_fields .items ()} 1189return func (** func_args ) 1190 1191# Adding the wrapped function as a 'run' method 1192setattr (dynamic_model ,"run" ,run_method_wrapper ) 1193return dynamic_model 1194 1195 1196def add_run_method_to_dynamic_model (model :type [BaseModel ],func :Callable [...,Any ]): 1197""" 1198Add a 'run' method to a dynamic Pydantic model, using the provided function. 1199 1200Args: 1201model (type[BaseModel]): Dynamic Pydantic model class. 1202func (Callable): Function to be added as a 'run' method to the model. 1203 1204Returns: 1205type[BaseModel]: Pydantic model class with the added 'run' method. 1206""" 1207 1208def run_method_wrapper (self ): 1209func_args = {name :getattr (self ,name )for name in model .model_fields } 1210return func (** func_args ) 1211 1212# Adding the wrapped function as a 'run' method 1213setattr (model ,"run" ,run_method_wrapper ) 1214 1215return model 1216 1217 1218def create_dynamic_models_from_dictionaries (dictionaries :list [dict [str ,Any ]]): 1219""" 1220Create a list of dynamic Pydantic model classes from a list of dictionaries. 1221 1222Args: 1223dictionaries (list[dict]): List of dictionaries representing model structures. 1224 1225Returns: 1226list[type[BaseModel]]: List of generated dynamic Pydantic model classes. 1227""" 1228dynamic_models = [] 1229for func in dictionaries : 1230model_name = format_model_and_field_name (func .get ("name" ,"" )) 1231dyn_model = convert_dictionary_to_pydantic_model (func ,model_name ) 1232dynamic_models .append (dyn_model ) 1233return dynamic_models 1234 1235 1236def map_grammar_names_to_pydantic_model_class (pydantic_model_list ): 1237output = {} 1238for model in pydantic_model_list : 1239output [format_model_and_field_name (model .__name__ )]= model 1240 1241return output 1242 1243 1244def json_schema_to_python_types (schema ): 1245type_map = { 1246"any" :Any , 1247"string" :str , 1248"number" :float , 1249"integer" :int , 1250"boolean" :bool , 1251"array" :list , 1252 } 1253return type_map [schema ] 1254 1255 1256def list_to_enum (enum_name ,values ): 1257return Enum (enum_name , {value :value for value in values }) 1258 1259 1260def convert_dictionary_to_pydantic_model (dictionary :dict [str ,Any ],model_name :str = "CustomModel" )-> type [Any ]: 1261""" 1262Convert a dictionary to a Pydantic model class. 1263 1264Args: 1265dictionary (dict): Dictionary representing the model structure. 1266model_name (str): Name of the generated Pydantic model. 1267 1268Returns: 1269type[BaseModel]: Generated Pydantic model class. 1270""" 1271fields :dict [str ,Any ]= {} 1272 1273if "properties" in dictionary : 1274for field_name ,field_data in dictionary .get ("properties" , {}).items (): 1275if field_data == "object" : 1276submodel = convert_dictionary_to_pydantic_model (dictionary ,f" { model_name } _ { field_name } " ) 1277fields [field_name ]= (submodel , ...) 1278else : 1279field_type = field_data .get ("type" ,"str" ) 1280 1281if field_data .get ("enum" , []): 1282fields [field_name ]= (list_to_enum (field_name ,field_data .get ("enum" , [])), ...) 1283elif field_type == "array" : 1284items = field_data .get ("items" , {}) 1285if items != {}: 1286array = {"properties" :items } 1287array_type = convert_dictionary_to_pydantic_model (array ,f" { model_name } _ { field_name } _items" ) 1288fields [field_name ]= (list [array_type ], ...)# ty: ignore[invalid-type-form] 1289else : 1290fields [field_name ]= (list , ...) 1291elif field_type == "object" : 1292submodel = convert_dictionary_to_pydantic_model (field_data ,f" { model_name } _ { field_name } " ) 1293fields [field_name ]= (submodel , ...) 1294elif field_type == "required" : 1295required = field_data .get ("enum" , []) 1296for key ,field in fields .items (): 1297if key not in required : 1298optional_type = fields [key ][0 ] 1299fields [key ]= (Optional [optional_type ], ...) 1300else : 1301field_type = json_schema_to_python_types (field_type ) 1302fields [field_name ]= (field_type , ...) 1303if "function" in dictionary : 1304for field_name ,field_data in dictionary .get ("function" , {}).items (): 1305if field_name == "name" : 1306model_name = field_data 1307elif field_name == "description" : 1308fields ["__doc__" ]= field_data 1309elif field_name == "parameters" : 1310return convert_dictionary_to_pydantic_model (field_data ,f" { model_name } " ) 1311 1312if "parameters" in dictionary : 1313field_data = {"function" :dictionary } 1314return convert_dictionary_to_pydantic_model (field_data ,f" { model_name } " ) 1315if "required" in dictionary : 1316required = dictionary .get ("required" , []) 1317for key ,field in fields .items (): 1318if key not in required : 1319optional_type = fields [key ][0 ] 1320fields [key ]= (Optional [optional_type ], ...) 1321custom_model = create_model (model_name ,** fields ) 1322return custom_model