Source code for xgrammar.grammar

"""This module provides classes representing grammars."""

import json
from typing import Any, Dict, List, Optional, Tuple, Type, Union, overload

from pydantic import BaseModel
from typing_extensions import deprecated

from .base import XGRObject, _core
from .structural_tag import StructuralTag, StructuralTagItem


def _convert_instance_to_str(instance: Union[str, Dict[str, Any], StructuralTag]) -> str:
    """Convert a instance to a string representation. It returns the schema in string format because
    it's faster to send to C++.

    This function handles different instance input types and converts them to a JSON string:
    - StructuralTag.
    - String inputs are returned as-is (assumed to be valid JSON)
    - Dictionary inputs are converted to JSON strings

    Parameters
    ----------
    instance : Union[str, StructuralTag, Dict[str, Any]]
        The instance to convert, which can be a StructuralTag,
        a JSON schema string, or a dictionary representing a JSON schema.

    Returns
    -------
    str
        The JSON schema as a string.

    Raises
    ------
    ValueError
        When the instance type is not supported.
    TypeError
        When he dictionary is not serializable.
    """
    if isinstance(instance, dict):
        return json.dumps(instance)
    elif isinstance(instance, str):
        return instance
    elif isinstance(instance, StructuralTag):
        return instance.model_dump_json()
    else:
        raise ValueError("Invalid instance type")


def _convert_schema_to_str(schema: Union[str, Type[BaseModel], Dict[str, Any]]) -> str:
    """Convert a schema to a string representation. It returns the schema in string format because
    it's faster to send to C++.

    This function handles different schema input types and converts them to a JSON string:
    - Pydantic models are converted using their schema methods
    - String inputs are returned as-is (assumed to be valid JSON)
    - Dictionary inputs are converted to JSON strings

    Parameters
    ----------
    schema : Union[str, Type[BaseModel], Dict[str, Any]]
        The schema to convert, which can be a Pydantic model class,
        a JSON schema string, or a dictionary representing a JSON schema.

    Returns
    -------
    str
        The JSON schema as a string.

    Raises
    ------
    ValueError
        When the schema type is not supported.
    TypeError
        When the dictionary is not serializable.
    """
    if isinstance(schema, type) and issubclass(schema, BaseModel):
        if hasattr(schema, "model_json_schema"):
            return json.dumps(schema.model_json_schema())
        if hasattr(schema, "schema_json"):
            return json.dumps(schema.schema_json())
        else:
            raise ValueError("The schema should have a model_json_schema or json_schema method.")
    elif isinstance(schema, str):
        return schema
    elif isinstance(schema, dict):
        return json.dumps(schema)
    else:
        raise ValueError("The schema should be a string or a Pydantic model.")


def _get_structural_tag_str_from_args(args: List[Any], kwargs: Dict[str, Any]) -> str:
    """Get the structural tag string from the arguments. It returns the structural tag in string
    format because it's faster to send to C++.

    Parameters
    ----------
    args : List[Any]
        The positional arguments.
    kwargs : Dict[str, Any]
        The keyword arguments.

    Returns
    -------
    str
        The structural tag string.

    Raises
    ------
    TypeError
        When the arguments are invalid.
    """
    if len(args) == 1:
        if isinstance(args[0], (str, dict, StructuralTag)):
            return _convert_instance_to_str(args[0])
        else:
            raise TypeError("Invalid argument type for from_structural_tag")
    elif len(args) == 2 and isinstance(args[0], list) and isinstance(args[1], list):
        return StructuralTag.from_legacy_structural_tag(args[0], args[1]).model_dump_json(
            indent=None
        )
    elif "structural_tag" in kwargs:
        return _convert_instance_to_str(kwargs["structural_tag"])
    elif "tags" in kwargs and "triggers" in kwargs:
        return StructuralTag.from_legacy_structural_tag(
            kwargs["tags"], kwargs["triggers"]
        ).model_dump_json(indent=None)
    else:
        raise TypeError("Invalid arguments for from_structural_tag")


[docs]class Grammar(XGRObject): """This class represents a grammar object in XGrammar, and can be used later in the grammar-guided generation. The Grammar object supports context-free grammar (CFG). EBNF (extended Backus-Naur Form) is used as the format of the grammar. There are many specifications for EBNF in the literature, and we follow the specification of GBNF (GGML BNF) in https://github.com/ggerganov/llama.cpp/blob/master/grammars/README.md. When printed, the grammar will be converted to GBNF format. """
[docs] def __str__(self) -> str: """Print the BNF grammar to a string, in EBNF format. Returns ------- grammar_string : str The BNF grammar string. """ return self._handle.to_string()
[docs] @staticmethod def from_ebnf(ebnf_string: str, *, root_rule_name: str = "root") -> "Grammar": """Construct a grammar from EBNF string. The EBNF string should follow the format in https://github.com/ggerganov/llama.cpp/blob/master/grammars/README.md. Parameters ---------- ebnf_string : str The grammar string in EBNF format. root_rule_name : str, default: "root" The name of the root rule in the grammar. Raises ------ RuntimeError When converting the regex pattern fails, with details about the parsing error. """ return Grammar._create_from_handle(_core.Grammar.from_ebnf(ebnf_string, root_rule_name))
[docs] @staticmethod def from_json_schema( schema: Union[str, Type[BaseModel], Dict[str, Any]], *, any_whitespace: bool = True, indent: Optional[int] = None, separators: Optional[Tuple[str, str]] = None, strict_mode: bool = True, max_whitespace_cnt: Optional[int] = None, print_converted_ebnf: bool = False, ) -> "Grammar": """Construct a grammar from JSON schema. Pydantic model or JSON schema string can be used to specify the schema. It allows any whitespace by default. If user want to specify the format of the JSON, set `any_whitespace` to False and use the `indent` and `separators` parameters. The meaning and the default values of the parameters follows the convention in json.dumps(). It internally converts the JSON schema to a EBNF grammar. Parameters ---------- schema : Union[str, Type[BaseModel], Dict[str, Any]] The schema string or Pydantic model or JSON schema dict. any_whitespace : bool, default: True Whether to use any whitespace. If True, the generated grammar will ignore the indent and separators parameters, and allow any whitespace. indent : Optional[int], default: None The number of spaces for indentation. If None, the output will be in one line. Note that specifying the indentation means forcing the LLM to generate JSON strings strictly formatted. However, some models may tend to generate JSON strings that are not strictly formatted. In this case, forcing the LLM to generate strictly formatted JSON strings may degrade the generation quality. See <https://github.com/sgl-project/sglang/issues/2216#issuecomment-2516192009> for more details. separators : Optional[Tuple[str, str]], default: None Two separators used in the schema: comma and colon. Examples: (",", ":"), (", ", ": "). If None, the default separators will be used: (",", ": ") when the indent is not None, and (", ", ": ") otherwise. strict_mode : bool, default: True Whether to use strict mode. In strict mode, the generated grammar will not allow properties and items that is not specified in the schema. This is equivalent to setting unevaluatedProperties and unevaluatedItems to false. This helps LLM to generate accurate output in the grammar-guided generation with JSON schema. max_whitespace_cnt : Optional[int], default: None The maximum number of whitespace characters allowed between elements, such like keys, values, separators and so on. If None, there is no limit on the number of whitespace characters. If specified, it will limit the number of whitespace characters to at most max_whitespace_cnt. It should be a positive integer. print_converted_ebnf : bool, default: False If True, the converted EBNF string will be printed. For debugging purposes. Returns ------- grammar : Grammar The constructed grammar. Raises ------ RuntimeError When converting the json schema fails, with details about the parsing error. """ schema_str = _convert_schema_to_str(schema) return Grammar._create_from_handle( _core.Grammar.from_json_schema( schema_str, any_whitespace, indent, separators, strict_mode, max_whitespace_cnt, print_converted_ebnf, ) )
[docs] @staticmethod def from_regex(regex_string: str, *, print_converted_ebnf: bool = False) -> "Grammar": """Create a grammar from a regular expression string. Parameters ---------- regex_string : str The regular expression pattern to create the grammar from. print_converted_ebnf : bool, default: False This method will convert the regex pattern to EBNF first. If this is true, the converted EBNF string will be printed. For debugging purposes. Default: False. Returns ------- grammar : Grammar The constructed grammar from the regex pattern. Raises ------ RuntimeError When parsing the regex pattern fails, with details about the parsing error. """ return Grammar._create_from_handle( _core.Grammar.from_regex(regex_string, print_converted_ebnf) )
@overload @staticmethod def from_structural_tag( structural_tag: Union[StructuralTag, str, Dict[str, Any]] ) -> "Grammar": ... @overload @staticmethod @deprecated( "from_structural_tag(tags, triggers) is deprecated. Construct structural tag with the " "StructuralTag class instead." ) def from_structural_tag(tags: List[StructuralTagItem], triggers: List[str]) -> "Grammar": ...
[docs] @staticmethod def from_structural_tag(*args, **kwargs) -> "Grammar": """Create a grammar from a structural tag. See the Structural Tag Usage in XGrammar documentation for its usage. This method supports two calling patterns: 1. Single structural tag parameter: from_structural_tag(structural_tag) 2. Legacy pattern (deprecated): from_structural_tag(tags, triggers) Parameters ---------- structural_tag : Union[StructuralTag, str, Dict[str, Any]] The structural tag either as a StructuralTag object, or a JSON string or a dictionary. tags : List[StructuralTagItem] (Deprecated) The structural tags. Use StructuralTag class instead. triggers : List[str] (Deprecated) The triggers. Use StructuralTag class instead. Returns ------- grammar : Grammar The constructed grammar from the structural tag. Raises ------ InvalidJSONError When the structural tag is not a valid JSON string. InvalidStructuralTagError When the structural tag is not valid. TypeError When the arguments are invalid. Notes ----- The legacy pattern from_structural_tag(tags, triggers) is deprecated. Use the StructuralTag class to construct structural tags instead. For the deprecated pattern: The structural tag handles the dispatching of different grammars based on the tags and triggers: it initially allows any output, until a trigger is encountered, then dispatch to the corresponding tag; when the end tag is encountered, the grammar will allow any following output, until the next trigger is encountered. See the Advanced Topics of the Structural Tag in XGrammar documentation for its semantic. Structural Tag in XGrammar documentation for its semantic. """ structural_tag_str = _get_structural_tag_str_from_args(args, kwargs) return Grammar._create_from_handle(_core.Grammar.from_structural_tag(structural_tag_str))
[docs] @staticmethod def builtin_json_grammar() -> "Grammar": """Get the grammar of standard JSON. This is compatible with the official JSON grammar specification in https://www.json.org/json-en.html. Returns ------- grammar : Grammar The JSON grammar. """ return Grammar._create_from_handle(_core.Grammar.builtin_json_grammar())
[docs] @staticmethod def concat(*grammars: "Grammar") -> "Grammar": """Create a grammar that matches the concatenation of the grammars in the list. That is equivalent to using the `+` operator to concatenate the grammars in the list. Parameters ---------- grammars : List[Grammar] The grammars to create the concatenation of. Returns ------- grammar : Grammar The concatenation of the grammars. """ grammar_handles = [grammar._handle for grammar in grammars] return Grammar._create_from_handle(_core.Grammar.concat(grammar_handles))
[docs] @staticmethod def union(*grammars: "Grammar") -> "Grammar": """Create a grammar that matches any of the grammars in the list. That is equivalent to using the `|` operator to concatenate the grammars in the list. Parameters ---------- grammars : List[Grammar] The grammars to create the union of. Returns ------- grammar : Grammar The union of the grammars. """ grammar_handles = [grammar._handle for grammar in grammars] return Grammar._create_from_handle(_core.Grammar.union(grammar_handles))
[docs] def serialize_json(self) -> str: """Serialize the grammar to a JSON string. Returns ------- json_string : str The JSON string. """ return self._handle.serialize_json()
[docs] @staticmethod def deserialize_json(json_string: str) -> "Grammar": """Deserialize a grammar from a JSON string. Parameters ---------- json_string : str The JSON string. Returns ------- grammar : Grammar The deserialized grammar. Raises ------ InvalidJSONError When the JSON string is invalid. DeserializeFormatError When the JSON string does not follow the serialization format of the grammar. DeserializeVersionError When the __VERSION__ field in the JSON string is not the same as the current version. """ return Grammar._create_from_handle(_core.Grammar.deserialize_json(json_string))