dynamo_llm/preprocessor/
tools.rs

1// SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16mod request;
17mod response;
18
19pub use request::*;
20pub use response::*;
21use serde_json::Value;
22use std::collections::HashMap;
23use uuid::Uuid;
24
25/// Matches and processes tool calling patterns in LLM responses
26///
27/// Supports multiple formats for tool calls:
28/// - Single/multiple function calls with parameters/arguments
29/// - Auto or user selected tool usage
30pub struct ToolCallingMatcher {
31    tool_choice: ToolChoice,
32}
33
34// Same as CalledFunction with named parameters
35#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
36pub struct CalledFunctionParameters {
37    pub name: String,
38    pub parameters: HashMap<String, Value>,
39}
40
41// Same as CalledFunction with named parameters
42#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
43pub struct CalledFunctionArguments {
44    pub name: String,
45    pub arguments: HashMap<String, Value>,
46}
47
48impl ToolCallingMatcher {
49    pub fn new(tool_choice: ToolChoice) -> anyhow::Result<Self> {
50        Ok(Self { tool_choice })
51    }
52
53    pub fn get_call(&self, message: &str) -> anyhow::Result<Vec<ToolCallResponse>> {
54        if matches!(self.tool_choice, ToolChoice::None) {
55            return Ok(Vec::new());
56        }
57
58        if let Ok(deser) = serde_json::from_str::<CalledFunctionParameters>(message) {
59            let id = format!("call-{}", Uuid::new_v4());
60            Ok(vec![ToolCallResponse {
61                id,
62                tp: ToolCallType::Function,
63                function: CalledFunction {
64                    name: deser.name,
65                    arguments: serde_json::to_string(&deser.parameters)?,
66                },
67            }])
68        } else if let Ok(deser) = serde_json::from_str::<Vec<CalledFunctionParameters>>(message) {
69            Ok(deser
70                .into_iter()
71                .map(|deser| {
72                    let id = format!("call-{}", Uuid::new_v4());
73                    Ok(ToolCallResponse {
74                        id,
75                        tp: ToolCallType::Function,
76                        function: CalledFunction {
77                            name: deser.name,
78                            arguments: serde_json::to_string(&deser.parameters)?,
79                        },
80                    })
81                })
82                .collect::<anyhow::Result<Vec<_>>>()?)
83        } else if let Ok(deser) = serde_json::from_str::<CalledFunctionArguments>(message) {
84            let id = format!("call-{}", Uuid::new_v4());
85            Ok(vec![ToolCallResponse {
86                id,
87                tp: ToolCallType::Function,
88                function: CalledFunction {
89                    name: deser.name,
90                    arguments: serde_json::to_string(&deser.arguments)?,
91                },
92            }])
93        } else if let Ok(deser) = serde_json::from_str::<Vec<CalledFunctionArguments>>(message) {
94            Ok(deser
95                .into_iter()
96                .map(|deser| {
97                    let id = format!("call-{}", Uuid::new_v4());
98                    Ok(ToolCallResponse {
99                        id,
100                        tp: ToolCallType::Function,
101                        function: CalledFunction {
102                            name: deser.name,
103                            arguments: serde_json::to_string(&deser.arguments)?,
104                        },
105                    })
106                })
107                .collect::<anyhow::Result<Vec<_>>>()?)
108        } else {
109            if matches!(self.tool_choice, ToolChoice::Tool(_)) {
110                anyhow::bail!("Tool choice was required but no tools were called.")
111            }
112            Ok(Vec::new())
113        }
114    }
115}