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
16crate::wire_string_enum! {
17    /// The service tier used for processing the request.
18    ///
19    /// This enum represents the different service tiers that can be specified when
20    /// making a request to the API. Each tier corresponds to different performance
21    /// characteristics and pricing models.
22    pub enum ServiceTier {
23        /// Automatically select the service tier based on project settings.
24        Auto => "auto",
25        /// Use the default service tier with standard pricing and performance.
26        Default => "default",
27        /// Use the flex service tier for flexible processing requirements.
28        Flex => "flex",
29        /// Use the scale service tier for scalable processing needs.
30        Scale => "scale",
31        /// Use the priority service tier for high-priority requests.
32        Priority => "priority",
33        /// Fast mode. Request-level opt-in for
34        /// [Fast mode](https://platform.openai.com/docs/guides/fast-mode); the
35        /// response reports the actual tier as `priority`.
36        Fast => "fast",
37    }
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, Serialize, 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, Serialize, 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, Serialize, 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
94crate::wire_string_enum! {
95    /// The object type of a moderation result. Always `moderation_result`.
96    pub enum ModerationResultType {
97        ModerationResult => "moderation_result",
98    }
99}
100
101#[derive(Debug, Deserialize, Serialize, Clone)]
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    ///
135    /// `Some` only when the backend sends a recognized value; some
136    /// non-OpenAI gateways omit or repurpose the field.
137    pub object: Option<ChatCompletionObject>,
138    /// Usage statistics for the completion request.
139    pub usage: Option<CompletionUsage>,
140    /// Moderation results for the request input and generated output.
141    ///
142    /// Present when moderated completions are requested via the `moderation`
143    /// request parameter.
144    pub moderation: Option<ChatModeration>,
145
146    /// vLLM: log probabilities of the prompt tokens, one entry per prompt
147    /// position, or `null` for positions the server did not report. Each
148    /// entry maps a token ID to its [`crate::vllm::Logprob`].
149    ///
150    /// Requested with `vllm_sampling.prompt_logprobs`; OpenAI has no
151    /// equivalent.
152    #[cfg(feature = "vllm")]
153    pub prompt_logprobs: Option<Vec<Option<std::collections::HashMap<u32, crate::vllm::Logprob>>>>,
154    /// vLLM: the prompt's token IDs after chat-template rendering.
155    #[cfg(feature = "vllm")]
156    pub prompt_token_ids: Option<Vec<u32>>,
157    /// vLLM: the fully rendered prompt text.
158    ///
159    /// Only set when the request set `vllm_chat.return_prompt_text`.
160    #[cfg(feature = "vllm")]
161    pub prompt_text: Option<String>,
162    /// vLLM: KV-transfer parameters for disaggregated prefill, echoing and
163    /// extending the request's `kv_transfer_params`.
164    #[cfg(feature = "vllm")]
165    pub kv_transfer_params: Option<serde_json::Map<String, serde_json::Value>>,
166    /// vLLM: encoder-cache transfer parameters, echoing and extending the
167    /// request's `ec_transfer_params`.
168    #[cfg(feature = "vllm")]
169    pub ec_transfer_params: Option<serde_json::Map<String, serde_json::Value>>,
170
171    /// Z.ai / GLM: the request identifier, echoing the request's `request_id`
172    /// or the one GLM generated. Not part of the OpenAI schema.
173    #[cfg(feature = "zai")]
174    pub request_id: Option<String>,
175    /// Z.ai / GLM: the web-search results behind the answer, when the
176    /// `web_search` tool ran. Not part of the OpenAI schema.
177    #[cfg(feature = "zai")]
178    pub web_search: Option<Vec<crate::zai::WebSearchResult>>,
179}
180
181crate::wire_string_enum! {
182    /// The object type, which is always `chat.completion`.
183    pub enum ChatCompletionObject {
184        /// The object type is always `chat.completion`.
185        ChatCompletion => "chat.completion",
186    }
187}
188
189#[derive(Debug, Deserialize, Serialize, Clone)]
190pub struct Choice {
191    /// The reason the model stopped generating tokens.
192    ///
193    /// This will be `stop` if the model hit a natural stop point or a provided stop
194    /// sequence, `length` if the maximum number of tokens specified in the request was
195    /// reached, `content_filter` if content was omitted due to a flag from our content
196    /// filters, `tool_calls` if the model called a tool, or `function_call`
197    /// (deprecated) if the model called a function.
198    pub finish_reason: FinishReason,
199    /// The index of the choice in the list of choices.
200    pub index: u32,
201    /// Log probability information for the choice.
202    pub logprobs: Option<ChoiceLogprobs>,
203    /// A chat completion message generated by the model.
204    pub message: ChatCompletionMessage,
205
206    /// vLLM: which terminator ended generation — the matched stop string, or
207    /// the matched token ID. Not part of the OpenAI schema; `finish_reason`
208    /// alone only reports `stop` for both cases.
209    #[cfg(feature = "vllm")]
210    pub stop_reason: Option<crate::vllm::StopReason>,
211    /// vLLM: the generated token IDs, for tracing tokens in agent scenarios.
212    /// Only set when the request set `vllm_chat.return_token_ids`.
213    #[cfg(feature = "vllm")]
214    pub token_ids: Option<Vec<u32>>,
215    /// vLLM: per-token expert routing decisions for mixture-of-experts
216    /// models, as base64-encoded NumPy `.npy` bytes of shape
217    /// `(num_tokens - 1, num_layers, num_experts_per_tok)`.
218    ///
219    /// Only set when the server runs with `--enable-return-routed-experts`.
220    #[cfg(feature = "vllm")]
221    pub routed_experts: Option<String>,
222}
223
224crate::wire_string_enum! {
225    /// The reason the model stopped generating tokens.
226    ///
227    /// Values that are not part of the official API (some gateways emit
228    /// their own, e.g. `eos`) are preserved as
229    /// [`FinishReason::Unknown`] instead of failing deserialization.
230    pub enum FinishReason {
231        /// The maximum number of tokens specified in the request was reached.
232        Length => "length",
233        /// The model hit a natural stop point or a provided stop sequence.
234        Stop => "stop",
235        /// The model called a tool.
236        ToolCalls => "tool_calls",
237        /// The model called a function (deprecated).
238        FunctionCall => "function_call",
239        /// Content was omitted due to a flag from our content filters.
240        ContentFilter => "content_filter",
241        /// DeepSeek: the request is interrupted due to insufficient resource
242        /// of the inference system.
243        InsufficientSystemResource => "insufficient_system_resource",
244    }
245}
246
247#[derive(Debug, Deserialize, Serialize, Clone)]
248pub struct ChatCompletionMessage {
249    /// The role of the author of this message. This shall always
250    /// be [`Role::Assistant`]
251    pub role: Role,
252    /// If the audio output modality is requested, this object contains data
253    /// about the audio response from the model.
254    /// [Learn more from OpenAI](https://platform.openai.com/docs/guides/audio).
255    pub audio: Option<ChatCompletionAudio>,
256    /// The contents of the message.
257    pub content: Option<String>,
258    /// For thinking models: the reasoning contents of the assistant
259    /// message, before the final answer.
260    #[cfg(feature = "reasoning")]
261    pub reasoning_content: Option<String>,
262    /// vLLM: the reasoning contents of the assistant message, under the key
263    /// vLLM actually emits.
264    ///
265    /// vLLM accepts `reasoning_content` on the way in but serializes the
266    /// chain of thought as `reasoning` on the way out — in its `ChatMessage`
267    /// the two names are aliases of one field, and `reasoning` is the
268    /// serialization name. Current vLLM does **not** emit
269    /// `reasoning_content` in responses, so for a vLLM backend the field
270    /// above stays `None` and this one carries the chain of thought. Map it
271    /// back onto `reasoning_content` when feeding it into a follow-up
272    /// request.
273    #[cfg(feature = "vllm")]
274    pub reasoning: Option<String>,
275    /// The tool calls generated by the model, such as function calls.
276    /// Tool calls deserialization is not supported yet.
277    pub tool_calls: Option<Vec<ChatCompletionMessageToolCall>>,
278    /// The refusal message generated by the model.
279    pub refusal: Option<String>,
280    /// Annotations for the message, when applicable, such as URL citations
281    /// when the model uses a web search tool.
282    pub annotations: Option<Vec<Annotation>>,
283}
284
285/// If the audio output modality is requested, this object contains data about
286/// the audio response from the model.
287/// [Learn more from OpenAI](https://platform.openai.com/docs/guides/audio).
288#[derive(Debug, Deserialize, Serialize, Clone)]
289pub struct ChatCompletionAudio {
290    /// Unique identifier for this audio response.
291    pub id: String,
292    /// Base64 encoded audio bytes generated by the model, in the format
293    /// specified in the request.
294    pub data: String,
295    /// The Unix timestamp (in seconds) for when this audio response will no
296    /// longer be accessible on the server for use in multi-turn conversations.
297    pub expires_at: u64,
298    /// Transcript of the audio generated by the model.
299    pub transcript: String,
300}
301
302/// An annotation for a chat completion message.
303#[derive(Debug, Deserialize, Serialize, Clone)]
304pub struct Annotation {
305    /// The type of the annotation. Always `url_citation`.
306    #[serde(rename = "type")]
307    pub type_: AnnotationType,
308    /// The URL citation.
309    pub url_citation: UrlCitation,
310}
311
312crate::wire_string_enum! {
313    /// The type of an annotation.
314    pub enum AnnotationType {
315        /// A URL citation when using web search.
316        UrlCitation => "url_citation",
317    }
318}
319
320/// A URL citation when the model uses a web search tool.
321#[derive(Debug, Deserialize, Serialize, Clone)]
322pub struct UrlCitation {
323    /// The index of the first character of the URL citation in the message.
324    pub start_index: usize,
325    /// The index of the last character of the URL citation in the message.
326    pub end_index: usize,
327    /// The title of the web resource.
328    pub title: String,
329    /// The URL of the web resource.
330    pub url: String,
331}
332
333#[derive(Debug, Deserialize, Serialize, Clone)]
334#[serde(tag = "type", rename_all = "snake_case")]
335pub enum ChatCompletionMessageToolCall {
336    /// The type of the tool. Currently, only `function` is supported.
337    /// The field { type = "function" } is added automatically.
338    Function {
339        /// The ID of the tool call.
340        id: String,
341        /// The function that the model called.
342        function: MessageToolCallFunction,
343    },
344    /// The type of the tool. Always `custom`.
345    /// The field { type = "custom" } is added automatically.
346    Custom {
347        /// The id of the tool call.
348        id: String,
349        /// The custom tool that the model called.
350        custom: MessageToolCallCustom,
351    },
352}
353
354#[derive(Debug, Deserialize, Serialize, Clone)]
355pub struct MessageToolCallCustom {
356    /// The input for the custom tool call generated by the model.
357    pub input: String,
358    /// The name of the custom tool to call.
359    pub name: String,
360}
361
362#[derive(Debug, Deserialize, Serialize, Clone)]
363pub struct MessageToolCallFunction {
364    /// The arguments to call the function with, as generated by the model in JSON
365    /// format. Note that the model does not always generate valid JSON, and may
366    /// hallucinate parameters not defined by your function schema. Validate the
367    /// arguments in your code before calling your function.
368    pub arguments: String,
369    /// The name of the function to call.
370    pub name: String,
371}
372
373crate::wire_string_enum! {
374    /// The role of the author of a message, as reported in responses and
375    /// streamed deltas.
376    ///
377    /// Values that are not part of the official API are preserved as
378    /// [`Role::Unknown`] instead of failing deserialization.
379    pub enum Role {
380        /// The author is the model.
381        Assistant => "assistant",
382        /// The author is a developer-defined persona.
383        Developer => "developer",
384        /// The author is the system prompt.
385        System => "system",
386        /// The author is a tool response.
387        Tool => "tool",
388        /// The author is the end user.
389        User => "user",
390    }
391}
392
393/// Legacy alias of [`Role`].
394pub type ResponseRole = Role;
395
396#[derive(Debug, Deserialize, Serialize, Clone)]
397pub struct ChoiceLogprobs {
398    /// A list of message content tokens with log probability information.
399    pub content: Option<Vec<TokenLogProb>>,
400    /// A list of reasoning content tokens with log probability information.
401    /// Only present for thinking models.
402    #[cfg(feature = "reasoning")]
403    pub reasoning_content: Option<Vec<TokenLogProb>>,
404    /// A list of message refusal tokens with log probability information.
405    pub refusal: Option<Vec<TokenLogProb>>,
406}
407
408#[derive(Debug, Deserialize, Serialize, Clone)]
409pub struct TokenLogProb {
410    /// The token.
411    pub token: String,
412    /// The log probability of this token, if it is within the top 20 most likely
413    /// tokens. Otherwise, the value `-9999.0` is used to signify that the token is very
414    /// unlikely.
415    pub logprob: f32,
416    /// A list of integers representing the UTF-8 bytes representation of the token.
417    ///
418    /// Useful in instances where characters are represented by multiple tokens and
419    /// their byte representations must be combined to generate the correct text
420    /// representation. Can be `null` if there is no bytes representation for the token.
421    pub bytes: Option<Vec<u8>>,
422    /// List of the most likely tokens and their log probability, at this token
423    /// position. In rare cases, there may be fewer than the number of requested
424    /// `top_logprobs` returned.
425    pub top_logprobs: Vec<TopLogprob>,
426}
427
428#[derive(Debug, Deserialize, Serialize, Clone)]
429pub struct TopLogprob {
430    /// The token.
431    pub token: String,
432    /// A list of integers representing the UTF-8 bytes representation of the token.
433    ///
434    /// Useful in instances where characters are represented by multiple tokens and
435    /// their byte representations must be combined to generate the correct text
436    /// representation. Can be `null` if there is no bytes representation for the token.
437    pub logprob: f32,
438    /// List of the most likely tokens and their log probability, at this token
439    /// position. In rare cases, there may be fewer than the number of requested
440    /// `top_logprobs` returned.
441    pub bytes: Option<Vec<u8>>,
442}
443
444#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
445pub struct CompletionUsage {
446    /// Number of tokens in the generated completion.
447    pub completion_tokens: u64,
448    /// Number of tokens in the prompt.
449    pub prompt_tokens: u64,
450
451    /// DeepSeek: number of tokens in the prompt that hits the context cache.
452    #[cfg(feature = "deepseek")]
453    pub prompt_cache_hit_tokens: Option<u64>,
454    /// DeepSeek: number of tokens in the prompt that misses the context cache.
455    #[cfg(feature = "deepseek")]
456    pub prompt_cache_miss_tokens: Option<u64>,
457
458    /// Total number of tokens used in the request (prompt + completion).
459    pub total_tokens: u64,
460    /// Breakdown of tokens used in a completion.
461    pub completion_tokens_details: Option<CompletionTokensDetails>,
462    /// Breakdown of tokens used in the prompt.
463    pub prompt_tokens_details: Option<PromptTokensDetails>,
464}
465
466#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
467pub struct CompletionTokensDetails {
468    /// When using Predicted Outputs, the number of tokens in the prediction that
469    /// appeared in the completion.
470    pub accepted_prediction_tokens: Option<u64>,
471    /// Audio input tokens generated by the model.
472    pub audio_tokens: Option<u64>,
473    /// Tokens generated by the model for reasoning.
474    pub reasoning_tokens: Option<u64>,
475    /// When using Predicted Outputs, the number of tokens in the prediction that did
476    /// not appear in the completion. However, like reasoning tokens, these tokens are
477    /// still counted in the total completion tokens for purposes of billing, output,
478    /// and context window limits.
479    pub rejected_prediction_tokens: Option<u64>,
480    /// vLLM: how many of the completion tokens were produced by speculative
481    /// decoding. Not part of the OpenAI schema.
482    #[cfg(feature = "vllm")]
483    pub num_speculative_tokens: Option<u64>,
484}
485
486#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
487pub struct PromptTokensDetails {
488    /// Audio input tokens present in the prompt.
489    pub audio_tokens: Option<u64>,
490    /// Cached tokens present in the prompt.
491    pub cached_tokens: Option<u64>,
492}
493
494impl FromStr for ChatCompletion {
495    type Err = crate::errors::OapiError;
496
497    fn from_str(content: &str) -> Result<Self, Self::Err> {
498        let parse_result: Result<ChatCompletion, _> = serde_json::from_str(content)
499            .map_err(|e| OapiError::DeserializationError(e.to_string()));
500        parse_result
501    }
502}
503
504#[cfg(test)]
505mod test {
506    use super::*;
507
508    #[test]
509    fn service_tier_parses_official_values() {
510        for (raw, is_fast) in [
511            (r#""auto""#, false),
512            (r#""default""#, false),
513            (r#""flex""#, false),
514            (r#""scale""#, false),
515            (r#""priority""#, false),
516            (r#""fast""#, true),
517        ] {
518            let tier: ServiceTier =
519                serde_json::from_str(raw).unwrap_or_else(|e| panic!("failed to parse {raw}: {e}"));
520            assert_eq!(matches!(tier, ServiceTier::Fast), is_fast, "raw: {raw}");
521        }
522    }
523
524    #[test]
525    fn no_streaming_example_deepseek() {
526        let json = r#"{
527          "id": "30f6413a-a827-4cf3-9898-f13a8634b798",
528          "object": "chat.completion",
529          "created": 1757944111,
530          "model": "deepseek-chat",
531          "choices": [
532            {
533              "index": 0,
534              "message": {
535                "role": "assistant",
536                "content": "Hello! How can I help you today? 😊"
537              },
538              "logprobs": null,
539              "finish_reason": "stop"
540            }
541          ],
542          "usage": {
543            "prompt_tokens": 10,
544            "completion_tokens": 11,
545            "total_tokens": 21,
546            "prompt_tokens_details": {
547              "cached_tokens": 0
548            },
549            "prompt_cache_hit_tokens": 0,
550            "prompt_cache_miss_tokens": 10
551          },
552          "system_fingerprint": "fp_08f168e49b_prod0820_fp8_kvcache"
553        }"#;
554
555        let parsed = ChatCompletion::from_str(json);
556        match parsed {
557            Ok(_) => {}
558            Err(e) => {
559                panic!("Failed to deserialize: {}", e);
560            }
561        }
562    }
563
564    #[test]
565    fn no_streaming_example_qwen() {
566        let json = r#"{
567            "choices": [
568                {
569                    "message": {
570                        "role": "assistant",
571                        "content": "我是阿里云开发的一款超大规模语言模型,我叫通义千问。"
572                    },
573                    "finish_reason": "stop",
574                    "index": 0,
575                    "logprobs": null
576                }
577            ],
578            "object": "chat.completion",
579            "usage": {
580                "prompt_tokens": 3019,
581                "completion_tokens": 104,
582                "total_tokens": 3123,
583                "prompt_tokens_details": {
584                    "cached_tokens": 2048
585                }
586            },
587            "created": 1735120033,
588            "system_fingerprint": null,
589            "model": "qwen-plus",
590            "id": "chatcmpl-6ada9ed2-7f33-9de2-8bb0-78bd4035025a"
591        }"#;
592
593        let parsed = ChatCompletion::from_str(json);
594        match parsed {
595            Ok(_) => {}
596            Err(e) => {
597                panic!("Failed to deserialize: {}", e);
598            }
599        }
600    }
601}