gglib_core/normalize/
registry.rs1use super::parser::ToolCallParser;
22use super::parsers::{delimited::DelimitedToolCallParser, standard::StandardJsonParser};
23use super::tags;
24use crate::domain::dialect::DialectSpec;
25
26#[must_use]
35pub fn dialect_for_tags(model_tags: &[String]) -> Option<DialectSpec> {
36 for t in model_tags {
37 match t.as_str() {
39 tags::FORMAT_QWEN_XML | tags::FORMAT_HERMES => return Some(DialectSpec::qwen_xml()),
40 _ => {}
41 }
42 }
43 None
44}
45
46#[must_use]
57pub fn get_parser(dialect: Option<&DialectSpec>) -> Box<dyn ToolCallParser> {
58 match dialect {
59 Some(spec) => Box::new(DelimitedToolCallParser::new(spec.clone())),
60 None => Box::new(StandardJsonParser::new()),
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 #[test]
69 fn no_dialect_yields_standard_parser() {
70 let mut p = get_parser(None);
71 let out = p.push_text("hello");
72 assert_eq!(out.forward_text, "hello");
73 }
74
75 #[test]
76 fn qwen_tag_maps_to_the_builtin_spec_and_parses() {
77 let dialect = dialect_for_tags(&[tags::FORMAT_QWEN_XML.to_owned()]);
78 assert_eq!(dialect, Some(DialectSpec::qwen_xml()));
79
80 let mut p = get_parser(dialect.as_ref());
81 let out = p.push_text(r#"<tool_call>{"name":"x","arguments":{}}</tool_call>"#);
82 let f = p.finish();
83 assert_eq!(out.tool_calls.len(), 1);
84 assert!(
85 f.tool_calls.is_empty(),
86 "tool calls flush in push, not finish"
87 );
88 }
89
90 #[test]
94 fn hermes_tag_maps_to_the_builtin_spec() {
95 let dialect = dialect_for_tags(&[tags::FORMAT_HERMES.to_owned()]);
96 assert_eq!(dialect, Some(DialectSpec::qwen_xml()));
97 }
98
99 #[test]
100 fn unknown_tags_yield_no_dialect() {
101 assert_eq!(
102 dialect_for_tags(&["format:does-not-exist".to_owned()]),
103 None
104 );
105 assert_eq!(dialect_for_tags(&[]), None);
106
107 let mut p = get_parser(None);
108 let out = p.push_text("<tool_call>passthrough</tool_call>");
109 assert_eq!(out.forward_text, "<tool_call>passthrough</tool_call>");
110 }
111
112 #[test]
113 fn first_recognised_tag_wins() {
114 let tags_v = vec![
115 "format:does-not-exist".to_owned(),
116 tags::FORMAT_QWEN_XML.to_owned(),
117 ];
118 let dialect = dialect_for_tags(&tags_v);
119 let mut p = get_parser(dialect.as_ref());
120 let out = p.push_text(r#"<tool_call>{"name":"x","arguments":{}}</tool_call>"#);
121 assert_eq!(out.forward_text, "");
122 assert_eq!(out.tool_calls.len(), 1);
123 }
124
125 #[test]
127 fn an_explicit_spec_drives_the_delimited_parser() {
128 let spec = DialectSpec {
129 tool_open: "«TC»".to_owned(),
130 tool_close: "«/TC»".to_owned(),
131 ..DialectSpec::qwen_xml()
132 };
133 let mut p = get_parser(Some(&spec));
134 let out = p.push_text(r#"«TC»{"name":"x","arguments":{}}«/TC»"#);
135 assert_eq!(out.tool_calls.len(), 1);
136 assert_eq!(out.forward_text, "");
137 }
138}