use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Role {
System,
User,
Assistant,
Tool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum Part {
Text {
text: String,
},
ToolCall {
id: String,
name: String,
arguments: Value,
},
ToolResult {
call_id: String,
content: String,
is_error: bool,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Message {
pub role: Role,
pub parts: Vec<Part>,
}
impl Message {
pub fn new(role: Role, parts: Vec<Part>) -> Self {
Self { role, parts }
}
pub fn user(text: impl Into<String>) -> Self {
Self::text(Role::User, text)
}
pub fn assistant(text: impl Into<String>) -> Self {
Self::text(Role::Assistant, text)
}
pub fn text(role: Role, text: impl Into<String>) -> Self {
Self::new(role, vec![Part::Text { text: text.into() }])
}
pub fn tool_result(
call_id: impl Into<String>,
content: impl Into<String>,
is_error: bool,
) -> Self {
Self::new(
Role::Tool,
vec![Part::ToolResult {
call_id: call_id.into(),
content: content.into(),
is_error,
}],
)
}
pub fn text_content(&self) -> String {
self.parts
.iter()
.filter_map(|p| match p {
Part::Text { text } => Some(text.as_str()),
_ => None,
})
.collect::<Vec<_>>()
.join("")
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Tool {
pub name: String,
pub description: String,
pub parameters: Value,
}
impl Tool {
pub fn new(name: impl Into<String>, description: impl Into<String>, parameters: Value) -> Self {
Self {
name: name.into(),
description: description.into(),
parameters,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ChatRequest {
pub model: String,
pub system: Option<String>,
pub messages: Vec<Message>,
pub tools: Vec<Tool>,
pub max_tokens: Option<u32>,
pub temperature: Option<f32>,
}
pub const DEFAULT_MAX_TOKENS: u32 = 4096;
impl ChatRequest {
pub fn new(model: impl Into<String>) -> Self {
Self {
model: model.into(),
..Default::default()
}
}
pub fn system(mut self, system: impl Into<String>) -> Self {
self.system = Some(system.into());
self
}
pub fn message(mut self, message: Message) -> Self {
self.messages.push(message);
self
}
pub fn tool(mut self, tool: Tool) -> Self {
self.tools.push(tool);
self
}
pub fn max_tokens(mut self, max_tokens: u32) -> Self {
self.max_tokens = Some(max_tokens);
self
}
pub fn temperature(mut self, temperature: f32) -> Self {
self.temperature = Some(temperature);
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StopReason {
EndTurn,
ToolUse,
MaxTokens,
Paused,
Refusal,
Other(String),
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Usage {
pub input_tokens: u32,
pub output_tokens: u32,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ChatResponse {
pub model: String,
pub message: Message,
pub stop_reason: StopReason,
pub usage: Usage,
}
impl ChatResponse {
pub fn text(&self) -> String {
self.message.text_content()
}
pub fn tool_calls(&self) -> Vec<(String, String, Value)> {
self.message
.parts
.iter()
.filter_map(|p| match p {
Part::ToolCall {
id,
name,
arguments,
} => Some((id.clone(), name.clone(), arguments.clone())),
_ => None,
})
.collect()
}
}