use crate::error::LingerError;
use crate::RequestId;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::BTreeMap;
#[derive(Clone, Debug, Serialize, PartialEq)]
#[non_exhaustive]
pub struct CreateChatCompletionRequest {
pub model: String,
pub messages: Vec<ChatMessage>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_completion_tokens: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
#[serde(flatten)]
pub extra: BTreeMap<String, Value>,
}
impl CreateChatCompletionRequest {
pub fn builder() -> CreateChatCompletionRequestBuilder {
CreateChatCompletionRequestBuilder::default()
}
}
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct CreateChatCompletionRequestBuilder {
model: Option<String>,
messages: Vec<ChatMessage>,
max_completion_tokens: Option<u32>,
temperature: Option<f32>,
extra: BTreeMap<String, Value>,
}
impl CreateChatCompletionRequestBuilder {
pub fn model(mut self, model: impl Into<String>) -> Self {
self.model = Some(model.into());
self
}
pub fn message(mut self, message: ChatMessage) -> Self {
self.messages.push(message);
self
}
pub fn max_completion_tokens(mut self, max_completion_tokens: u32) -> Self {
self.max_completion_tokens = Some(max_completion_tokens);
self
}
pub fn temperature(mut self, temperature: f32) -> Self {
self.temperature = Some(temperature);
self
}
pub fn extra(mut self, name: impl Into<String>, value: Value) -> Self {
self.extra.insert(name.into(), value);
self
}
pub fn build(self) -> Result<CreateChatCompletionRequest, LingerError> {
let model = self
.model
.filter(|value| !value.trim().is_empty())
.ok_or_else(|| LingerError::invalid_config("model is required"))?;
if self.messages.is_empty() {
return Err(LingerError::invalid_config("messages is required"));
}
if self
.messages
.iter()
.any(|message| message.role.trim().is_empty() || message.content.is_empty())
{
return Err(LingerError::invalid_config(
"message role and content must not be empty",
));
}
Ok(CreateChatCompletionRequest {
model,
messages: self.messages,
max_completion_tokens: self.max_completion_tokens,
temperature: self.temperature,
extra: self.extra,
})
}
}
#[derive(Clone, Debug, Default, Serialize, PartialEq)]
#[non_exhaustive]
pub struct ModifyChatCompletionRequest {
pub metadata: BTreeMap<String, String>,
}
impl ModifyChatCompletionRequest {
pub fn builder() -> ModifyChatCompletionRequestBuilder {
ModifyChatCompletionRequestBuilder::default()
}
}
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct ModifyChatCompletionRequestBuilder {
metadata: BTreeMap<String, String>,
}
impl ModifyChatCompletionRequestBuilder {
pub fn metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
pub fn build(self) -> Result<ModifyChatCompletionRequest, LingerError> {
validate_metadata(&self.metadata)?;
Ok(ModifyChatCompletionRequest {
metadata: self.metadata,
})
}
}
#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
#[non_exhaustive]
pub struct ChatMessage {
pub role: String,
pub content: String,
}
impl ChatMessage {
pub fn developer(content: impl Into<String>) -> Self {
Self::new("developer", content)
}
pub fn system(content: impl Into<String>) -> Self {
Self::new("system", content)
}
pub fn user(content: impl Into<String>) -> Self {
Self::new("user", content)
}
pub fn assistant(content: impl Into<String>) -> Self {
Self::new("assistant", content)
}
pub fn new(role: impl Into<String>, content: impl Into<String>) -> Self {
Self {
role: role.into(),
content: content.into(),
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[non_exhaustive]
pub struct ChatCompletion {
pub id: String,
pub object: String,
pub created: u64,
pub model: String,
#[serde(default)]
pub choices: Vec<ChatCompletionChoice>,
#[serde(default)]
pub usage: Option<ChatCompletionUsage>,
#[serde(flatten)]
pub extra: BTreeMap<String, Value>,
#[serde(skip)]
request_id: Option<RequestId>,
}
impl ChatCompletion {
pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
self.request_id = request_id;
self
}
pub fn request_id(&self) -> Option<&RequestId> {
self.request_id.as_ref()
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[non_exhaustive]
pub struct ChatCompletionPage {
pub object: String,
#[serde(default)]
pub data: Vec<ChatCompletion>,
#[serde(default)]
pub first_id: Option<String>,
#[serde(default)]
pub last_id: Option<String>,
pub has_more: bool,
#[serde(skip)]
request_id: Option<RequestId>,
}
impl ChatCompletionPage {
pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
self.request_id = request_id;
self
}
pub fn request_id(&self) -> Option<&RequestId> {
self.request_id.as_ref()
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[non_exhaustive]
pub struct ChatCompletionDeletion {
pub id: String,
pub object: String,
pub deleted: bool,
#[serde(skip)]
request_id: Option<RequestId>,
}
impl ChatCompletionDeletion {
pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
self.request_id = request_id;
self
}
pub fn request_id(&self) -> Option<&RequestId> {
self.request_id.as_ref()
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[non_exhaustive]
pub struct ChatCompletionChoice {
pub index: u32,
pub message: ChatCompletionMessage,
#[serde(default)]
pub finish_reason: Option<String>,
#[serde(flatten)]
pub extra: BTreeMap<String, Value>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[non_exhaustive]
pub struct ChatCompletionMessage {
pub role: String,
#[serde(default)]
pub content: Option<String>,
#[serde(flatten)]
pub extra: BTreeMap<String, Value>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[non_exhaustive]
pub struct ChatCompletionStoredMessage {
pub id: String,
pub object: String,
pub role: String,
#[serde(default)]
pub content: Option<String>,
#[serde(flatten)]
pub extra: BTreeMap<String, Value>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[non_exhaustive]
pub struct ChatCompletionMessagePage {
pub object: String,
#[serde(default)]
pub data: Vec<ChatCompletionStoredMessage>,
#[serde(default)]
pub first_id: Option<String>,
#[serde(default)]
pub last_id: Option<String>,
pub has_more: bool,
#[serde(skip)]
request_id: Option<RequestId>,
}
impl ChatCompletionMessagePage {
pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
self.request_id = request_id;
self
}
pub fn request_id(&self) -> Option<&RequestId> {
self.request_id.as_ref()
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[non_exhaustive]
pub struct ChatCompletionUsage {
pub prompt_tokens: u64,
pub completion_tokens: u64,
pub total_tokens: u64,
}
fn validate_metadata(metadata: &BTreeMap<String, String>) -> Result<(), LingerError> {
for key in metadata.keys() {
if key.trim().is_empty() {
return Err(LingerError::invalid_config(
"metadata keys must not be empty",
));
}
}
Ok(())
}