diff --git a/async-openai/src/file.rs b/async-openai/src/file.rs index cfca19c7..b8b1ed81 100644 --- a/async-openai/src/file.rs +++ b/async-openai/src/file.rs @@ -70,7 +70,7 @@ impl<'c, C: Config> Files<'c, C> { #[cfg(test)] mod tests { use crate::{ - types::{CreateFileRequestArgs, FilePurpose}, + types::{CreateFileRequestArgs, FilePurpose, FileExpiresAfter, FileExpiresAfterAnchor}, Client, }; @@ -89,6 +89,7 @@ mod tests { let request = CreateFileRequestArgs::default() .file(test_file_path) .purpose(FilePurpose::FineTune) + .expires_after(FileExpiresAfter{ anchor: FileExpiresAfterAnchor::CreatedAt, seconds: 3600 }) .build() .unwrap(); @@ -111,6 +112,7 @@ mod tests { assert_eq!(openai_file.bytes, retrieved_file.bytes); assert_eq!(openai_file.filename, retrieved_file.filename); assert_eq!(openai_file.purpose, retrieved_file.purpose); + assert_eq!(openai_file.expires_at, retrieved_file.expires_at); /* // "To help mitigate abuse, downloading of fine-tune training files is disabled for free accounts." diff --git a/async-openai/src/types/file.rs b/async-openai/src/types/file.rs index 9a2e5090..ebd90a83 100644 --- a/async-openai/src/types/file.rs +++ b/async-openai/src/types/file.rs @@ -19,6 +19,22 @@ pub enum FilePurpose { Vision, } +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +pub enum FileExpiresAfterAnchor { + #[default] + #[serde(rename = "created_at")] + CreatedAt +} + +#[derive(Debug, Default, Clone, PartialEq)] +pub struct FileExpiresAfter { + /// Anchor timestamp after which the expiration policy applies. Supported anchors: `created_at`. + pub anchor: FileExpiresAfterAnchor, + + /// The number of seconds after the anchor time that the file will expire. Must be between 3600 (1 hour) and 2592000 (30 days). + pub seconds: u32, +} + #[derive(Debug, Default, Clone, Builder, PartialEq)] #[builder(name = "CreateFileRequestArgs")] #[builder(pattern = "mutable")] @@ -33,6 +49,9 @@ pub struct CreateFileRequest { /// /// Use "assistants" for [Assistants](https://platform.openai.com/docs/api-reference/assistants) and [Message](https://platform.openai.com/docs/api-reference/messages) files, "vision" for Assistants image file inputs, "batch" for [Batch API](https://platform.openai.com/docs/guides/batch), and "fine-tune" for [Fine-tuning](https://platform.openai.com/docs/api-reference/fine-tuning). pub purpose: FilePurpose, + + /// The expiration policy for a file. By default, files with `purpose=batch` expire after 30 days and all other files are persisted until they are manually deleted. + pub expires_after: Option, } #[derive(Debug, Deserialize, Clone, PartialEq, Serialize)] @@ -77,6 +96,8 @@ pub struct OpenAIFile { pub bytes: u32, /// The Unix timestamp (in seconds) for when the file was created. pub created_at: u32, + /// The Unix timestamp (in seconds) for when the file will expire. + pub expires_at: Option, /// The name of the file. pub filename: String, /// The intended purpose of the file. Supported values are `assistants`, `assistants_output`, `batch`, `batch_output`, `fine-tune`, `fine-tune-results` and `vision`. diff --git a/async-openai/src/types/impls.rs b/async-openai/src/types/impls.rs index 8646e8f9..d11bf604 100644 --- a/async-openai/src/types/impls.rs +++ b/async-openai/src/types/impls.rs @@ -29,7 +29,7 @@ use super::{ CreateSpeechResponse, CreateTranscriptionRequest, CreateTranslationRequest, DallE2ImageSize, EmbeddingInput, FileInput, FilePurpose, FunctionName, Image, ImageInput, ImageModel, ImageResponseFormat, ImageSize, ImageUrl, ImagesResponse, ModerationInput, Prompt, Role, Stop, - TimestampGranularity, + TimestampGranularity, FileExpiresAfterAnchor }; /// for `impl_from!(T, Enum)`, implements @@ -278,6 +278,18 @@ impl Display for FilePurpose { } } +impl Display for FileExpiresAfterAnchor { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::CreatedAt => "created_at", + } + ) + } +} + impl ImagesResponse { /// Save each image in a dedicated Tokio task and return paths to saved files. /// For [ResponseFormat::Url] each file is downloaded in dedicated Tokio task. @@ -970,9 +982,14 @@ impl AsyncTryFrom for reqwest::multipart::Form { async fn try_from(request: CreateFileRequest) -> Result { let file_part = create_file_part(request.file.source).await?; - let form = reqwest::multipart::Form::new() + let mut form = reqwest::multipart::Form::new() .part("file", file_part) .text("purpose", request.purpose.to_string()); + + if let Some(expires_after) = request.expires_after { + form = form.text("expires_after[anchor]", expires_after.anchor.to_string()) + .text("expires_after[seconds]", expires_after.seconds.to_string()); + } Ok(form) } } diff --git a/async-openai/src/vector_store_files.rs b/async-openai/src/vector_store_files.rs index 5ecaac06..cb7d2748 100644 --- a/async-openai/src/vector_store_files.rs +++ b/async-openai/src/vector_store_files.rs @@ -113,6 +113,7 @@ mod tests { String::from(":3").into_bytes(), ), purpose: FilePurpose::Assistants, + expires_after: None, }) .await?;