Skip to main content

async_openai/
file.rs

1use bytes::Bytes;
2
3use crate::{
4    config::Config,
5    error::OpenAIError,
6    types::files::{CreateFileRequest, DeleteFileResponse, ListFilesResponse, OpenAIFile},
7    Client, RequestOptions,
8};
9
10/// Files are used to upload documents that can be used with features like Assistants and Fine-tuning.
11pub struct Files<'c, C: Config> {
12    client: &'c Client<C>,
13    pub(crate) request_options: RequestOptions,
14}
15
16impl<'c, C: Config> Files<'c, C> {
17    pub fn new(client: &'c Client<C>) -> Self {
18        Self {
19            client,
20            request_options: RequestOptions::new(),
21        }
22    }
23
24    /// Upload a file that can be used across various endpoints. Individual files can be up to 512 MB,
25    /// and each project can store up to 2.5 TB of files in total. There is no organization-wide storage limit.
26    ///
27    /// The Assistants API supports files up to 2 million tokens and of specific file types. See the [Assistants Tools guide](https://platform.openai.com/docs/assistants/tools) for details.
28    ///
29    /// The Fine-tuning API only supports `.jsonl` files. The input also has certain required formats for fine-tuning [chat](https://platform.openai.com/docs/api-reference/fine-tuning/chat-input) or [completions](https://platform.openai.com/docs/api-reference/fine-tuning/completions-input) models.
30    ///
31    /// The Batch API only supports `.jsonl` files up to 200 MB in size. The input also has a specific required [format](https://platform.openai.com/docs/api-reference/batch/request-input).
32    #[crate::byot(
33        T0 = Clone,
34        R = serde::de::DeserializeOwned,
35        where_clause =  "reqwest::multipart::Form: crate::traits::AsyncTryFrom<T0, Error = OpenAIError>",
36    )]
37    pub async fn create(&self, request: CreateFileRequest) -> Result<OpenAIFile, OpenAIError> {
38        self.client
39            .post_form("/files", request, &self.request_options)
40            .await
41    }
42
43    /// Returns a list of files that belong to the user's organization.
44    #[crate::byot(R = serde::de::DeserializeOwned)]
45    pub async fn list(&self) -> Result<ListFilesResponse, OpenAIError> {
46        self.client.get("/files", &self.request_options).await
47    }
48
49    /// Returns information about a specific file.
50    #[crate::byot(T0 = std::fmt::Display, R = serde::de::DeserializeOwned)]
51    pub async fn retrieve(&self, file_id: &str) -> Result<OpenAIFile, OpenAIError> {
52        self.client
53            .get(format!("/files/{file_id}").as_str(), &self.request_options)
54            .await
55    }
56
57    /// Delete a file.
58    #[crate::byot(T0 = std::fmt::Display, R = serde::de::DeserializeOwned)]
59    pub async fn delete(&self, file_id: &str) -> Result<DeleteFileResponse, OpenAIError> {
60        self.client
61            .delete(format!("/files/{file_id}").as_str(), &self.request_options)
62            .await
63    }
64
65    /// Returns the contents of the specified file
66    pub async fn content(&self, file_id: &str) -> Result<Bytes, OpenAIError> {
67        let (bytes, _headers) = self
68            .client
69            .get_raw(
70                format!("/files/{file_id}/content").as_str(),
71                &self.request_options,
72            )
73            .await?;
74        Ok(bytes)
75    }
76}
77
78#[cfg(all(test, feature = "file"))]
79mod tests {
80    use crate::{
81        traits::RequestOptionsBuilder,
82        types::files::{
83            CreateFileRequestArgs, FileExpirationAfter, FileExpirationAfterAnchor, FilePurpose,
84        },
85        Client,
86    };
87
88    #[tokio::test]
89    async fn test_file_mod() {
90        let test_file_path = "/tmp/test.jsonl";
91        let contents = concat!(
92            "{\"prompt\": \"<prompt text>\", \"completion\": \"<ideal generated text>\"}\n", // \n is to make it valid jsonl
93            "{\"prompt\": \"<prompt text>\", \"completion\": \"<ideal generated text>\"}"
94        );
95
96        tokio::fs::write(test_file_path, contents).await.unwrap();
97
98        let client = Client::new();
99
100        let request = CreateFileRequestArgs::default()
101            .file(test_file_path)
102            .purpose(FilePurpose::FineTune)
103            .expires_after(FileExpirationAfter {
104                anchor: FileExpirationAfterAnchor::CreatedAt,
105                seconds: 3600,
106            })
107            .build()
108            .unwrap();
109
110        let openai_file = client.files().create(request).await.unwrap();
111
112        assert_eq!(openai_file.bytes, 135);
113        assert_eq!(openai_file.filename, "test.jsonl");
114        //assert_eq!(openai_file.purpose, "fine-tune");
115
116        //assert_eq!(openai_file.status, Some("processed".to_owned())); // uploaded or processed
117        let query = [("purpose", "fine-tune")];
118
119        let list_files = client.files().query(&query).unwrap().list().await.unwrap();
120
121        assert_eq!(list_files.data.into_iter().last().unwrap(), openai_file);
122
123        let retrieved_file = client.files().retrieve(&openai_file.id).await.unwrap();
124
125        assert_eq!(openai_file.created_at, retrieved_file.created_at);
126        assert_eq!(openai_file.bytes, retrieved_file.bytes);
127        assert_eq!(openai_file.filename, retrieved_file.filename);
128        assert_eq!(openai_file.purpose, retrieved_file.purpose);
129        assert_eq!(openai_file.expires_at, retrieved_file.expires_at);
130
131        /*
132        // "To help mitigate abuse, downloading of fine-tune training files is disabled for free accounts."
133        let retrieved_contents = client.files().retrieve_content(&openai_file.id)
134            .await
135            .unwrap();
136
137        assert_eq!(contents, retrieved_contents);
138        */
139
140        // Sleep to prevent "File is still processing. Check back later."
141        tokio::time::sleep(std::time::Duration::from_secs(15)).await;
142        let delete_response = client.files().delete(&openai_file.id).await.unwrap();
143
144        assert_eq!(openai_file.id, delete_response.id);
145        assert!(delete_response.deleted);
146    }
147
148    // Ensures that list files succeeds if there are no files in account
149    // Prerequisite: No files in account
150    #[tokio::test]
151    async fn test_empty_file_list() {
152        let client = Client::new();
153        let result = client.files().list().await.unwrap();
154        assert!(result.data.is_empty());
155    }
156}