[Rust Frontend] Use the MiMo structural-tag builder (#59148)
Signed-off-by: Bugen Zhao <i@bugenzhao.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Bugen Zhao committed
Sep 30, 2026 at 09:34 UTC
8039d3cbfd55be2aa172d544042c53b4e966369d
4 files changed
+34
-5
rust/Cargo.lock
+2
-2
@@ -6605,9 +6605,9 @@ checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
6605
6606
[[package]]
6607
name = "xgrammar-structural-tag"
6608
-version = "0.3.0+xgrammar.0.2.7.82505d0"
6608
+version = "0.4.1+xgrammar.0.2.8.9778737"
6609
source = "registry+https://github.com/rust-lang/crates.io-index"
6610
-checksum = "3864005575c2ab76057f789898f59fb9fa1378d07c5ceabc24764e012d0e90bb"
6610
+checksum = "06637fe6ff753ef929b4cea2933b9432f78335255c6931dc748957122b9b492c"
6611
dependencies = [
6612
"auto_impl",
6613
"serde",
rust/Cargo.toml
+1
-1
@@ -153,7 +153,7 @@ vllm-text = { path = "src/text" }
153
vllm-tokenizer = { path = "src/tokenizer" }
154
vllm-tracing = { path = "src/tracing" }
155
winnow = { version = "1.0.2", features = ["simd"] }
156
-xgrammar-structural-tag = "0.3.0"
156
+xgrammar-structural-tag = "0.4.1"
157
zeromq = { version = "0.6.0", default-features = false, features = [
158
"tokio-runtime",
159
"all-transport",
rust/src/parser/src/tool/mimo.rs
+30
-1
@@ -26,7 +26,8 @@ impl ToolParser for MiMoToolParser {
26
}
27
28
fn structural_tag_builder(&self) -> Option<&dyn StructuralTagBuilder> {
29
- self.inner.structural_tag_builder()
29
+ // MiMo's template emits compact tags without the newlines Qwen3-Coder uses.
30
+ Some(xgrammar_structural_tag::Model::Mimo.builder())
31
}
32
33
fn parse_into(&mut self, chunk: &str, output: &mut ToolParserOutput) -> Result<()> {
@@ -88,4 +89,32 @@ mod tests {
89
);
90
}
91
}
92
+
93
+ #[test]
94
+ fn structural_tag_uses_compact_mimo_tags() {
95
+ use xgrammar_structural_tag::builders::StructuralTagOptions;
96
+ use xgrammar_structural_tag::{
97
+ FunctionDefinition, FunctionToolParam, ToolChoice, ToolParam, build_structural_tag,
98
+ };
99
+
100
+ let parser = MiMoToolParser::create(&[]).unwrap();
101
+ let builder = parser.structural_tag_builder().expect("MiMo exposes a builder");
102
+ let tools = [ToolParam::Function(FunctionToolParam::new(
103
+ FunctionDefinition::new("get_weather"),
104
+ ))];
105
+ let tag = build_structural_tag(
106
+ builder,
107
+ &tools,
108
+ ToolChoice::required(),
109
+ StructuralTagOptions::default().with_reasoning(false),
110
+ )
111
+ .unwrap();
112
+ let value = serde_json::to_value(tag).unwrap();
113
+ assert_eq!(value["format"]["triggers"], json!(["<tool_call>"]));
114
+ assert_eq!(
115
+ value["format"]["tags"][0]["begin"],
116
+ "<tool_call><function=get_weather>"
117
+ );
118
+ assert_eq!(value["format"]["tags"][0]["end"], "</function></tool_call>");
119
+ }
120
}
rust/src/parser/src/unified/kimi_k3/structural_tag.rs
+1
-1
@@ -395,7 +395,7 @@ mod tests {
395
)
396
.unwrap();
397
398
- expect![[r##"{"type":"structural_tag","format":{"type":"sequence","elements":[{"type":"optional","content":{"type":"const_string","value":"<|open|>response<|sep|>"}},{"type":"tag","begin":"","content":{"type":"any_text","excludes":["<|open|>","<|close|>","<|end_of_msg|>"],"max_tokens":null,"max_chars":null},"end":"<|close|>response<|sep|>"},{"type":"tag","begin":"<|open|>tools<|sep|>","content":{"type":"tags_with_separator","tags":[{"type":"tag","begin":"<|open|>call tool=\"get_weather\" index=\"","content":{"type":"sequence","elements":[{"type":"regex","pattern":"[1-9][0-9]*"},{"type":"const_string","value":"\"<|sep|>"},{"type":"or","elements":[{"type":"plus","content":{"type":"or","elements":[{"type":"tag","begin":"<|open|>argument key=\"unit\" type=\"string\"<|sep|>","content":{"type":"or","elements":[{"type":"const_string","value":"celsius"},{"type":"const_string","value":"fahrenheit"}]},"end":"<|close|>argument<|sep|>"},{"type":"tag","begin":"<|open|>argument key=\"place\" type=\"object\"<|sep|>","content":{"type":"json_schema","json_schema":{"$ref":"#/$defs/place","type":"object","$defs":{"place":{"type":"object","properties":{"city":{"type":"string"}}}}},"style":"json","any_order":false,"max_whitespace_cnt":null},"end":"<|close|>argument<|sep|>"}]}},{"type":"tag","begin":"<|open|>json type=\"object\"<|sep|>","content":{"type":"json_schema","json_schema":{"$defs":{"place":{"type":"object","properties":{"city":{"type":"string"}}}},"type":"object","properties":{"unit":{"type":"string","enum":["celsius","fahrenheit"]},"place":{"$ref":"#/$defs/place","type":"object"}},"required":["place"]},"style":"json","any_order":false,"max_whitespace_cnt":null},"end":"<|close|>json<|sep|>"}]}]},"end":"<|close|>call<|sep|>"}],"separator":"","at_least_one":true,"stop_after_first":false},"end":"<|close|>tools<|sep|>"},{"type":"optional","content":{"type":"const_string","value":"<|close|>message<|sep|>"}}]}}"##]].assert_eq(&tag.to_json_string().unwrap());
398
+ expect![[r##"{"type":"structural_tag","format":{"type":"sequence","elements":[{"type":"optional","content":{"type":"const_string","value":"<|open|>response<|sep|>"}},{"type":"tag","begin":"","content":{"type":"any_text","excludes":["<|open|>","<|close|>","<|end_of_msg|>"],"max_tokens":null,"max_chars":null},"end":"<|close|>response<|sep|>"},{"type":"tag","begin":"<|open|>tools<|sep|>","content":{"type":"tags_with_separator","tags":[{"type":"tag","begin":"<|open|>call tool=\"get_weather\" index=\"","content":{"type":"sequence","elements":[{"type":"regex","pattern":"[1-9][0-9]*"},{"type":"const_string","value":"\"<|sep|>"},{"type":"or","elements":[{"type":"plus","content":{"type":"or","elements":[{"type":"tag","begin":"<|open|>argument key=\"unit\" type=\"string\"<|sep|>","content":{"type":"or","elements":[{"type":"const_string","value":"celsius"},{"type":"const_string","value":"fahrenheit"}]},"end":"<|close|>argument<|sep|>"},{"type":"tag","begin":"<|open|>argument key=\"place\" type=\"object\"<|sep|>","content":{"type":"json_schema","json_schema":{"$ref":"#/$defs/place","type":"object","$defs":{"place":{"type":"object","properties":{"city":{"type":"string"}}}}},"style":"json","any_order":false,"max_whitespace_cnt":null,"excludes":[]},"end":"<|close|>argument<|sep|>"}]}},{"type":"tag","begin":"<|open|>json type=\"object\"<|sep|>","content":{"type":"json_schema","json_schema":{"$defs":{"place":{"type":"object","properties":{"city":{"type":"string"}}}},"type":"object","properties":{"unit":{"type":"string","enum":["celsius","fahrenheit"]},"place":{"$ref":"#/$defs/place","type":"object"}},"required":["place"]},"style":"json","any_order":false,"max_whitespace_cnt":null,"excludes":[]},"end":"<|close|>json<|sep|>"}]}]},"end":"<|close|>call<|sep|>"}],"separator":"","at_least_one":true,"stop_after_first":false},"end":"<|close|>tools<|sep|>"},{"type":"optional","content":{"type":"const_string","value":"<|close|>message<|sep|>"}}]}}"##]].assert_eq(&tag.to_json_string().unwrap());
399
}
400
401
#[test]