openai_interface/chat/
mod.rs

1//! Response to a given `chat` conversation.
2
3use std::str::FromStr;
4
5use serde::{Deserialize, Serialize};
6
7use crate::errors::OapiError;
8
9pub mod create;
10pub mod retrieve;
11pub mod update;
12
13/// The service tier used for processing the request.
14///
15/// This enum represents the different service tiers that can be specified when
16/// making a request to the API. Each tier corresponds to different performance
17/// characteristics and pricing models.
18#[derive(Debug, Serialize, Deserialize, Clone)]
19#[serde(rename_all = "lowercase")]
20pub enum ServiceTier {
21    /// Automatically select the service tier based on project settings.
22    Auto,
23    /// Use the default service tier with standard pricing and performance.
24    Default,
25    /// Use the flex service tier for flexible processing requirements.
26    Flex,
27    /// Use the scale service tier for scalable processing needs.
28    Scale,
29    /// Use the priority service tier for high-priority requests.
30    Priority,
31}
32
33#[derive(Debug, Deserialize)]
34pub struct ChatCompletion {
35    /// A unique identifier for the chat completion.
36    pub id: String,
37    /// A list of chat completion choices. Can be more than one
38    /// if `n` is greater than 1.
39    pub choices: Vec<Choice>,
40    /// The Unix timestamp (in seconds) of when the chat completion was created.
41    pub created: u64,
42    /// The model used for the chat completion.
43    pub model: String,
44    /// Specifies the processing type used for serving the request.
45    ///
46    /// - If set to 'auto', then the request will be processed with the service tier
47    ///   configured in the Project settings. Unless otherwise configured, the Project
48    ///   will use 'default'.
49    /// - If set to 'default', then the request will be processed with the standard
50    ///   pricing and performance for the selected model.
51    /// - If set to '[flex](https://platform.openai.com/docs/guides/flex-processing)' or
52    ///   '[priority](https://openai.com/api-priority-processing/)', then the request
53    ///   will be processed with the corresponding service tier.
54    /// - When not set, the default behavior is 'auto'.
55    ///
56    /// When the `service_tier` parameter is set, the response body will include the
57    /// `service_tier` value based on the processing mode actually used to serve the
58    /// request. This response value may be different from the value set in the
59    /// parameter.
60    pub service_tier: Option<ServiceTier>,
61    /// The system fingerprint used for the chat completion.
62    /// Can be used in conjunction with the `seed` request parameter to understand when
63    /// backend changes have been made that might impact determinism.
64    pub system_fingerprint: Option<String>,
65    /// The object type, which is always `chat.completion`.
66    pub object: ChatCompletionObject,
67    /// Usage statistics for the completion request.
68    pub usage: Option<CompletionUsage>,
69}
70
71/// The object type, which is always `chat.completion`.
72#[derive(Debug, Deserialize)]
73pub enum ChatCompletionObject {
74    /// The object type is always `chat.completion`.
75    #[serde(rename = "chat.completion")]
76    ChatCompletion,
77}
78
79#[derive(Debug, Deserialize)]
80pub struct Choice {
81    /// The reason the model stopped generating tokens.
82    ///
83    /// This will be `stop` if the model hit a natural stop point or a provided stop
84    /// sequence, `length` if the maximum number of tokens specified in the request was
85    /// reached, `content_filter` if content was omitted due to a flag from our content
86    /// filters, `tool_calls` if the model called a tool, or `function_call`
87    /// (deprecated) if the model called a function.
88    pub finish_reason: FinishReason,
89    /// The index of the choice in the list of choices.
90    pub index: usize,
91    /// Log probability information for the choice.
92    pub logprobs: Option<ChoiceLogprobs>,
93    /// A chat completion message generated by the model.
94    pub message: ChatCompletionMessage,
95}
96
97#[derive(Debug, Deserialize, PartialEq)]
98#[serde(rename_all = "snake_case")]
99pub enum FinishReason {
100    Length,
101    Stop,
102    ToolCalls,
103    FunctionCall,
104    ContentFilter,
105    /// This choice can only be found in the manual of DeepSeek
106    InsufficientSystemResource,
107}
108
109/// Fields that are not supported yet:
110/// - _audio_: If the audio output modality is requested, this object contains
111/// data about the audio response from the model.
112/// [Learn more from OpenAI](https://platform.openai.com/docs/guides/audio).
113#[derive(Debug, Deserialize)]
114pub struct ChatCompletionMessage {
115    /// The role of the author of this message. This shall always
116    /// be ResponseRole::Assistant
117    pub role: ResponseRole,
118    /// The contents of the message.
119    pub content: Option<String>,
120    pub reasoning_content: Option<String>,
121    /// The tool calls generated by the model, such as function calls.
122    /// Tool calls deserialization is not supported yet.
123    pub tool_calls: Option<Vec<ChatCompletionMessageToolCall>>,
124}
125
126#[derive(Debug, Deserialize)]
127#[serde(tag = "type", rename_all = "snake_case")]
128pub enum ChatCompletionMessageToolCall {
129    /// The type of the tool. Currently, only `function` is supported.
130    /// The field { type = "function" } is added automatically.
131    Function {
132        /// The ID of the tool call.
133        id: String,
134        /// The function that the model called.
135        function: String, // function type
136    },
137    /// The type of the tool. Always `custom`.
138    /// The field { type = "custom" } is added automatically.
139    Custom {
140        /// The id of the tool call.
141        id: String,
142        /// The custom tool that the model called.
143        custom: MessageToolCallCustom,
144    },
145}
146
147#[derive(Debug, Deserialize)]
148pub struct MessageToolCallCustom {
149    /// The input for the custom tool call generated by the model.
150    pub input: String,
151    /// The name of the custom tool to call.
152    pub name: String,
153}
154
155#[derive(Debug, Deserialize)]
156pub struct MessageToolCallFunction {
157    /// The arguments to call the function with, as generated by the model in JSON
158    /// format. Note that the model does not always generate valid JSON, and may
159    /// hallucinate parameters not defined by your function schema. Validate the
160    /// arguments in your code before calling your function.
161    pub arguments: String,
162    /// The name of the function to call.
163    pub name: String,
164}
165
166#[derive(Debug, Deserialize)]
167#[serde(rename_all = "snake_case")]
168pub enum ResponseRole {
169    /// The role of the response message is always assistant.
170    Assistant,
171}
172
173#[derive(Debug, Deserialize)]
174pub struct ChoiceLogprobs {
175    /// A list of message content tokens with log probability information.
176    pub content: Option<Vec<TokenLogProb>>,
177    /// Only found in DeepSeek's manual.
178    pub reasoning_content: Option<Vec<TokenLogProb>>,
179    /// A list of message refusal tokens with log probability information.
180    pub refusal: Option<Vec<TokenLogProb>>,
181}
182
183#[derive(Debug, Deserialize)]
184pub struct TokenLogProb {
185    /// The token.
186    pub token: String,
187    /// The log probability of this token, if it is within the top 20 most likely
188    /// tokens. Otherwise, the value `-9999.0` is used to signify that the token is very
189    /// unlikely.
190    pub logprob: f32,
191    /// A list of integers representing the UTF-8 bytes representation of the token.
192    ///
193    /// Useful in instances where characters are represented by multiple tokens and
194    /// their byte representations must be combined to generate the correct text
195    /// representation. Can be `null` if there is no bytes representation for the token.
196    pub bytes: Option<Vec<u8>>,
197    /// List of the most likely tokens and their log probability, at this token
198    /// position. In rare cases, there may be fewer than the number of requested
199    /// `top_logprobs` returned.
200    pub top_logprobs: Vec<TopLogprob>,
201}
202
203#[derive(Debug, Deserialize)]
204pub struct TopLogprob {
205    /// The token.
206    pub token: String,
207    /// A list of integers representing the UTF-8 bytes representation of the token.
208    ///
209    /// Useful in instances where characters are represented by multiple tokens and
210    /// their byte representations must be combined to generate the correct text
211    /// representation. Can be `null` if there is no bytes representation for the token.
212    pub logprob: f32,
213    /// List of the most likely tokens and their log probability, at this token
214    /// position. In rare cases, there may be fewer than the number of requested
215    /// `top_logprobs` returned.
216    pub bytes: Option<Vec<u8>>,
217}
218
219#[derive(Debug, Deserialize)]
220pub struct CompletionUsage {
221    /// Number of tokens in the generated completion.
222    pub completion_tokens: usize,
223    /// Number of tokens in the prompt.
224    pub prompt_tokens: usize,
225
226    // These two fields seem to be DeepSeek specific.
227    /// Number of tokens in the prompt that hits the context cache.
228    pub prompt_cache_hit_tokens: Option<usize>,
229    /// Number of tokens in the prompt that misses the context cache.
230    pub prompt_cache_miss_tokens: Option<usize>,
231
232    /// Total number of tokens used in the request (prompt + completion).
233    pub total_tokens: usize,
234    /// Breakdown of tokens used in a completion.
235    pub completion_tokens_details: Option<CompletionTokensDetails>,
236    /// Breakdown of tokens used in the prompt.
237    pub prompt_tokens_details: Option<PromptTokensDetails>,
238}
239
240#[derive(Debug, Deserialize)]
241pub struct CompletionTokensDetails {
242    /// When using Predicted Outputs, the number of tokens in the prediction that
243    /// appeared in the completion.
244    pub accepted_prediction_tokens: Option<usize>,
245    /// Audio input tokens generated by the model.
246    pub audio_tokens: Option<usize>,
247    /// Tokens generated by the model for reasoning.
248    pub reasoning_tokens: Option<usize>,
249    /// When using Predicted Outputs, the number of tokens in the prediction that did
250    /// not appear in the completion. However, like reasoning tokens, these tokens are
251    /// still counted in the total completion tokens for purposes of billing, output,
252    /// and context window limits.
253    pub rejected_prediction_tokens: Option<usize>,
254}
255
256#[derive(Debug, Deserialize)]
257pub struct PromptTokensDetails {
258    /// Audio input tokens present in the prompt.
259    pub audio_tokens: Option<usize>,
260    /// Cached tokens present in the prompt.
261    pub cached_tokens: Option<usize>,
262}
263
264impl FromStr for ChatCompletion {
265    type Err = crate::errors::OapiError;
266
267    fn from_str(content: &str) -> Result<Self, Self::Err> {
268        let parse_result: Result<ChatCompletion, _> = serde_json::from_str(content)
269            .map_err(|e| OapiError::DeserializationError(e.to_string()));
270        parse_result
271    }
272}
273
274#[cfg(test)]
275mod test {
276    use super::*;
277
278    #[test]
279    fn no_streaming_example_deepseek() {
280        let json = r#"{
281          "id": "30f6413a-a827-4cf3-9898-f13a8634b798",
282          "object": "chat.completion",
283          "created": 1757944111,
284          "model": "deepseek-chat",
285          "choices": [
286            {
287              "index": 0,
288              "message": {
289                "role": "assistant",
290                "content": "Hello! How can I help you today? 😊"
291              },
292              "logprobs": null,
293              "finish_reason": "stop"
294            }
295          ],
296          "usage": {
297            "prompt_tokens": 10,
298            "completion_tokens": 11,
299            "total_tokens": 21,
300            "prompt_tokens_details": {
301              "cached_tokens": 0
302            },
303            "prompt_cache_hit_tokens": 0,
304            "prompt_cache_miss_tokens": 10
305          },
306          "system_fingerprint": "fp_08f168e49b_prod0820_fp8_kvcache"
307        }"#;
308
309        let parsed = ChatCompletion::from_str(json);
310        match parsed {
311            Ok(_) => {}
312            Err(e) => {
313                panic!("Failed to deserialize: {}", e);
314            }
315        }
316    }
317
318    #[test]
319    fn no_streaming_example_qwen() {
320        let json = r#"{
321            "choices": [
322                {
323                    "message": {
324                        "role": "assistant",
325                        "content": "我是阿里云开发的一款超大规模语言模型,我叫通义千问。"
326                    },
327                    "finish_reason": "stop",
328                    "index": 0,
329                    "logprobs": null
330                }
331            ],
332            "object": "chat.completion",
333            "usage": {
334                "prompt_tokens": 3019,
335                "completion_tokens": 104,
336                "total_tokens": 3123,
337                "prompt_tokens_details": {
338                    "cached_tokens": 2048
339                }
340            },
341            "created": 1735120033,
342            "system_fingerprint": null,
343            "model": "qwen-plus",
344            "id": "chatcmpl-6ada9ed2-7f33-9de2-8bb0-78bd4035025a"
345        }"#;
346
347        let parsed = ChatCompletion::from_str(json);
348        match parsed {
349            Ok(_) => {}
350            Err(e) => {
351                panic!("Failed to deserialize: {}", e);
352            }
353        }
354    }
355}