1use std::{collections::HashMap, sync::Arc};
4
5use openai_protocol::common::{Tool, ToolChoice};
6use parking_lot::RwLock;
7use serde_json::json;
8use tokio::sync::Mutex;
9
10use crate::{
11 parsers::{
12 CohereParser, DeepSeek31Parser, DeepSeekDsmlParser, DeepSeekParser, Gemma4Parser,
13 Glm4MoeParser, HyV4Parser, InklingParser, JsonParser, KimiK2Parser, KimiK3Parser,
14 LlamaParser, MinimaxM2Parser, MinimaxM3Parser, MistralParser, PassthroughParser,
15 PythonicParser, QwenParser, QwenXmlParser, 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
30type ReasoningPrefixFn = fn(GrammarStart) -> Option<serde_json::Value>;
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
48pub enum GrammarStart {
49 #[default]
53 Unknown,
54 AfterReasoning,
58 FirstToken,
62}
63
64impl GrammarStart {
65 pub const LABEL: &'static str = "constrained_decoding_start";
67 pub const AFTER_REASONING: &'static str = "after_reasoning";
69 pub const FIRST_TOKEN: &'static str = "first_token";
71
72 pub fn from_label(value: Option<&str>) -> Self {
75 match value {
76 Some(Self::AFTER_REASONING) => Self::AfterReasoning,
77 Some(Self::FIRST_TOKEN) => Self::FirstToken,
78 _ => Self::Unknown,
79 }
80 }
81}
82
83#[derive(Debug, Clone)]
85pub enum ToolConstraint {
86 JsonSchema(String),
89 StructuralTag(String),
92}
93
94impl ToolConstraint {
95 pub fn to_tuple(&self) -> (String, String) {
97 match self {
98 ToolConstraint::JsonSchema(s) => ("json_schema".to_string(), s.clone()),
99 ToolConstraint::StructuralTag(s) => ("structural_tag".to_string(), s.clone()),
100 }
101 }
102
103 pub fn is_json_schema(&self) -> bool {
105 matches!(self, ToolConstraint::JsonSchema(_))
106 }
107}
108
109struct ParserEntry {
114 creator: ParserCreator,
115 build_structural_tag: Option<BuildStructuralTagFn>,
116 reasoning_prefix: Option<ReasoningPrefixFn>,
117}
118
119#[derive(Clone)]
121pub struct ParserRegistry {
122 entries: Arc<RwLock<HashMap<String, Arc<ParserEntry>>>>,
124 pool: Arc<RwLock<HashMap<String, PooledParser>>>,
126 model_mapping: Arc<RwLock<HashMap<String, String>>>,
128 default_parser: Arc<RwLock<String>>,
130}
131
132impl ParserRegistry {
133 pub fn new() -> Self {
135 Self {
136 entries: Arc::new(RwLock::new(HashMap::new())),
137 pool: Arc::new(RwLock::new(HashMap::new())),
138 model_mapping: Arc::new(RwLock::new(HashMap::new())),
139 default_parser: Arc::new(RwLock::new("passthrough".to_string())),
140 }
141 }
142
143 pub fn register_parser<F>(&self, name: &str, creator: F)
145 where
146 F: Fn() -> Box<dyn ToolParser> + Send + Sync + 'static,
147 {
148 let mut entries = self.entries.write();
149 entries.insert(
150 name.to_string(),
151 Arc::new(ParserEntry {
152 creator: Arc::new(creator),
153 build_structural_tag: None,
154 reasoning_prefix: None,
155 }),
156 );
157 }
158
159 pub fn register_parser_with_structural_tag<F>(
164 &self,
165 name: &str,
166 creator: F,
167 build_structural_tag: fn(&[Tool], bool) -> serde_json::Value,
168 ) where
169 F: Fn() -> Box<dyn ToolParser> + Send + Sync + 'static,
170 {
171 let mut entries = self.entries.write();
172 entries.insert(
173 name.to_string(),
174 Arc::new(ParserEntry {
175 creator: Arc::new(creator),
176 build_structural_tag: Some(Arc::new(build_structural_tag)),
177 reasoning_prefix: None,
178 }),
179 );
180 }
181
182 pub fn register_reasoning_prefix(&self, name: &str, reasoning_prefix: ReasoningPrefixFn) {
197 let mut entries = self.entries.write();
198 let Some(existing) = entries.get(name).map(Arc::clone) else {
199 debug_assert!(false, "reasoning prefix for unregistered parser {name}");
200 return;
201 };
202 debug_assert!(
203 existing.build_structural_tag.is_some(),
204 "reasoning prefix for parser {name} without a structural tag"
205 );
206 entries.insert(
207 name.to_string(),
208 Arc::new(ParserEntry {
209 creator: Arc::clone(&existing.creator),
210 build_structural_tag: existing.build_structural_tag.clone(),
211 reasoning_prefix: Some(reasoning_prefix),
212 }),
213 );
214 }
215
216 pub fn has_reasoning_prefix(&self, configured: Option<&str>) -> bool {
220 configured.is_some_and(|name| {
221 self.entries
222 .read()
223 .get(name)
224 .is_some_and(|entry| entry.reasoning_prefix.is_some())
225 })
226 }
227
228 pub fn structural_tag_for_grammar_start(
238 &self,
239 configured_parser: Option<&str>,
240 tag: &str,
241 start: GrammarStart,
242 ) -> Result<Option<String>, String> {
243 if start == GrammarStart::Unknown {
244 return Ok(None);
245 }
246 let Some(prefix) = configured_parser.and_then(|name| {
247 self.entries
248 .read()
249 .get(name)
250 .and_then(|entry| entry.reasoning_prefix)
251 }) else {
252 return Ok(None);
253 };
254 let mut tag: serde_json::Value = serde_json::from_str(tag)
255 .map_err(|e| format!("Failed to parse structural tag: {e}"))?;
256 let Some(format) = tag.get_mut("format") else {
257 return Ok(None);
258 };
259 let wrapped = format["type"] == "sequence"
260 && format["elements"]
261 .as_array()
262 .is_some_and(|elements| elements.len() == 2)
263 && prefix(GrammarStart::Unknown)
264 .is_some_and(|unknown| format["elements"][0] == unknown);
265 if !wrapped {
266 return Ok(None);
267 }
268 let calls = format["elements"][1].take();
269 let shaped = match prefix(start) {
270 Some(prefix) => json!({
271 "format": {
272 "type": "sequence",
273 "elements": [prefix, calls],
274 }
275 }),
276 None => json!({ "format": calls }),
277 };
278 serde_json::to_string(&shaped)
279 .map(Some)
280 .map_err(|e| format!("Failed to serialize structural tag: {e}"))
281 }
282
283 pub fn map_model(&self, model: impl Into<String>, parser: impl Into<String>) {
285 let mut mapping = self.model_mapping.write();
286 mapping.insert(model.into(), parser.into());
287 }
288
289 pub fn get_pooled_parser(&self, name: &str) -> Option<PooledParser> {
292 {
294 let pool = self.pool.read();
295 if let Some(parser) = pool.get(name) {
296 return Some(Arc::clone(parser));
297 }
298 }
299
300 let entries = self.entries.read();
302 if let Some(entry) = entries.get(name) {
303 let parser = Arc::new(Mutex::new((entry.creator)()));
304
305 let mut pool = self.pool.write();
307 pool.insert(name.to_string(), Arc::clone(&parser));
308
309 Some(parser)
310 } else {
311 None
312 }
313 }
314
315 pub fn has_parser(&self, name: &str) -> bool {
317 let entries = self.entries.read();
318 entries.contains_key(name)
319 }
320
321 pub fn create_parser(&self, name: &str) -> Option<Box<dyn ToolParser>> {
324 let entries = self.entries.read();
325 entries.get(name).map(|entry| (entry.creator)())
326 }
327
328 pub fn has_structural_tag(&self, name: &str) -> bool {
330 let entries = self.entries.read();
331 entries
332 .get(name)
333 .is_some_and(|e| e.build_structural_tag.is_some())
334 }
335
336 pub fn has_structural_tag_for_parser(&self, configured: Option<&str>) -> bool {
340 configured.is_some_and(|p| self.has_structural_tag(p))
341 }
342
343 pub fn uses_native_tool_format(&self, configured: Option<&str>) -> bool {
347 self.has_structural_tag_for_parser(configured)
348 || configured.is_some_and(|p| p == "gemma4" && self.has_parser(p))
349 }
350
351 pub fn generate_tool_constraint(
364 &self,
365 configured_parser: Option<&str>,
366 tools: &[Tool],
367 tool_choice: &ToolChoice,
368 reasoning: bool,
369 ) -> Result<Option<ToolConstraint>, String> {
370 if tools.is_empty() {
371 return Ok(None);
372 }
373
374 if !tool_choice.forces_call() {
375 return Ok(None);
376 }
377
378 if configured_parser == Some("gemma4") && self.has_parser("gemma4") {
381 return Ok(None);
382 }
383
384 if let Some(name) = configured_parser {
386 let entries = self.entries.read();
387 if let Some(entry) = entries.get(name) {
388 if let Some(build_fn) = entry.build_structural_tag.as_ref() {
389 let mut tag = build_fn(tools, true);
391 if let (true, Some(prefix)) = (reasoning, entry.reasoning_prefix) {
392 if let Some(prefix) = prefix(GrammarStart::Unknown) {
393 tag = wrap_in_reasoning_prefix(tag, prefix)?;
394 }
395 }
396 let json_str = serde_json::to_string(&tag)
397 .map_err(|e| format!("Failed to serialize structural tag: {e}"))?;
398 return Ok(Some(ToolConstraint::StructuralTag(json_str)));
399 }
400 }
401 }
402
403 match tool_choice {
405 ToolChoice::Function { .. } => {
406 let params_schema = serde_json::to_string(&tools[0].function.parameters)
407 .map_err(|e| format!("Failed to serialize tool parameters: {e}"))?;
408 Ok(Some(ToolConstraint::JsonSchema(params_schema)))
409 }
410 _ => {
411 let schema = build_required_array_schema(tools)?;
412 Ok(Some(ToolConstraint::JsonSchema(schema)))
413 }
414 }
415 }
416
417 pub fn resolve_model_to_parser(&self, model: &str) -> Option<String> {
420 let mapping = self.model_mapping.read();
421 if let Some(parser_name) = mapping.get(model) {
423 return Some(parser_name.clone());
424 }
425 let model_lower = model.to_lowercase();
428 mapping
429 .iter()
430 .filter_map(|(pattern, parser_name)| {
431 let stem = pattern.strip_suffix('*')?;
432 model_lower
433 .contains(&stem.to_lowercase())
434 .then_some((stem, parser_name))
435 })
436 .max_by_key(|(stem, _)| stem.len())
437 .map(|(_, parser_name)| parser_name.clone())
438 }
439
440 pub fn has_parser_for_model(&self, model: &str) -> bool {
442 self.resolve_model_to_parser(model)
443 .is_some_and(|name| self.has_parser(&name))
444 }
445
446 pub fn create_for_model(&self, model: &str) -> Option<Box<dyn ToolParser>> {
448 let parser_name = self
449 .resolve_model_to_parser(model)
450 .unwrap_or_else(|| self.default_parser.read().clone());
451 self.create_parser(&parser_name)
452 }
453
454 pub fn get_pooled_for_model(&self, model: &str) -> Option<PooledParser> {
456 let parser_name = self
457 .resolve_model_to_parser(model)
458 .unwrap_or_else(|| self.default_parser.read().clone());
459 self.get_pooled_parser(&parser_name)
460 }
461
462 pub fn clear_pool(&self) {
464 let mut pool = self.pool.write();
465 pool.clear();
466 }
467
468 pub fn set_default_parser(&self, name: impl Into<String>) {
470 let mut default = self.default_parser.write();
471 *default = name.into();
472 }
473}
474
475impl Default for ParserRegistry {
476 fn default() -> Self {
477 Self::new()
478 }
479}
480
481#[derive(Clone)]
483pub struct ParserFactory {
484 registry: ParserRegistry,
485}
486
487impl ParserFactory {
488 pub fn new() -> Self {
490 let registry = ParserRegistry::new();
491
492 registry.register_parser("passthrough", || Box::new(PassthroughParser::new()));
494 registry.register_parser("json", || Box::new(JsonParser::new()));
495 registry.register_parser("gemma4", || Box::new(Gemma4Parser::new()));
496 registry.register_parser_with_structural_tag(
497 "mistral",
498 || Box::new(MistralParser::new()),
499 MistralParser::build_structural_tag,
500 );
501 registry.register_parser("qwen", || Box::new(QwenParser::new()));
502 registry.register_parser("qwen_xml", || Box::new(QwenXmlParser::new()));
503 registry.register_parser("qwen_coder", || Box::new(QwenXmlParser::new()));
504 registry.register_parser("nemotron", || Box::new(QwenXmlParser::new()));
507 registry.register_parser("pythonic", || Box::new(PythonicParser::new()));
508 registry.register_parser("llama", || Box::new(LlamaParser::new()));
509 registry.register_parser("deepseek", || Box::new(DeepSeekParser::new()));
510 registry.register_parser("deepseek31", || Box::new(DeepSeek31Parser::new()));
511 registry.register_parser("deepseek32", || Box::new(DeepSeekDsmlParser::v32()));
512 registry.register_parser("deepseek_v4", || Box::new(DeepSeekDsmlParser::v4()));
513 registry.register_parser_with_structural_tag(
514 "deepseek_v41",
515 || Box::new(DeepSeekDsmlParser::v41()),
516 DeepSeekDsmlParser::build_v41_structural_tag,
517 );
518 registry.register_parser("glm45_moe", || Box::new(Glm4MoeParser::glm45()));
519 registry.register_parser_with_structural_tag(
520 "glm47_moe",
521 || Box::new(Glm4MoeParser::glm47()),
522 Glm4MoeParser::build_structural_tag,
523 );
524 registry.register_reasoning_prefix("glm47_moe", Glm4MoeParser::reasoning_prefix);
525 registry.register_parser("step3", || Box::new(Step3Parser::new()));
526 registry.register_parser("sarashina", || Box::new(SarashinaParser::new()));
527 registry.register_parser_with_structural_tag(
528 "kimik2",
529 || Box::new(KimiK2Parser::new()),
530 KimiK2Parser::build_structural_tag,
531 );
532 registry.register_parser_with_structural_tag(
533 "kimi_k3",
534 || Box::new(KimiK3Parser::new()),
535 KimiK3Parser::build_structural_tag,
536 );
537 registry.register_parser_with_structural_tag(
538 "inkling",
539 || Box::new(InklingParser::new()),
540 InklingParser::build_structural_tag,
541 );
542 registry.register_parser("minimax_m2", || Box::new(MinimaxM2Parser::new()));
543 registry.register_parser_with_structural_tag(
544 "minimax_m3",
545 || Box::new(MinimaxM3Parser::new()),
546 MinimaxM3Parser::build_structural_tag,
547 );
548 registry.register_reasoning_prefix("minimax_m3", MinimaxM3Parser::reasoning_prefix);
549 registry.register_parser_with_structural_tag(
550 "hy_v4",
551 || Box::new(HyV4Parser::new()),
552 HyV4Parser::build_structural_tag,
553 );
554 registry.register_reasoning_prefix("hy_v4", HyV4Parser::reasoning_prefix);
555 registry.register_parser("cohere", || Box::new(CohereParser::new()));
556
557 Self::register_default_mappings(®istry);
559
560 Self { registry }
561 }
562
563 fn register_default_mappings(registry: &ParserRegistry) {
564 registry.map_model("gpt-4*", "json");
566 registry.map_model("gpt-3.5*", "json");
567 registry.map_model("gpt-4o*", "json");
568
569 registry.map_model("claude-*", "json");
571
572 registry.map_model("mistral-*", "mistral");
574 registry.map_model("mixtral-*", "mistral");
575
576 registry.map_model("Qwen/Qwen3.5*", "qwen_xml");
579 registry.map_model("Qwen3.5*", "qwen_xml");
580 registry.map_model("qwen3.5*", "qwen_xml");
581 registry.map_model("qwen/qwen3.5*", "qwen_xml");
582 registry.map_model("Qwen/Qwen3.6*", "qwen_xml");
583 registry.map_model("Qwen3.6*", "qwen_xml");
584 registry.map_model("qwen3.6*", "qwen_xml");
585 registry.map_model("qwen/qwen3.6*", "qwen_xml");
586 registry.map_model("Qwen/Qwen3.8*", "qwen_xml");
587 registry.map_model("Qwen3.8*", "qwen_xml");
588 registry.map_model("qwen3.8*", "qwen_xml");
589 registry.map_model("qwen/qwen3.8*", "qwen_xml");
590 registry.map_model("Qwen/Qwen3-Coder*", "qwen_xml");
591 registry.map_model("Qwen3-Coder*", "qwen_xml");
592 registry.map_model("qwen3-coder*", "qwen_xml");
593 registry.map_model("qwen/qwen3-coder*", "qwen_xml");
594 registry.map_model("qwen*", "qwen");
596 registry.map_model("Qwen*", "qwen");
597
598 registry.map_model("nemotron-3*", "qwen_xml");
605
606 registry.map_model("llama-4*", "pythonic");
608 registry.map_model("meta-llama-4*", "pythonic");
609 registry.map_model("llama-3.2*", "llama");
610 registry.map_model("meta-llama-3.2*", "llama");
611 registry.map_model("llama-*", "json");
612 registry.map_model("meta-llama-*", "json");
613
614 registry.map_model("deepseek-v3*", "deepseek");
616 registry.map_model("deepseek-ai/DeepSeek-V3*", "deepseek");
617 registry.map_model("deepseek-v3.1*", "deepseek31");
618 registry.map_model("deepseek-ai/DeepSeek-V3.1*", "deepseek31");
619 registry.map_model("deepseek-v3.2-exp*", "deepseek31");
621 registry.map_model("deepseek-ai/DeepSeek-V3.2-Exp*", "deepseek31");
622 registry.map_model("deepseek-v3.2*", "deepseek32");
624 registry.map_model("deepseek-ai/DeepSeek-V3.2*", "deepseek32");
625 registry.map_model("deepseek-v4*", "deepseek_v4");
627 registry.map_model("deepseek-ai/DeepSeek-V4*", "deepseek_v4");
628 registry.map_model("deepseek-v4.1*", "deepseek_v41");
630 registry.map_model("deepseek_v41*", "deepseek_v41");
631 registry.map_model("deepseek-v41*", "deepseek_v41");
632 registry.map_model("deepseek-ai/DeepSeek-V4.1*", "deepseek_v41");
633 registry.map_model("deepseek-ai/DeepSeek-V41*", "deepseek_v41");
636 registry.map_model("deepseek-*", "pythonic");
637
638 registry.map_model("glm-4.5*", "glm45_moe");
640 registry.map_model("glm-4.6*", "glm45_moe");
641 registry.map_model("glm-4.7*", "glm47_moe");
642 registry.map_model("glm-5*", "glm47_moe");
643 registry.map_model("glm-*", "json");
644
645 registry.map_model("sarashina*", "sarashina");
647 registry.map_model("Sarashina*", "sarashina");
648
649 registry.map_model("step3*", "step3");
651 registry.map_model("Step-3*", "step3");
652
653 registry.map_model("kimi-k2*", "kimik2");
655 registry.map_model("Kimi-K2*", "kimik2");
656 registry.map_model("moonshot*/Kimi-K2*", "kimik2");
657 registry.map_model("hy4*", "hy_v4");
658 registry.map_model("hy_v4", "hy_v4");
659 registry.map_model("kimi-k3*", "kimi_k3");
660 registry.map_model("Kimi-K3*", "kimi_k3");
661 registry.map_model("moonshot*/Kimi-K3*", "kimi_k3");
662 registry.map_model("kimi_k3*", "kimi_k3");
666 registry.map_model("Kimi_K3*", "kimi_k3");
667 registry.map_model("moonshot*/Kimi_K3*", "kimi_k3");
668
669 registry.map_model("inkling*", "inkling");
671 registry.map_model("Inkling*", "inkling");
672
673 registry.map_model("minimax*", "minimax_m2");
676 registry.map_model("MiniMax*", "minimax_m2");
677 registry.map_model("minimax-m3*", "minimax_m3");
679 registry.map_model("MiniMax-M3*", "minimax_m3");
680 registry.map_model("MiniMaxAI/MiniMax-M3*", "minimax_m3");
681 registry.map_model("mm-m3*", "minimax_m3");
682
683 registry.map_model("command-r*", "cohere");
685 registry.map_model("command-r-plus*", "cohere");
686 registry.map_model("command-a*", "cohere");
687 registry.map_model("c4ai-command*", "cohere");
688 registry.map_model("cohere*", "cohere");
689 registry.map_model("CohereForAI*", "cohere");
690
691 registry.map_model("gemini-*", "json");
693 registry.map_model("palm-*", "json");
694 registry.map_model("gemma-4*", "gemma4");
695 registry.map_model("gemma4*", "gemma4");
696 registry.map_model("gemma-*", "json");
697 }
698
699 #[expect(
703 clippy::expect_used,
704 reason = "passthrough parser is registered in new(); None indicates a bug in registration logic"
705 )]
706 pub fn get_pooled(&self, model_id: &str) -> PooledParser {
707 self.registry
708 .get_pooled_for_model(model_id)
709 .unwrap_or_else(|| {
710 self.registry
712 .get_pooled_parser("passthrough")
713 .expect("Passthrough parser should always be registered")
714 })
715 }
716
717 pub fn registry(&self) -> &ParserRegistry {
719 &self.registry
720 }
721
722 pub fn clear_pool(&self) {
724 self.registry.clear_pool();
725 }
726
727 pub fn get_parser(&self, model_id: &str) -> Option<Arc<dyn ToolParser>> {
730 let parser_type = self
731 .registry
732 .resolve_model_to_parser(model_id)
733 .unwrap_or_else(|| self.registry.default_parser.read().clone());
734 let entries = self.registry.entries.read();
735 entries.get(&parser_type).map(|entry| {
736 let boxed_parser = (entry.creator)();
737 Arc::from(boxed_parser)
738 })
739 }
740
741 pub fn has_parser(&self, name: &str) -> bool {
743 self.registry.entries.read().contains_key(name)
744 }
745
746 pub fn list_parsers(&self) -> Vec<String> {
748 self.registry.entries.read().keys().cloned().collect()
749 }
750}
751
752impl Default for ParserFactory {
753 fn default() -> Self {
754 Self::new()
755 }
756}
757
758fn wrap_in_reasoning_prefix(
762 mut tag: serde_json::Value,
763 prefix: serde_json::Value,
764) -> Result<serde_json::Value, String> {
765 let format = tag
766 .get_mut("format")
767 .map(serde_json::Value::take)
768 .ok_or_else(|| {
769 "structural tag has no `format` to wrap in a reasoning prefix".to_string()
770 })?;
771 Ok(json!({
772 "format": {
773 "type": "sequence",
774 "elements": [prefix, format],
775 }
776 }))
777}
778
779fn build_required_array_schema(tools: &[Tool]) -> Result<String, String> {
781 let mut any_of_schemas = Vec::with_capacity(tools.len());
782 for tool in tools {
783 let tool_schema = json!({
784 "properties": {
785 "name": {
786 "type": "string",
787 "enum": [tool.function.name]
788 },
789 "parameters": tool.function.parameters
790 },
791 "required": ["name", "parameters"]
792 });
793 any_of_schemas.push(tool_schema);
794 }
795
796 let mut all_defs: serde_json::Map<String, serde_json::Value> = serde_json::Map::new();
798 for tool in tools {
799 if let serde_json::Value::Object(params) = &tool.function.parameters {
800 if let Some(serde_json::Value::Object(defs)) = params.get("$defs") {
801 for (def_name, def_schema) in defs {
802 if let Some(existing) = all_defs.get(def_name) {
803 if existing != def_schema {
804 return Err(format!(
805 "Tool definition '{def_name}' has multiple conflicting schemas"
806 ));
807 }
808 } else {
809 all_defs.insert(def_name.clone(), def_schema.clone());
810 }
811 }
812 }
813 }
814 }
815
816 let mut array_schema = json!({
817 "type": "array",
818 "minItems": 1,
819 "items": {
820 "type": "object",
821 "anyOf": any_of_schemas
822 }
823 });
824
825 if !all_defs.is_empty() {
826 if let serde_json::Value::Object(ref mut obj) = array_schema {
827 obj.insert("$defs".to_string(), serde_json::Value::Object(all_defs));
828 }
829 }
830
831 serde_json::to_string(&array_schema)
832 .map_err(|e| format!("Failed to serialize tool schema: {e}"))
833}