use crate::error::OpenAIAgentError;
use crate::models::{ToolDefinition, ToolSpec};
use async_trait::async_trait;
use serde::{de::DeserializeOwned, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use std::fmt::Debug;
use std::future::Future;
use std::marker::PhantomData;
use std::sync::Arc;
#[async_trait]
pub trait RegisteredTool: Send + Sync {
fn definition(&self) -> ToolDefinition;
async fn execute(&self, arguments: Value) -> Result<String, OpenAIAgentError>;
}
pub struct FunctionTool<F, Args, Fut, R>
where
F: Fn(Args) -> Fut + Send + Sync,
Args: DeserializeOwned + Serialize + Debug + Send + Sync,
Fut: Future<Output = Result<R, OpenAIAgentError>> + Send,
R: ToString + Send + Sync, {
name: String,
description: String,
function: F,
_args: PhantomData<Args>,
_fut: PhantomData<Fut>,
_result: PhantomData<R>,
}
impl<F, Args, Fut, R> FunctionTool<F, Args, Fut, R>
where
F: Fn(Args) -> Fut + Send + Sync,
Args: DeserializeOwned + Serialize + Debug + Send + Sync + 'static,
Fut: Future<Output = Result<R, OpenAIAgentError>> + Send,
R: ToString + Send + Sync, {
pub fn new(name: impl Into<String>, description: impl Into<String>, function: F) -> Self {
Self {
name: name.into(),
description: description.into(),
function,
_args: PhantomData,
_fut: PhantomData,
_result: PhantomData,
}
}
}
#[async_trait]
impl<F, Args, Fut, R> RegisteredTool for FunctionTool<F, Args, Fut, R>
where
F: Fn(Args) -> Fut + Send + Sync + 'static + std::clone::Clone,
Args: DeserializeOwned + Serialize + Debug + Send + Sync + 'static + schemars::JsonSchema,
Fut: Future<Output = Result<R, OpenAIAgentError>> + Send + 'static + std::marker::Sync,
R: ToString + Send + Sync + 'static,
{
fn definition(&self) -> ToolDefinition {
let schema = schemars::schema_for!(Args);
let mut schema_value = serde_json::to_value(schema).unwrap_or_else(|_| {
serde_json::json!({
"type": "object",
"properties": {},
})
});
if let Some(schema_obj) = schema_value.as_object_mut() {
schema_obj.insert(
"additionalProperties".to_string(),
serde_json::Value::Bool(false)
);
}
ToolDefinition {
name: self.name.clone(),
description: self.description.clone(),
parameters: schema_value,
strict: Some(true),
}
}
async fn execute(&self, arguments: Value) -> Result<String, OpenAIAgentError> {
let function = self.function.clone();
let args: Args = serde_json::from_value(arguments.clone())
.map_err(|e| OpenAIAgentError::Parse(format!("Failed to parse arguments: {}", e)))?;
let result = function(args).await?;
Ok(result.to_string())
}
}
#[derive(Default, Clone)]
pub struct ToolRegistry {
tools: HashMap<String, Arc<dyn RegisteredTool>>,
}
impl ToolRegistry {
pub fn new() -> Self {
Self {
tools: HashMap::new(),
}
}
pub fn register<T>(&mut self, tool: T)
where
T: RegisteredTool + 'static,
{
let definition = tool.definition();
self.tools.insert(definition.name.clone(), Arc::new(tool));
}
pub fn register_fn<F, Args, Fut, R>(
&mut self,
name: impl Into<String>,
description: impl Into<String>,
function: F,
) -> &mut Self
where
F: Fn(Args) -> Fut + Send + Sync + Clone + 'static,
Args: DeserializeOwned + Serialize + Debug + Send + Sync + 'static + schemars::JsonSchema,
Fut: Future<Output = Result<R, OpenAIAgentError>> + Send + 'static + std::marker::Sync,
R: ToString + Send + Sync + 'static,
{
let tool = FunctionTool::new(name, description, function);
self.register(tool);
self
}
pub fn get(&self, name: &str) -> Option<Arc<dyn RegisteredTool>> {
self.tools.get(name).cloned()
}
pub fn is_empty(&self) -> bool {
self.tools.is_empty()
}
pub fn definitions(&self) -> Vec<ToolSpec> {
self.tools
.values()
.map(|t| {
let def = t.definition();
ToolSpec {
r#type: "function".to_string(),
function: def,
}
})
.collect()
}
}