openai_safe/
domain.rs

1use anyhow::{anyhow, Result};
2use log::{error, info};
3use reqwest::{header, Client};
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6
7use crate::enums::{OpenAIAssistantRole, OpenAIRunStatus, OpenAIToolTypes};
8
9//OpenAI API response type format for Completions API
10#[derive(Deserialize, Serialize, Debug, Clone)]
11pub struct OpenAPICompletionsResponse {
12    pub id: Option<String>,
13    pub object: Option<String>,
14    pub created: Option<u32>,
15    pub model: Option<String>,
16    pub choices: Option<Vec<OpenAPICompletionsChoices>>,
17    pub usage: Option<OpenAPIUsage>,
18}
19
20#[derive(Deserialize, Serialize, Debug, Clone)]
21pub struct OpenAPICompletionsChoices {
22    pub text: Option<String>,
23    pub index: Option<u32>,
24    pub logprobs: Option<u32>,
25    pub finish_reason: Option<String>,
26}
27
28//OpenAI API response type format for Chat API
29#[derive(Deserialize, Serialize, Debug, Clone)]
30pub struct OpenAPIChatResponse {
31    pub id: Option<String>,
32    pub object: Option<String>,
33    pub created: Option<u32>,
34    pub model: Option<String>,
35    pub choices: Option<Vec<OpenAPIChatChoices>>,
36    pub usage: Option<OpenAPIUsage>,
37}
38
39#[derive(Deserialize, Serialize, Debug, Clone)]
40pub struct OpenAPIChatChoices {
41    pub message: OpenAPIChatMessage,
42    pub index: Option<u32>,
43    pub finish_reason: Option<String>,
44}
45
46#[derive(Deserialize, Serialize, Debug, Clone)]
47pub struct OpenAPIChatMessage {
48    pub role: String,
49    pub content: Option<String>,
50    pub function_call: Option<OpenAPIChatFunctionCall>,
51}
52
53#[derive(Deserialize, Serialize, Debug, Clone)]
54pub struct OpenAPIChatFunctionCall {
55    pub(crate) name: String,
56    pub(crate) arguments: String,
57}
58
59#[derive(Deserialize, Serialize, Debug, Clone)]
60pub struct OpenAPIUsage {
61    prompt_tokens: Option<u32>,
62    completion_tokens: Option<u32>,
63    total_tokens: Option<u32>,
64}
65
66#[derive(Deserialize, Serialize, Debug, Clone)]
67pub struct OpenAIRateLimit {
68    pub tpm: usize, // tokens-per-minute
69    pub rpm: usize, // requests-per-minute
70}
71
72#[derive(Deserialize, Serialize, Debug, Clone, JsonSchema)]
73pub struct OpenAIDataResponse<T: JsonSchema> {
74    pub data: T,
75}
76
77#[derive(Deserialize, Serialize, Debug, Clone)]
78pub struct OpenAIAssistantResp {
79    pub id: String,
80    pub object: String,
81    pub created_at: u32,
82    pub name: Option<String>,
83    pub description: Option<String>,
84    pub instructions: Option<String>,
85    pub model: String,
86    pub tools: Vec<OpenAITools>,
87    pub file_ids: Vec<String>,
88}
89
90#[derive(Deserialize, Serialize, Debug, Clone)]
91pub struct OpenAITools {
92    #[serde(rename(deserialize = "type", serialize = "type"))]
93    tool_type: OpenAIToolTypes,
94}
95
96#[derive(Deserialize, Serialize, Debug, Clone)]
97pub struct OpenAIThreadResp {
98    pub id: String,
99    pub object: String,
100    pub created_at: u32,
101}
102
103#[derive(Deserialize, Serialize, Debug, Clone)]
104pub struct OpenAIMessageListResp {
105    pub object: String,
106    pub data: Vec<OpenAIMessageResp>,
107}
108
109#[derive(Deserialize, Serialize, Debug, Clone)]
110pub struct OpenAIMessageResp {
111    pub id: String,
112    pub object: String,
113    pub created_at: u32,
114    pub thread_id: String,
115    pub role: OpenAIAssistantRole,
116    pub content: Vec<OpenAIContent>,
117    //Other fields omitted as no use for now
118}
119
120#[derive(Deserialize, Serialize, Debug, Clone)]
121pub struct OpenAIContent {
122    #[serde(rename(deserialize = "type", serialize = "type"))]
123    pub content_type: String,
124    pub text: Option<OpenAIContentText>,
125}
126
127#[derive(Deserialize, Serialize, Debug, Clone)]
128pub struct OpenAIContentText {
129    pub value: String,
130}
131
132#[derive(Deserialize, Serialize, Debug, Clone)]
133pub struct OpenAIRunResp {
134    pub id: String,
135    pub object: String,
136    pub created_at: u32,
137    pub status: OpenAIRunStatus,
138}
139
140#[derive(Deserialize, Serialize, Debug, Clone)]
141pub struct OpenAIFile {
142    pub id: String,
143    debug: bool,
144    api_key: String,
145}
146
147#[derive(Deserialize, Serialize, Debug, Clone)]
148pub struct OpenAIFileResp {
149    id: String,
150}
151
152impl OpenAIFile {
153    //Constructor
154    pub async fn new(file_bytes: Vec<u8>, open_ai_key: &str, debug: bool) -> Result<Self> {
155        let mut new_file = OpenAIFile {
156            id: "this-will-be-overwritten".to_string(),
157            debug,
158            api_key: open_ai_key.to_string(),
159        };
160        //Upload file and get the ID
161        new_file.upload_file(file_bytes).await?;
162        Ok(new_file)
163    }
164
165    /*
166     * This function uploads a file to OpenAI and assigns it for use with Assistant API
167     */
168    async fn upload_file(&mut self, file_bytes: Vec<u8>) -> Result<()> {
169        let files_url = "https://api.openai.com/v1/files";
170
171        let form = reqwest::multipart::Form::new()
172            .text("purpose", "assistants")
173            .part(
174                "file",
175                reqwest::multipart::Part::bytes(file_bytes).file_name("file.pdf"),
176            );
177
178        //Make the API call
179        let client = Client::new();
180
181        let response = client
182            .post(files_url)
183            .header(header::CONTENT_TYPE, "application/json")
184            .header("OpenAI-Beta", "assistants=v1")
185            .bearer_auth(&self.api_key)
186            .multipart(form)
187            .send()
188            .await?;
189
190        let response_status = response.status();
191        let response_text = response.text().await?;
192
193        if self.debug {
194            info!(
195                "[debug] OpenAI Files status API response: [{}] {:#?}",
196                &response_status, &response_text
197            );
198        }
199
200        //Deserialize the string response into the Message object to confirm if there were any errors
201        let response_deser: OpenAIFileResp =
202            serde_json::from_str(&response_text).map_err(|error| {
203                error!(
204                    "[OpenAIAssistant] Files API response serialization error: {}",
205                    &error
206                );
207                anyhow!("Error: {}", error)
208            })?;
209
210        self.id = response_deser.id;
211
212        Ok(())
213    }
214}