use serde::{Deserialize, Serialize};
use crate::{Completion, Message, Tool, ToolResult, Toolbox};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum Effort {
Off,
Low,
Medium,
High,
#[serde(rename = "xhigh")]
XHigh,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Request {
pub model: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub system: Option<String>,
#[serde(default)]
pub messages: Vec<Message>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tools: Vec<Tool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_tokens: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reasoning: Option<Effort>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub include_usage: Option<bool>,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub send_reasoning: bool,
}
impl Request {
pub fn new(model: impl Into<String>) -> Self {
Self {
model: model.into(),
system: None,
messages: Vec::new(),
tools: Vec::new(),
max_tokens: None,
temperature: None,
reasoning: None,
include_usage: None,
send_reasoning: false,
}
}
pub fn system(mut self, prompt: impl Into<String>) -> Self {
self.system = Some(prompt.into());
self
}
pub fn user(self, text: impl Into<String>) -> Self {
self.message(Message::user(text))
}
pub fn message(mut self, message: Message) -> Self {
self.messages.push(message);
self
}
pub fn assistant(self, done: Completion) -> Self {
self.message(Message::from(done))
}
pub fn tool_result(self, call_id: impl Into<String>, content: impl Into<String>) -> Self {
self.message(Message::tool_result(ToolResult::new(call_id, content)))
}
pub fn tool_results(mut self, results: impl IntoIterator<Item = ToolResult>) -> Self {
self.messages
.extend(results.into_iter().map(Message::tool_result));
self
}
pub fn tool(mut self, tool: Tool) -> Self {
self.tools.push(tool);
self
}
pub fn tools(mut self, toolbox: &(impl Toolbox + ?Sized)) -> Self {
self.tools.extend(toolbox.tools());
self
}
pub fn reasoning(mut self, effort: Effort) -> Self {
self.reasoning = Some(effort);
self
}
pub fn max_tokens(mut self, tokens: u64) -> Self {
self.max_tokens = Some(tokens);
self
}
pub fn temperature(mut self, temperature: f32) -> Self {
self.temperature = Some(temperature);
self
}
pub fn include_usage(mut self, include: bool) -> Self {
self.include_usage = Some(include);
self
}
pub fn send_reasoning(mut self, send: bool) -> Self {
self.send_reasoning = send;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{FinishReason, Part, Role, ToolCall};
#[test]
fn a_tool_round_trip_reads_as_the_conversation() {
let mut done = Completion::new(FinishReason::ToolCalls);
done.calls = vec![
ToolCall::new("call-a", "lookup", r#"{"value":1}"#),
ToolCall::new("call-b", "lookup", r#"{"value":2}"#),
];
let request = Request::new("m")
.system("Be brief.")
.tool(Tool::new("lookup", "Look up a value."))
.user("Use lookup")
.assistant(done)
.tool_results([
ToolResult::new("call-a", "42"),
ToolResult::new("call-b", "43"),
]);
let roles: Vec<_> = request.messages.iter().map(|m| m.role).collect();
assert_eq!(roles, [Role::User, Role::Assistant, Role::Tool, Role::Tool]);
assert_eq!(request.messages[1].parts.len(), 2);
assert_eq!(
request.messages[3].parts,
[Part::ToolResult(ToolResult::new("call-b", "43"))]
);
assert_eq!(request.system.as_deref(), Some("Be brief."));
}
#[test]
fn nothing_is_set_until_asked() {
let request = Request::new("m");
assert_eq!(request.max_tokens, None);
assert_eq!(request.temperature, None);
assert_eq!(request.reasoning, None);
assert_eq!(request.include_usage, None);
assert!(!request.send_reasoning);
let request = request
.max_tokens(16)
.temperature(0.5)
.reasoning(Effort::Low)
.include_usage(false)
.send_reasoning(true);
assert_eq!(request.max_tokens, Some(16));
assert_eq!(request.temperature, Some(0.5));
assert_eq!(request.reasoning, Some(Effort::Low));
assert_eq!(request.include_usage, Some(false));
assert!(request.send_reasoning);
}
}