Skip to main content

ds_api/raw/request/
chat_completion.rs

1use serde::{Deserialize, Serialize};
2
3use super::{
4    message::Message, model::Model, response_format::ResponseFormat, stop::Stop,
5    stream_options::StreamOptions, thinking::Thinking, tool::Tool, tool_choice::ToolChoice,
6};
7
8#[derive(Debug, Default, Serialize, Deserialize)]
9#[serde(default)]
10pub struct ChatCompletionRequest {
11    /// List of messages in the conversation.
12    pub messages: Vec<Message>,
13
14    /// The model ID to use. Use `deepseek-chat` for faster responses or `deepseek-reasoner` for deeper reasoning capabilities.
15    pub model: Model,
16
17    /// Controls switching between reasoning (thinking) and non-reasoning modes.
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub thinking: Option<Thinking>,
20
21    /// Possible values: >= -2 and <= 2
22    /// Default value: 0
23    /// A number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text,
24    /// reducing the chance of repeated content.
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub frequency_penalty: Option<f32>,
27
28    /// Maximum number of tokens to generate for the completion in a single request.
29    /// The combined length of input and output tokens is limited by the model's context window.
30    /// See documentation for ranges and defaults.
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub max_tokens: Option<u32>,
33
34    /// Possible values: >= -2 and <= 2
35    /// Default value: 0
36    /// A number between -2.0 and 2.0. Positive values penalize new tokens if they already appear in the text,
37    /// encouraging the model to introduce new topics.
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub presence_penalty: Option<f32>,
40
41    /// An object specifying the format the model must output.
42    /// Set to `{ "type": "json_object" }` to enable JSON mode which enforces valid JSON output.
43    /// Note: When using JSON mode you must also instruct the model via system or user messages to output JSON.
44    /// Otherwise the model may emit whitespace until token limits are reached which can appear to hang.
45    /// Also, if `finish_reason == "length"`, the output may be truncated due to `max_tokens` or context limits.
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub response_format: Option<ResponseFormat>,
48
49    /// A string or up to 16 strings. Generation will stop when one of these tokens is encountered.
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub stop: Option<Stop>,
52
53    /// If true, the response will be streamed as SSE (server-sent events). The stream ends with `data: [DONE]`.
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub stream: Option<bool>,
56
57    /// Options related to streaming output. Only valid when `stream` is true.
58    /// `include_usage`: boolean
59    /// If true, an extra chunk with `usage` (aggregate token counts) will be sent before the final `data: [DONE]`.
60    /// Other chunks also include `usage` but with a null value.
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub stream_options: Option<StreamOptions>,
63
64    /// Possible values: <= 2
65    /// Default value: 1
66    /// Sampling temperature between 0 and 2. Higher values (e.g. 0.8) produce more random output;
67    /// lower values (e.g. 0.2) make output more focused and deterministic.
68    /// Typically change either `temperature` or `top_p`, not both.
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub temperature: Option<f32>,
71
72    /// Possible values: <= 1
73    /// Default value: 1
74    /// An alternative to temperature that considers only the top `p` probability mass.
75    /// For example, `top_p = 0.1` means only tokens comprising the top 10% probability mass are considered.
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub top_p: Option<f32>,
78
79    /// List of tools the model may call. Currently only `function` is supported.
80    /// Provide a list of functions that accept JSON input. Up to 128 functions are supported.
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub tools: Option<Vec<Tool>>,
83
84    /// Controls how the model may call tools:
85    /// - `none`: the model will not call tools and will produce a normal message.
86    /// - `auto`: the model can choose to produce a message or call one or more tools.
87    /// - `required`: the model must call one or more tools.
88    ///
89    /// Specifying a particular tool via `{"type":"function","function":{"name":"my_function"}}` forces the model to call that tool.
90    ///
91    /// Default is `none` when no tools exist; when tools exist the default is `auto`.
92    #[serde(skip_serializing_if = "Option::is_none")]
93    pub tool_choice: Option<ToolChoice>,
94
95    /// logprobs boolean NULLABLE
96    /// Return log-probabilities for the output tokens. If true, logprobs for each output token are returned.
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub logprobs: Option<bool>,
99
100    /// Possible values: <= 20
101    /// An integer N between 0 and 20 that returns the top-N token log-probabilities for each output position.
102    /// When specifying this parameter, `logprobs` must be true.
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub top_logprobs: Option<u32>,
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110    use crate::raw::request::message::Role;
111
112    #[test]
113    fn test_chat_completion_request_serialization() {
114        let request = ChatCompletionRequest {
115            messages: vec![Message {
116                role: Role::User,
117                content: Some("Hello, world!".to_string()),
118                name: None,
119                tool_call_id: None,
120                tool_calls: None,
121                reasoning_content: None,
122                prefix: None,
123            }],
124            model: Model::DeepseekChat,
125            thinking: None,
126            frequency_penalty: Some(0.5),
127            max_tokens: Some(100),
128            presence_penalty: None,
129            response_format: None,
130            stop: None,
131            stream: Some(false),
132            stream_options: None,
133            temperature: Some(0.7),
134            top_p: None,
135            tools: None,
136            tool_choice: None,
137            logprobs: None,
138            top_logprobs: None,
139        };
140
141        let json = serde_json::to_string(&request).unwrap();
142        let parsed: ChatCompletionRequest = serde_json::from_str(&json).unwrap();
143
144        assert_eq!(parsed.messages.len(), 1);
145        assert_eq!(
146            parsed.messages[0].content.as_ref().unwrap(),
147            "Hello, world!"
148        );
149        assert!(matches!(parsed.model, Model::DeepseekChat));
150        assert_eq!(parsed.frequency_penalty, Some(0.5));
151        assert_eq!(parsed.max_tokens, Some(100));
152        assert_eq!(parsed.stream, Some(false));
153        assert_eq!(parsed.temperature, Some(0.7));
154    }
155
156    #[test]
157    fn test_default_chat_completion_request() {
158        let request = ChatCompletionRequest::default();
159
160        assert!(request.messages.is_empty());
161        assert!(matches!(request.model, Model::DeepseekChat));
162        assert!(request.thinking.is_none());
163        assert!(request.frequency_penalty.is_none());
164        assert!(request.max_tokens.is_none());
165        assert!(request.presence_penalty.is_none());
166        assert!(request.response_format.is_none());
167        assert!(request.stop.is_none());
168        assert!(request.stream.is_none());
169        assert!(request.stream_options.is_none());
170        assert!(request.temperature.is_none());
171        assert!(request.top_p.is_none());
172        assert!(request.tools.is_none());
173        assert!(request.tool_choice.is_none());
174        assert!(request.logprobs.is_none());
175        assert!(request.top_logprobs.is_none());
176    }
177}