use std::{error::Error, future::Future, marker::PhantomData};
use async_trait::async_trait;
use serde::de::DeserializeOwned;
use serde_json::{json, Value};
use super::Tool;
pub struct FunctionTool<T, F> {
name: String,
description: String,
parameters: Value,
func: F,
_input: PhantomData<fn() -> T>,
}
impl<T, F> FunctionTool<T, F> {
pub fn new<N: Into<String>, D: Into<String>>(
name: N,
description: D,
parameters: Value,
func: F,
) -> Self {
Self {
name: name.into(),
description: description.into(),
parameters,
func,
_input: PhantomData,
}
}
}
impl<F> FunctionTool<String, F> {
pub fn text<N: Into<String>, D: Into<String>>(name: N, description: D, func: F) -> Self {
let description = description.into();
let parameters = json!({
"type": "object",
"properties": {
"input": { "type": "string", "description": description }
},
"required": ["input"]
});
Self::new(name, description, parameters, func)
}
}
impl<T: DeserializeOwned, F> FunctionTool<T, F> {
fn decode(&self, value: Value) -> Result<T, Box<dyn Error>> {
let first_err = match serde_json::from_value::<T>(value.clone()) {
Ok(input) => return Ok(input),
Err(e) => e,
};
let fallbacks = [
value.get("input").cloned(),
value.as_str().and_then(|s| serde_json::from_str(s).ok()),
(!value.is_string()).then(|| Value::String(value.to_string())),
];
fallbacks
.into_iter()
.flatten()
.find_map(|candidate| serde_json::from_value::<T>(candidate).ok())
.ok_or_else(|| {
format!(
"invalid input for tool `{}`: {first_err}. Expected arguments matching: {}",
self.name, self.parameters
)
.into()
})
}
}
#[async_trait]
impl<T, F, Fut, O, E> Tool for FunctionTool<T, F>
where
T: DeserializeOwned + Send + 'static,
F: Fn(T) -> Fut + Send + Sync,
Fut: Future<Output = Result<O, E>> + Send,
O: ToString,
E: Into<Box<dyn Error>>,
{
fn name(&self) -> String {
self.name.clone()
}
fn description(&self) -> String {
self.description.clone()
}
fn parameters(&self) -> Value {
self.parameters.clone()
}
async fn parse_input(&self, input: &str) -> Value {
serde_json::from_str(input).unwrap_or_else(|_| Value::String(input.to_string()))
}
async fn run(&self, input: Value) -> Result<String, Box<dyn Error>> {
let input = self.decode(input)?;
(self.func)(input)
.await
.map(|output| output.to_string())
.map_err(Into::into)
}
}
#[cfg(test)]
mod tests;