use std::fmt;
use std::sync::Arc;
use ferrin_spec::BoxFuture;
use ferrin_spec::CallOptions;
use ferrin_spec::JsonObject;
use ferrin_spec::ToolDefinition;
use ferrin_spec::error::ProviderError;
use crate::middleware::LanguageModelMiddleware;
use crate::middleware::MiddlewareContext;
pub type ExampleFormatFn = Arc<dyn Fn(&JsonObject, usize) -> String + Send + Sync>;
#[derive(Clone)]
pub struct AddToolInputExamples {
prefix: String,
format: Option<ExampleFormatFn>,
remove: bool,
}
impl fmt::Debug for AddToolInputExamples {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AddToolInputExamples")
.field("prefix", &self.prefix)
.field("custom_format", &self.format.is_some())
.field("remove", &self.remove)
.finish()
}
}
#[must_use]
pub fn add_tool_input_examples() -> AddToolInputExamples {
AddToolInputExamples {
prefix: "Input Examples:".to_owned(),
format: None,
remove: true,
}
}
impl AddToolInputExamples {
#[must_use]
pub fn prefix(mut self, prefix: impl Into<String>) -> Self {
self.prefix = prefix.into();
self
}
#[must_use]
pub fn format(
mut self,
format: impl Fn(&JsonObject, usize) -> String + Send + Sync + 'static,
) -> Self {
self.format = Some(Arc::new(format));
self
}
#[must_use]
pub fn remove(mut self, remove: bool) -> Self {
self.remove = remove;
self
}
fn format_example(&self, example: &JsonObject, index: usize) -> String {
match &self.format {
Some(format) => format(example, index),
None => serde_json::to_string(example).unwrap_or_default(),
}
}
#[must_use]
pub fn apply(&self, mut options: CallOptions) -> CallOptions {
for tool in &mut options.tools {
if let ToolDefinition::Function {
description,
input_examples,
..
} = tool
&& !input_examples.is_empty()
{
let formatted = input_examples
.iter()
.enumerate()
.map(|(index, example)| self.format_example(example, index))
.collect::<Vec<_>>()
.join("\n");
let section = format!("{}\n{formatted}", self.prefix);
*description = Some(match description.take() {
Some(existing) if !existing.is_empty() => format!("{existing}\n\n{section}"),
_ => section,
});
if self.remove {
input_examples.clear();
}
}
}
options
}
}
impl LanguageModelMiddleware for AddToolInputExamples {
fn transform_params<'a>(
&'a self,
options: CallOptions,
_ctx: MiddlewareContext<'a>,
) -> BoxFuture<'a, Result<CallOptions, ProviderError>> {
let options = self.apply(options);
Box::pin(async move { Ok(options) })
}
}