use std::str::FromStr;
use serde::{Deserialize, Serialize};
use crate::errors::OapiError;
pub mod create;
pub mod delete;
pub mod retrieve;
pub mod update;
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "lowercase")]
pub enum ServiceTier {
Auto,
Default,
Flex,
Scale,
Priority,
}
#[derive(Debug, Deserialize)]
pub struct ChatCompletion {
pub id: String,
pub choices: Vec<Choice>,
pub created: u64,
pub model: String,
pub service_tier: Option<ServiceTier>,
pub system_fingerprint: Option<String>,
pub object: ChatCompletionObject,
pub usage: Option<CompletionUsage>,
}
#[derive(Debug, Deserialize)]
pub enum ChatCompletionObject {
#[serde(rename = "chat.completion")]
ChatCompletion,
}
#[derive(Debug, Deserialize)]
pub struct Choice {
pub finish_reason: FinishReason,
pub index: usize,
pub logprobs: Option<ChoiceLogprobs>,
pub message: ChatCompletionMessage,
}
#[derive(Debug, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum FinishReason {
Length,
Stop,
ToolCalls,
FunctionCall,
ContentFilter,
#[cfg(feature = "deepseek")]
InsufficientSystemResource,
}
#[derive(Debug, Deserialize)]
pub struct ChatCompletionMessage {
pub role: ResponseRole,
pub audio: Option<ChatCompletionAudio>,
pub content: Option<String>,
#[cfg(feature = "deepseek")]
pub reasoning_content: Option<String>,
pub tool_calls: Option<Vec<ChatCompletionMessageToolCall>>,
pub refusal: Option<String>,
pub annotations: Option<Vec<Annotation>>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct ChatCompletionAudio {
pub id: String,
pub data: String,
pub expires_at: u64,
pub transcript: String,
}
#[derive(Debug, Deserialize, Clone)]
pub struct Annotation {
#[serde(rename = "type")]
pub type_: AnnotationType,
pub url_citation: UrlCitation,
}
#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "snake_case")]
pub enum AnnotationType {
UrlCitation,
}
#[derive(Debug, Deserialize, Clone)]
pub struct UrlCitation {
pub start_index: usize,
pub end_index: usize,
pub title: String,
pub url: String,
}
#[derive(Debug, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ChatCompletionMessageToolCall {
Function {
id: String,
function: MessageToolCallFunction,
},
Custom {
id: String,
custom: MessageToolCallCustom,
},
}
#[derive(Debug, Deserialize)]
pub struct MessageToolCallCustom {
pub input: String,
pub name: String,
}
#[derive(Debug, Deserialize)]
pub struct MessageToolCallFunction {
pub arguments: String,
pub name: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ResponseRole {
Assistant,
}
#[derive(Debug, Deserialize)]
pub struct ChoiceLogprobs {
pub content: Option<Vec<TokenLogProb>>,
#[cfg(feature = "deepseek")]
pub reasoning_content: Option<Vec<TokenLogProb>>,
pub refusal: Option<Vec<TokenLogProb>>,
}
#[derive(Debug, Deserialize)]
pub struct TokenLogProb {
pub token: String,
pub logprob: f32,
pub bytes: Option<Vec<u8>>,
pub top_logprobs: Vec<TopLogprob>,
}
#[derive(Debug, Deserialize)]
pub struct TopLogprob {
pub token: String,
pub logprob: f32,
pub bytes: Option<Vec<u8>>,
}
#[derive(Debug, Deserialize)]
pub struct CompletionUsage {
pub completion_tokens: usize,
pub prompt_tokens: usize,
#[cfg(feature = "deepseek")]
pub prompt_cache_hit_tokens: Option<usize>,
#[cfg(feature = "deepseek")]
pub prompt_cache_miss_tokens: Option<usize>,
pub total_tokens: usize,
pub completion_tokens_details: Option<CompletionTokensDetails>,
pub prompt_tokens_details: Option<PromptTokensDetails>,
}
#[derive(Debug, Deserialize)]
pub struct CompletionTokensDetails {
pub accepted_prediction_tokens: Option<usize>,
pub audio_tokens: Option<usize>,
pub reasoning_tokens: Option<usize>,
pub rejected_prediction_tokens: Option<usize>,
}
#[derive(Debug, Deserialize)]
pub struct PromptTokensDetails {
pub audio_tokens: Option<usize>,
pub cached_tokens: Option<usize>,
}
impl FromStr for ChatCompletion {
type Err = crate::errors::OapiError;
fn from_str(content: &str) -> Result<Self, Self::Err> {
let parse_result: Result<ChatCompletion, _> = serde_json::from_str(content)
.map_err(|e| OapiError::DeserializationError(e.to_string()));
parse_result
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn no_streaming_example_deepseek() {
let json = r#"{
"id": "30f6413a-a827-4cf3-9898-f13a8634b798",
"object": "chat.completion",
"created": 1757944111,
"model": "deepseek-chat",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Hello! How can I help you today? 😊"
},
"logprobs": null,
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 10,
"completion_tokens": 11,
"total_tokens": 21,
"prompt_tokens_details": {
"cached_tokens": 0
},
"prompt_cache_hit_tokens": 0,
"prompt_cache_miss_tokens": 10
},
"system_fingerprint": "fp_08f168e49b_prod0820_fp8_kvcache"
}"#;
let parsed = ChatCompletion::from_str(json);
match parsed {
Ok(_) => {}
Err(e) => {
panic!("Failed to deserialize: {}", e);
}
}
}
#[test]
fn no_streaming_example_qwen() {
let json = r#"{
"choices": [
{
"message": {
"role": "assistant",
"content": "我是阿里云开发的一款超大规模语言模型,我叫通义千问。"
},
"finish_reason": "stop",
"index": 0,
"logprobs": null
}
],
"object": "chat.completion",
"usage": {
"prompt_tokens": 3019,
"completion_tokens": 104,
"total_tokens": 3123,
"prompt_tokens_details": {
"cached_tokens": 2048
}
},
"created": 1735120033,
"system_fingerprint": null,
"model": "qwen-plus",
"id": "chatcmpl-6ada9ed2-7f33-9de2-8bb0-78bd4035025a"
}"#;
let parsed = ChatCompletion::from_str(json);
match parsed {
Ok(_) => {}
Err(e) => {
panic!("Failed to deserialize: {}", e);
}
}
}
}