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}