Skip to main content

openai_interface/chat/
mod.rs

1//! # Chat Completions API Module
2//!
3//! This module provides components shared by many submodules.
4
5use std::str::FromStr;
6
7use serde::{Deserialize, Serialize};
8
9use crate::errors::OapiError;
10
11pub mod create;
12pub mod delete;
13pub mod retrieve;
14pub mod update;
15
16/// The service tier used for processing the request.
17///
18/// This enum represents the different service tiers that can be specified when
19/// making a request to the API. Each tier corresponds to different performance
20/// characteristics and pricing models.
21#[derive(Debug, Serialize, Deserialize, Clone)]
22#[serde(rename_all = "lowercase")]
23pub enum ServiceTier {
24    /// Automatically select the service tier based on project settings.
25    Auto,
26    /// Use the default service tier with standard pricing and performance.
27    Default,
28    /// Use the flex service tier for flexible processing requirements.
29    Flex,
30    /// Use the scale service tier for scalable processing needs.
31    Scale,
32    /// Use the priority service tier for high-priority requests.
33    Priority,
34    /// Fast mode. Request-level opt-in for
35    /// [Fast mode](https://platform.openai.com/docs/guides/fast-mode); the
36    /// response reports the actual tier as `priority`.
37    Fast,
38}
39
40/// Moderation results for the request input and generated output.
41///
42/// Present on the response when moderated completions are requested via the
43/// `moderation` request parameter.
44#[derive(Debug, Deserialize, Clone)]
45pub struct ChatModeration {
46    /// Moderation for the request input.
47    pub input: ModerationSide,
48    /// Moderation for the generated output.
49    pub output: ModerationSide,
50}
51
52/// The moderation outcome for one side (input or output) of the completion:
53/// either successful results or an error produced while attempting
54/// moderation.
55#[derive(Debug, Deserialize, Clone)]
56#[serde(tag = "type", rename_all = "snake_case")]
57pub enum ModerationSide {
58    /// Successful moderation results. Always `moderation_results`.
59    ModerationResults {
60        /// The moderation model used to generate the results.
61        model: String,
62        /// A list of moderation results.
63        results: Vec<ModerationSideResult>,
64    },
65    /// An error produced while attempting moderation. Always `error`.
66    Error {
67        /// The error code.
68        code: String,
69        /// The error message.
70        message: String,
71    },
72}
73
74/// A moderation result produced for the response input or generated output.
75#[derive(Debug, Deserialize, Clone)]
76pub struct ModerationSideResult {
77    /// A dictionary of moderation categories to booleans, `true` if the
78    /// input is flagged under this category.
79    pub categories: std::collections::HashMap<String, bool>,
80    /// Which modalities of input are reflected by the score for each
81    /// category, e.g. `["text"]` or `["text", "image"]`.
82    pub category_applied_input_types: std::collections::HashMap<String, Vec<String>>,
83    /// A dictionary of moderation categories to scores.
84    pub category_scores: std::collections::HashMap<String, f64>,
85    /// A boolean indicating whether the content was flagged by any category.
86    pub flagged: bool,
87    /// The moderation model that produced this result.
88    pub model: String,
89    /// The object type, which is always `moderation_result`.
90    #[serde(rename = "type")]
91    pub type_: ModerationResultType,
92}
93
94/// The object type of a moderation result. Always `moderation_result`.
95#[derive(Debug, Deserialize, Clone)]
96#[serde(rename_all = "snake_case")]
97pub enum ModerationResultType {
98    ModerationResult,
99}
100
101#[derive(Debug, Deserialize)]
102pub struct ChatCompletion {
103    /// A unique identifier for the chat completion.
104    pub id: String,
105    /// A list of chat completion choices. Can be more than one
106    /// if `n` is greater than 1.
107    pub choices: Vec<Choice>,
108    /// The Unix timestamp (in seconds) of when the chat completion was created.
109    pub created: u64,
110    /// The model used for the chat completion.
111    pub model: String,
112    /// Specifies the processing type used for serving the request.
113    ///
114    /// - If set to 'auto', then the request will be processed with the service tier
115    ///   configured in the Project settings. Unless otherwise configured, the Project
116    ///   will use 'default'.
117    /// - If set to 'default', then the request will be processed with the standard
118    ///   pricing and performance for the selected model.
119    /// - If set to '[flex](https://platform.openai.com/docs/guides/flex-processing)' or
120    ///   '[priority](https://openai.com/api-priority-processing/)', then the request
121    ///   will be processed with the corresponding service tier.
122    /// - When not set, the default behavior is 'auto'.
123    ///
124    /// When the `service_tier` parameter is set, the response body will include the
125    /// `service_tier` value based on the processing mode actually used to serve the
126    /// request. This response value may be different from the value set in the
127    /// parameter.
128    pub service_tier: Option<ServiceTier>,
129    /// The system fingerprint used for the chat completion.
130    /// Can be used in conjunction with the `seed` request parameter to understand when
131    /// backend changes have been made that might impact determinism.
132    pub system_fingerprint: Option<String>,
133    /// The object type, which is always `chat.completion`.
134    pub object: ChatCompletionObject,
135    /// Usage statistics for the completion request.
136    pub usage: Option<CompletionUsage>,
137    /// Moderation results for the request input and generated output.
138    ///
139    /// Present when moderated completions are requested via the `moderation`
140    /// request parameter.
141    pub moderation: Option<ChatModeration>,
142}
143
144/// The object type, which is always `chat.completion`.
145#[derive(Debug, Deserialize)]
146pub enum ChatCompletionObject {
147    /// The object type is always `chat.completion`.
148    #[serde(rename = "chat.completion")]
149    ChatCompletion,
150}
151
152#[derive(Debug, Deserialize)]
153pub struct Choice {
154    /// The reason the model stopped generating tokens.
155    ///
156    /// This will be `stop` if the model hit a natural stop point or a provided stop
157    /// sequence, `length` if the maximum number of tokens specified in the request was
158    /// reached, `content_filter` if content was omitted due to a flag from our content
159    /// filters, `tool_calls` if the model called a tool, or `function_call`
160    /// (deprecated) if the model called a function.
161    pub finish_reason: FinishReason,
162    /// The index of the choice in the list of choices.
163    pub index: usize,
164    /// Log probability information for the choice.
165    pub logprobs: Option<ChoiceLogprobs>,
166    /// A chat completion message generated by the model.
167    pub message: ChatCompletionMessage,
168}
169
170#[derive(Debug, Deserialize, PartialEq)]
171#[serde(rename_all = "snake_case")]
172pub enum FinishReason {
173    Length,
174    Stop,
175    ToolCalls,
176    FunctionCall,
177    ContentFilter,
178    /// DeepSeek: the request is interrupted due to insufficient resource
179    /// of the inference system.
180    #[cfg(feature = "deepseek")]
181    InsufficientSystemResource,
182}
183
184#[derive(Debug, Deserialize)]
185pub struct ChatCompletionMessage {
186    /// The role of the author of this message. This shall always
187    /// be ResponseRole::Assistant
188    pub role: ResponseRole,
189    /// If the audio output modality is requested, this object contains data
190    /// about the audio response from the model.
191    /// [Learn more from OpenAI](https://platform.openai.com/docs/guides/audio).
192    pub audio: Option<ChatCompletionAudio>,
193    /// The contents of the message.
194    pub content: Option<String>,
195    /// DeepSeek: for thinking mode only. The reasoning contents of the
196    /// assistant message, before the final answer.
197    #[cfg(feature = "deepseek")]
198    pub reasoning_content: Option<String>,
199    /// The tool calls generated by the model, such as function calls.
200    /// Tool calls deserialization is not supported yet.
201    pub tool_calls: Option<Vec<ChatCompletionMessageToolCall>>,
202    /// The refusal message generated by the model.
203    pub refusal: Option<String>,
204    /// Annotations for the message, when applicable, such as URL citations
205    /// when the model uses a web search tool.
206    pub annotations: Option<Vec<Annotation>>,
207}
208
209/// If the audio output modality is requested, this object contains data about
210/// the audio response from the model.
211/// [Learn more from OpenAI](https://platform.openai.com/docs/guides/audio).
212#[derive(Debug, Deserialize, Clone)]
213pub struct ChatCompletionAudio {
214    /// Unique identifier for this audio response.
215    pub id: String,
216    /// Base64 encoded audio bytes generated by the model, in the format
217    /// specified in the request.
218    pub data: String,
219    /// The Unix timestamp (in seconds) for when this audio response will no
220    /// longer be accessible on the server for use in multi-turn conversations.
221    pub expires_at: u64,
222    /// Transcript of the audio generated by the model.
223    pub transcript: String,
224}
225
226/// An annotation for a chat completion message.
227#[derive(Debug, Deserialize, Clone)]
228pub struct Annotation {
229    /// The type of the annotation. Always `url_citation`.
230    #[serde(rename = "type")]
231    pub type_: AnnotationType,
232    /// The URL citation.
233    pub url_citation: UrlCitation,
234}
235
236#[derive(Debug, Deserialize, Clone)]
237#[serde(rename_all = "snake_case")]
238pub enum AnnotationType {
239    /// A URL citation when using web search.
240    UrlCitation,
241}
242
243/// A URL citation when the model uses a web search tool.
244#[derive(Debug, Deserialize, Clone)]
245pub struct UrlCitation {
246    /// The index of the first character of the URL citation in the message.
247    pub start_index: usize,
248    /// The index of the last character of the URL citation in the message.
249    pub end_index: usize,
250    /// The title of the web resource.
251    pub title: String,
252    /// The URL of the web resource.
253    pub url: String,
254}
255
256#[derive(Debug, Deserialize)]
257#[serde(tag = "type", rename_all = "snake_case")]
258pub enum ChatCompletionMessageToolCall {
259    /// The type of the tool. Currently, only `function` is supported.
260    /// The field { type = "function" } is added automatically.
261    Function {
262        /// The ID of the tool call.
263        id: String,
264        /// The function that the model called.
265        function: MessageToolCallFunction,
266    },
267    /// The type of the tool. Always `custom`.
268    /// The field { type = "custom" } is added automatically.
269    Custom {
270        /// The id of the tool call.
271        id: String,
272        /// The custom tool that the model called.
273        custom: MessageToolCallCustom,
274    },
275}
276
277#[derive(Debug, Deserialize)]
278pub struct MessageToolCallCustom {
279    /// The input for the custom tool call generated by the model.
280    pub input: String,
281    /// The name of the custom tool to call.
282    pub name: String,
283}
284
285#[derive(Debug, Deserialize)]
286pub struct MessageToolCallFunction {
287    /// The arguments to call the function with, as generated by the model in JSON
288    /// format. Note that the model does not always generate valid JSON, and may
289    /// hallucinate parameters not defined by your function schema. Validate the
290    /// arguments in your code before calling your function.
291    pub arguments: String,
292    /// The name of the function to call.
293    pub name: String,
294}
295
296#[derive(Debug, Deserialize)]
297#[serde(rename_all = "snake_case")]
298pub enum ResponseRole {
299    /// The role of the response message is always assistant.
300    Assistant,
301}
302
303#[derive(Debug, Deserialize)]
304pub struct ChoiceLogprobs {
305    /// A list of message content tokens with log probability information.
306    pub content: Option<Vec<TokenLogProb>>,
307    /// DeepSeek: a list of reasoning content tokens with log probability
308    /// information. Only present for thinking models.
309    #[cfg(feature = "deepseek")]
310    pub reasoning_content: Option<Vec<TokenLogProb>>,
311    /// A list of message refusal tokens with log probability information.
312    pub refusal: Option<Vec<TokenLogProb>>,
313}
314
315#[derive(Debug, Deserialize)]
316pub struct TokenLogProb {
317    /// The token.
318    pub token: String,
319    /// The log probability of this token, if it is within the top 20 most likely
320    /// tokens. Otherwise, the value `-9999.0` is used to signify that the token is very
321    /// unlikely.
322    pub logprob: f32,
323    /// A list of integers representing the UTF-8 bytes representation of the token.
324    ///
325    /// Useful in instances where characters are represented by multiple tokens and
326    /// their byte representations must be combined to generate the correct text
327    /// representation. Can be `null` if there is no bytes representation for the token.
328    pub bytes: Option<Vec<u8>>,
329    /// List of the most likely tokens and their log probability, at this token
330    /// position. In rare cases, there may be fewer than the number of requested
331    /// `top_logprobs` returned.
332    pub top_logprobs: Vec<TopLogprob>,
333}
334
335#[derive(Debug, Deserialize)]
336pub struct TopLogprob {
337    /// The token.
338    pub token: String,
339    /// A list of integers representing the UTF-8 bytes representation of the token.
340    ///
341    /// Useful in instances where characters are represented by multiple tokens and
342    /// their byte representations must be combined to generate the correct text
343    /// representation. Can be `null` if there is no bytes representation for the token.
344    pub logprob: f32,
345    /// List of the most likely tokens and their log probability, at this token
346    /// position. In rare cases, there may be fewer than the number of requested
347    /// `top_logprobs` returned.
348    pub bytes: Option<Vec<u8>>,
349}
350
351#[derive(Debug, Deserialize)]
352pub struct CompletionUsage {
353    /// Number of tokens in the generated completion.
354    pub completion_tokens: usize,
355    /// Number of tokens in the prompt.
356    pub prompt_tokens: usize,
357
358    /// DeepSeek: number of tokens in the prompt that hits the context cache.
359    #[cfg(feature = "deepseek")]
360    pub prompt_cache_hit_tokens: Option<usize>,
361    /// DeepSeek: number of tokens in the prompt that misses the context cache.
362    #[cfg(feature = "deepseek")]
363    pub prompt_cache_miss_tokens: Option<usize>,
364
365    /// Total number of tokens used in the request (prompt + completion).
366    pub total_tokens: usize,
367    /// Breakdown of tokens used in a completion.
368    pub completion_tokens_details: Option<CompletionTokensDetails>,
369    /// Breakdown of tokens used in the prompt.
370    pub prompt_tokens_details: Option<PromptTokensDetails>,
371}
372
373#[derive(Debug, Deserialize)]
374pub struct CompletionTokensDetails {
375    /// When using Predicted Outputs, the number of tokens in the prediction that
376    /// appeared in the completion.
377    pub accepted_prediction_tokens: Option<usize>,
378    /// Audio input tokens generated by the model.
379    pub audio_tokens: Option<usize>,
380    /// Tokens generated by the model for reasoning.
381    pub reasoning_tokens: Option<usize>,
382    /// When using Predicted Outputs, the number of tokens in the prediction that did
383    /// not appear in the completion. However, like reasoning tokens, these tokens are
384    /// still counted in the total completion tokens for purposes of billing, output,
385    /// and context window limits.
386    pub rejected_prediction_tokens: Option<usize>,
387}
388
389#[derive(Debug, Deserialize)]
390pub struct PromptTokensDetails {
391    /// Audio input tokens present in the prompt.
392    pub audio_tokens: Option<usize>,
393    /// Cached tokens present in the prompt.
394    pub cached_tokens: Option<usize>,
395}
396
397impl FromStr for ChatCompletion {
398    type Err = crate::errors::OapiError;
399
400    fn from_str(content: &str) -> Result<Self, Self::Err> {
401        let parse_result: Result<ChatCompletion, _> = serde_json::from_str(content)
402            .map_err(|e| OapiError::DeserializationError(e.to_string()));
403        parse_result
404    }
405}
406
407#[cfg(test)]
408mod test {
409    use super::*;
410
411    #[test]
412    fn service_tier_parses_official_values() {
413        for (raw, is_fast) in [
414            (r#""auto""#, false),
415            (r#""default""#, false),
416            (r#""flex""#, false),
417            (r#""scale""#, false),
418            (r#""priority""#, false),
419            (r#""fast""#, true),
420        ] {
421            let tier: ServiceTier =
422                serde_json::from_str(raw).unwrap_or_else(|e| panic!("failed to parse {raw}: {e}"));
423            assert_eq!(matches!(tier, ServiceTier::Fast), is_fast, "raw: {raw}");
424        }
425    }
426
427    #[test]
428    fn no_streaming_example_deepseek() {
429        let json = r#"{
430          "id": "30f6413a-a827-4cf3-9898-f13a8634b798",
431          "object": "chat.completion",
432          "created": 1757944111,
433          "model": "deepseek-chat",
434          "choices": [
435            {
436              "index": 0,
437              "message": {
438                "role": "assistant",
439                "content": "Hello! How can I help you today? 😊"
440              },
441              "logprobs": null,
442              "finish_reason": "stop"
443            }
444          ],
445          "usage": {
446            "prompt_tokens": 10,
447            "completion_tokens": 11,
448            "total_tokens": 21,
449            "prompt_tokens_details": {
450              "cached_tokens": 0
451            },
452            "prompt_cache_hit_tokens": 0,
453            "prompt_cache_miss_tokens": 10
454          },
455          "system_fingerprint": "fp_08f168e49b_prod0820_fp8_kvcache"
456        }"#;
457
458        let parsed = ChatCompletion::from_str(json);
459        match parsed {
460            Ok(_) => {}
461            Err(e) => {
462                panic!("Failed to deserialize: {}", e);
463            }
464        }
465    }
466
467    #[test]
468    fn no_streaming_example_qwen() {
469        let json = r#"{
470            "choices": [
471                {
472                    "message": {
473                        "role": "assistant",
474                        "content": "我是阿里云开发的一款超大规模语言模型,我叫通义千问。"
475                    },
476                    "finish_reason": "stop",
477                    "index": 0,
478                    "logprobs": null
479                }
480            ],
481            "object": "chat.completion",
482            "usage": {
483                "prompt_tokens": 3019,
484                "completion_tokens": 104,
485                "total_tokens": 3123,
486                "prompt_tokens_details": {
487                    "cached_tokens": 2048
488                }
489            },
490            "created": 1735120033,
491            "system_fingerprint": null,
492            "model": "qwen-plus",
493            "id": "chatcmpl-6ada9ed2-7f33-9de2-8bb0-78bd4035025a"
494        }"#;
495
496        let parsed = ChatCompletion::from_str(json);
497        match parsed {
498            Ok(_) => {}
499            Err(e) => {
500                panic!("Failed to deserialize: {}", e);
501            }
502        }
503    }
504}