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]))