use serde::{Deserialize, Serialize};
#[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(untagged)]
pub enum Content {
Text(String),
Parts(Vec<ContentPart>),
}
impl Content {
pub fn text(s: impl Into<String>) -> Self {
Self::Text(s.into())
}
}
impl<T: Into<String>> From<T> for Content {
fn from(s: T) -> Self {
Self::Text(s.into())
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ContentPart {
Text {
text: String,
},
ImageUrl {
image_url: ImageUrl,
},
}
impl ContentPart {
pub fn text(s: impl Into<String>) -> Self {
Self::Text { text: s.into() }
}
pub fn image(url: impl Into<String>) -> Self {
Self::ImageUrl {
image_url: ImageUrl {
url: url.into(),
detail: None,
},
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ImageUrl {
pub url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub detail: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Message {
pub role: Role,
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<Content>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_calls: Option<Vec<ToolCall>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_call_id: Option<String>,
}
impl Message {
pub fn system(content: impl Into<Content>) -> Self {
Self::simple(Role::System, content)
}
pub fn user(content: impl Into<Content>) -> Self {
Self::simple(Role::User, content)
}
pub fn assistant(content: impl Into<Content>) -> Self {
Self::simple(Role::Assistant, content)
}
pub fn tool(tool_call_id: impl Into<String>, content: impl Into<Content>) -> Self {
Self {
role: Role::Tool,
content: Some(content.into()),
tool_calls: None,
tool_call_id: Some(tool_call_id.into()),
}
}
fn simple(role: Role, content: impl Into<Content>) -> Self {
Self {
role,
content: Some(content.into()),
tool_calls: None,
tool_call_id: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum Tool {
Function {
function: FunctionDef,
},
}
impl Tool {
pub fn function(
name: impl Into<String>,
description: impl Into<String>,
parameters: serde_json::Value,
) -> Self {
Self::Function {
function: FunctionDef {
name: name.into(),
description: Some(description.into()),
parameters: Some(parameters),
},
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FunctionDef {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parameters: Option<serde_json::Value>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ToolCall {
pub id: String,
#[serde(rename = "type", default = "default_tool_type")]
pub kind: String,
pub function: FunctionCall,
}
fn default_tool_type() -> String {
"function".to_string()
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FunctionCall {
pub name: String,
pub arguments: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ToolChoice {
Mode(String),
Named {
#[serde(rename = "type")]
kind: String,
function: NamedFunction,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct NamedFunction {
pub name: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ResponseFormat {
Text,
JsonObject,
JsonSchema {
json_schema: JsonSchema,
},
}
impl ResponseFormat {
pub fn json_schema(name: impl Into<String>, schema: serde_json::Value) -> Self {
Self::JsonSchema {
json_schema: JsonSchema {
name: name.into(),
schema,
strict: Some(true),
},
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct JsonSchema {
pub name: String,
pub schema: serde_json::Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub strict: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ChatRequest {
pub model: String,
pub messages: Vec<Message>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stream: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_tokens: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<Tool>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_choice: Option<ToolChoice>,
#[serde(skip_serializing_if = "Option::is_none")]
pub response_format: Option<ResponseFormat>,
}
impl ChatRequest {
pub fn new(model: impl Into<String>, messages: Vec<Message>) -> Self {
Self {
model: model.into(),
messages,
stream: None,
temperature: None,
max_tokens: None,
tools: None,
tool_choice: None,
response_format: None,
}
}
#[must_use]
pub fn temperature(mut self, t: f32) -> Self {
self.temperature = Some(t);
self
}
#[must_use]
pub fn max_tokens(mut self, n: u32) -> Self {
self.max_tokens = Some(n);
self
}
#[must_use]
pub fn tools(mut self, tools: Vec<Tool>) -> Self {
self.tools = Some(tools);
self
}
#[must_use]
pub fn tool_choice(mut self, choice: ToolChoice) -> Self {
self.tool_choice = Some(choice);
self
}
#[must_use]
pub fn response_format(mut self, format: ResponseFormat) -> Self {
self.response_format = Some(format);
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Usage {
pub prompt_tokens: u32,
pub completion_tokens: u32,
pub total_tokens: u32,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ChatResponse {
pub id: String,
pub model: String,
pub choices: Vec<ResponseChoice>,
#[serde(default)]
pub usage: Option<Usage>,
}
impl ChatResponse {
pub fn content(&self) -> Option<&str> {
self.choices
.first()
.and_then(|c| c.message.content.as_ref())
.and_then(|c| match c {
Content::Text(s) => Some(s.as_str()),
Content::Parts(_) => None,
})
}
pub fn tool_calls(&self) -> &[ToolCall] {
self.choices
.first()
.and_then(|c| c.message.tool_calls.as_deref())
.unwrap_or(&[])
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ResponseChoice {
pub index: u32,
pub message: Message,
#[serde(default)]
pub finish_reason: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ChatChunk {
#[serde(default)]
pub id: String,
pub choices: Vec<ChunkChoice>,
#[serde(default)]
pub usage: Option<Usage>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ChunkChoice {
pub index: u32,
pub delta: Delta,
#[serde(default)]
pub finish_reason: Option<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct Delta {
#[serde(default)]
pub role: Option<String>,
#[serde(default)]
pub content: Option<String>,
#[serde(default)]
pub tool_calls: Option<Vec<ToolCallDelta>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ToolCallDelta {
pub index: u32,
#[serde(default)]
pub id: Option<String>,
#[serde(default)]
pub function: Option<FunctionDelta>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct FunctionDelta {
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub arguments: Option<String>,
}