1use crate::json_repair::{parse_tolerant_json, JsonRepairError};
9use serde::{de::DeserializeOwned, Deserialize, Serialize};
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct ToolCall {
16 pub id: String,
18
19 #[serde(rename = "type")]
21 pub tool_type: String,
22
23 pub function: FunctionCall,
25}
26
27impl ToolCall {
28 pub fn new(
30 id: impl Into<String>,
31 name: impl Into<String>,
32 arguments: impl Into<String>,
33 ) -> Self {
34 Self {
35 id: id.into(),
36 tool_type: "function".to_string(),
37 function: FunctionCall {
38 name: name.into(),
39 arguments: arguments.into(),
40 },
41 }
42 }
43
44 pub fn name(&self) -> &str {
46 &self.function.name
47 }
48
49 pub fn arguments(&self) -> &str {
51 &self.function.arguments
52 }
53
54 pub fn parse_arguments<T: DeserializeOwned>(&self) -> Result<T, JsonRepairError> {
60 parse_tolerant_json(&self.function.arguments)
61 }
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct FunctionCall {
67 pub name: String,
69
70 pub arguments: String,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct ToolCallResult {
77 pub tool_call_id: String,
79
80 pub role: String,
82
83 pub content: String,
85}
86
87impl ToolCallResult {
88 pub fn new(tool_call_id: impl Into<String>, content: impl Into<String>) -> Self {
90 Self {
91 tool_call_id: tool_call_id.into(),
92 role: "tool".to_string(),
93 content: content.into(),
94 }
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101 use serde_json::json;
102 use std::collections::HashMap;
103
104 #[test]
105 fn test_tool_call() {
106 let call = ToolCall::new(
107 "call_123",
108 "calculator",
109 json!({"expression": "2 + 3"}).to_string(),
110 );
111
112 assert_eq!(call.id, "call_123");
113 assert_eq!(call.name(), "calculator");
114
115 let args: HashMap<String, String> = call.parse_arguments().unwrap();
116 assert_eq!(args.get("expression").unwrap(), "2 + 3");
117 }
118
119 #[test]
120 fn test_parse_arguments_tolerates_messy_llm_json() {
121 let call = ToolCall::new(
123 "call_456",
124 "weather",
125 r#"{"city": "beijing", "unit": "celsius",} plus extra text"#,
126 );
127
128 let args: HashMap<String, String> = call.parse_arguments().unwrap();
129 assert_eq!(args.get("city").unwrap(), "beijing");
130 assert_eq!(args.get("unit").unwrap(), "celsius");
131 }
132
133 #[test]
134 fn test_tool_call_result() {
135 let result = ToolCallResult::new("call_123", "5");
136
137 assert_eq!(result.tool_call_id, "call_123");
138 assert_eq!(result.role, "tool");
139 assert_eq!(result.content, "5");
140 }
141}