openai_tools/common/function.rs
1//! Function module for OpenAI tools.
2//!
3//! This module provides the `Function` struct and related functionality for representing
4//! and working with OpenAI function calls. Functions are used to define callable operations
5//! that can be invoked by OpenAI's API, including their parameters, descriptions, and
6//! execution modes.
7//!
8//! The `Function` struct is a core component that is used throughout the OpenAI tools library:
9//!
10//! - In [`crate::common::message`] - Used within `ToolCall` structures to represent function calls in messages
11//! - In [`crate::common::tool`] - Used within `Tool` structures to define available functions for OpenAI models
12//! - In [`crate::chat::request`] - Used in Chat Completion API requests for function calling capabilities
13//! - In [`crate::responses::request`] - Used in Responses API requests for structured interactions
14//!
15//! ## Key Features
16//!
17//! - **Serialization/Deserialization**: Custom implementations for clean JSON output
18//! - **Flexible Parameters**: Support for complex parameter schemas via [`crate::common::parameters::Parameters`]
19//! - **Argument Handling**: Support for both JSON string and structured argument formats
20//! - **Strict Mode**: Optional strict execution mode for enhanced validation
21//!
22//! ## Usage Patterns
23//!
24//! Functions are typically used in two main contexts:
25//!
26//! 1. **Tool Definition**: When creating tools that OpenAI models can call
27//! 2. **Function Execution**: When processing function calls from OpenAI responses
28
29use crate::common::{
30 errors::{OpenAIToolError, Result as OpenAIToolResult},
31 parameters::Parameters,
32};
33use serde::{ser::SerializeStruct, Deserialize, Serialize};
34use serde_json::Value;
35use std::collections::HashMap;
36
37/// Represents a function that can be called by OpenAI tools.
38///
39/// This structure contains metadata about a function including its name, description,
40/// parameters, and whether it should be executed in strict mode.
41#[derive(Debug, Clone, Default)]
42pub struct Function {
43 /// The name of the function
44 pub name: String,
45 /// Optional description of what the function does
46 pub description: Option<String>,
47 /// Optional parameters that the function accepts
48 pub parameters: Option<Parameters>,
49 /// Optional arguments passed to the function as key-value pairs
50 pub arguments: Option<HashMap<String, Value>>,
51 /// Whether the function should be executed in strict mode
52 pub strict: bool,
53}
54
55impl Function {
56 /// Creates a new Function instance with the specified parameters.
57 ///
58 /// # Arguments
59 ///
60 /// * `name` - The name of the function
61 /// * `description` - A description of what the function does
62 /// * `parameters` - The parameters that the function accepts
63 /// * `strict` - Whether the function should be executed in strict mode
64 ///
65 /// # Returns
66 ///
67 /// A new Function instance
68 pub fn new<T: AsRef<str>, U: AsRef<str>>(name: T, description: U, parameters: Parameters, strict: bool) -> Self {
69 Self {
70 name: name.as_ref().to_string(),
71 description: Some(description.as_ref().to_string()),
72 parameters: Some(parameters),
73 strict,
74 ..Default::default()
75 }
76 }
77
78 /// Returns the function arguments as a HashMap.
79 ///
80 /// # Returns
81 ///
82 /// * `Ok(HashMap<String, Value>)` - The arguments as a map if they exist
83 /// * `Err(OpenAIToolError)` - If the arguments are not set
84 ///
85 /// # Errors
86 ///
87 /// This function will return an error if the arguments are not set.
88 pub fn arguments_as_map(&self) -> OpenAIToolResult<HashMap<String, Value>> {
89 if let Some(args) = &self.arguments {
90 Ok(args.clone())
91 } else {
92 Err(OpenAIToolError::from(anyhow::anyhow!("Function arguments are not set")))
93 }
94 }
95}
96
97/// Custom serialization implementation for Function.
98///
99/// This implementation ensures that only non-None optional fields are serialized,
100/// keeping the JSON output clean and avoiding null values for optional fields.
101impl Serialize for Function {
102 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
103 where
104 S: serde::Serializer,
105 {
106 let mut state = serializer.serialize_struct("Function", 4)?;
107 state.serialize_field("name", &self.name)?;
108 if let Some(description) = &self.description {
109 state.serialize_field("description", description)?;
110 }
111 if let Some(parameters) = &self.parameters {
112 state.serialize_field("parameters", parameters)?;
113 }
114 if let Some(arguments) = &self.arguments {
115 state.serialize_field("arguments", arguments)?;
116 }
117 state.serialize_field("strict", &self.strict)?;
118 state.end()
119 }
120}
121
122/// Custom deserialization implementation for Function.
123///
124/// This implementation handles the deserialization of Function from JSON,
125/// parsing arguments as JSON strings and converting parameters from JSON objects.
126/// The `name` field is required, while other fields are optional.
127impl<'de> Deserialize<'de> for Function {
128 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
129 where
130 D: serde::Deserializer<'de>,
131 {
132 let mut function = Function::default();
133 let map: HashMap<String, Value> = HashMap::deserialize(deserializer)?;
134
135 if let Some(name) = map.get("name").and_then(Value::as_str) {
136 function.name = name.to_string();
137 } else {
138 return Err(serde::de::Error::missing_field("name"));
139 }
140
141 let arguments = map.get("arguments").and_then(Value::as_str);
142 if let Some(args) = arguments {
143 function.arguments = serde_json::from_str(args).ok();
144 } else {
145 function.arguments = None;
146 }
147
148 let parameters = map.get("parameters").and_then(Value::as_object);
149 if let Some(params) = parameters {
150 function.parameters = Some(Parameters::deserialize(Value::Object(params.clone())).map_err(serde::de::Error::custom)?);
151 } else {
152 function.parameters = None;
153 }
154
155 function.description = map.get("description").and_then(Value::as_str).map(String::from);
156 function.strict = map.get("strict").and_then(Value::as_bool).unwrap_or(false);
157
158 Ok(function)
159 }
160}