1use std::{collections::HashMap, sync::Arc};
4
5use openai_protocol::common::{Tool, ToolChoice, ToolChoiceValue};
6use parking_lot::RwLock;
7use serde_json::json;
8use tokio::sync::Mutex;
9
10use crate::{
11 parsers::{
12 CohereParser, DeepSeek31Parser, DeepSeekDsmlParser, DeepSeekParser, Glm4MoeParser,
13 InklingParser, JsonParser, KimiK2Parser, KimiK3Parser, LlamaParser, MinimaxM2Parser,
14 MistralParser, PassthroughParser, PythonicParser, QwenParser, QwenXmlParser,
15 SarashinaParser, Step3Parser,
16 },
17 traits::ToolParser,
18};
19
20pub type PooledParser = Arc<Mutex<Box<dyn ToolParser>>>;
22
23type ParserCreator = Arc<dyn Fn() -> Box<dyn ToolParser> + Send + Sync>;
25
26type BuildStructuralTagFn = Arc<dyn Fn(&[Tool], bool) -> serde_json::Value + Send + Sync>;
29
30#[derive(Debug, Clone)]
32pub enum ToolConstraint {
33 JsonSchema(String),
36 StructuralTag(String),
39}
40
41impl ToolConstraint {
42 pub fn to_tuple(&self) -> (String, String) {
44 match self {
45 ToolConstraint::JsonSchema(s) => ("json_schema".to_string(), s.clone()),
46 ToolConstraint::StructuralTag(s) => ("structural_tag".to_string(), s.clone()),
47 }
48 }
49
50 pub fn is_json_schema(&self) -> bool {
52 matches!(self, ToolConstraint::JsonSchema(_))
53 }
54}
55
56struct ParserEntry {
61 creator: ParserCreator,
62 build_structural_tag: Option<BuildStructuralTagFn>,
63}
64
65#[derive(Clone)]
67pub struct ParserRegistry {
68 entries: Arc<RwLock<HashMap<String, Arc<ParserEntry>>>>,
70 pool: Arc<RwLock<HashMap<String, PooledParser>>>,
72 model_mapping: Arc<RwLock<HashMap<String, String>>>,
74 default_parser: Arc<RwLock<String>>,
76}
77
78impl ParserRegistry {
79 pub fn new() -> Self {
81 Self {
82 entries: Arc::new(RwLock::new(HashMap::new())),
83 pool: Arc::new(RwLock::new(HashMap::new())),
84 model_mapping: Arc::new(RwLock::new(HashMap::new())),
85 default_parser: Arc::new(RwLock::new("passthrough".to_string())),
86 }
87 }
88
89 pub fn register_parser<F>(&self, name: &str, creator: F)
91 where
92 F: Fn() -> Box<dyn ToolParser> + Send + Sync + 'static,
93 {
94 let mut entries = self.entries.write();
95 entries.insert(
96 name.to_string(),
97 Arc::new(ParserEntry {
98 creator: Arc::new(creator),
99 build_structural_tag: None,
100 }),
101 );
102 }
103
104 pub fn register_parser_with_structural_tag<F>(
109 &self,
110 name: &str,
111 creator: F,
112 build_structural_tag: fn(&[Tool], bool) -> serde_json::Value,
113 ) where
114 F: Fn() -> Box<dyn ToolParser> + Send + Sync + 'static,
115 {
116 let mut entries = self.entries.write();
117 entries.insert(
118 name.to_string(),
119 Arc::new(ParserEntry {
120 creator: Arc::new(creator),
121 build_structural_tag: Some(Arc::new(build_structural_tag)),
122 }),
123 );
124 }
125
126 pub fn map_model(&self, model: impl Into<String>, parser: impl Into<String>) {
128 let mut mapping = self.model_mapping.write();
129 mapping.insert(model.into(), parser.into());
130 }
131
132 pub fn get_pooled_parser(&self, name: &str) -> Option<PooledParser> {
135 {
137 let pool = self.pool.read();
138 if let Some(parser) = pool.get(name) {
139 return Some(Arc::clone(parser));
140 }
141 }
142
143 let entries = self.entries.read();
145 if let Some(entry) = entries.get(name) {
146 let parser = Arc::new(Mutex::new((entry.creator)()));
147
148 let mut pool = self.pool.write();
150 pool.insert(name.to_string(), Arc::clone(&parser));
151
152 Some(parser)
153 } else {
154 None
155 }
156 }
157
158 pub fn has_parser(&self, name: &str) -> bool {
160 let entries = self.entries.read();
161 entries.contains_key(name)
162 }
163
164 pub fn create_parser(&self, name: &str) -> Option<Box<dyn ToolParser>> {
167 let entries = self.entries.read();
168 entries.get(name).map(|entry| (entry.creator)())
169 }
170
171 pub fn has_structural_tag(&self, name: &str) -> bool {
173 let entries = self.entries.read();
174 entries
175 .get(name)
176 .is_some_and(|e| e.build_structural_tag.is_some())
177 }
178
179 pub fn has_structural_tag_for_parser(&self, configured: Option<&str>) -> bool {
183 configured.is_some_and(|p| self.has_structural_tag(p))
184 }
185
186 pub fn generate_tool_constraint(
192 &self,
193 configured_parser: Option<&str>,
194 tools: &[Tool],
195 tool_choice: &ToolChoice,
196 ) -> Result<Option<ToolConstraint>, String> {
197 if tools.is_empty() {
198 return Ok(None);
199 }
200
201 let at_least_one = match tool_choice {
202 ToolChoice::Value(ToolChoiceValue::Required) => true,
203 ToolChoice::Function { .. } => true,
204 ToolChoice::AllowedTools { mode, .. } if mode == "required" => true,
205 _ => return Ok(None),
206 };
207
208 if let Some(name) = configured_parser {
210 let entries = self.entries.read();
211 if let Some(entry) = entries.get(name) {
212 if let Some(build_fn) = entry.build_structural_tag.as_ref() {
213 let tag = build_fn(tools, at_least_one);
214 let json_str = serde_json::to_string(&tag)
215 .map_err(|e| format!("Failed to serialize structural tag: {e}"))?;
216 return Ok(Some(ToolConstraint::StructuralTag(json_str)));
217 }
218 }
219 }
220
221 match tool_choice {
223 ToolChoice::Function { .. } => {
224 let params_schema = serde_json::to_string(&tools[0].function.parameters)
225 .map_err(|e| format!("Failed to serialize tool parameters: {e}"))?;
226 Ok(Some(ToolConstraint::JsonSchema(params_schema)))
227 }
228 _ => {
229 let schema = build_required_array_schema(tools)?;
230 Ok(Some(ToolConstraint::JsonSchema(schema)))
231 }
232 }
233 }
234
235 pub fn resolve_model_to_parser(&self, model: &str) -> Option<String> {
238 let mapping = self.model_mapping.read();
239 if let Some(parser_name) = mapping.get(model) {
241 return Some(parser_name.clone());
242 }
243 let model_lower = model.to_lowercase();
246 mapping
247 .iter()
248 .filter_map(|(pattern, parser_name)| {
249 let stem = pattern.strip_suffix('*')?;
250 model_lower
251 .contains(&stem.to_lowercase())
252 .then_some((stem, parser_name))
253 })
254 .max_by_key(|(stem, _)| stem.len())
255 .map(|(_, parser_name)| parser_name.clone())
256 }
257
258 pub fn has_parser_for_model(&self, model: &str) -> bool {
260 self.resolve_model_to_parser(model)
261 .is_some_and(|name| self.has_parser(&name))
262 }
263
264 pub fn create_for_model(&self, model: &str) -> Option<Box<dyn ToolParser>> {
266 let parser_name = self
267 .resolve_model_to_parser(model)
268 .unwrap_or_else(|| self.default_parser.read().clone());
269 self.create_parser(&parser_name)
270 }
271
272 pub fn get_pooled_for_model(&self, model: &str) -> Option<PooledParser> {
274 let parser_name = self
275 .resolve_model_to_parser(model)
276 .unwrap_or_else(|| self.default_parser.read().clone());
277 self.get_pooled_parser(&parser_name)
278 }
279
280 pub fn clear_pool(&self) {
282 let mut pool = self.pool.write();
283 pool.clear();
284 }
285
286 pub fn set_default_parser(&self, name: impl Into<String>) {
288 let mut default = self.default_parser.write();
289 *default = name.into();
290 }
291}
292
293impl Default for ParserRegistry {
294 fn default() -> Self {
295 Self::new()
296 }
297}
298
299#[derive(Clone)]
301pub struct ParserFactory {
302 registry: ParserRegistry,
303}
304
305impl ParserFactory {
306 pub fn new() -> Self {
308 let registry = ParserRegistry::new();
309
310 registry.register_parser("passthrough", || Box::new(PassthroughParser::new()));
312 registry.register_parser("json", || Box::new(JsonParser::new()));
313 registry.register_parser_with_structural_tag(
314 "mistral",
315 || Box::new(MistralParser::new()),
316 MistralParser::build_structural_tag,
317 );
318 registry.register_parser("qwen", || Box::new(QwenParser::new()));
319 registry.register_parser("qwen_xml", || Box::new(QwenXmlParser::new()));
320 registry.register_parser("qwen_coder", || Box::new(QwenXmlParser::new()));
321 registry.register_parser("nemotron", || Box::new(QwenXmlParser::new()));
324 registry.register_parser("pythonic", || Box::new(PythonicParser::new()));
325 registry.register_parser("llama", || Box::new(LlamaParser::new()));
326 registry.register_parser("deepseek", || Box::new(DeepSeekParser::new()));
327 registry.register_parser("deepseek31", || Box::new(DeepSeek31Parser::new()));
328 registry.register_parser("deepseek32", || Box::new(DeepSeekDsmlParser::v32()));
329 registry.register_parser("deepseek_v4", || Box::new(DeepSeekDsmlParser::v4()));
330 registry.register_parser("glm45_moe", || Box::new(Glm4MoeParser::glm45()));
331 registry.register_parser("glm47_moe", || Box::new(Glm4MoeParser::glm47()));
332 registry.register_parser("step3", || Box::new(Step3Parser::new()));
333 registry.register_parser("sarashina", || Box::new(SarashinaParser::new()));
334 registry.register_parser_with_structural_tag(
335 "kimik2",
336 || Box::new(KimiK2Parser::new()),
337 KimiK2Parser::build_structural_tag,
338 );
339 registry.register_parser_with_structural_tag(
340 "kimi_k3",
341 || Box::new(KimiK3Parser::new()),
342 KimiK3Parser::build_structural_tag,
343 );
344 registry.register_parser_with_structural_tag(
345 "inkling",
346 || Box::new(InklingParser::new()),
347 InklingParser::build_structural_tag,
348 );
349 registry.register_parser("minimax_m2", || Box::new(MinimaxM2Parser::new()));
350 registry.register_parser("cohere", || Box::new(CohereParser::new()));
351
352 Self::register_default_mappings(®istry);
354
355 Self { registry }
356 }
357
358 fn register_default_mappings(registry: &ParserRegistry) {
359 registry.map_model("gpt-4*", "json");
361 registry.map_model("gpt-3.5*", "json");
362 registry.map_model("gpt-4o*", "json");
363
364 registry.map_model("claude-*", "json");
366
367 registry.map_model("mistral-*", "mistral");
369 registry.map_model("mixtral-*", "mistral");
370
371 registry.map_model("Qwen/Qwen3.5*", "qwen_xml");
374 registry.map_model("Qwen3.5*", "qwen_xml");
375 registry.map_model("qwen3.5*", "qwen_xml");
376 registry.map_model("qwen/qwen3.5*", "qwen_xml");
377 registry.map_model("Qwen/Qwen3.6*", "qwen_xml");
378 registry.map_model("Qwen3.6*", "qwen_xml");
379 registry.map_model("qwen3.6*", "qwen_xml");
380 registry.map_model("qwen/qwen3.6*", "qwen_xml");
381 registry.map_model("Qwen/Qwen3.8*", "qwen_xml");
382 registry.map_model("Qwen3.8*", "qwen_xml");
383 registry.map_model("qwen3.8*", "qwen_xml");
384 registry.map_model("qwen/qwen3.8*", "qwen_xml");
385 registry.map_model("Qwen/Qwen3-Coder*", "qwen_xml");
386 registry.map_model("Qwen3-Coder*", "qwen_xml");
387 registry.map_model("qwen3-coder*", "qwen_xml");
388 registry.map_model("qwen/qwen3-coder*", "qwen_xml");
389 registry.map_model("qwen*", "qwen");
391 registry.map_model("Qwen*", "qwen");
392
393 registry.map_model("nemotron-3*", "qwen_xml");
400
401 registry.map_model("llama-4*", "pythonic");
403 registry.map_model("meta-llama-4*", "pythonic");
404 registry.map_model("llama-3.2*", "llama");
405 registry.map_model("meta-llama-3.2*", "llama");
406 registry.map_model("llama-*", "json");
407 registry.map_model("meta-llama-*", "json");
408
409 registry.map_model("deepseek-v3*", "deepseek");
411 registry.map_model("deepseek-ai/DeepSeek-V3*", "deepseek");
412 registry.map_model("deepseek-v3.1*", "deepseek31");
413 registry.map_model("deepseek-ai/DeepSeek-V3.1*", "deepseek31");
414 registry.map_model("deepseek-v3.2-exp*", "deepseek31");
416 registry.map_model("deepseek-ai/DeepSeek-V3.2-Exp*", "deepseek31");
417 registry.map_model("deepseek-v3.2*", "deepseek32");
419 registry.map_model("deepseek-ai/DeepSeek-V3.2*", "deepseek32");
420 registry.map_model("deepseek-v4*", "deepseek_v4");
422 registry.map_model("deepseek-ai/DeepSeek-V4*", "deepseek_v4");
423 registry.map_model("deepseek-*", "pythonic");
424
425 registry.map_model("glm-4.5*", "glm45_moe");
427 registry.map_model("glm-4.6*", "glm45_moe");
428 registry.map_model("glm-4.7*", "glm47_moe");
429 registry.map_model("glm-5*", "glm47_moe");
430 registry.map_model("glm-*", "json");
431
432 registry.map_model("sarashina*", "sarashina");
434 registry.map_model("Sarashina*", "sarashina");
435
436 registry.map_model("step3*", "step3");
438 registry.map_model("Step-3*", "step3");
439
440 registry.map_model("kimi-k2*", "kimik2");
442 registry.map_model("Kimi-K2*", "kimik2");
443 registry.map_model("moonshot*/Kimi-K2*", "kimik2");
444 registry.map_model("kimi-k3*", "kimi_k3");
445 registry.map_model("Kimi-K3*", "kimi_k3");
446 registry.map_model("moonshot*/Kimi-K3*", "kimi_k3");
447 registry.map_model("kimi_k3*", "kimi_k3");
451 registry.map_model("Kimi_K3*", "kimi_k3");
452 registry.map_model("moonshot*/Kimi_K3*", "kimi_k3");
453
454 registry.map_model("inkling*", "inkling");
456 registry.map_model("Inkling*", "inkling");
457
458 registry.map_model("minimax*", "minimax_m2");
460 registry.map_model("MiniMax*", "minimax_m2");
461
462 registry.map_model("command-r*", "cohere");
464 registry.map_model("command-r-plus*", "cohere");
465 registry.map_model("command-a*", "cohere");
466 registry.map_model("c4ai-command*", "cohere");
467 registry.map_model("cohere*", "cohere");
468 registry.map_model("CohereForAI*", "cohere");
469
470 registry.map_model("gemini-*", "json");
472 registry.map_model("palm-*", "json");
473 registry.map_model("gemma-*", "json");
474 }
475
476 #[expect(
480 clippy::expect_used,
481 reason = "passthrough parser is registered in new(); None indicates a bug in registration logic"
482 )]
483 pub fn get_pooled(&self, model_id: &str) -> PooledParser {
484 self.registry
485 .get_pooled_for_model(model_id)
486 .unwrap_or_else(|| {
487 self.registry
489 .get_pooled_parser("passthrough")
490 .expect("Passthrough parser should always be registered")
491 })
492 }
493
494 pub fn registry(&self) -> &ParserRegistry {
496 &self.registry
497 }
498
499 pub fn clear_pool(&self) {
501 self.registry.clear_pool();
502 }
503
504 pub fn get_parser(&self, model_id: &str) -> Option<Arc<dyn ToolParser>> {
507 let parser_type = self
508 .registry
509 .resolve_model_to_parser(model_id)
510 .unwrap_or_else(|| self.registry.default_parser.read().clone());
511 let entries = self.registry.entries.read();
512 entries.get(&parser_type).map(|entry| {
513 let boxed_parser = (entry.creator)();
514 Arc::from(boxed_parser)
515 })
516 }
517
518 pub fn has_parser(&self, name: &str) -> bool {
520 self.registry.entries.read().contains_key(name)
521 }
522
523 pub fn list_parsers(&self) -> Vec<String> {
525 self.registry.entries.read().keys().cloned().collect()
526 }
527}
528
529impl Default for ParserFactory {
530 fn default() -> Self {
531 Self::new()
532 }
533}
534
535fn build_required_array_schema(tools: &[Tool]) -> Result<String, String> {
537 let mut any_of_schemas = Vec::with_capacity(tools.len());
538 for tool in tools {
539 let tool_schema = json!({
540 "properties": {
541 "name": {
542 "type": "string",
543 "enum": [tool.function.name]
544 },
545 "parameters": tool.function.parameters
546 },
547 "required": ["name", "parameters"]
548 });
549 any_of_schemas.push(tool_schema);
550 }
551
552 let mut all_defs: serde_json::Map<String, serde_json::Value> = serde_json::Map::new();
554 for tool in tools {
555 if let serde_json::Value::Object(params) = &tool.function.parameters {
556 if let Some(serde_json::Value::Object(defs)) = params.get("$defs") {
557 for (def_name, def_schema) in defs {
558 if let Some(existing) = all_defs.get(def_name) {
559 if existing != def_schema {
560 return Err(format!(
561 "Tool definition '{def_name}' has multiple conflicting schemas"
562 ));
563 }
564 } else {
565 all_defs.insert(def_name.clone(), def_schema.clone());
566 }
567 }
568 }
569 }
570 }
571
572 let mut array_schema = json!({
573 "type": "array",
574 "minItems": 1,
575 "items": {
576 "type": "object",
577 "anyOf": any_of_schemas
578 }
579 });
580
581 if !all_defs.is_empty() {
582 if let serde_json::Value::Object(ref mut obj) = array_schema {
583 obj.insert("$defs".to_string(), serde_json::Value::Object(all_defs));
584 }
585 }
586
587 serde_json::to_string(&array_schema)
588 .map_err(|e| format!("Failed to serialize tool schema: {e}"))
589}