metrale_grammar/structural_tag/format.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2//
3// Structural-tag format AST — port of the `Format` variant family and
4// `StructuralTag` from `cpp/structural_tag.h`.
5//
6// The C++ uses a `std::variant` of ten `*Format` structs plus several
7// `private:` analyzer-only fields (`detected_end_strs_`, `is_unlimited_`).
8// Here that is a single `Format` enum; the analyzer-only fields are
9// public-in-crate so the analyzer pass (`analyzer.rs`) can fill them in,
10// exactly as the C++ `friend class StructuralTagAnalyzer` does.
11
12/// The schema style used to convert a [`Format::JsonSchema`] into EBNF.
13/// Port of `JSONSchemaFormat::style` (`"json"`, `"qwen_xml"`,
14/// `"minimax_xml"`, `"deepseek_xml"`).
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum SchemaStyle {
17 /// Plain JSON schema (`JSONSchemaToEBNF`).
18 Json,
19 /// Qwen XML tool-calling style.
20 QwenXml,
21 /// MiniMax XML tool-calling style.
22 MiniMaxXml,
23 /// DeepSeek XML tool-calling style.
24 DeepSeekXml,
25}
26
27/// One `begin content end` tag. Port of `TagFormat`.
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct TagFormat {
30 /// The begin marker, e.g. `<tool_call>`.
31 pub begin: String,
32 /// The tag body format.
33 pub content: Box<Format>,
34 /// Accepted end markers. Multiple markers => any of them ends the
35 /// tag. May be emptied by the analyzer for unlimited content.
36 pub end: Vec<String>,
37}
38
39/// A structural-tag format node. Port of the `Format` `std::variant`.
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub enum Format {
42 /// A fixed literal string. Port of `ConstStringFormat`.
43 ConstString(String),
44 /// Content conforming to a JSON schema (in the given style).
45 /// Port of `JSONSchemaFormat`.
46 JsonSchema {
47 /// The JSON-schema document, serialized as a JSON string.
48 json_schema: String,
49 /// Conversion style.
50 style: SchemaStyle,
51 },
52 /// Arbitrary text, optionally excluding some substrings. Port of
53 /// `AnyTextFormat`.
54 AnyText {
55 /// Substrings that must not appear in the matched text.
56 excludes: Vec<String>,
57 /// End strings detected by the analyzer from an enclosing tag.
58 detected_end_strs: Vec<String>,
59 },
60 /// Content conforming to a raw EBNF grammar. Port of `GrammarFormat`.
61 Grammar(String),
62 /// Content conforming to a regex pattern. Port of `RegexFormat`.
63 Regex(String),
64 /// An ordered concatenation of sub-formats. Port of `SequenceFormat`.
65 Sequence {
66 /// The element formats.
67 elements: Vec<Format>,
68 /// Set by the analyzer: true if the last element is unlimited.
69 is_unlimited: bool,
70 },
71 /// An alternation of sub-formats. Port of `OrFormat`.
72 Or {
73 /// The alternative formats.
74 elements: Vec<Format>,
75 /// Set by the analyzer: true if (all) elements are unlimited.
76 is_unlimited: bool,
77 },
78 /// A single tag. Port of `TagFormat`.
79 Tag(TagFormat),
80 /// Triggered tag dispatch — the tool-call envelope mechanism.
81 /// Port of `TriggeredTagsFormat`.
82 TriggeredTags {
83 /// Trigger prefixes; each tag's `begin` starts with one trigger.
84 triggers: Vec<String>,
85 /// The tags dispatched to.
86 tags: Vec<TagFormat>,
87 /// Substrings excluded from inter-tag free text.
88 excludes: Vec<String>,
89 /// If true, at least one tag must be produced.
90 at_least_one: bool,
91 /// If true, generation stops after the first tag.
92 stop_after_first: bool,
93 /// End strings detected by the analyzer from an enclosing tag.
94 detected_end_strs: Vec<String>,
95 },
96 /// Tags joined by a separator. Port of `TagsWithSeparatorFormat`.
97 TagsWithSeparator {
98 /// The tags.
99 tags: Vec<TagFormat>,
100 /// The separator string between tags.
101 separator: String,
102 /// If true, at least one tag must be produced.
103 at_least_one: bool,
104 /// If true, generation stops after the first tag.
105 stop_after_first: bool,
106 /// End strings detected by the analyzer from an enclosing tag.
107 detected_end_strs: Vec<String>,
108 },
109}
110
111/// The top-level structural tag. Port of `StructuralTag`.
112#[derive(Debug, Clone, PartialEq, Eq)]
113pub struct StructuralTag {
114 /// The root format.
115 pub format: Format,
116}
117
118/// A single legacy structural-tag item: `begin`, a JSON `schema`
119/// string, and `end`. Port of the public `StructuralTagItem` struct
120/// from `include/xgrammar/grammar.h`.
121///
122/// This is the tool-calling envelope descriptor: each item says
123/// "a tool call starts with `begin`, its arguments conform to
124/// `schema`, and it ends with `end`".
125#[derive(Debug, Clone, PartialEq, Eq)]
126pub struct StructuralTagItem {
127 /// The begin marker of the tag, e.g. `<function=get_weather>`.
128 pub begin: String,
129 /// The JSON-schema document (as a string) for the tag body.
130 pub schema: String,
131 /// The end marker of the tag, e.g. `</function>`.
132 pub end: String,
133}
134
135impl StructuralTagItem {
136 /// Construct a [`StructuralTagItem`].
137 pub fn new(
138 begin: impl Into<String>,
139 schema: impl Into<String>,
140 end: impl Into<String>,
141 ) -> Self {
142 Self {
143 begin: begin.into(),
144 schema: schema.into(),
145 end: end.into(),
146 }
147 }
148}