1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use std::fmt;
4
5#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
50pub struct FunctionSpecification {
51 pub name: String,
52 pub description: Option<String>,
53 pub parameters: Option<Parameters>,
54}
55
56#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
60pub struct Parameters {
61 #[serde(rename = "type")]
62 pub type_: String,
63 pub properties: HashMap<String, Property>,
64 pub required: Vec<String>,
65}
66
67#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
68pub struct Property {
69 #[serde(rename = "type")]
70 pub type_: String,
71 pub description: Option<String>,
72 #[serde(rename = "enum")]
73 pub enum_: Option<Vec<String>>,
74}
75
76impl FunctionSpecification {
77 pub fn new(
78 name: String,
79 description: Option<String>,
80 parameters: Option<Parameters>,
81 ) -> FunctionSpecification {
82 FunctionSpecification {
83 name,
84 description,
85 parameters,
86 }
87 }
88}
89
90impl fmt::Display for FunctionSpecification {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 write!(f, "{{\"name\":\"{}\"", self.name)?;
98 if let Some(description) = &self.description {
99 write!(f, ",\"description\":\"{}\"", description)?;
100 }
101 if let Some(parameters) = &self.parameters {
102 write!(f, ",\"parameters\":{}", parameters)?;
103 } else {
104 write!(
105 f,
106 ",\"parameters\":{{\"type\":\"object\",\"properties\":{{}}}}"
107 )?;
108 }
109 write!(f, "}}")
110 }
111}
112
113impl fmt::Display for Parameters {
114 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115 write!(f, "{{\"type\":\"{}\"", self.type_)?;
116 if !self.properties.is_empty() {
117 write!(f, ",\"properties\":{{")?;
118 for (i, (key, value)) in self.properties.iter().enumerate() {
119 write!(f, "\"{}\":{}", key, value)?;
120 if i < self.properties.len() - 1 {
121 write!(f, ",")?;
122 }
123 }
124 write!(f, "}}")?;
125 }
126 if !self.required.is_empty() {
127 write!(f, ",\"required\":[")?;
128 for (i, required) in self.required.iter().enumerate() {
129 write!(f, "\"{}\"", required)?;
130 if i < self.required.len() - 1 {
131 write!(f, ",")?;
132 }
133 }
134 write!(f, "]")?;
135 }
136 write!(f, "}}")
137 }
138}
139
140impl fmt::Display for Property {
141 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142 write!(f, "{{\"type\":\"{}\"", self.type_)?;
143 if let Some(description) = &self.description {
144 write!(f, ",\"description\":\"{}\"", description)?;
145 }
146 if let Some(enum_) = &self.enum_ {
147 write!(f, ",\"enum\":[")?;
148 for (i, enum_value) in enum_.iter().enumerate() {
149 write!(f, "\"{}\"", enum_value)?;
150 if i < enum_.len() - 1 {
151 write!(f, ",")?;
152 }
153 }
154 write!(f, "]")?;
155 }
156 write!(f, "}}")
157 }
158}
159
160#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn test_function_specification_new() {
169 let name = "get_current_weather".to_string();
170 let description = "Get the current weather in a given location".to_string();
171 let parameters = Parameters {
172 type_: "object".to_string(),
173 properties: HashMap::new(),
174 required: vec![],
175 };
176 let function_specification = FunctionSpecification::new(
177 name.clone(),
178 Some(description.clone()),
179 Some(parameters.clone()),
180 );
181 assert_eq!(function_specification.name, name);
182 assert_eq!(function_specification.description, Some(description));
183 assert_eq!(function_specification.parameters, Some(parameters));
184 }
185
186 #[test]
187 fn test_deserialize_function_specification() {
188 let json = r#"
189 {
190 "name": "get_current_weather",
191 "description": "Get the current weather in a given location",
192 "parameters": {
193 "type": "object",
194 "properties": {
195 "location": {
196 "type": "string",
197 "description": "The city and state, e.g. San Francisco, CA"
198 },
199 "unit": {
200 "type": "string",
201 "enum": ["celsius", "fahrenheit"]
202 }
203 },
204 "required": ["location"]
205 }
206 }
207 "#;
208 let function_specification: FunctionSpecification = serde_json::from_str(json)
209 .expect("Could not parse correctly the function specification");
210 assert_eq!(function_specification.name, "get_current_weather");
211 assert_eq!(
212 function_specification.description,
213 Some("Get the current weather in a given location".to_string())
214 );
215 let params = function_specification.parameters.expect("No parameters");
216 assert_eq!(params.type_, "object");
217 assert_eq!(params.properties.len(), 2);
218 assert_eq!(params.required.len(), 1);
219
220 let location = params
221 .properties
222 .get("location")
223 .expect("Could not find location property");
224 assert_eq!(location.type_, "string");
225 assert_eq!(
226 location.description,
227 Some("The city and state, e.g. San Francisco, CA".to_string())
228 );
229
230 let unit = params
231 .properties
232 .get("unit")
233 .expect("Could not find unit property");
234 assert_eq!(unit.type_, "string");
235 assert_eq!(unit.description, None);
236 assert_eq!(
237 unit.enum_,
238 Some(vec!["celsius".to_string(), "fahrenheit".to_string()])
239 );
240 }
241
242 #[test]
243 fn test_display_no_parameters() {
244 let function_specification = FunctionSpecification::new(
245 "get_current_weather".to_string(),
246 Some("Get the current weather in a given location".to_string()),
247 None,
248 );
249 assert_eq!(
250 function_specification.to_string(),
251 "{\"name\":\"get_current_weather\",\"description\":\"Get the current weather in a given location\",\"parameters\":{\"type\":\"object\",\"properties\":{}}}"
252 );
253 }
254
255 #[test]
256 fn test_display_parameters_with_properties() {
257 let mut properties = HashMap::new();
258 properties.insert(
259 "unit".to_string(),
260 Property {
261 type_: "string".to_string(),
262 description: None,
263 enum_: Some(vec!["celsius".to_string(), "fahrenheit".to_string()]),
264 },
265 );
266 let parameters = Parameters {
267 type_: "object".to_string(),
268 properties,
269 required: vec!["unit".to_string()],
270 };
271 assert_eq!(
272 parameters.to_string(),
273 "{\"type\":\"object\",\"properties\":{\"unit\":{\"type\":\"string\",\"enum\":[\"celsius\",\"fahrenheit\"]}},\"required\":[\"unit\"]}"
274 );
275 }
276
277 #[test]
278 fn test_display_parameters_without_properties() {
279 let parameters = Parameters {
280 type_: "object".to_string(),
281 properties: HashMap::new(),
282 required: vec!["location".to_string()],
283 };
284 assert_eq!(
285 parameters.to_string(),
286 "{\"type\":\"object\",\"required\":[\"location\"]}"
287 );
288 }
289
290 #[test]
291 fn test_display_property_with_description_and_enum() {
292 let property = Property {
293 type_: "string".to_string(),
294 description: Some("The city and state, e.g. San Francisco, CA".to_string()),
295 enum_: Some(vec!["celsius".to_string(), "fahrenheit".to_string()]),
296 };
297 assert_eq!(
298 property.to_string(),
299 "{\"type\":\"string\",\"description\":\"The city and state, e.g. San Francisco, CA\",\"enum\":[\"celsius\",\"fahrenheit\"]}"
300 );
301 }
302
303 #[test]
304 fn test_display_property_with_description() {
305 let property = Property {
306 type_: "string".to_string(),
307 description: Some("The city and state, e.g. San Francisco, CA".to_string()),
308 enum_: None,
309 };
310 assert_eq!(
311 property.to_string(),
312 "{\"type\":\"string\",\"description\":\"The city and state, e.g. San Francisco, CA\"}"
313 );
314 }
315
316 #[test]
317 fn test_display_property_with_enum() {
318 let property = Property {
319 type_: "string".to_string(),
320 description: None,
321 enum_: Some(vec!["celsius".to_string(), "fahrenheit".to_string()]),
322 };
323 assert_eq!(
324 property.to_string(),
325 "{\"type\":\"string\",\"enum\":[\"celsius\",\"fahrenheit\"]}"
326 );
327 }
328
329 #[test]
330 fn test_display_function_specification() {
331 let mut properties = HashMap::new();
332 properties.insert(
333 "unit".to_string(),
334 Property {
335 type_: "string".to_string(),
336 description: None,
337 enum_: Some(vec!["celsius".to_string(), "fahrenheit".to_string()]),
338 },
339 );
340 let parameters = Parameters {
341 type_: "object".to_string(),
342 properties,
343 required: vec!["unit".to_string()],
344 };
345 let function_specification = FunctionSpecification {
346 name: "get_current_weather".to_string(),
347 description: Some("Get the current weather in a given location".to_string()),
348 parameters: Some(parameters),
349 };
350 assert_eq!(
351 function_specification.to_string(),
352 "{\"name\":\"get_current_weather\",\"description\":\"Get the current weather in a given location\",\"parameters\":{\"type\":\"object\",\"properties\":{\"unit\":{\"type\":\"string\",\"enum\":[\"celsius\",\"fahrenheit\"]}},\"required\":[\"unit\"]}}"
353 );
354 }
355}