flynn_openai/
file.rs

1use bytes::Bytes;
2use serde::Serialize;
3
4use crate::{
5    config::Config,
6    error::OpenAIError,
7    types::{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 100 GB.
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 100 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    pub async fn create(&self, request: CreateFileRequest) -> Result<OpenAIFile, OpenAIError> {
31        self.client.post_form("/files", request).await
32    }
33
34    /// Returns a list of files that belong to the user's organization.
35    pub async fn list<Q>(&self, query: &Q) -> Result<ListFilesResponse, OpenAIError>
36    where
37        Q: Serialize + ?Sized,
38    {
39        self.client.get_with_query("/files", query).await
40    }
41
42    /// Returns information about a specific file.
43    pub async fn retrieve(&self, file_id: &str) -> Result<OpenAIFile, OpenAIError> {
44        self.client.get(format!("/files/{file_id}").as_str()).await
45    }
46
47    /// Delete a file.
48    pub async fn delete(&self, file_id: &str) -> Result<DeleteFileResponse, OpenAIError> {
49        self.client
50            .delete(format!("/files/{file_id}").as_str())
51            .await
52    }
53
54    /// Returns the contents of the specified file
55    pub async fn content(&self, file_id: &str) -> Result<Bytes, OpenAIError> {
56        self.client
57            .get_raw(format!("/files/{file_id}/content").as_str())
58            .await
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use crate::{
65        types::{CreateFileRequestArgs, FilePurpose},
66        Client,
67    };
68    use crate::config::OpenAIConfig;
69
70    #[tokio::test]
71    async fn test_file_mod() {
72        let test_file_path = "/tmp/test.jsonl";
73        let contents = concat!(
74            "{\"prompt\": \"<prompt text>\", \"completion\": \"<ideal generated text>\"}\n", // \n is to make it valid jsonl
75            "{\"prompt\": \"<prompt text>\", \"completion\": \"<ideal generated text>\"}"
76        );
77
78        tokio::fs::write(test_file_path, contents).await.unwrap();
79
80        let api_key = "sk-5574034e4fc9421fb9293c000283eef3"; // 替换为您的 API 密钥
81        let config = OpenAIConfig::new()
82            .with_api_key(api_key)
83            .with_api_base("https://dashscope.aliyuncs.com/compatible-mode/v1");
84
85        let client = Client::with_config(config);
86
87        let request = CreateFileRequestArgs::default()
88            .file(test_file_path)
89            .purpose(FilePurpose::FileExtract)
90            .build()
91            .unwrap();
92
93        let openai_file = client.files().create(request).await.unwrap();
94
95        assert_eq!(openai_file.bytes, 135);
96        assert_eq!(openai_file.filename, "test.jsonl");
97        //assert_eq!(openai_file.purpose, "fine-tune");
98
99        //assert_eq!(openai_file.status, Some("processed".to_owned())); // uploaded or processed
100        let query = [("purpose", "fine-tune")];
101
102        let list_files = client.files().list(&query).await.unwrap();
103
104        assert_eq!(list_files.data.into_iter().last().unwrap(), openai_file);
105
106        let retrieved_file = client.files().retrieve(&openai_file.id).await.unwrap();
107
108        assert_eq!(openai_file.created_at, retrieved_file.created_at);
109        assert_eq!(openai_file.bytes, retrieved_file.bytes);
110        assert_eq!(openai_file.filename, retrieved_file.filename);
111        assert_eq!(openai_file.purpose, retrieved_file.purpose);
112
113        /*
114        // "To help mitigate abuse, downloading of fine-tune training files is disabled for free accounts."
115        let retrieved_contents = client.files().retrieve_content(&openai_file.id)
116            .await
117            .unwrap();
118
119        assert_eq!(contents, retrieved_contents);
120        */
121
122        // Sleep to prevent "File is still processing. Check back later."
123        tokio::time::sleep(std::time::Duration::from_secs(15)).await;
124        let delete_response = client.files().delete(&openai_file.id).await.unwrap();
125
126        assert_eq!(openai_file.id, delete_response.id);
127        assert!(delete_response.deleted);
128    }
129}