use serde::{Deserialize, Serialize};
use crate::adapters::ToolAdapter;
use crate::ToolMeta;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OpenAiFunction {
pub name: String,
pub description: String,
pub parameters: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OpenAiTool {
#[serde(rename = "type")]
pub tool_type: String,
pub function: OpenAiFunction,
}
#[derive(Debug, Clone, Deserialize)]
pub struct OpenAiFunctionCall {
pub name: String,
pub arguments: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct OpenAiToolCall {
pub id: String,
#[serde(rename = "type")]
pub tool_type: String,
pub function: OpenAiFunctionCall,
}
#[derive(Debug, Clone, Deserialize)]
pub struct OpenAiCallRequest {
pub tool_call: OpenAiToolCall,
}
#[derive(Debug, Clone, Serialize)]
pub struct OpenAiToolResponse {
pub tool_call_id: String,
pub role: String,
pub content: String,
}
pub struct OpenAiAdapter;
impl ToolAdapter for OpenAiAdapter {
type ToolSpec = OpenAiTool;
type CallRequest = OpenAiCallRequest;
type CallResponse = OpenAiToolResponse;
fn to_spec(meta: &ToolMeta) -> OpenAiTool {
OpenAiTool {
tool_type: "function".to_string(),
function: OpenAiFunction {
name: meta.name.to_string(),
description: meta.description.to_string(),
parameters: (meta.schema)().clone(),
},
}
}
fn from_request(req: OpenAiCallRequest) -> Result<(String, serde_json::Value), crate::ToolError> {
let params: serde_json::Value = serde_json::from_str(&req.tool_call.function.arguments)
.map_err(|e| crate::ToolError::invalid_params_with_source(
"Failed to parse function arguments",
e
))?;
Ok((req.tool_call.function.name, params))
}
fn to_response(output: serde_json::Value) -> OpenAiToolResponse {
OpenAiToolResponse {
tool_call_id: String::new(), role: "tool".to_string(),
content: serde_json::to_string(&output).unwrap_or_else(|_| output.to_string()),
}
}
}
impl OpenAiAdapter {
pub fn to_response_with_id(output: serde_json::Value, tool_call_id: String) -> OpenAiToolResponse {
OpenAiToolResponse {
tool_call_id,
role: "tool".to_string(),
content: serde_json::to_string(&output).unwrap_or_else(|_| output.to_string()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Tool, ToolContext, ToolError};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[derive(Deserialize, JsonSchema)]
struct TestParams {
value: i32,
}
#[derive(Serialize, JsonSchema)]
struct TestOutput {
result: i32,
}
struct TestTool;
impl Tool for TestTool {
type Params = TestParams;
type Output = TestOutput;
const NAME: &'static str = "test_openai";
const DESCRIPTION: &'static str = "Test tool for OpenAI";
async fn call(
_ctx: Arc<ToolContext>,
params: Self::Params,
) -> Result<Self::Output, ToolError> {
Ok(TestOutput {
result: params.value * 2,
})
}
}
inventory::submit! {
crate::ToolMeta {
name: TestTool::NAME,
description: TestTool::DESCRIPTION,
call: |ctx, params| {
Box::pin(async move {
let params: TestParams = serde_json::from_value(params)?;
let result = <TestTool as Tool>::call(ctx, params).await?;
Ok(serde_json::to_value(result)?)
})
},
schema: || <TestTool as Tool>::schema(),
examples: || <TestTool as Tool>::EXAMPLES,
}
}
#[test]
fn test_to_spec() {
let meta = crate::find_tool("test_openai").unwrap();
let spec = OpenAiAdapter::to_spec(meta);
assert_eq!(spec.tool_type, "function");
assert_eq!(spec.function.name, "test_openai");
assert!(spec.function.parameters.is_object());
}
#[test]
fn test_from_request() {
let req = OpenAiCallRequest {
tool_call: OpenAiToolCall {
id: "call_123".to_string(),
tool_type: "function".to_string(),
function: OpenAiFunctionCall {
name: "test_openai".to_string(),
arguments: r#"{"value": 5}"#.to_string(),
},
},
};
let (name, params) = OpenAiAdapter::from_request(req).unwrap();
assert_eq!(name, "test_openai");
assert_eq!(params["value"], 5);
}
#[test]
fn test_to_response() {
let output = serde_json::json!({"result": 10});
let response = OpenAiAdapter::to_response(output);
assert_eq!(response.role, "tool");
assert!(response.content.contains("result"));
}
#[test]
fn test_to_response_with_id() {
let output = serde_json::json!({"result": 10});
let response = OpenAiAdapter::to_response_with_id(output, "call_456".to_string());
assert_eq!(response.tool_call_id, "call_456");
assert_eq!(response.role, "tool");
}
}