Skip to content

Grammar Generator

Grammar Generator

The functions from this module are used to generate GBNF grammars for the framework.

llama_cpp_agent.gbnf_grammar_generator.gbnf_grammar_from_pydantic_models

PydanticDataType

Bases: Enum

Defines the data types supported by the grammar_generator.

Attributes:

  • STRING (str) –

    Represents a string data type.

  • BOOLEAN (str) –

    Represents a boolean data type.

  • INTEGER (str) –

    Represents an integer data type.

  • FLOAT (str) –

    Represents a float data type.

  • OBJECT (str) –

    Represents an object data type.

  • ARRAY (str) –

    Represents an array data type.

  • ENUM (str) –

    Represents an enum data type.

  • CUSTOM_CLASS (str) –

    Represents a custom class data type.

Source code in llama_cpp_agent/gbnf_grammar_generator/gbnf_grammar_from_pydantic_models.py
class PydanticDataType(Enum):
    """
    Defines the data types supported by the grammar_generator.

    Attributes:
        STRING (str): Represents a string data type.
        BOOLEAN (str): Represents a boolean data type.
        INTEGER (str): Represents an integer data type.
        FLOAT (str): Represents a float data type.
        OBJECT (str): Represents an object data type.
        ARRAY (str): Represents an array data type.
        ENUM (str): Represents an enum data type.
        CUSTOM_CLASS (str): Represents a custom class data type.
    """

    STRING = "string"
    TRIPLE_QUOTED_STRING = "triple_quoted_string"
    MARKDOWN_CODE_BLOCK = "markdown_code_block"
    BOOLEAN = "boolean"
    INTEGER = "number"
    FLOAT = "number"
    OBJECT = "object"
    ARRAY = "array"
    ENUM = "enum"
    ANY = "any"
    NULL = "null"
    CUSTOM_CLASS = "custom-class"
    CUSTOM_DICT = "custom-dict"
    SET = "set"

generate_list_rule(element_type)

Generate a GBNF rule for a list of a given element type.

:param element_type: The type of the elements in the list (e.g., 'string'). :return: A string representing the GBNF rule for a list of the given type.

Source code in llama_cpp_agent/gbnf_grammar_generator/gbnf_grammar_from_pydantic_models.py
def generate_list_rule(element_type):
    """
    Generate a GBNF rule for a list of a given element type.

    :param element_type: The type of the elements in the list (e.g., 'string').
    :return: A string representing the GBNF rule for a list of the given type.
    """
    rule_name = f"{map_pydantic_type_to_gbnf(element_type)}-list"
    element_rule = map_pydantic_type_to_gbnf(element_type)
    list_rule = rf'{rule_name} ::= "["  {element_rule} (","  {element_rule})* "]"'
    return list_rule

regex_to_gbnf(regex_pattern)

Translate a basic regex pattern to a GBNF rule. Note: This function handles only a subset of simple regex patterns.

Source code in llama_cpp_agent/gbnf_grammar_generator/gbnf_grammar_from_pydantic_models.py
def regex_to_gbnf(regex_pattern: str) -> str:
    """
    Translate a basic regex pattern to a GBNF rule.
    Note: This function handles only a subset of simple regex patterns.
    """
    gbnf_rule = regex_pattern

    # Translate common regex components to GBNF
    gbnf_rule = gbnf_rule.replace("\\d", "[0-9]")
    gbnf_rule = gbnf_rule.replace("\\s", "[ \t\n]")

    # Handle quantifiers and other regex syntax that is similar in GBNF
    # (e.g., '*', '+', '?', character classes)

    return gbnf_rule

generate_gbnf_integer_rules(max_digit=None, min_digit=None)

Generate GBNF Integer Rules

Generates GBNF (Generalized Backus-Naur Form) rules for integers based on the given maximum and minimum digits.

Parameters:

  • max_digit (int, default: None ) –

    The maximum number of digits for the integer. Default is None.

  • min_digit (int, default: None ) –

    The minimum number of digits for the integer. Default is None.

Returns:

  • integer_rule ( str ) –

    The identifier for the integer rule generated.

  • additional_rules ( list ) –

    A list of additional rules generated based on the given maximum and minimum digits.

Source code in llama_cpp_agent/gbnf_grammar_generator/gbnf_grammar_from_pydantic_models.py
def generate_gbnf_integer_rules(max_digit=None, min_digit=None):
    """

    Generate GBNF Integer Rules

    Generates GBNF (Generalized Backus-Naur Form) rules for integers based on the given maximum and minimum digits.

    Parameters:
        max_digit (int): The maximum number of digits for the integer. Default is None.
        min_digit (int): The minimum number of digits for the integer. Default is None.

    Returns:
        integer_rule (str): The identifier for the integer rule generated.
        additional_rules (list): A list of additional rules generated based on the given maximum and minimum digits.

    """
    additional_rules = []

    # Define the rule identifier based on max_digit and min_digit
    integer_rule = "integer-part"
    if max_digit is not None:
        integer_rule += f"-max{max_digit}"
    if min_digit is not None:
        integer_rule += f"-min{min_digit}"

    # Handling Integer Rules
    if max_digit is not None or min_digit is not None:
        # Start with an empty rule part
        integer_rule_part = ""

        # Add mandatory digits as per min_digit
        if min_digit is not None:
            integer_rule_part += "[0-9] " * min_digit

        # Add optional digits up to max_digit
        if max_digit is not None:
            optional_digits = max_digit - (min_digit if min_digit is not None else 0)
            integer_rule_part += "".join(["[0-9]? " for _ in range(optional_digits)])

        # Trim the rule part and append it to additional rules
        integer_rule_part = integer_rule_part.strip()
        if integer_rule_part:
            additional_rules.append(f"{integer_rule} ::= {integer_rule_part}")

    return integer_rule, additional_rules

generate_gbnf_float_rules(max_digit=None, min_digit=None, max_precision=None, min_precision=None)

Generate GBNF float rules based on the given constraints.

:param max_digit: Maximum number of digits in the integer part (default: None) :param min_digit: Minimum number of digits in the integer part (default: None) :param max_precision: Maximum number of digits in the fractional part (default: None) :param min_precision: Minimum number of digits in the fractional part (default: None) :return: A tuple containing the float rule and additional rules as a list

Example Usage: max_digit = 3 min_digit = 1 max_precision = 2 min_precision = 1 generate_gbnf_float_rules(max_digit, min_digit, max_precision, min_precision)

Output: ('float-3-1-2-1', ['integer-part-max3-min1 ::= [0-9][] [0-9]?', 'fractional-part-max2-min1 ::= [0-9][]?', 'float-3-1-2-1 ::= integer-part-max3-min1 "." fractional-part-max2-min *1'])

Note: GBNF stands for Generalized Backus-Naur Form, which is a notation technique to specify the syntax of programming languages or other formal grammars.

Source code in llama_cpp_agent/gbnf_grammar_generator/gbnf_grammar_from_pydantic_models.py
def generate_gbnf_float_rules(
    max_digit=None, min_digit=None, max_precision=None, min_precision=None
):
    """
    Generate GBNF float rules based on the given constraints.

    :param max_digit: Maximum number of digits in the integer part (default: None)
    :param min_digit: Minimum number of digits in the integer part (default: None)
    :param max_precision: Maximum number of digits in the fractional part (default: None)
    :param min_precision: Minimum number of digits in the fractional part (default: None)
    :return: A tuple containing the float rule and additional rules as a list

    Example Usage:
    max_digit = 3
    min_digit = 1
    max_precision = 2
    min_precision = 1
    generate_gbnf_float_rules(max_digit, min_digit, max_precision, min_precision)

    Output:
    ('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
    *1'])

    Note:
    GBNF stands for Generalized Backus-Naur Form, which is a notation technique to specify the syntax of programming languages or other formal grammars.
    """
    additional_rules = []

    # Define the integer part rule
    integer_part_rule = (
        "integer-part"
        + (f"-max{max_digit}" if max_digit is not None else "")
        + (f"-min{min_digit}" if min_digit is not None else "")
    )

    # Define the fractional part rule based on precision constraints
    fractional_part_rule = "fractional-part"
    fractional_rule_part = ""
    if max_precision is not None or min_precision is not None:
        fractional_part_rule += (
            f"-max{max_precision}" if max_precision is not None else ""
        ) + (f"-min{min_precision}" if min_precision is not None else "")
        # Minimum number of digits
        fractional_rule_part = "[0-9]" * (
            min_precision if min_precision is not None else 1
        )
        # Optional additional digits
        fractional_rule_part += "".join(
            [" [0-9]?"]
            * (
                (max_precision - (min_precision if min_precision is not None else 1))
                if max_precision is not None
                else 0
            )
        )
        additional_rules.append(f"{fractional_part_rule} ::= {fractional_rule_part}")

    # Define the float rule
    float_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'}"
    additional_rules.append(
        f'{float_rule} ::= {integer_part_rule} "." {fractional_part_rule}'
    )

    # Generating the integer part rule definition, if necessary
    if max_digit is not None or min_digit is not None:
        integer_rule_part = "[0-9]"
        if min_digit is not None and min_digit > 1:
            integer_rule_part += " [0-9]" * (min_digit - 1)
        if max_digit is not None:
            integer_rule_part += "".join(
                [" [0-9]?"] * (max_digit - (min_digit if min_digit is not None else 1))
            )
        additional_rules.append(f"{integer_part_rule} ::= {integer_rule_part.strip()}")

    return float_rule, additional_rules

generate_gbnf_rule_for_type(model_name, field_name, field_type, is_optional, processed_models, created_rules, field_info=None)

Generate GBNF rule for a given field type.

:param model_name: Name of the model.

:param field_name: Name of the field. :param field_type: Type of the field. :param is_optional: Whether the field is optional. :param processed_models: List of processed models. :param created_rules: List of created rules. :param field_info: Additional information about the field (optional).

:return: Tuple containing the GBNF type and a list of additional rules. :rtype: tuple[str, list]

Source code in llama_cpp_agent/gbnf_grammar_generator/gbnf_grammar_from_pydantic_models.py
def generate_gbnf_rule_for_type(
    model_name,
    field_name,
    field_type,
    is_optional,
    processed_models,
    created_rules,
    field_info=None,
) -> tuple[str, list[str]]:
    """
    Generate GBNF rule for a given field type.

    :param model_name: Name of the model.

    :param field_name: Name of the field.
    :param field_type: Type of the field.
    :param is_optional: Whether the field is optional.
    :param processed_models: List of processed models.
    :param created_rules: List of created rules.
    :param field_info: Additional information about the field (optional).

    :return: Tuple containing the GBNF type and a list of additional rules.
    :rtype: tuple[str, list]
    """
    rules = []

    field_name = format_model_and_field_name(field_name)
    gbnf_type = map_pydantic_type_to_gbnf(field_type)

    if isclass(field_type) and issubclass(field_type, BaseModel):
        nested_model_name = format_model_and_field_name(field_type.__name__)
        nested_model_rules, _ = generate_gbnf_grammar(
            field_type, processed_models, created_rules
        )
        rules.extend(nested_model_rules)
        gbnf_type, rules = nested_model_name, rules
    elif isclass(field_type) and issubclass(field_type, Enum):
        enum_values = [
            f'"\\"{e.value}\\""' for e in field_type
        ]  # Adding escaped quotes
        enum_rule = f"{model_name}-{field_name} ::= {' | '.join(enum_values)}"
        rules.append(enum_rule)
        gbnf_type, rules = model_name + "-" + field_name, rules
    elif get_origin(field_type) == list:  # Array
        element_type = get_args(field_type)[0]
        element_rule_name, additional_rules = generate_gbnf_rule_for_type(
            model_name,
            f"{field_name}-element",
            element_type,
            is_optional,
            processed_models,
            created_rules,
        )
        rules.extend(additional_rules)
        array_rule = rf"""{model_name}-{field_name} ::= "[" ws ({element_rule_name})? ("," ws {element_rule_name})* ws "]" """
        rules.append(array_rule)
        gbnf_type, rules = model_name + "-" + field_name, rules

    elif get_origin(field_type) == set or field_type == set:  # Array
        element_type = get_args(field_type)[0]
        element_rule_name, additional_rules = generate_gbnf_rule_for_type(
            model_name,
            f"{field_name}-element",
            element_type,
            is_optional,
            processed_models,
            created_rules,
        )
        rules.extend(additional_rules)
        array_rule = rf"""{model_name}-{field_name} ::= "[" ws ({element_rule_name})? ("," ws {element_rule_name})* ws "]" """
        rules.append(array_rule)
        gbnf_type, rules = model_name + "-" + field_name, rules

    elif gbnf_type.startswith("custom-class-"):
        rules.append(get_members_structure(field_type, gbnf_type))
    elif gbnf_type.startswith("custom-dict-"):
        key_type, value_type = get_args(field_type)

        additional_key_type, additional_key_rules = generate_gbnf_rule_for_type(
            model_name,
            f"{field_name}-key-type",
            key_type,
            is_optional,
            processed_models,
            created_rules,
        )
        additional_value_type, additional_value_rules = generate_gbnf_rule_for_type(
            model_name,
            f"{field_name}-value-type",
            value_type,
            is_optional,
            processed_models,
            created_rules,
        )
        rules.extend(
            [
                rf'{gbnf_type} ::= "{{"  ( {additional_key_type} ": "  {additional_value_type} ("," "\n" ws {additional_key_type} ":"  {additional_value_type})*  )? "}}" '
            ]
        )
        rules.extend(additional_key_rules)
        rules.extend(additional_value_rules)
    elif gbnf_type.startswith("union-"):
        union_types = get_args(field_type)
        union_rules = []

        for union_type in union_types:
            if isinstance(union_type, GenericAlias):
                union_gbnf_type, union_rules_list = generate_gbnf_rule_for_type(
                    model_name,
                    field_name,
                    union_type,
                    False,
                    processed_models,
                    created_rules,
                )
                union_rules.append(union_gbnf_type)
                rules.extend(union_rules_list)

            elif not issubclass(union_type, type(None)):
                union_gbnf_type, union_rules_list = generate_gbnf_rule_for_type(
                    model_name,
                    field_name,
                    union_type,
                    False,
                    processed_models,
                    created_rules,
                )
                union_rules.append(union_gbnf_type)
                rules.extend(union_rules_list)

        # Defining the union grammar rule separately
        if len(union_rules) == 1:
            union_grammar_rule = f"{model_name}-{field_name}-optional ::= {' | '.join(union_rules)} | null"
        else:
            uni = []
            for rule in union_rules:
                if rule not in uni:
                    uni.append(rule)
            union_grammar_rule = (
                f"{model_name}-{field_name}-union ::= {' | '.join(uni)}"
            )
        rules.append(union_grammar_rule)
        if len(union_rules) == 1:
            gbnf_type = f"{model_name}-{field_name}-optional"
        else:
            gbnf_type = f"{model_name}-{field_name}-union"
    elif isclass(field_type) and issubclass(field_type, str):
        if (
            field_info
            and hasattr(field_info, "json_schema_extra")
            and field_info.json_schema_extra is not None
        ):
            triple_quoted_string = field_info.json_schema_extra.get(
                "triple_quoted_string", False
            )
            markdown_string = field_info.json_schema_extra.get(
                "markdown_code_block", False
            )

            gbnf_type = (
                PydanticDataType.TRIPLE_QUOTED_STRING.value
                if triple_quoted_string
                else PydanticDataType.STRING.value
            )
            gbnf_type = (
                PydanticDataType.MARKDOWN_CODE_BLOCK.value
                if markdown_string
                else gbnf_type
            )

        elif field_info and hasattr(field_info, "pattern"):
            # Convert regex pattern to grammar rule
            regex_pattern = field_info.regex.pattern
            gbnf_type = f"pattern-{field_name} ::= {regex_to_gbnf(regex_pattern)}"
        else:
            gbnf_type = PydanticDataType.STRING.value

    elif (
        isclass(field_type)
        and issubclass(field_type, float)
        and field_info
        and hasattr(field_info, "json_schema_extra")
        and field_info.json_schema_extra is not None
    ):
        # Retrieve precision attributes for floats
        max_precision = (
            field_info.json_schema_extra.get("max_precision")
            if field_info and hasattr(field_info, "json_schema_extra")
            else None
        )
        min_precision = (
            field_info.json_schema_extra.get("min_precision")
            if field_info and hasattr(field_info, "json_schema_extra")
            else None
        )
        max_digits = (
            field_info.json_schema_extra.get("max_digit")
            if field_info and hasattr(field_info, "json_schema_extra")
            else None
        )
        min_digits = (
            field_info.json_schema_extra.get("min_digit")
            if field_info and hasattr(field_info, "json_schema_extra")
            else None
        )

        # Generate GBNF rule for float with given attributes
        gbnf_type, rules = generate_gbnf_float_rules(
            max_digit=max_digits,
            min_digit=min_digits,
            max_precision=max_precision,
            min_precision=min_precision,
        )

    elif (
        isclass(field_type)
        and issubclass(field_type, int)
        and field_info
        and hasattr(field_info, "json_schema_extra")
        and field_info.json_schema_extra is not None
    ):
        # Retrieve digit attributes for integers
        max_digits = (
            field_info.json_schema_extra.get("max_digit")
            if field_info and hasattr(field_info, "json_schema_extra")
            else None
        )
        min_digits = (
            field_info.json_schema_extra.get("min_digit")
            if field_info and hasattr(field_info, "json_schema_extra")
            else None
        )

        # Generate GBNF rule for integer with given attributes
        gbnf_type, rules = generate_gbnf_integer_rules(
            max_digit=max_digits, min_digit=min_digits
        )
    else:
        gbnf_type, rules = gbnf_type, []

    return gbnf_type, rules

generate_gbnf_grammar(model, processed_models, created_rules)

Generate GBnF Grammar

Generates a GBnF grammar for a given model.

:param model: A Pydantic model class to generate the grammar for. Must be a subclass of BaseModel. :param processed_models: A set of already processed models to prevent infinite recursion. :param created_rules: A dict containing already created rules to prevent duplicates. :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. Example Usage:

model = MyModel
processed_models = set()
created_rules = dict()

gbnf_grammar = generate_gbnf_grammar(model, processed_models, created_rules)

Source code in llama_cpp_agent/gbnf_grammar_generator/gbnf_grammar_from_pydantic_models.py
def generate_gbnf_grammar(
    model: type[BaseModel],
    processed_models: set[type[BaseModel]],
    created_rules: dict[str, list[str]],
) -> tuple[list[str], bool]:
    """

    Generate GBnF Grammar

    Generates a GBnF grammar for a given model.

    :param model: A Pydantic model class to generate the grammar for. Must be a subclass of BaseModel.
    :param processed_models: A set of already processed models to prevent infinite recursion.
    :param created_rules: A dict containing already created rules to prevent duplicates.
    :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.
    Example Usage:
    ```
    model = MyModel
    processed_models = set()
    created_rules = dict()

    gbnf_grammar = generate_gbnf_grammar(model, processed_models, created_rules)
    ```
    """
    if model in processed_models:
        return [], False

    processed_models.add(model)
    model_name = format_model_and_field_name(model.__name__)

    if not issubclass(model, BaseModel):
        # For non-Pydantic classes, generate model_fields from __annotations__ or __init__
        if hasattr(model, "__annotations__") and model.__annotations__:
            model_fields = {
                name: (typ, ...) for name, typ in model.__annotations__.items()
            }
        else:
            init_signature = inspect.signature(model.__init__)
            parameters = init_signature.parameters
            model_fields = {
                name: (param.annotation, param.default)
                for name, param in parameters.items()
                if name != "self"
            }
    else:
        # For Pydantic models, use model_fields and check for ellipsis (required fields)
        model_fields = model.__annotations__

    model_rule_parts = []
    nested_rules = []
    has_markdown_code_block = False
    has_triple_quoted_string = False
    look_for_markdown_code_block = False
    look_for_triple_quoted_string = False
    for field_name, field_info in model_fields.items():
        if not issubclass(model, BaseModel):
            field_type, default_value = field_info
            # Check if the field is optional (not required)
            is_optional = (default_value is not inspect.Parameter.empty) and (
                default_value is not Ellipsis
            )
        else:
            field_type = field_info
            field_info = model.model_fields[field_name]
            is_optional = (
                field_info.is_required is False and get_origin(field_type) is Optional
            )
        rule_name, additional_rules = generate_gbnf_rule_for_type(
            model_name,
            format_model_and_field_name(field_name),
            field_type,
            is_optional,
            processed_models,
            created_rules,
            field_info,
        )
        look_for_markdown_code_block = (
            True if rule_name == "markdown_code_block" else False
        )
        look_for_triple_quoted_string = (
            True if rule_name == "triple_quoted_string" else False
        )
        if not look_for_markdown_code_block and not look_for_triple_quoted_string:
            if rule_name not in created_rules:
                created_rules[rule_name] = additional_rules
            model_rule_parts.append(
                f' ws "\\"{field_name}\\"" ": " {rule_name}'
            )  # Adding escaped quotes
            nested_rules.extend(additional_rules)
        else:
            has_triple_quoted_string = look_for_triple_quoted_string
            has_markdown_code_block = look_for_markdown_code_block

    fields_joined = r' "," '.join(model_rule_parts)
    if fields_joined != "":
        model_rule = rf'{model_name} ::= "{{" {fields_joined} '
    else:
        model_rule = rf'{model_name} ::= "{{" "}}"'

    has_special_string = False
    if has_triple_quoted_string:
        model_rule += '"\\n" ws "}"'
        model_rule += '"\\n" triple-quoted-string'
        has_special_string = True
    elif has_markdown_code_block:
        model_rule += '"\\n" ws "}"'
        model_rule += '"\\n" markdown-code-block'
        has_special_string = True
    else:
        model_rule += ' ws "}"'
    all_rules = [model_rule] + nested_rules

    return all_rules, has_special_string

generate_gbnf_grammar_from_pydantic_models(models, outer_object_name=None, outer_object_content=None, list_of_outputs=False, add_inner_thoughts=True, allow_only_inner_thoughts=True, inner_thought_field_name='chain_of_thought', add_request_heartbeat=True, request_heartbeat_field_name='request_heartbeat', request_heartbeat_models=None)

Generate GBNF Grammar from Pydantic Models.

This 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 * grammar.

Parameters:

  • models (list[type[BaseModel]]) –

    A list of Pydantic models to generate the grammar from.

  • outer_object_name (str, default: None ) –

    Outer object name for the GBNF grammar. If None, no outer object will be generated. Eg. "function" for function calling.

  • outer_object_content (str, default: None ) –

    Content for the outer rule in the GBNF grammar. Eg. "function_parameters" or "params" for function calling.

  • list_of_outputs (str, default: False ) –

    Allows a list of output objects

  • add_inner_thoughts (bool, default: True ) –

    Add inner thoughts to the grammar. Defaults to True.

  • allow_only_inner_thoughts (bool, default: True ) –

    Allow only inner thoughts. Defaults to True.

Returns: str: The generated GBNF grammar string.

Examples:

models = [UserModel, PostModel] grammar = generate_gbnf_grammar_from_pydantic(models) print(grammar)

Output:
root ::= UserModel | PostModel
...
Source code in llama_cpp_agent/gbnf_grammar_generator/gbnf_grammar_from_pydantic_models.py
def generate_gbnf_grammar_from_pydantic_models(
    models: list[BaseModel],
    outer_object_name: str | None = None,
    outer_object_content: str | None = None,
    list_of_outputs: bool = False,
    add_inner_thoughts: bool = True,
    allow_only_inner_thoughts: bool = True,
    inner_thought_field_name: str = "chain_of_thought",
    add_request_heartbeat: bool = True,
    request_heartbeat_field_name: str = "request_heartbeat",
    request_heartbeat_models: list[str] = None,
) -> str:
    """
    Generate GBNF Grammar from Pydantic Models.

    This 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
    * grammar.

    Args:
        models (list[type[BaseModel]]): A list of Pydantic models to generate the grammar from.
        outer_object_name (str): Outer object name for the GBNF grammar. If None, no outer object will be generated. Eg. "function" for function calling.
        outer_object_content (str): Content for the outer rule in the GBNF grammar. Eg. "function_parameters" or "params" for function calling.
        list_of_outputs (str, optional): Allows a list of output objects
        add_inner_thoughts (bool, optional): Add inner thoughts to the grammar. Defaults to True.
        allow_only_inner_thoughts (bool, optional): Allow only inner thoughts. Defaults to True.
    Returns:
        str: The generated GBNF grammar string.

    Examples:
        models = [UserModel, PostModel]
        grammar = generate_gbnf_grammar_from_pydantic(models)
        print(grammar)
        # Output:
        # root ::= UserModel | PostModel
        # ...
    """
    if request_heartbeat_models is None:
        request_heartbeat_models = []
    processed_models: set[type[BaseModel]] = set()
    all_rules = []
    created_rules: dict[str, list[str]] = {}
    if outer_object_name is None:
        for model in models:
            model_rules, _ = generate_gbnf_grammar(
                model, processed_models, created_rules
            )
            all_rules.extend(model_rules)

        if list_of_outputs:
            root_rule = (
                r'root ::= (" "| "\n") "[" ws grammar-models ("," ws grammar-models)* "\n" "]"'
                + "\n"
            )
        else:
            root_rule = r'root ::= (" "| "\n") grammar-models' + "\n"
        root_rule += "grammar-models ::= " + " | ".join(
            [format_model_and_field_name(model.__name__) for model in models]
        )
        all_rules.insert(0, root_rule)
        return "\n".join(all_rules) + get_primitive_grammar("\n".join(all_rules))
    elif outer_object_name is not None:
        if list_of_outputs:
            root_rule = (
                rf'root ::= (" "| "\n") "[" ws {format_model_and_field_name(outer_object_name)} ("," ws {format_model_and_field_name(outer_object_name)})* "\n" "]"'
                + "\n"
            )
        else:
            root_rule = f"root ::= {format_model_and_field_name(outer_object_name)}\n"

        if add_inner_thoughts:
            if allow_only_inner_thoughts:
                model_rule = rf'{format_model_and_field_name(outer_object_name)} ::= (" "| "\n") "{{" ws "\"{inner_thought_field_name}\""  ":" ws string (("," ws "\"{outer_object_name}\""  ":" ws grammar-models)? | ws "}}")'
            else:
                model_rule = rf'{format_model_and_field_name(outer_object_name)} ::= (" "| "\n") "{{" ws "\"{inner_thought_field_name}\""  ":" ws string "," ws "\"{outer_object_name}\""  ":" ws grammar-models '
        else:
            model_rule = rf'{format_model_and_field_name(outer_object_name)} ::= (" "| "\n") "{{" ws "\"{outer_object_name}\""  ": " ws grammar-models'

        fields_joined = " | ".join(
            [
                rf"{format_model_and_field_name(model.__name__)}-grammar-model"
                for model in models
            ]
        )

        grammar_model_rules = f"\ngrammar-models ::= {fields_joined}"
        mod_rules = []
        for model in models:
            mod_rule = (
                rf"{format_model_and_field_name(model.__name__)}-grammar-model ::= "
            )
            mod_rule += (
                rf'"\"{model.__name__}\"" "," ws "\"{outer_object_content}\"" ": " {format_model_and_field_name(model.__name__)}'
                + "\n"
            )
            mod_rules.append(mod_rule)
        grammar_model_rules += "\n" + "\n".join(mod_rules)

        for model in models:
            model_rules, has_special_string = generate_gbnf_grammar(
                model, processed_models, created_rules
            )
            if add_request_heartbeat and model.__name__ in request_heartbeat_models:
                model_rules[
                    0
                ] += rf' "," ws "\"{request_heartbeat_field_name}\""  ":" ws boolean '
            # if not has_special_string:
            #     model_rules[0] += r' ws "}"'

            all_rules.extend(model_rules)

        all_rules.insert(0, root_rule + model_rule + grammar_model_rules)
        return "\n".join(all_rules) + get_primitive_grammar("\n".join(all_rules))

get_primitive_grammar(grammar)

Returns the needed GBNF primitive grammar for a given GBNF grammar string.

Parameters:

  • grammar (str) –

    The string containing the GBNF grammar.

Returns:

  • str

    GBNF primitive grammar string.

Source code in llama_cpp_agent/gbnf_grammar_generator/gbnf_grammar_from_pydantic_models.py
def get_primitive_grammar(grammar):
    """
    Returns the needed GBNF primitive grammar for a given GBNF grammar string.

    Args:
        grammar (str): The string containing the GBNF grammar.

    Returns:
        str: GBNF primitive grammar string.
    """
    type_list: list[type[object]] = []
    if "string-list" in grammar:
        type_list.append(str)
    if "boolean-list" in grammar:
        type_list.append(bool)
    if "integer-list" in grammar:
        type_list.append(int)
    if "float-list" in grammar:
        type_list.append(float)
    additional_grammar = [generate_list_rule(t) for t in type_list]
    primitive_grammar = r"""
boolean ::= "true" | "false"
null ::= "null"
string ::= "\"" (
        [^"\\] |
        "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
      )* "\""
ws ::= ([ \t\n]+)
number ::= "-"? ([0-9]+ | [0-9]+ "." [0-9]+) ([eE] [-+]? [0-9]+)?"""

    any_block = ""
    if "custom-class-any" in grammar:
        any_block = """
value ::= object | array | string | number | boolean | null

object ::=
  "{" ws (
            string ":" ws value
    ("," ws string ":" ws value)*
  )? "}"

array  ::=
  "[" ws (
            value
    ("," ws value)*
  )? "]"
"""

    markdown_code_block_grammar = ""
    if "markdown-code-block" in grammar:
        markdown_code_block_grammar = r'''
markdown-code-block ::= opening-triple-ticks markdown-code-block-content closing-triple-ticks
markdown-code-block-content ::= ( [^`] | "`" [^`] |  "`"  "`" [^`]  )*
opening-triple-ticks ::= "```" "python" "\n" | "```" "c" "\n" | "```" "cpp" "\n" | "```" "txt" "\n" | "```" "text" "\n" | "```" "json" "\n" | "```" "javascript" "\n" | "```" "css" "\n" | "```" "html" "\n" | "```" "markdown" "\n"
closing-triple-ticks ::= "```" "\n"'''

    if "triple-quoted-string" in grammar:
        markdown_code_block_grammar = r"""
triple-quoted-string ::= triple-quotes triple-quoted-string-content triple-quotes
triple-quoted-string-content ::= ( [^'] | "'" [^'] |  "'"  "'" [^']  )*
triple-quotes ::= "'''" """
    return (
        "\n"
        + "\n".join(additional_grammar)
        + any_block
        + primitive_grammar
        + markdown_code_block_grammar
    )

save_gbnf_grammar_and_documentation(grammar, documentation, grammar_file_path='./grammar.gbnf', documentation_file_path='./grammar_documentation.md')

Save GBNF grammar and documentation to specified files.

Parameters:

  • grammar (str) –

    GBNF grammar string.

  • documentation (str) –

    Documentation string.

  • grammar_file_path (str, default: './grammar.gbnf' ) –

    File path to save the GBNF grammar.

  • documentation_file_path (str, default: './grammar_documentation.md' ) –

    File path to save the documentation.

Returns:

  • None

Source code in llama_cpp_agent/gbnf_grammar_generator/gbnf_grammar_from_pydantic_models.py
def save_gbnf_grammar_and_documentation(
    grammar,
    documentation,
    grammar_file_path="./grammar.gbnf",
    documentation_file_path="./grammar_documentation.md",
):
    """
    Save GBNF grammar and documentation to specified files.

    Args:
        grammar (str): GBNF grammar string.
        documentation (str): Documentation string.
        grammar_file_path (str): File path to save the GBNF grammar.
        documentation_file_path (str): File path to save the documentation.

    Returns:
        None
    """
    try:
        with open(grammar_file_path, "w") as file:
            file.write(grammar + get_primitive_grammar(grammar))
        print(f"Grammar successfully saved to {grammar_file_path}")
    except IOError as e:
        print(f"An error occurred while saving the grammar file: {e}")

    try:
        with open(documentation_file_path, "w") as file:
            file.write(documentation)
        print(f"Documentation successfully saved to {documentation_file_path}")
    except IOError as e:
        print(f"An error occurred while saving the documentation file: {e}")

remove_empty_lines(string)

Remove empty lines from a string.

Parameters:

  • string (str) –

    Input string.

Returns:

  • str

    String with empty lines removed.

Source code in llama_cpp_agent/gbnf_grammar_generator/gbnf_grammar_from_pydantic_models.py
def remove_empty_lines(string):
    """
    Remove empty lines from a string.

    Args:
        string (str): Input string.

    Returns:
        str: String with empty lines removed.
    """
    lines = string.splitlines()
    non_empty_lines = [line for line in lines if line.strip() != ""]
    string_no_empty_lines = "\n".join(non_empty_lines)
    return string_no_empty_lines

generate_and_save_gbnf_grammar_and_documentation(pydantic_model_list, grammar_file_path='./generated_grammar.gbnf', documentation_file_path='./generated_grammar_documentation.md', outer_object_name=None, outer_object_content=None, model_prefix='Output Model', fields_prefix='Output Fields', list_of_outputs=False, documentation_with_field_description=True, add_inner_thoughts=False, allow_only_inner_thoughts=False, inner_thoughts_field_name='thoughts_and_reasoning', add_request_heartbeat=False, request_heartbeat_field_name='request_heartbeat', request_heartbeat_models=None)

Generate GBNF grammar and documentation, and save them to specified files.

Parameters:

  • pydantic_model_list

    List of Pydantic model classes.

  • grammar_file_path (str, default: './generated_grammar.gbnf' ) –

    File path to save the generated GBNF grammar.

  • documentation_file_path (str, default: './generated_grammar_documentation.md' ) –

    File path to save the generated documentation.

  • outer_object_name (str, default: None ) –

    Outer object name for the GBNF grammar. If None, no outer object will be generated. Eg. "function" for function calling.

  • outer_object_content (str, default: None ) –

    Content for the outer rule in the GBNF grammar. Eg. "function_parameters" or "params" for function calling.

  • model_prefix (str, default: 'Output Model' ) –

    Prefix for the model section in the documentation.

  • fields_prefix (str, default: 'Output Fields' ) –

    Prefix for the fields section in the documentation.

  • list_of_outputs (bool, default: False ) –

    Whether the output is a list of items.

  • documentation_with_field_description (bool, default: True ) –

    Include field descriptions in the documentation.

  • add_inner_thoughts (bool, default: False ) –

    Add inner thoughts to the grammar. This is useful for adding comments or reasoning to the output.

  • allow_only_inner_thoughts (bool, default: False ) –

    Allow only inner thoughts. If True, only inner thoughts will be allowed in the output.

  • inner_thoughts_field_name (str, default: 'thoughts_and_reasoning' ) –

    Field name for inner thoughts. Default is "thoughts_and_reasoning".

  • add_request_heartbeat (bool, default: False ) –

    Add request heartbeat to the grammar. This allows the LLM to decide when to return control to the system.

  • request_heartbeat_field_name (str, default: 'request_heartbeat' ) –

    Field name for request heartbeat. Default is "request_heartbeat".

  • request_heartbeat_models (List[str], default: None ) –

    List of models that will have a request heartbeat field.

Returns: None

Source code in llama_cpp_agent/gbnf_grammar_generator/gbnf_grammar_from_pydantic_models.py
def generate_and_save_gbnf_grammar_and_documentation(
    pydantic_model_list,
    grammar_file_path="./generated_grammar.gbnf",
    documentation_file_path="./generated_grammar_documentation.md",
    outer_object_name: str | None = None,
    outer_object_content: str | None = None,
    model_prefix: str = "Output Model",
    fields_prefix: str = "Output Fields",
    list_of_outputs: bool = False,
    documentation_with_field_description=True,
    add_inner_thoughts: bool = False,
    allow_only_inner_thoughts: bool = False,
    inner_thoughts_field_name: str = "thoughts_and_reasoning",
    add_request_heartbeat: bool = False,
    request_heartbeat_field_name: str = "request_heartbeat",
    request_heartbeat_models: List[str] = None,
):
    """
    Generate GBNF grammar and documentation, and save them to specified files.

    Args:
        pydantic_model_list: List of Pydantic model classes.
        grammar_file_path (str): File path to save the generated GBNF grammar.
        documentation_file_path (str): File path to save the generated documentation.
        outer_object_name (str): Outer object name for the GBNF grammar. If None, no outer object will be generated. Eg. "function" for function calling.
        outer_object_content (str): Content for the outer rule in the GBNF grammar. Eg. "function_parameters" or "params" for function calling.
        model_prefix (str): Prefix for the model section in the documentation.
        fields_prefix (str): Prefix for the fields section in the documentation.
        list_of_outputs (bool): Whether the output is a list of items.
        documentation_with_field_description (bool): Include field descriptions in the documentation.
        add_inner_thoughts (bool): Add inner thoughts to the grammar. This is useful for adding comments or reasoning to the output.
        allow_only_inner_thoughts (bool): Allow only inner thoughts. If True, only inner thoughts will be allowed in the output.
        inner_thoughts_field_name (str): Field name for inner thoughts. Default is "thoughts_and_reasoning".
        add_request_heartbeat (bool): Add request heartbeat to the grammar. This allows the LLM to decide when to return control to the system.
        request_heartbeat_field_name (str): Field name for request heartbeat. Default is "request_heartbeat".
        request_heartbeat_models (List[str]): List of models that will have a request heartbeat field.
    Returns:
        None
    """
    documentation = generate_markdown_documentation(
        pydantic_model_list,
        model_prefix,
        fields_prefix,
        documentation_with_field_description=documentation_with_field_description,
    )
    grammar = generate_gbnf_grammar_from_pydantic_models(
        pydantic_model_list,
        outer_object_name,
        outer_object_content,
        list_of_outputs,
        add_inner_thoughts,
        allow_only_inner_thoughts,
        inner_thoughts_field_name,
        add_request_heartbeat,
        request_heartbeat_field_name,
        request_heartbeat_models,
    )
    grammar = remove_empty_lines(grammar)
    save_gbnf_grammar_and_documentation(
        grammar, documentation, grammar_file_path, documentation_file_path
    )

generate_gbnf_grammar_and_documentation(pydantic_model_list, outer_object_name=None, outer_object_content=None, model_prefix='Output Model', fields_prefix='Output Fields', list_of_outputs=False, documentation_with_field_description=True, add_inner_thoughts=False, allow_only_inner_thoughts=False, inner_thoughts_field_name='thoughts_and_reasoning', add_request_heartbeat=False, request_heartbeat_field_name='request_heartbeat', request_heartbeat_models=None)

Generate GBNF grammar and documentation for a list of Pydantic models.

Parameters:

  • pydantic_model_list

    List of Pydantic model classes.

  • outer_object_name (str, default: None ) –

    Outer object name for the GBNF grammar. If None, no outer object will be generated. Eg. "function" for function calling.

  • outer_object_content (str, default: None ) –

    Content for the outer rule in the GBNF grammar. E.g., "function_parameters" or "params" for function calling.

  • model_prefix (str, default: 'Output Model' ) –

    Prefix for the model section in the documentation.

  • fields_prefix (str, default: 'Output Fields' ) –

    Prefix for the fields section in the documentation.

  • list_of_outputs (bool, default: False ) –

    Whether the output is a list of items.

  • documentation_with_field_description (bool, default: True ) –

    Include field descriptions in the documentation.

  • add_inner_thoughts (bool, default: False ) –

    Add inner thoughts to the grammar. This is useful for adding comments or reasoning to the output.

  • allow_only_inner_thoughts (bool, default: False ) –

    Allow only inner thoughts. If True, only inner thoughts will be allowed in the output.

  • inner_thoughts_field_name (str, default: 'thoughts_and_reasoning' ) –

    Field name for inner thoughts. Default is "thoughts_and_reasoning".

  • add_request_heartbeat (bool, default: False ) –

    Add request heartbeat to the grammar. This allows the LLM to decide when to return control to the system.

  • request_heartbeat_field_name (str, default: 'request_heartbeat' ) –

    Field name for request heartbeat. Default is "request_heartbeat".

  • request_heartbeat_models (List[str], default: None ) –

    List of models that will have a request heartbeat field.

Returns:

  • tuple

    GBNF grammar string, documentation string.

Source code in llama_cpp_agent/gbnf_grammar_generator/gbnf_grammar_from_pydantic_models.py
def generate_gbnf_grammar_and_documentation(
    pydantic_model_list,
    outer_object_name: str | None = None,
    outer_object_content: str | None = None,
    model_prefix: str = "Output Model",
    fields_prefix: str = "Output Fields",
    list_of_outputs: bool = False,
    documentation_with_field_description=True,
    add_inner_thoughts: bool = False,
    allow_only_inner_thoughts: bool = False,
    inner_thoughts_field_name: str = "thoughts_and_reasoning",
    add_request_heartbeat: bool = False,
    request_heartbeat_field_name: str = "request_heartbeat",
    request_heartbeat_models: List[str] = None,
):
    """
    Generate GBNF grammar and documentation for a list of Pydantic models.

    Args:
        pydantic_model_list: List of Pydantic model classes.
        outer_object_name (str): Outer object name for the GBNF grammar. If None, no outer object will be generated. Eg. "function" for function calling.
        outer_object_content (str): Content for the outer rule in the GBNF grammar. E.g., "function_parameters" or "params" for function calling.
        model_prefix (str): Prefix for the model section in the documentation.
        fields_prefix (str): Prefix for the fields section in the documentation.
        list_of_outputs (bool): Whether the output is a list of items.
        documentation_with_field_description (bool): Include field descriptions in the documentation.
        add_inner_thoughts (bool): Add inner thoughts to the grammar. This is useful for adding comments or reasoning to the output.
        allow_only_inner_thoughts (bool): Allow only inner thoughts. If True, only inner thoughts will be allowed in the output.
        inner_thoughts_field_name (str): Field name for inner thoughts. Default is "thoughts_and_reasoning".
        add_request_heartbeat (bool): Add request heartbeat to the grammar. This allows the LLM to decide when to return control to the system.
        request_heartbeat_field_name (str): Field name for request heartbeat. Default is "request_heartbeat".
        request_heartbeat_models (List[str]): List of models that will have a request heartbeat field.

    Returns:
        tuple: GBNF grammar string, documentation string.
    """
    documentation = generate_text_documentation(
        copy(pydantic_model_list),
        model_prefix,
        fields_prefix,
        documentation_with_field_description=documentation_with_field_description,
    )
    grammar = generate_gbnf_grammar_from_pydantic_models(
        pydantic_model_list,
        outer_object_name,
        outer_object_content,
        list_of_outputs,
        add_inner_thoughts,
        allow_only_inner_thoughts,
        inner_thoughts_field_name,
        add_request_heartbeat,
        request_heartbeat_field_name,
        request_heartbeat_models,
    )
    grammar = remove_empty_lines(grammar + get_primitive_grammar(grammar))
    return grammar, documentation

generate_gbnf_grammar_and_documentation_from_dictionaries(dictionaries, outer_object_name=None, outer_object_content=None, model_prefix='Output Model', fields_prefix='Output Fields', list_of_outputs=False, documentation_with_field_description=True, add_inner_thoughts=False, allow_only_inner_thoughts=False, inner_thoughts_field_name='thoughts_and_reasoning', add_request_heartbeat=False, request_heartbeat_field_name='request_heartbeat', request_heartbeat_models=None)

Generate GBNF grammar and documentation from a list of dictionaries.

Parameters:

  • dictionaries (list[dict]) –

    List of dictionaries representing Pydantic models.

  • outer_object_name (str, default: None ) –

    Outer object name for the GBNF grammar. If None, no outer object will be generated. Eg. "function" for function calling.

  • outer_object_content (str, default: None ) –

    Content for the outer rule in the GBNF grammar. Eg. "function_parameters" or "params" for function calling.

  • model_prefix (str, default: 'Output Model' ) –

    Prefix for the model section in the documentation.

  • fields_prefix (str, default: 'Output Fields' ) –

    Prefix for the fields section in the documentation.

  • list_of_outputs (bool, default: False ) –

    Whether the output is a list of items.

  • documentation_with_field_description (bool, default: True ) –

    Include field descriptions in the documentation.

  • add_inner_thoughts (bool, default: False ) –

    Add inner thoughts to the grammar. This is useful for adding comments or reasoning to the output.

  • allow_only_inner_thoughts (bool, default: False ) –

    Allow only inner thoughts. If True, only inner thoughts will be allowed in the output.

  • inner_thoughts_field_name (str, default: 'thoughts_and_reasoning' ) –

    Field name for inner thoughts. Default is "thoughts_and_reasoning".

  • add_request_heartbeat (bool, default: False ) –

    Add request heartbeat to the grammar. This allows the LLM to decide when to return control to the system.

  • request_heartbeat_field_name (str, default: 'request_heartbeat' ) –

    Field name for request heartbeat. Default is "request_heartbeat".

  • request_heartbeat_models (List[str], default: None ) –

    List of models that will have a request heartbeat field.

Returns:

  • tuple

    GBNF grammar string, documentation string.

Source code in llama_cpp_agent/gbnf_grammar_generator/gbnf_grammar_from_pydantic_models.py
def generate_gbnf_grammar_and_documentation_from_dictionaries(
    dictionaries: list[dict[str, Any]],
    outer_object_name: str | None = None,
    outer_object_content: str | None = None,
    model_prefix: str = "Output Model",
    fields_prefix: str = "Output Fields",
    list_of_outputs: bool = False,
    documentation_with_field_description=True,
    add_inner_thoughts: bool = False,
    allow_only_inner_thoughts: bool = False,
    inner_thoughts_field_name: str = "thoughts_and_reasoning",
    add_request_heartbeat: bool = False,
    request_heartbeat_field_name: str = "request_heartbeat",
    request_heartbeat_models: List[str] = None,
):
    """
    Generate GBNF grammar and documentation from a list of dictionaries.

    Args:
        dictionaries (list[dict]): List of dictionaries representing Pydantic models.
        outer_object_name (str): Outer object name for the GBNF grammar. If None, no outer object will be generated. Eg. "function" for function calling.
        outer_object_content (str): Content for the outer rule in the GBNF grammar. Eg. "function_parameters" or "params" for function calling.
        model_prefix (str): Prefix for the model section in the documentation.
        fields_prefix (str): Prefix for the fields section in the documentation.
        list_of_outputs (bool): Whether the output is a list of items.
        documentation_with_field_description (bool): Include field descriptions in the documentation.
        add_inner_thoughts (bool): Add inner thoughts to the grammar. This is useful for adding comments or reasoning to the output.
        allow_only_inner_thoughts (bool): Allow only inner thoughts. If True, only inner thoughts will be allowed in the output.
        inner_thoughts_field_name (str): Field name for inner thoughts. Default is "thoughts_and_reasoning".
        add_request_heartbeat (bool): Add request heartbeat to the grammar. This allows the LLM to decide when to return control to the system.
        request_heartbeat_field_name (str): Field name for request heartbeat. Default is "request_heartbeat".
        request_heartbeat_models (List[str]): List of models that will have a request heartbeat field.

    Returns:
        tuple: GBNF grammar string, documentation string.
    """
    pydantic_model_list = create_dynamic_models_from_dictionaries(dictionaries)
    documentation = generate_markdown_documentation(
        copy(pydantic_model_list),
        model_prefix,
        fields_prefix,
        documentation_with_field_description=documentation_with_field_description,
    )
    grammar = generate_gbnf_grammar_from_pydantic_models(
        pydantic_model_list,
        outer_object_name,
        outer_object_content,
        list_of_outputs,
        add_inner_thoughts,
        allow_only_inner_thoughts,
        inner_thoughts_field_name,
        add_request_heartbeat,
        request_heartbeat_field_name,
        request_heartbeat_models,
    )
    grammar = remove_empty_lines(grammar + get_primitive_grammar(grammar))
    return grammar, documentation

create_dynamic_model_from_function(func, add_inner_thoughts=False, inner_thoughts_field_name='inner_thoughts')

Creates a dynamic Pydantic model from a given function's type hints and adds the function as a 'run' method.

Parameters:

  • func (Callable) –

    A function with type hints from which to create the model.

  • add_inner_thoughts (bool, default: False ) –

    Add an 'inner_thoughts' field at the params level to the model. Default is False.

  • inner_thoughts_field_name (str, default: 'inner_thoughts' ) –

    Field name for inner thoughts. Default is "inner_thoughts".

Returns:

  • A dynamic Pydantic model class with the provided function as a 'run' method.

Source code in llama_cpp_agent/gbnf_grammar_generator/gbnf_grammar_from_pydantic_models.py
def create_dynamic_model_from_function(
    func: Callable[..., Any],
    add_inner_thoughts: bool = False,
    inner_thoughts_field_name: str = "inner_thoughts",
):
    """
    Creates a dynamic Pydantic model from a given function's type hints and adds the function as a 'run' method.

    Args:
        func (Callable): A function with type hints from which to create the model.
        add_inner_thoughts (bool): Add an 'inner_thoughts' field at the params level to the model. Default is False.
        inner_thoughts_field_name (str): Field name for inner thoughts. Default is "inner_thoughts".

    Returns:
        A dynamic Pydantic model class with the provided function as a 'run' method.
    """

    # Get the signature of the function
    sig = inspect.signature(func)

    # Parse the docstring
    assert func.__doc__ is not None
    docstring = parse(func.__doc__)

    dynamic_fields = {}
    param_docs = []
    if add_inner_thoughts:
        dynamic_fields[inner_thoughts_field_name] = (str, None)
    for param in sig.parameters.values():
        # Exclude 'self' parameter
        if param.name == "self":
            continue

        # Assert that the parameter has a type annotation
        if param.annotation == inspect.Parameter.empty:
            raise TypeError(
                f"Parameter '{param.name}' in function '{func.__name__}' lacks a type annotation"
            )

        # Find the parameter's description in the docstring
        param_doc = next(
            (d for d in docstring.params if d.arg_name == param.name), None
        )

        # Assert that the parameter has a description
        if not param_doc or not param_doc.description:
            raise ValueError(
                f"Parameter '{param.name}' in function '{func.__name__}' lacks a description in the docstring"
            )

        # Add parameter details to the schema
        param_docs.append((param.name, param_doc))
        if param.default == inspect.Parameter.empty:
            default_value = ...
        else:
            default_value = param.default
        dynamic_fields[param.name] = (
            param.annotation if param.annotation != inspect.Parameter.empty else str,
            default_value,
        )

    # Creating the dynamic model
    dynamic_model = create_model(f"{func.__name__}", **dynamic_fields)  # type: ignore[call-overload]
    if add_inner_thoughts:
        dynamic_model.model_fields[
            inner_thoughts_field_name
        ].description = "Deep inner monologue private to you only."
    for name, param_doc in param_docs:
        dynamic_model.model_fields[name].description = param_doc.description

    dynamic_model.__doc__ = (
        (docstring.short_description if docstring.short_description is not None else "")
        + "\n"
        + (docstring.long_description if docstring.long_description is not None else "")
    )

    def run_method_wrapper(self):
        func_args = {name: getattr(self, name) for name, _ in dynamic_fields.items()}
        return func(**func_args)

    # Adding the wrapped function as a 'run' method
    setattr(dynamic_model, "run", run_method_wrapper)
    return dynamic_model

add_run_method_to_dynamic_model(model, func)

Add a 'run' method to a dynamic Pydantic model, using the provided function.

Parameters:

  • model (type[BaseModel]) –

    Dynamic Pydantic model class.

  • func (Callable) –

    Function to be added as a 'run' method to the model.

Returns:

  • type[BaseModel]: Pydantic model class with the added 'run' method.

Source code in llama_cpp_agent/gbnf_grammar_generator/gbnf_grammar_from_pydantic_models.py
def add_run_method_to_dynamic_model(model: type[BaseModel], func: Callable[..., Any]):
    """
    Add a 'run' method to a dynamic Pydantic model, using the provided function.

    Args:
        model (type[BaseModel]): Dynamic Pydantic model class.
        func (Callable): Function to be added as a 'run' method to the model.

    Returns:
        type[BaseModel]: Pydantic model class with the added 'run' method.
    """

    def run_method_wrapper(self):
        func_args = {name: getattr(self, name) for name in model.model_fields}
        return func(**func_args)

    # Adding the wrapped function as a 'run' method
    setattr(model, "run", run_method_wrapper)

    return model

create_dynamic_models_from_dictionaries(dictionaries)

Create a list of dynamic Pydantic model classes from a list of dictionaries.

Parameters:

  • dictionaries (list[dict]) –

    List of dictionaries representing model structures.

Returns:

  • list[Type[BaseModel]]: List of generated dynamic Pydantic model classes.

Source code in llama_cpp_agent/gbnf_grammar_generator/gbnf_grammar_from_pydantic_models.py
def create_dynamic_models_from_dictionaries(dictionaries: list[dict[str, Any]]):
    """
    Create a list of dynamic Pydantic model classes from a list of dictionaries.

    Args:
        dictionaries (list[dict]): List of dictionaries representing model structures.

    Returns:
        list[Type[BaseModel]]: List of generated dynamic Pydantic model classes.
    """
    dynamic_models = []
    for func in dictionaries:
        model_name = format_model_and_field_name(func.get("name", ""))
        dyn_model = convert_dictionary_to_pydantic_model(func, model_name)
        dynamic_models.append(dyn_model)
    return dynamic_models

convert_dictionary_to_pydantic_model(dictionary, model_name='CustomModel', docs=None, docs_function=None)

Convert a dictionary to a Pydantic model class.

Parameters:

  • dictionary (dict) –

    Dictionary representing the model structure.

  • model_name (str, default: 'CustomModel' ) –

    Name of the generated Pydantic model.

Returns:

  • type[Any]

    type[BaseModel]: Generated Pydantic model class.

Source code in llama_cpp_agent/gbnf_grammar_generator/gbnf_grammar_from_pydantic_models.py
def convert_dictionary_to_pydantic_model(
    dictionary: dict[str, Any],
    model_name: str = "CustomModel",
    docs: dict[str, str] = None,
    docs_function: dict[str, str] = None,
) -> type[Any]:
    """
    Convert a dictionary to a Pydantic model class.

    Args:
        dictionary (dict): Dictionary representing the model structure.
        model_name (str): Name of the generated Pydantic model.

    Returns:
        type[BaseModel]: Generated Pydantic model class.
    """
    fields: dict[str, Any] = {}
    if docs is None:
        docs = {}
    if docs_function is None:
        docs_function = {}
    if "properties" in dictionary:
        for field_name, field_data in dictionary.get("properties", {}).items():
            if field_data == "object":
                submodel = convert_dictionary_to_pydantic_model(
                    dictionary, f"{model_name}_{field_name}", docs, docs_function
                )
                fields[field_name] = (submodel, ...)

            else:
                field_type = field_data.get("type", "string")
                if field_data.get("description", None):
                    docs[field_name] = field_data["description"]
                if field_data.get("enum", []):
                    fields[field_name] = (
                        list_to_enum(field_name, field_data.get("enum", [])),
                        ...,
                    )
                elif field_type == "array":
                    items = field_data.get("items", {})
                    if items != {}:
                        array = {"properties": items}
                        array_type = convert_dictionary_to_pydantic_model(
                            array,
                            f"{model_name}_{field_name}_items",
                            docs,
                            docs_function,
                        )
                        fields[field_name] = (List[array_type], ...)  # type: ignore[valid-type]
                    else:
                        fields[field_name] = (list, ...)
                elif field_type == "object":
                    submodel = convert_dictionary_to_pydantic_model(
                        field_data, f"{model_name}_{field_name}", docs, docs_function
                    )
                    fields[field_name] = (submodel, ...)
                elif field_type == "required":
                    required = field_data
                    for key, field in fields.items():
                        if key not in required:
                            fields[key] = (Optional[fields[key][0]], ...)
                else:
                    field_type = json_schema_to_python_types(field_type)
                    fields[field_name] = (field_type, ...)
    if "function" in dictionary:
        for field_name, field_data in dictionary.get("function", {}).items():
            if field_name == "name":
                model_name = field_data
            elif field_name == "description":
                docs_function["__doc__"] = field_data
            elif field_name == "parameters":
                return convert_dictionary_to_pydantic_model(
                    field_data, f"{model_name}", docs, docs_function
                )

    if "parameters" in dictionary:
        field_data = {"function": dictionary}
        return convert_dictionary_to_pydantic_model(
            field_data, f"{model_name}", docs, docs_function
        )
    if "required" in dictionary:
        required = dictionary.get("required", [])
        for key, field in fields.items():
            if key not in required:
                fields[key] = (Optional[fields[key][0]], ...)
    custom_model = create_model(model_name, **fields)

    if "__doc__" in docs_function:
        custom_model.__doc__ = docs_function["__doc__"]
    for field_name, doc in docs.items():
        custom_model.model_fields[field_name].description = doc

    return custom_model