dynamo_parsers/tool_calling/json/
mod.rs

1// SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4pub mod base_json_parser;
5pub mod deepseek_parser;
6
7pub use super::{config, response};
8pub use base_json_parser::{detect_tool_call_start_basic_json, try_tool_call_parse_basic_json};
9pub use deepseek_parser::{detect_tool_call_start_deepseek_v3_1, parse_tool_calls_deepseek_v3_1};
10
11pub use super::config::JsonParserConfig;
12pub use super::response::ToolCallResponse;
13
14#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
15pub enum JsonParserType {
16    // Basic is generic json parser which can handle most of the cases
17    Basic,
18    // Model Specific JSON Parsers
19    DeepseekV31,
20}
21
22impl Default for JsonParserType {
23    fn default() -> Self {
24        Self::Basic
25    }
26}
27
28pub fn try_tool_call_parse_json(
29    message: &str,
30    config: &JsonParserConfig,
31) -> anyhow::Result<(Vec<ToolCallResponse>, Option<String>)> {
32    match config.parser_type {
33        JsonParserType::Basic => try_tool_call_parse_basic_json(message, config),
34        JsonParserType::DeepseekV31 => parse_tool_calls_deepseek_v3_1(message, config),
35    }
36}
37
38pub fn detect_tool_call_start_json(chunk: &str, config: &JsonParserConfig) -> bool {
39    match config.parser_type {
40        JsonParserType::Basic => detect_tool_call_start_basic_json(chunk, config),
41        JsonParserType::DeepseekV31 => detect_tool_call_start_deepseek_v3_1(chunk, config),
42    }
43}
44
45pub fn find_tool_call_end_position_json(
46    chunk: &str,
47    parser: &str,
48    config: &JsonParserConfig,
49) -> usize {
50    match parser {
51        "hermes" | "nemotron_deci" => {
52            if let Some(end_token) = config.tool_call_end_tokens.first() {
53                if let Some(pos) = chunk.find(end_token) {
54                    pos + end_token.len()
55                } else {
56                    chunk.len()
57                }
58            } else {
59                chunk.len()
60            }
61        }
62        "mistral" | "phi4" => {
63            if let Some(pos) = chunk.rfind(']') {
64                pos + 1
65            } else {
66                chunk.len()
67            }
68        }
69        _ => chunk.len(),
70    }
71}