openai_interface/chat/response/
no_streaming.rs

1use serde::Deserialize;
2
3use crate::errors::ResponseError;
4
5#[derive(Debug, Deserialize)]
6pub struct Completion {
7    pub id: String,
8    pub choices: Vec<ResponseChoice>,
9    pub created: u64,
10    pub model: String,
11    pub system_fingerprint: String,
12    pub object: String,
13    pub usage: CompletionUsage,
14}
15
16#[derive(Debug, Deserialize)]
17pub struct ResponseChoice {
18    pub finish_reason: FinishReason,
19    pub index: usize,
20    pub message: ResponseMessage,
21    pub logprobs: Option<ResponseLogprobs>,
22}
23
24#[derive(Debug, Deserialize, PartialEq)]
25#[serde(rename_all = "snake_case")]
26pub enum FinishReason {
27    Length,
28    Stop,
29    ContentFilter,
30    InsufficientSystemResource,
31}
32
33#[derive(Debug, Deserialize)]
34pub struct ResponseMessage {
35    /// This shall always be ResponseRole::Assistant
36    pub role: ResponseRole,
37    pub content: Option<String>,
38    pub reasoning_content: Option<String>,
39    /// Tool calls deserialization is not supported yet.
40    pub tool_calls: Option<String>,
41}
42
43#[derive(Debug, Deserialize)]
44#[serde(rename_all = "snake_case")]
45pub enum ResponseRole {
46    User,
47    Assistant,
48    System,
49    Tool,
50}
51
52#[derive(Debug, Deserialize)]
53pub struct ResponseLogprobs {
54    pub content: Vec<LogProb>,
55    pub reasoning_content: Vec<LogProb>,
56}
57
58#[derive(Debug, Deserialize)]
59pub struct LogProb {
60    pub token: String,
61    pub logprob: f32,
62    pub bytes: Option<Vec<u8>>,
63    pub top_logprobs: Vec<TopLogprob>,
64}
65
66#[derive(Debug, Deserialize)]
67pub struct TopLogprob {
68    pub token: String,
69    pub logprob: f32,
70    pub bytes: Option<Vec<u8>>,
71}
72
73#[derive(Debug, Deserialize)]
74pub struct CompletionUsage {
75    pub completion_tokens: usize,
76    pub prompt_tokens: usize,
77
78    // These two fields seem to be DeepSeek specific.
79    pub prompt_cache_hit_tokens: Option<usize>,
80    pub prompt_cache_miss_tokens: Option<usize>,
81
82    pub total_tokens: usize,
83    pub completion_tokens_details: Option<CompletionTokensDetails>,
84}
85
86#[derive(Debug, Deserialize)]
87pub struct CompletionTokensDetails {
88    pub reasoning_tokens: usize,
89}
90
91impl Completion {
92    pub fn parse_string(content: &str) -> Result<Self, crate::errors::ResponseError> {
93        let parse_result: Result<Completion, _> = serde_json::from_str(content)
94            .map_err(|e| ResponseError::DeserializationError(e.to_string()));
95        parse_result
96    }
97}
98
99#[cfg(test)]
100mod test {
101
102    #[test]
103    fn no_streaming_example_deepseek() {
104        let json = r#"{
105          "id": "30f6413a-a827-4cf3-9898-f13a8634b798",
106          "object": "chat.completion",
107          "created": 1757944111,
108          "model": "deepseek-chat",
109          "choices": [
110            {
111              "index": 0,
112              "message": {
113                "role": "assistant",
114                "content": "Hello! How can I help you today? 😊"
115              },
116              "logprobs": null,
117              "finish_reason": "stop"
118            }
119          ],
120          "usage": {
121            "prompt_tokens": 10,
122            "completion_tokens": 11,
123            "total_tokens": 21,
124            "prompt_tokens_details": {
125              "cached_tokens": 0
126            },
127            "prompt_cache_hit_tokens": 0,
128            "prompt_cache_miss_tokens": 10
129          },
130          "system_fingerprint": "fp_08f168e49b_prod0820_fp8_kvcache"
131        }"#;
132
133        let parsed = super::Completion::parse_string(json);
134        match parsed {
135            Ok(_) => {}
136            Err(e) => {
137                panic!("Failed to deserialize: {}", e);
138            }
139        }
140    }
141}