Source code for xgrammar.builtin_structural_tag

from typing import Any, Callable, Dict, List, Literal, Optional, Union

from .structural_tag import (
    AnyTextFormat,
    ConstStringFormat,
    JSONSchemaFormat,
    QwenXMLParameterFormat,
    RegexFormat,
    SequenceFormat,
    StructuralTag,
    TagFormat,
    TagsWithSeparatorFormat,
    TriggeredTagsFormat,
)

# ---------- API Functions ----------


BuiltinSupportedModels = Literal[
    "llama",
    "qwen",
    "qwen_coder",
    "kimi",
    "deepseek_r1",
    "harmony",
    "deepseek_v3_2",
    "minimax",
    "glm47",
]


[docs]def get_builtin_structural_tag( model: BuiltinSupportedModels, reasoning: bool = True, tools: List[Dict[str, Any]] = [], builtin_tools: List[Dict[str, Any]] = [], force_empty_reasoning: bool = False, ) -> StructuralTag: r"""Get structural tag for model. This function can generate structural tag for the given model with the given tools, builtin tools and reasoning mode. Parameters ---------- model : BuiltinModels The model type of the structural tag template. reasoning : bool Whether to enable reasoning mode. i.e. whether to enable the <think> and </think> tags. tools : List[Dict[str, Any]] A list of tools, each tool should have a "function" key, which is a dictionary containing "name" and "parameters" fields. builtin_tools : List[Dict[str, Any]] A list of builtin tools, each builtin tool should have a "function" key, which is a dictionary containing "name" and "parameters" fields. This is only used for Harmony style. force_empty_reasoning : bool Whether to force empty reasoning mode. i.e. The model will output the empty thinking content at the beginning of the response. Some models like Qwen3, DeepSeek-R1 and etc. prefer empty-thinking mode to disable reasoning mode instead of non-thinking mode. Returns ------- StructuralTag A structural tag for function calling format. """ if not isinstance(reasoning, bool): raise ValueError("The 'reasoning' key in the input_dict must be a boolean.") if not isinstance(force_empty_reasoning, bool): raise ValueError("The 'force_empty_reasoning' key in the input_dict must be a boolean.") _validate_tool_function(tools) _validate_tool_function(builtin_tools) func = _get_builtin_structural_tag_function(model) input_dict = { "tools": tools, "builtin_tools": builtin_tools, "reasoning": reasoning, "force_empty_reasoning": force_empty_reasoning, } return func(input_dict)
[docs]def get_builtin_structural_tag_supported_models( strucutural_tag_style: Optional[BuiltinSupportedModels] = None, ) -> Union[Dict[str, List[str]], List[str]]: """Get supported models for a given structural tag style. If strucutural_tag_style is not provided, return all supported models. Parameters ---------- strucutural_tag_style : Optional[BuiltinModels] The structural tag style. Returns ------- Union[Dict[str, List[str]], List[str]] A dictionary of supported models for each structural tag style, or a list of supported models. """ if strucutural_tag_style is None: return _structural_tag_supported_models else: return _structural_tag_supported_models[strucutural_tag_style]
# ---------- Helper Functions And Constants ---------- _structural_tag_registry: Dict[ BuiltinSupportedModels, Callable[[Dict[str, Any]], StructuralTag] ] = {} _structural_tag_supported_models: Dict[BuiltinSupportedModels, List[str]] = {} _THINK_EXCLUDE_TOKENS = ["<think>", "</think>"] def _validate_tool_function(tools: Any) -> None: if not isinstance(tools, list): raise ValueError("The 'tools' key in the input_dict must be a list.") for tool in tools: if "function" not in tool: continue function = tool["function"] if "name" not in function: raise ValueError("Each function in the 'tools' list must have 'name' key.") if not isinstance(function["name"], str): raise ValueError("The 'name' key in each tool must be a string.") if ("strict" in function and function["strict"] is False) or ("parameters" not in function): continue else: parameters = function["parameters"] if not (isinstance(parameters, dict) or isinstance(parameters, bool)): raise ValueError("The 'parameters' key in each tool must be a dict or a boolean.") def _get_function_parameters(function: Dict[str, Any]) -> Union[Dict[str, Any], bool]: if ("strict" in function and function["strict"] is False) or ("parameters" not in function): return True return function["parameters"] def _register_builtin_structural_tag(name: str, supported_models: List[str]): """Register a structural tag template.""" def decorator(func): _structural_tag_registry[name] = func _structural_tag_supported_models[name] = supported_models return func return decorator def _get_builtin_structural_tag_function( format_type: BuiltinSupportedModels, ) -> Callable[[Dict[str, Any]], StructuralTag]: """Get builtin structural tag template function by format type. In all the structural tag template formats, users should provide a list of tools, each tool should have a "function" key, which is a dictionary containing "name" and "parameters" fields. Besides, for the OpenAI Harmony Response Format, users should also provide a list of builtin tools, each builtin tool should have a "function" key, which is a dictionary containing "name" and "parameters" fields. In addition, for the "qwen", "deepseek_r1" and "harmony" formats, "reasoning" key can be provided to enable/disable reasoning mode. By default, reasoning mode is enabled. Examples -------- .. code-block:: python from xgrammar import get_builtin_structural_tag_template_function, Grammar tools = [ {"function": {"name": "tool1", "parameters": {"param1": {"type": "string"}}}}, {"function": {"name": "tool2", "parameters": {"param2": {"type": "integer"}}}}, ] builtin_tools = [ {"function": {"name": "builtin_tool1", "parameters": {"param1": {"type": "string"}}}}, {"function": {"name": "builtin_tool2", "parameters": {"param2": {"type": "integer"}}}}, ] template_structural_tag = get_builtin_structural_tag_template_function("harmony") structural_tag = template_structural_tag({"tools": tools, "builtin_tools": builtin_tools}) grammar = Grammar.from_structural_tag(structural_tag) The above grammar can be used to construct a grammar that matches the function calling format of the specified model. Parameters ---------- format_type : BuiltinModels The format type of the structural tag template. Currently supported format types are: - "llama": Llama3.1 style structural tag format. Supported Models: Llama 3, Llama 4 and other models that follow the same style. - "qwen": Qwen3 style structural tag format. Supported Models: Qwen3 and other models that follow the same style. - "qwen_coder": Qwen-Coder style structural tag format. Supported Models: Qwen3-Coder, Qwen3-Coder-Next and other models that follow the same style. - "kimi": Kimi-K2 style structural tag format. Supported Models: Kimi-K2, Kimi-K2.5 and other models that follow the same style. - "deepseek_r1": Deepseek-R1 style structural tag format. Supported Models: Deepseek-V3.1, Deepseek-R1, Deepseek-V3.2-exp and other models that follow the same style. - "harmony": OpenAI Harmony Response Format (gpt-oss). Supported Models: GPT-oss and other models that follow the same style. Returns ------- Callable[[Dict[str, Any]], StructuralTag] The corresponding structural tag template function for the given format type. Raises ------ ValueError If the format type is unknown. """ func = _structural_tag_registry.get(format_type) if func is None: support_types = list(_structural_tag_registry.keys()) raise ValueError(f"Unknown format type: {format_type}, support types: {support_types}") return func # ---------- Each Built-in Structural Tag Function ---------- @_register_builtin_structural_tag("llama", ["Meta-Llama-3", "Llama-3.1", "Llama-3.2", "Llama-4"]) def _get_llama_structural_tag(input_dict: Dict[str, Any]) -> StructuralTag: """Get Llama style structural tag format. Reference: https://www.llama.com/docs/model-cards-and-prompt-formats/llama3_1/ The input_dict should be a dictionary with the following keys: - "tools": a list of tools, each tool should have a "function" key, which is a dictionary containing "name" and "parameters" fields. - "reasoning": a boolean indicating whether to enable reasoning mode. - "force_empty_reasoning": a boolean; when reasoning is on, if True use empty-thinking, if False use thinking. Returns ------- StructuralTag A structural tag for function calling format. This format is used by Llama 3 and other models that follow the same style. """ tools = input_dict.get("tools", []) reasoning = input_dict.get("reasoning", True) force_empty_reasoning = input_dict.get("force_empty_reasoning", False) tags = [] for tool in tools: if "function" not in tool: continue function = tool["function"] parameters = _get_function_parameters(function) name = function["name"] tags.append( TagFormat( begin=('{"name": "' + name + '", "parameters": '), content=JSONSchemaFormat(json_schema=parameters), end="}", ) ) if len(tags) > 0: suffix_tag = TriggeredTagsFormat( triggers=['{"name": '], tags=tags, excludes=_THINK_EXCLUDE_TOKENS ) else: suffix_tag = AnyTextFormat(excludes=_THINK_EXCLUDE_TOKENS) if not reasoning: return StructuralTag(format=suffix_tag) if force_empty_reasoning: prefix_tag = ConstStringFormat(value="<think>\n\n</think>") else: prefix_tag = TagFormat(begin="<think>", content=AnyTextFormat(), end="</think>") return StructuralTag(format=SequenceFormat(elements=[prefix_tag, suffix_tag])) @_register_builtin_structural_tag("kimi", ["Kimi-K2", "Kimi-K2.5"]) def _get_kimi_structural_tag(input_dict: Dict[str, Any]) -> StructuralTag: """Get Kimi-K2 style structural tag format. Reference: https://huggingface.co/moonshotai/Kimi-K2-Instruct/blob/main/docs/tool_call_guidance.md The input_dict should be a dictionary with the following keys: - "tools": a list of tools, each tool should have a "function" key, which is a dictionary containing "name" and "parameters" fields. - "reasoning": a boolean indicating whether to enable reasoning mode. - "force_empty_reasoning": a boolean; when reasoning is on, if True use empty-thinking, if False use thinking. Returns ------- StructuralTag A structural tag template. This format is used by Kimi-K2 and other models that follow the same style. """ tools = input_dict.get("tools", []) reasoning = input_dict.get("reasoning", True) force_empty_reasoning = input_dict.get("force_empty_reasoning", False) tags = [] for tool in tools: if "function" not in tool: continue function = tool["function"] parameters = _get_function_parameters(function) name = function["name"] tags.append( TagFormat( begin=f"<|tool_call_begin|>functions.{name}:", content=SequenceFormat( elements=[ RegexFormat(pattern=r"\d+"), ConstStringFormat(value="<|tool_call_argument_begin|>"), JSONSchemaFormat(json_schema=parameters), ] ), end="<|tool_call_end|>", ) ) if len(tags) > 0: suffix_tag = TriggeredTagsFormat( triggers=["<|tool_call_begin|>"], tags=tags, excludes=_THINK_EXCLUDE_TOKENS ) else: suffix_tag = AnyTextFormat(excludes=_THINK_EXCLUDE_TOKENS) if not reasoning: return StructuralTag(format=suffix_tag) if force_empty_reasoning: prefix_tag = ConstStringFormat(value="<think></think>") else: prefix_tag = TagFormat(begin="<think>", content=AnyTextFormat(), end="</think>") return StructuralTag(format=SequenceFormat(elements=[prefix_tag, suffix_tag])) @_register_builtin_structural_tag( "deepseek_r1", ["DeepSeek-V3.1", "DeepSeek-R1", "DeepSeek-V3.2-exp"] ) def _get_deepseek_structural_tag(input_dict: Dict[str, Any]) -> StructuralTag: """Get DeepSeek-R1 style structural tag format. Reference: https://huggingface.co/deepseek-ai/DeepSeek-V3.1/blob/main/tokenizer_config.json The input_dict should be a dictionary with the following keys: - "tools": a list of tools, each tool should have a "function" key, which is a dictionary containing "name" and "parameters" fields. - "reasoning": a boolean indicating whether to enable reasoning mode. - "force_empty_reasoning": a boolean; when reasoning is on, if True use empty-thinking, if False use thinking. Returns ------- StructuralTag A structural tag for function calling format. This format is used by DeepSeek-R1 and other models that follow the same style. """ tools = input_dict.get("tools", []) reasoning = input_dict.get("reasoning", True) force_empty_reasoning = input_dict.get("force_empty_reasoning", False) tags = [] for tool in tools: if "function" not in tool: continue function = tool["function"] parameters = _get_function_parameters(function) name = function["name"] tags.append( TagFormat( begin=f"<|tool▁calls▁begin|><|tool▁call▁begin|>{name}<|tool▁sep|>", content=JSONSchemaFormat(json_schema=parameters), end="<|tool▁call▁end|>", ) ) if len(tags) > 0: suffix_tag = TriggeredTagsFormat( triggers=["<|tool▁calls▁begin|><|tool▁call▁begin|>"], tags=tags, excludes=_THINK_EXCLUDE_TOKENS, ) else: suffix_tag = AnyTextFormat(excludes=_THINK_EXCLUDE_TOKENS) if not reasoning: return StructuralTag(format=suffix_tag) if force_empty_reasoning: prefix_tag = ConstStringFormat(value="</think>") else: prefix_tag = TagFormat(begin="", content=AnyTextFormat(), end="</think>") return StructuralTag(format=SequenceFormat(elements=[prefix_tag, suffix_tag])) @_register_builtin_structural_tag("qwen_coder", ["Qwen3-Coder", "Qwen3-Coder-Next"]) def _get_qwen_coder_structural_tag(input_dict: Dict[str, Any]) -> StructuralTag: """Get Qwen3-Coder style structural tag format. Reference: https://huggingface.co/Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8/blob/main/chat_template.jinja The input_dict should be a dictionary with the following keys: - "tools": a list of tools, each tool should have a "function" key, which is a dictionary containing "name" and "parameters" fields. - "reasoning": a boolean indicating whether to enable reasoning mode. - "force_empty_reasoning": a boolean; when reasoning is on, if True use empty-thinking, if False use thinking. Returns ------- StructuralTag A structural tag for function calling format. This format is used by Qwen3-Coder and other models that follow the same style. """ tools = input_dict.get("tools", []) reasoning = input_dict.get("reasoning", True) force_empty_reasoning = input_dict.get("force_empty_reasoning", False) tags = [] for tool in tools: if "function" not in tool: continue function = tool["function"] parameters = _get_function_parameters(function) name = function["name"] tags.append( TagFormat( begin=f"<tool_call>\n<function={name}>\n", content=QwenXMLParameterFormat(json_schema=parameters), end="\n</function>\n</tool_call>", ) ) if len(tags) > 0: suffix_tag = TriggeredTagsFormat( triggers=["<tool_call>\n<function="], tags=tags, excludes=_THINK_EXCLUDE_TOKENS ) else: suffix_tag = AnyTextFormat(excludes=_THINK_EXCLUDE_TOKENS) if not reasoning: return StructuralTag(format=suffix_tag) if force_empty_reasoning: prefix_tag = ConstStringFormat(value="<think>\n\n</think>") else: prefix_tag = TagFormat(begin="<think>", content=AnyTextFormat(), end="</think>") return StructuralTag(format=SequenceFormat(elements=[prefix_tag, suffix_tag])) @_register_builtin_structural_tag("qwen", ["Qwen3"]) def _get_qwen_structural_tag(input_dict: Dict[str, Any]) -> StructuralTag: """Get Qwen3 style structural tag format. Reference: https://qwen.readthedocs.io/en/latest/framework/function_call.html The input_dict should be a dictionary with the following keys: - "tools": a list of tools, each tool should have a "function" key, which is a dictionary containing "name" and "parameters" fields. - "reasoning": a boolean indicating whether to enable reasoning mode. Returns ------- StructuralTag A structural tag template. This format is used by Qwen3 and other models that follow the same style. """ tools = input_dict.get("tools", []) reasoning = input_dict.get("reasoning", True) force_empty_reasoning = input_dict.get("force_empty_reasoning", False) tags = [] for tool in tools: if "function" not in tool: continue function = tool["function"] parameters = _get_function_parameters(function) name = function["name"] tags.append( TagFormat( begin=('<tool_call>\n{"name": "' + name + '", "arguments": '), content=JSONSchemaFormat(json_schema=parameters), end="}\n</tool_call>", ) ) if len(tags) > 0: suffix_tag = TriggeredTagsFormat( triggers=["<tool_call>"], tags=tags, excludes=_THINK_EXCLUDE_TOKENS ) else: suffix_tag = AnyTextFormat(excludes=_THINK_EXCLUDE_TOKENS) if not reasoning: return StructuralTag(format=suffix_tag) if force_empty_reasoning: prefix_tag = ConstStringFormat(value="<think>\n\n</think>") else: prefix_tag = TagFormat(begin="<think>", content=AnyTextFormat(), end="</think>") sequence_format = SequenceFormat(elements=[prefix_tag, suffix_tag]) return StructuralTag(format=sequence_format) @_register_builtin_structural_tag("harmony", ["gpt-oss"]) def _get_harmony_structural_tag(input_dict: Dict[str, Any]) -> StructuralTag: """Get harmony(gpt-oss) style structural tag format. Reference: https://developers.openai.com/cookbook/articles/openai-harmony Reference: https://huggingface.co/openai/gpt-oss-120b/blob/main/chat_template.jinja The input_dict should be a dictionary with the following keys: - "tools": a list of tools, each tool should have a "function" key, which is a dictionary containing "name" and "parameters" fields. - "builtin_tools": a list of builtin tools, each builtin tool should have a "function" key, which is a dictionary containing "name" and "parameters" fields. - "reasoning": a boolean indicating whether to enable reasoning mode. - "force_empty_reasoning": a boolean; when reasoning is on, if True use empty-thinking, if False use thinking. Returns ------- StructuralTag A structural tag template. This format is in OpenAI Harmony Response Format, which is used by GPT-oss and other models that follow the same style. """ tools = input_dict.get("tools", []) reasoning = input_dict.get("reasoning", True) force_empty_reasoning = input_dict.get("force_empty_reasoning", False) builtin_tools = input_dict.get("builtin_tools", []) tags = [] if reasoning: if force_empty_reasoning: analysis_tag = TagFormat( begin="<|channel|>analysis<|message|>", content=ConstStringFormat(value="<|end|>"), end="", ) else: analysis_tag = TagFormat( begin="<|channel|>analysis<|message|>", content=AnyTextFormat(), end="<|end|>" ) tags.append(analysis_tag) for tool in tools: if "function" not in tool: continue function = tool["function"] parameters = _get_function_parameters(function) name = function["name"] tags.append( TagFormat( begin=f"<|channel|>commentary to={name}<|constrain|>json<|message|>", content=JSONSchemaFormat(json_schema=parameters), end="<|call|>", ) ) for tool in builtin_tools: if "function" not in tool: continue function = tool["function"] parameters = _get_function_parameters(function) name = function["name"] tags.append( TagFormat( begin=f"<|channel|>analysis to={name}<|message|>", content=JSONSchemaFormat(json_schema=parameters), end="<|call|>", ) ) final_tag = TagFormat( begin="<|channel|>final<|message|>", content=AnyTextFormat(), end="<|end|>" ) tags.append(final_tag) tags_with_separator = TagsWithSeparatorFormat(tags=tags, separator="<|start|>assistant") return StructuralTag(format=tags_with_separator) @_register_builtin_structural_tag("deepseek_v3_2", ["DeepSeek-V3.2"]) def _get_deepseek_v3_2_structural_tag(input_dict: Dict[str, Any]) -> StructuralTag: tools = input_dict.get("tools", []) reasoning = input_dict.get("reasoning", True) force_empty_reasoning = input_dict.get("force_empty_reasoning", False) tags = [] for tool in tools: if "function" not in tool: continue function = tool["function"] parameters = _get_function_parameters(function) name = function["name"] tags.append( TagFormat( begin='<|DSML|invoke name="' + name + '">\n', content=JSONSchemaFormat(json_schema=parameters, style="deepseek_xml"), end="</|DSML|invoke>\n", ) ) # generate function calling triggered tag if len(tags) > 0: function_calling_tags = TagsWithSeparatorFormat( tags=tags, separator="\n", at_least_one=True ) suffix_tag = TriggeredTagsFormat( triggers=["<|DSML|function_calls>"], tags=[ TagFormat( begin="<|DSML|function_calls>\n", content=function_calling_tags, end="</|DSML|function_calls>\n", ) ], excludes=_THINK_EXCLUDE_TOKENS, ) else: suffix_tag = AnyTextFormat(excludes=_THINK_EXCLUDE_TOKENS) if not reasoning: return StructuralTag(format=suffix_tag) if force_empty_reasoning: prefix_tag = ConstStringFormat(value="<think>\n\n</think>") else: prefix_tag = TagFormat(begin="<think>", content=AnyTextFormat(), end="</think>") sequence_format = SequenceFormat(elements=[prefix_tag, suffix_tag]) return StructuralTag(format=sequence_format) @_register_builtin_structural_tag("minimax", ["MiniMax-M2.5"]) def _get_minimax_structural_tag(input_dict: Dict[str, Any]) -> StructuralTag: tools = input_dict.get("tools", []) reasoning = input_dict.get("reasoning", True) force_empty_reasoning = input_dict.get("force_empty_reasoning", False) tags = [] for tool in tools: if "function" not in tool: continue function = tool["function"] parameters = _get_function_parameters(function) name = function["name"] tags.append( TagFormat( begin='<invoke name="' + name + '">\n', content=JSONSchemaFormat(json_schema=parameters, style="minimax_xml"), end="</invoke>\n", ) ) # generate function calling triggered tag if len(tags) > 0: function_calling_tags = TagsWithSeparatorFormat( tags=tags, separator="\n", at_least_one=True ) suffix_tag = TriggeredTagsFormat( triggers=["<minimax:tool_call>"], tags=[ TagFormat( begin="<minimax:tool_call>\n", content=function_calling_tags, end="</minimax:tool_call>\n", ) ], excludes=_THINK_EXCLUDE_TOKENS, ) else: suffix_tag = AnyTextFormat(excludes=_THINK_EXCLUDE_TOKENS) if not reasoning: return StructuralTag(format=suffix_tag) if force_empty_reasoning: prefix_tag = ConstStringFormat(value="<think>\n\n</think>") else: prefix_tag = TagFormat(begin="<think>", content=AnyTextFormat(), end="</think>") sequence_format = SequenceFormat(elements=[prefix_tag, suffix_tag]) return StructuralTag(format=sequence_format) @_register_builtin_structural_tag("glm47", ["GLM-5", "GLM-4.7"]) def _get_glm47_structural_tag(input_dict: Dict[str, Any]) -> StructuralTag: """Get GLM-4.7/GLM-5 style structural tag format. The GLM tool calling format uses XML-like tags: <tool_call>function_name <arg_key>key</arg_key><arg_value>value</arg_value> </tool_call> The input_dict should be a dictionary with the following keys: - "tools": a list of tools, each tool should have a "function" key, which is a dictionary containing "name" and "parameters" fields. - "reasoning": a boolean indicating whether to enable reasoning mode. - "force_empty_reasoning": a boolean; when reasoning is on, if True use empty-thinking, if False use thinking. Returns ------- StructuralTag A structural tag for GLM function calling format. """ tools = input_dict.get("tools", []) reasoning = input_dict.get("reasoning", True) force_empty_reasoning = input_dict.get("force_empty_reasoning", False) tags = [] for tool in tools: if "function" not in tool: continue function = tool["function"] parameters = function["parameters"] name = function["name"] tags.append( TagFormat( begin=f"<tool_call>{name}", content=JSONSchemaFormat(json_schema=parameters, style="glm_xml"), end="</tool_call>", ) ) if len(tags) > 0: suffix_tag = TriggeredTagsFormat( triggers=["<tool_call>"], tags=tags, excludes=_THINK_EXCLUDE_TOKENS ) else: suffix_tag = AnyTextFormat(excludes=_THINK_EXCLUDE_TOKENS) if not reasoning: return StructuralTag(format=suffix_tag) if force_empty_reasoning: prefix_tag = ConstStringFormat(value="<think>\n\n</think>") else: prefix_tag = TagFormat(begin="<think>", content=AnyTextFormat(), end="</think>") return StructuralTag(format=SequenceFormat(elements=[prefix_tag, suffix_tag]))