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
119        if let Some(arguments) = &self.arguments {
120            if !arguments.is_empty() {
121                state.serialize_field("arguments", &serde_json::to_string(arguments).expect("Failed to serialize arguments in Function"))?;
122            }
123        }
124        state.end()
125    }
126}
127
128/// Custom deserialization implementation for Function.
129///
130/// This implementation handles the deserialization of Function from JSON,
131/// parsing arguments as JSON strings and converting parameters from JSON objects.
132/// The `name` field is required, while other fields are optional.
133impl<'de> Deserialize<'de> for Function {
134    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
135    where
136        D: serde::Deserializer<'de>,
137    {
138        let mut function = Function::default();
139        let map: HashMap<String, Value> = HashMap::deserialize(deserializer)?;
140
141        if let Some(name) = map.get("name").and_then(Value::as_str) {
142            function.name = name.to_string();
143        } else {
144            return Err(serde::de::Error::missing_field("name"));
145        }
146
147        let arguments = map.get("arguments").and_then(Value::as_str);
148        if let Some(args) = arguments {
149            function.arguments = serde_json::from_str(args).ok();
150        } else {
151            function.arguments = None;
152        }
153
154        let parameters = map.get("parameters").and_then(Value::as_object);
155        if let Some(params) = parameters {
156            function.parameters = Some(Parameters::deserialize(Value::Object(params.clone())).map_err(serde::de::Error::custom)?);
157        } else {
158            function.parameters = None;
159        }
160
161        function.description = map.get("description").and_then(Value::as_str).map(String::from);
162        function.strict = map.get("strict").and_then(Value::as_bool).unwrap_or(false);
163
164        Ok(function)
165    }
166}