use serde::{Deserialize, Serialize};
use crate::{CacheBreakpoint, ProviderData};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ToolDefinition {
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub parameters: serde_json::Value,
#[cfg_attr(
not(feature = "schema"),
doc = "[Self::from_schema]: crate#cargo-features"
)]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub strict: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cache: Option<CacheBreakpoint>,
#[serde(default, skip_serializing_if = "ProviderData::is_empty")]
pub provider_data: ProviderData,
}
impl ToolDefinition {
#[must_use]
pub fn new(name: impl Into<String>, parameters: serde_json::Value) -> Self {
Self {
name: name.into(),
description: None,
parameters,
strict: None,
cache: None,
provider_data: ProviderData::new(),
}
}
#[cfg(feature = "schema")]
#[must_use]
pub fn from_schema<T: schemars::JsonSchema>(name: impl Into<String>) -> Self {
Self::new(name, crate::schema::schema_for::<T>())
}
#[must_use]
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
#[must_use]
pub fn strict(mut self, strict: bool) -> Self {
self.strict = Some(strict);
self
}
#[must_use]
pub fn with_cache(mut self, breakpoint: CacheBreakpoint) -> Self {
self.cache = Some(breakpoint);
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum ToolChoice {
Auto,
#[doc(alias = "none")]
Disabled,
Required,
Specific {
name: String,
},
}
impl ToolChoice {
pub const KIND_NAMES: &'static [&'static str] = &["auto", "disabled", "required", "specific"];
#[must_use]
pub fn kind_name(&self) -> &'static str {
match self {
Self::Auto => "auto",
Self::Disabled => "disabled",
Self::Required => "required",
Self::Specific { .. } => "specific",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn every_kind_is_listed() {
let choices = [
ToolChoice::Auto,
ToolChoice::Disabled,
ToolChoice::Required,
ToolChoice::Specific { name: "t".into() },
];
let names: Vec<_> = choices.iter().map(ToolChoice::kind_name).collect();
assert_eq!(names, ToolChoice::KIND_NAMES);
for choice in choices {
let value = serde_json::to_value(&choice).unwrap();
let serialized = value
.as_str()
.map(str::to_owned)
.or_else(|| value.as_object()?.keys().next().cloned())
.unwrap();
assert_eq!(serialized, choice.kind_name());
}
}
#[test]
fn tool_definition_round_trip_and_minimal_shape() {
let tool = ToolDefinition::new("search", json!({"type": "object"}))
.description("Search")
.strict(true);
let value = serde_json::to_value(&tool).unwrap();
assert_eq!(value["strict"], true);
assert_eq!(
serde_json::from_value::<ToolDefinition>(value).unwrap(),
tool
);
let bare = serde_json::to_value(ToolDefinition::new("bare", json!({}))).unwrap();
assert_eq!(bare, json!({"name": "bare", "parameters": {}}));
}
#[test]
fn tool_choice_round_trip() {
for choice in [
ToolChoice::Auto,
ToolChoice::Disabled,
ToolChoice::Required,
ToolChoice::Specific { name: "x".into() },
] {
let value = serde_json::to_value(&choice).unwrap();
assert_eq!(serde_json::from_value::<ToolChoice>(value).unwrap(), choice);
}
}
}