Skip to main content

gproxy_protocol/protocol/openai/common/tools/
definitions.rs

1use serde::ser::SerializeStruct;
2use serde::{Deserialize, Deserializer, Serialize, Serializer};
3
4use super::super::{CustomToolGrammarSyntax, Extra, JsonSchema};
5
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
7#[non_exhaustive]
8pub struct FunctionDefinition {
9    pub name: String,
10    #[serde(skip_serializing_if = "Option::is_none")]
11    pub description: Option<String>,
12    #[serde(skip_serializing_if = "Option::is_none")]
13    pub parameters: Option<JsonSchema>,
14    #[serde(skip_serializing_if = "Option::is_none")]
15    pub strict: Option<bool>,
16    #[serde(
17        default,
18        flatten,
19        skip_serializing_if = "std::collections::BTreeMap::is_empty"
20    )]
21    pub extra: Extra,
22}
23
24#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
25#[non_exhaustive]
26pub struct FunctionCall {
27    pub arguments: String,
28    pub name: String,
29    #[serde(
30        default,
31        flatten,
32        skip_serializing_if = "std::collections::BTreeMap::is_empty"
33    )]
34    pub extra: Extra,
35}
36
37#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
38#[non_exhaustive]
39pub struct CustomToolDefinition {
40    pub name: String,
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub description: Option<String>,
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub format: Option<CustomToolInputFormat>,
45    #[serde(
46        default,
47        flatten,
48        skip_serializing_if = "std::collections::BTreeMap::is_empty"
49    )]
50    pub extra: Extra,
51}
52
53#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
54#[serde(untagged)]
55#[non_exhaustive]
56pub enum CustomToolInputFormat {
57    Text(CustomToolTextFormat),
58    Grammar(CustomToolGrammarFormat),
59}
60
61#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
62#[serde(deny_unknown_fields)]
63#[derive(gproxy_protocol_macros::WireBuilder)]
64#[non_exhaustive]
65pub struct CustomToolTextFormat {
66    #[serde(rename = "type")]
67    pub type_: CustomToolTextFormatType,
68}
69
70#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
71#[non_exhaustive]
72pub enum CustomToolTextFormatType {
73    #[serde(rename = "text")]
74    Text,
75}
76
77#[derive(Debug, Clone, PartialEq, gproxy_protocol_macros::WireBuilder)]
78#[non_exhaustive]
79pub struct CustomToolGrammarFormat {
80    pub type_: CustomToolGrammarFormatType,
81    pub grammar: CustomToolGrammar,
82}
83
84impl Serialize for CustomToolGrammarFormat {
85    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
86    where
87        S: Serializer,
88    {
89        // OpenAI's grammar format is flat on the wire. Keep the nested
90        // `grammar` Rust field for API compatibility while emitting the real
91        // provider shape: {"type":"grammar","definition":...,"syntax":...}.
92        let mut state = serializer.serialize_struct("CustomToolGrammarFormat", 3)?;
93        state.serialize_field("type", &self.type_)?;
94        state.serialize_field("definition", &self.grammar.definition)?;
95        state.serialize_field("syntax", &self.grammar.syntax)?;
96        state.end()
97    }
98}
99
100impl<'de> Deserialize<'de> for CustomToolGrammarFormat {
101    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
102    where
103        D: Deserializer<'de>,
104    {
105        #[derive(Deserialize)]
106        #[serde(deny_unknown_fields)]
107        struct Wire {
108            #[serde(rename = "type")]
109            type_: CustomToolGrammarFormatType,
110            definition: String,
111            syntax: CustomToolGrammarSyntax,
112        }
113
114        let wire = Wire::deserialize(deserializer)?;
115        Ok(Self {
116            type_: wire.type_,
117            grammar: CustomToolGrammar {
118                definition: wire.definition,
119                syntax: wire.syntax,
120            },
121        })
122    }
123}
124
125#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
126#[non_exhaustive]
127pub enum CustomToolGrammarFormatType {
128    #[serde(rename = "grammar")]
129    Grammar,
130}
131
132#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
133#[serde(deny_unknown_fields)]
134#[derive(gproxy_protocol_macros::WireBuilder)]
135#[non_exhaustive]
136pub struct CustomToolGrammar {
137    pub definition: String,
138    pub syntax: CustomToolGrammarSyntax,
139}
140
141#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
142#[non_exhaustive]
143pub struct NamedTool {
144    pub name: String,
145    #[serde(
146        default,
147        flatten,
148        skip_serializing_if = "std::collections::BTreeMap::is_empty"
149    )]
150    pub extra: Extra,
151}
152
153#[cfg(test)]
154mod tests {
155    use serde_json::json;
156
157    use super::*;
158
159    #[test]
160    fn custom_tool_grammar_format_matches_flat_openai_wire_shape() {
161        let wire = json!({
162            "type": "grammar",
163            "definition": "start: /[a-z]+/",
164            "syntax": "lark"
165        });
166
167        let format: CustomToolInputFormat = serde_json::from_value(wire.clone()).unwrap();
168        let CustomToolInputFormat::Grammar(format) = &format else {
169            panic!("expected grammar format");
170        };
171        assert_eq!(format.type_, CustomToolGrammarFormatType::Grammar);
172        assert_eq!(format.grammar.definition, "start: /[a-z]+/");
173        assert_eq!(format.grammar.syntax, CustomToolGrammarSyntax::Lark);
174        assert_eq!(serde_json::to_value(format).unwrap(), wire);
175    }
176}