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, LlamaParser, MinimaxM2Parser, MistralParser,
14 PassthroughParser, PythonicParser, QwenParser, QwenXmlParser, SarashinaParser, Step3Parser,
15 },
16 traits::ToolParser,
17};
18
19pub type PooledParser = Arc<Mutex<Box<dyn ToolParser>>>;
21
22type ParserCreator = Arc<dyn Fn() -> Box<dyn ToolParser> + Send + Sync>;
24
25type BuildStructuralTagFn = Arc<dyn Fn(&[Tool], bool) -> serde_json::Value + Send + Sync>;
28
29#[derive(Debug, Clone)]
31pub enum ToolConstraint {
32 JsonSchema(String),
35 StructuralTag(String),
38}
39
40impl ToolConstraint {
41 pub fn to_tuple(&self) -> (String, String) {
43 match self {
44 ToolConstraint::JsonSchema(s) => ("json_schema".to_string(), s.clone()),
45 ToolConstraint::StructuralTag(s) => ("structural_tag".to_string(), s.clone()),
46 }
47 }
48
49 pub fn is_json_schema(&self) -> bool {
51 matches!(self, ToolConstraint::JsonSchema(_))
52 }
53}
54
55struct ParserEntry {
60 creator: ParserCreator,
61 build_structural_tag: Option<BuildStructuralTagFn>,
62}
63
64#[derive(Clone)]
66pub struct ParserRegistry {
67 entries: Arc<RwLock<HashMap<String, Arc<ParserEntry>>>>,
69 pool: Arc<RwLock<HashMap<String, PooledParser>>>,
71 model_mapping: Arc<RwLock<HashMap<String, String>>>,
73 default_parser: Arc<RwLock<String>>,
75}
76
77impl ParserRegistry {
78 pub fn new() -> Self {
80 Self {
81 entries: Arc::new(RwLock::new(HashMap::new())),
82 pool: Arc::new(RwLock::new(HashMap::new())),
83 model_mapping: Arc::new(RwLock::new(HashMap::new())),
84 default_parser: Arc::new(RwLock::new("passthrough".to_string())),
85 }
86 }
87
88 pub fn register_parser<F>(&self, name: &str, creator: F)
90 where
91 F: Fn() -> Box<dyn ToolParser> + Send + Sync + 'static,
92 {
93 let mut entries = self.entries.write();
94 entries.insert(
95 name.to_string(),
96 Arc::new(ParserEntry {
97 creator: Arc::new(creator),
98 build_structural_tag: None,
99 }),
100 );
101 }
102
103 pub fn register_parser_with_structural_tag<F>(
108 &self,
109 name: &str,
110 creator: F,
111 build_structural_tag: fn(&[Tool], bool) -> serde_json::Value,
112 ) where
113 F: Fn() -> Box<dyn ToolParser> + Send + Sync + 'static,
114 {
115 let mut entries = self.entries.write();
116 entries.insert(
117 name.to_string(),
118 Arc::new(ParserEntry {
119 creator: Arc::new(creator),
120 build_structural_tag: Some(Arc::new(build_structural_tag)),
121 }),
122 );
123 }
124
125 pub fn map_model(&self, model: impl Into<String>, parser: impl Into<String>) {
127 let mut mapping = self.model_mapping.write();
128 mapping.insert(model.into(), parser.into());
129 }
130
131 pub fn get_pooled_parser(&self, name: &str) -> Option<PooledParser> {
134 {
136 let pool = self.pool.read();
137 if let Some(parser) = pool.get(name) {
138 return Some(Arc::clone(parser));
139 }
140 }
141
142 let entries = self.entries.read();
144 if let Some(entry) = entries.get(name) {
145 let parser = Arc::new(Mutex::new((entry.creator)()));
146
147 let mut pool = self.pool.write();
149 pool.insert(name.to_string(), Arc::clone(&parser));
150
151 Some(parser)
152 } else {
153 None
154 }
155 }
156
157 pub fn has_parser(&self, name: &str) -> bool {
159 let entries = self.entries.read();
160 entries.contains_key(name)
161 }
162
163 pub fn create_parser(&self, name: &str) -> Option<Box<dyn ToolParser>> {
166 let entries = self.entries.read();
167 entries.get(name).map(|entry| (entry.creator)())
168 }
169
170 pub fn has_structural_tag(&self, name: &str) -> bool {
172 let entries = self.entries.read();
173 entries
174 .get(name)
175 .is_some_and(|e| e.build_structural_tag.is_some())
176 }
177
178 pub fn has_structural_tag_for_parser(&self, configured: Option<&str>) -> bool {
182 configured.is_some_and(|p| self.has_structural_tag(p))
183 }
184
185 pub fn generate_tool_constraint(
191 &self,
192 configured_parser: Option<&str>,
193 tools: &[Tool],
194 tool_choice: &ToolChoice,
195 ) -> Result<Option<ToolConstraint>, String> {
196 if tools.is_empty() {
197 return Ok(None);
198 }
199
200 let at_least_one = match tool_choice {
201 ToolChoice::Value(ToolChoiceValue::Required) => true,
202 ToolChoice::Function { .. } => true,
203 ToolChoice::AllowedTools { mode, .. } if mode == "required" => true,
204 _ => return Ok(None),
205 };
206
207 if let Some(name) = configured_parser {
209 let entries = self.entries.read();
210 if let Some(entry) = entries.get(name) {
211 if let Some(build_fn) = entry.build_structural_tag.as_ref() {
212 let tag = build_fn(tools, at_least_one);
213 let json_str = serde_json::to_string(&tag)
214 .map_err(|e| format!("Failed to serialize structural tag: {e}"))?;
215 return Ok(Some(ToolConstraint::StructuralTag(json_str)));
216 }
217 }
218 }
219
220 match tool_choice {
222 ToolChoice::Function { .. } => {
223 let params_schema = serde_json::to_string(&tools[0].function.parameters)
224 .map_err(|e| format!("Failed to serialize tool parameters: {e}"))?;
225 Ok(Some(ToolConstraint::JsonSchema(params_schema)))
226 }
227 _ => {
228 let schema = build_required_array_schema(tools)?;
229 Ok(Some(ToolConstraint::JsonSchema(schema)))
230 }
231 }
232 }
233
234 pub fn resolve_model_to_parser(&self, model: &str) -> Option<String> {
237 let mapping = self.model_mapping.read();
238 if let Some(parser_name) = mapping.get(model) {
240 return Some(parser_name.clone());
241 }
242 let model_lower = model.to_lowercase();
245 mapping
246 .iter()
247 .filter_map(|(pattern, parser_name)| {
248 let stem = pattern.strip_suffix('*')?;
249 model_lower
250 .contains(&stem.to_lowercase())
251 .then_some((stem, parser_name))
252 })
253 .max_by_key(|(stem, _)| stem.len())
254 .map(|(_, parser_name)| parser_name.clone())
255 }
256
257 pub fn has_parser_for_model(&self, model: &str) -> bool {
259 self.resolve_model_to_parser(model)
260 .is_some_and(|name| self.has_parser(&name))
261 }
262
263 pub fn create_for_model(&self, model: &str) -> Option<Box<dyn ToolParser>> {
265 let parser_name = self
266 .resolve_model_to_parser(model)
267 .unwrap_or_else(|| self.default_parser.read().clone());
268 self.create_parser(&parser_name)
269 }
270
271 pub fn get_pooled_for_model(&self, model: &str) -> Option<PooledParser> {
273 let parser_name = self
274 .resolve_model_to_parser(model)
275 .unwrap_or_else(|| self.default_parser.read().clone());
276 self.get_pooled_parser(&parser_name)
277 }
278
279 pub fn clear_pool(&self) {
281 let mut pool = self.pool.write();
282 pool.clear();
283 }
284
285 pub fn set_default_parser(&self, name: impl Into<String>) {
287 let mut default = self.default_parser.write();
288 *default = name.into();
289 }
290}
291
292impl Default for ParserRegistry {
293 fn default() -> Self {
294 Self::new()
295 }
296}
297
298#[derive(Clone)]
300pub struct ParserFactory {
301 registry: ParserRegistry,
302}
303
304impl ParserFactory {
305 pub fn new() -> Self {
307 let registry = ParserRegistry::new();
308
309 registry.register_parser("passthrough", || Box::new(PassthroughParser::new()));
311 registry.register_parser("json", || Box::new(JsonParser::new()));
312 registry.register_parser_with_structural_tag(
313 "mistral",
314 || Box::new(MistralParser::new()),
315 MistralParser::build_structural_tag,
316 );
317 registry.register_parser("qwen", || Box::new(QwenParser::new()));
318 registry.register_parser("qwen_xml", || Box::new(QwenXmlParser::new()));
319 registry.register_parser("qwen_coder", || Box::new(QwenXmlParser::new()));
320 registry.register_parser("pythonic", || Box::new(PythonicParser::new()));
321 registry.register_parser("llama", || Box::new(LlamaParser::new()));
322 registry.register_parser("deepseek", || Box::new(DeepSeekParser::new()));
323 registry.register_parser("deepseek31", || Box::new(DeepSeek31Parser::new()));
324 registry.register_parser("deepseek32", || Box::new(DeepSeekDsmlParser::v32()));
325 registry.register_parser("deepseek_v4", || Box::new(DeepSeekDsmlParser::v4()));
326 registry.register_parser("glm45_moe", || Box::new(Glm4MoeParser::glm45()));
327 registry.register_parser("glm47_moe", || Box::new(Glm4MoeParser::glm47()));
328 registry.register_parser("step3", || Box::new(Step3Parser::new()));
329 registry.register_parser("sarashina", || Box::new(SarashinaParser::new()));
330 registry.register_parser_with_structural_tag(
331 "kimik2",
332 || Box::new(KimiK2Parser::new()),
333 KimiK2Parser::build_structural_tag,
334 );
335 registry.register_parser_with_structural_tag(
336 "inkling",
337 || Box::new(InklingParser::new()),
338 InklingParser::build_structural_tag,
339 );
340 registry.register_parser("minimax_m2", || Box::new(MinimaxM2Parser::new()));
341 registry.register_parser("cohere", || Box::new(CohereParser::new()));
342
343 Self::register_default_mappings(®istry);
345
346 Self { registry }
347 }
348
349 fn register_default_mappings(registry: &ParserRegistry) {
350 registry.map_model("gpt-4*", "json");
352 registry.map_model("gpt-3.5*", "json");
353 registry.map_model("gpt-4o*", "json");
354
355 registry.map_model("claude-*", "json");
357
358 registry.map_model("mistral-*", "mistral");
360 registry.map_model("mixtral-*", "mistral");
361
362 registry.map_model("Qwen/Qwen3.5*", "qwen_xml");
365 registry.map_model("Qwen3.5*", "qwen_xml");
366 registry.map_model("qwen3.5*", "qwen_xml");
367 registry.map_model("qwen/qwen3.5*", "qwen_xml");
368 registry.map_model("Qwen/Qwen3-Coder*", "qwen_xml");
369 registry.map_model("Qwen3-Coder*", "qwen_xml");
370 registry.map_model("qwen3-coder*", "qwen_xml");
371 registry.map_model("qwen/qwen3-coder*", "qwen_xml");
372 registry.map_model("qwen*", "qwen");
374 registry.map_model("Qwen*", "qwen");
375
376 registry.map_model("llama-4*", "pythonic");
378 registry.map_model("meta-llama-4*", "pythonic");
379 registry.map_model("llama-3.2*", "llama");
380 registry.map_model("meta-llama-3.2*", "llama");
381 registry.map_model("llama-*", "json");
382 registry.map_model("meta-llama-*", "json");
383
384 registry.map_model("deepseek-v3*", "deepseek");
386 registry.map_model("deepseek-ai/DeepSeek-V3*", "deepseek");
387 registry.map_model("deepseek-v3.1*", "deepseek31");
388 registry.map_model("deepseek-ai/DeepSeek-V3.1*", "deepseek31");
389 registry.map_model("deepseek-v3.2-exp*", "deepseek31");
391 registry.map_model("deepseek-ai/DeepSeek-V3.2-Exp*", "deepseek31");
392 registry.map_model("deepseek-v3.2*", "deepseek32");
394 registry.map_model("deepseek-ai/DeepSeek-V3.2*", "deepseek32");
395 registry.map_model("deepseek-v4*", "deepseek_v4");
397 registry.map_model("deepseek-ai/DeepSeek-V4*", "deepseek_v4");
398 registry.map_model("deepseek-*", "pythonic");
399
400 registry.map_model("glm-4.5*", "glm45_moe");
402 registry.map_model("glm-4.6*", "glm45_moe");
403 registry.map_model("glm-4.7*", "glm47_moe");
404 registry.map_model("glm-5*", "glm47_moe");
405 registry.map_model("glm-*", "json");
406
407 registry.map_model("sarashina*", "sarashina");
409 registry.map_model("Sarashina*", "sarashina");
410
411 registry.map_model("step3*", "step3");
413 registry.map_model("Step-3*", "step3");
414
415 registry.map_model("kimi-k2*", "kimik2");
417 registry.map_model("Kimi-K2*", "kimik2");
418 registry.map_model("moonshot*/Kimi-K2*", "kimik2");
419
420 registry.map_model("inkling*", "inkling");
422 registry.map_model("Inkling*", "inkling");
423
424 registry.map_model("minimax*", "minimax_m2");
426 registry.map_model("MiniMax*", "minimax_m2");
427
428 registry.map_model("command-r*", "cohere");
430 registry.map_model("command-r-plus*", "cohere");
431 registry.map_model("command-a*", "cohere");
432 registry.map_model("c4ai-command*", "cohere");
433 registry.map_model("cohere*", "cohere");
434 registry.map_model("CohereForAI*", "cohere");
435
436 registry.map_model("gemini-*", "json");
438 registry.map_model("palm-*", "json");
439 registry.map_model("gemma-*", "json");
440 }
441
442 #[expect(
446 clippy::expect_used,
447 reason = "passthrough parser is registered in new(); None indicates a bug in registration logic"
448 )]
449 pub fn get_pooled(&self, model_id: &str) -> PooledParser {
450 self.registry
451 .get_pooled_for_model(model_id)
452 .unwrap_or_else(|| {
453 self.registry
455 .get_pooled_parser("passthrough")
456 .expect("Passthrough parser should always be registered")
457 })
458 }
459
460 pub fn registry(&self) -> &ParserRegistry {
462 &self.registry
463 }
464
465 pub fn clear_pool(&self) {
467 self.registry.clear_pool();
468 }
469
470 pub fn get_parser(&self, model_id: &str) -> Option<Arc<dyn ToolParser>> {
473 let parser_type = self
474 .registry
475 .resolve_model_to_parser(model_id)
476 .unwrap_or_else(|| self.registry.default_parser.read().clone());
477 let entries = self.registry.entries.read();
478 entries.get(&parser_type).map(|entry| {
479 let boxed_parser = (entry.creator)();
480 Arc::from(boxed_parser)
481 })
482 }
483
484 pub fn has_parser(&self, name: &str) -> bool {
486 self.registry.entries.read().contains_key(name)
487 }
488
489 pub fn list_parsers(&self) -> Vec<String> {
491 self.registry.entries.read().keys().cloned().collect()
492 }
493}
494
495impl Default for ParserFactory {
496 fn default() -> Self {
497 Self::new()
498 }
499}
500
501fn build_required_array_schema(tools: &[Tool]) -> Result<String, String> {
503 let mut any_of_schemas = Vec::with_capacity(tools.len());
504 for tool in tools {
505 let tool_schema = json!({
506 "properties": {
507 "name": {
508 "type": "string",
509 "enum": [tool.function.name]
510 },
511 "parameters": tool.function.parameters
512 },
513 "required": ["name", "parameters"]
514 });
515 any_of_schemas.push(tool_schema);
516 }
517
518 let mut all_defs: serde_json::Map<String, serde_json::Value> = serde_json::Map::new();
520 for tool in tools {
521 if let serde_json::Value::Object(params) = &tool.function.parameters {
522 if let Some(serde_json::Value::Object(defs)) = params.get("$defs") {
523 for (def_name, def_schema) in defs {
524 if let Some(existing) = all_defs.get(def_name) {
525 if existing != def_schema {
526 return Err(format!(
527 "Tool definition '{def_name}' has multiple conflicting schemas"
528 ));
529 }
530 } else {
531 all_defs.insert(def_name.clone(), def_schema.clone());
532 }
533 }
534 }
535 }
536 }
537
538 let mut array_schema = json!({
539 "type": "array",
540 "minItems": 1,
541 "items": {
542 "type": "object",
543 "anyOf": any_of_schemas
544 }
545 });
546
547 if !all_defs.is_empty() {
548 if let serde_json::Value::Object(ref mut obj) = array_schema {
549 obj.insert("$defs".to_string(), serde_json::Value::Object(all_defs));
550 }
551 }
552
553 serde_json::to_string(&array_schema)
554 .map_err(|e| format!("Failed to serialize tool schema: {e}"))
555}