descry_tool_core/adapters/
openai.rs1use serde::{Deserialize, Serialize};
6
7use crate::adapters::ToolAdapter;
8use crate::ToolMeta;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct OpenAiFunction {
13 pub name: String,
15 pub description: String,
17 pub parameters: serde_json::Value,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct OpenAiTool {
24 #[serde(rename = "type")]
26 pub tool_type: String,
27 pub function: OpenAiFunction,
29}
30
31#[derive(Debug, Clone, Deserialize)]
33pub struct OpenAiFunctionCall {
34 pub name: String,
36 pub arguments: String,
38}
39
40#[derive(Debug, Clone, Deserialize)]
42pub struct OpenAiToolCall {
43 pub id: String,
45 #[serde(rename = "type")]
47 pub tool_type: String,
48 pub function: OpenAiFunctionCall,
50}
51
52#[derive(Debug, Clone, Deserialize)]
54pub struct OpenAiCallRequest {
55 pub tool_call: OpenAiToolCall,
57}
58
59#[derive(Debug, Clone, Serialize)]
61pub struct OpenAiToolResponse {
62 pub tool_call_id: String,
64 pub role: String,
66 pub content: String,
68}
69
70pub struct OpenAiAdapter;
72
73impl ToolAdapter for OpenAiAdapter {
74 type ToolSpec = OpenAiTool;
75 type CallRequest = OpenAiCallRequest;
76 type CallResponse = OpenAiToolResponse;
77
78 fn to_spec(meta: &ToolMeta) -> OpenAiTool {
79 OpenAiTool {
80 tool_type: "function".to_string(),
81 function: OpenAiFunction {
82 name: meta.name.to_string(),
83 description: meta.description.to_string(),
84 parameters: (meta.schema)().clone(),
85 },
86 }
87 }
88
89 fn from_request(req: OpenAiCallRequest) -> Result<(String, serde_json::Value), crate::ToolError> {
90 let params: serde_json::Value = serde_json::from_str(&req.tool_call.function.arguments)
91 .map_err(|e| crate::ToolError::invalid_params_with_source(
92 "Failed to parse function arguments",
93 e
94 ))?;
95
96 Ok((req.tool_call.function.name, params))
97 }
98
99 fn to_response(output: serde_json::Value) -> OpenAiToolResponse {
100 OpenAiToolResponse {
101 tool_call_id: String::new(), role: "tool".to_string(),
103 content: serde_json::to_string(&output).unwrap_or_else(|_| output.to_string()),
104 }
105 }
106}
107
108impl OpenAiAdapter {
109 pub fn to_response_with_id(output: serde_json::Value, tool_call_id: String) -> OpenAiToolResponse {
111 OpenAiToolResponse {
112 tool_call_id,
113 role: "tool".to_string(),
114 content: serde_json::to_string(&output).unwrap_or_else(|_| output.to_string()),
115 }
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122 use crate::{Tool, ToolContext, ToolError};
123 use schemars::JsonSchema;
124 use serde::{Deserialize, Serialize};
125 use std::sync::Arc;
126
127 #[derive(Deserialize, JsonSchema)]
128 struct TestParams {
129 value: i32,
130 }
131
132 #[derive(Serialize, JsonSchema)]
133 struct TestOutput {
134 result: i32,
135 }
136
137 struct TestTool;
138
139 impl Tool for TestTool {
140 type Params = TestParams;
141 type Output = TestOutput;
142 const NAME: &'static str = "test_openai";
143 const DESCRIPTION: &'static str = "Test tool for OpenAI";
144
145 async fn call(
146 _ctx: Arc<ToolContext>,
147 params: Self::Params,
148 ) -> Result<Self::Output, ToolError> {
149 Ok(TestOutput {
150 result: params.value * 2,
151 })
152 }
153 }
154
155 inventory::submit! {
156 crate::ToolMeta {
157 name: TestTool::NAME,
158 description: TestTool::DESCRIPTION,
159 call: |ctx, params| {
160 Box::pin(async move {
161 let params: TestParams = serde_json::from_value(params)?;
162 let result = <TestTool as Tool>::call(ctx, params).await?;
163 Ok(serde_json::to_value(result)?)
164 })
165 },
166 schema: || <TestTool as Tool>::schema(),
167 examples: || <TestTool as Tool>::EXAMPLES,
168 }
169 }
170
171 #[test]
172 fn test_to_spec() {
173 let meta = crate::find_tool("test_openai").unwrap();
174 let spec = OpenAiAdapter::to_spec(meta);
175
176 assert_eq!(spec.tool_type, "function");
177 assert_eq!(spec.function.name, "test_openai");
178 assert!(spec.function.parameters.is_object());
179 }
180
181 #[test]
182 fn test_from_request() {
183 let req = OpenAiCallRequest {
184 tool_call: OpenAiToolCall {
185 id: "call_123".to_string(),
186 tool_type: "function".to_string(),
187 function: OpenAiFunctionCall {
188 name: "test_openai".to_string(),
189 arguments: r#"{"value": 5}"#.to_string(),
190 },
191 },
192 };
193
194 let (name, params) = OpenAiAdapter::from_request(req).unwrap();
195 assert_eq!(name, "test_openai");
196 assert_eq!(params["value"], 5);
197 }
198
199 #[test]
200 fn test_to_response() {
201 let output = serde_json::json!({"result": 10});
202 let response = OpenAiAdapter::to_response(output);
203
204 assert_eq!(response.role, "tool");
205 assert!(response.content.contains("result"));
206 }
207
208 #[test]
209 fn test_to_response_with_id() {
210 let output = serde_json::json!({"result": 10});
211 let response = OpenAiAdapter::to_response_with_id(output, "call_456".to_string());
212
213 assert_eq!(response.tool_call_id, "call_456");
214 assert_eq!(response.role, "tool");
215 }
216}