async_openai/
file.rs

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