use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CacheControl {
#[serde(rename = "type")]
pub kind: String,
}
impl CacheControl {
pub fn ephemeral() -> Self {
Self {
kind: "ephemeral".into(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RequestUsageConfig {
pub include: bool,
}
impl RequestUsageConfig {
pub fn detailed() -> Self {
Self { include: true }
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ToolCall {
pub id: String,
#[serde(rename = "type")]
pub kind: String,
pub function: FunctionCall,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FunctionCall {
pub name: String,
pub arguments: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ToolDefinition {
#[serde(rename = "type")]
pub kind: String,
pub function: FunctionDefinition,
}
impl ToolDefinition {
pub fn function(function: FunctionDefinition) -> Self {
Self {
kind: "function".into(),
function,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FunctionDefinition {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parameters: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cache_control: Option<CacheControl>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ToolChoice {
None,
Auto,
Required,
Function(String),
}
pub fn openai_tool_choice(choice: ToolChoice) -> Value {
match choice {
ToolChoice::None => json!("none"),
ToolChoice::Auto => json!("auto"),
ToolChoice::Required => json!("required"),
ToolChoice::Function(name) => json!({"type": "function", "function": {"name": name}}),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tool_definition_serialises() {
let tool = ToolDefinition::function(FunctionDefinition {
name: "ping".into(),
description: None,
parameters: None,
cache_control: None,
});
let v: Value = serde_json::to_value(&tool).expect("serialise");
assert_eq!(v["type"], "function");
assert_eq!(v["function"]["name"], "ping");
assert!(v["function"].get("cache_control").is_none());
}
#[test]
fn function_definition_round_trip() {
let params = json!({"type": "object", "properties": {}});
let def = FunctionDefinition {
name: "my_fn".into(),
description: Some("does stuff".into()),
parameters: Some(params.clone()),
cache_control: Some(CacheControl::ephemeral()),
};
let s = serde_json::to_string(&def).expect("serialise");
let de: FunctionDefinition = serde_json::from_str(&s).expect("deserialise");
assert_eq!(de.name, "my_fn");
assert_eq!(de.parameters.as_ref(), Some(¶ms));
assert_eq!(de.cache_control, Some(CacheControl::ephemeral()));
}
#[test]
fn openai_tool_choice_maps_every_variant() {
assert_eq!(openai_tool_choice(ToolChoice::None), json!("none"));
assert_eq!(openai_tool_choice(ToolChoice::Auto), json!("auto"));
assert_eq!(openai_tool_choice(ToolChoice::Required), json!("required"));
assert_eq!(
openai_tool_choice(ToolChoice::Function("get_weather".into())),
json!({"type": "function", "function": {"name": "get_weather"}})
);
}
#[test]
fn cache_control_ephemeral_shape() {
let v = serde_json::to_value(CacheControl::ephemeral()).expect("serialise");
assert_eq!(v, json!({"type": "ephemeral"}));
}
#[test]
fn request_usage_detailed_shape() {
let v = serde_json::to_value(RequestUsageConfig::detailed()).expect("serialise");
assert_eq!(v, json!({"include": true}));
}
}