openai_interface/chat/create/request.rs
1//! This module contains the request body and POST method for the chat completion API.
2
3use std::collections::HashMap;
4
5use serde::Serialize;
6
7use crate::{
8 chat::ServiceTier,
9 rest::post::{Post, PostNoStream, PostStream},
10};
11
12/// Creates a model response for the given chat conversation.
13///
14/// # Example
15///
16/// ```rust
17/// use std::sync::LazyLock;
18/// use futures_util::StreamExt;
19/// use openai_interface::chat::create::request::{Message, RequestBody};
20/// use openai_interface::rest::post::PostStream;
21///
22/// const DEEPSEEK_API_KEY: LazyLock<&str> =
23/// LazyLock::new(|| include_str!("../../../keys/deepseek_domestic_key").trim());
24/// const DEEPSEEK_CHAT_URL: &'static str = "https://api.deepseek.com/chat/completions";
25/// const DEEPSEEK_MODEL: &'static str = "deepseek-chat";
26///
27/// #[tokio::main]
28/// async fn main() {
29/// let request = RequestBody {
30/// messages: vec![
31/// Message::System {
32/// content: "This is a request of test purpose. Reply briefly".to_string(),
33/// name: None,
34/// },
35/// Message::User {
36/// content: "What's your name?".to_string(),
37/// name: None,
38/// },
39/// ],
40/// model: DEEPSEEK_MODEL.to_string(),
41/// stream: true,
42/// ..Default::default()
43/// };
44///
45/// let mut response = request
46/// .get_stream_response_string(DEEPSEEK_CHAT_URL, *DEEPSEEK_API_KEY)
47/// .await
48/// .unwrap();
49///
50/// while let Some(chunk) = response.next().await {
51/// println!("{}", chunk.unwrap());
52/// }
53/// }
54/// ```
55#[derive(Serialize, Debug, Default, Clone)]
56pub struct RequestBody {
57 /// Other request bodies that are not in standard OpenAI API.
58 #[serde(flatten, skip_serializing_if = "Option::is_none")]
59 pub extra_body: Option<ExtraBody>,
60
61 /// Other request bodies that are not in standard OpenAI API and
62 /// not included in the ExtraBody struct.
63 #[serde(flatten, skip_serializing_if = "Option::is_none")]
64 pub extra_body_map: Option<serde_json::Map<String, serde_json::Value>>,
65
66 /// Number between -2.0 and 2.0. Positive values penalize new tokens based on their
67 /// existing frequency in the text so far, decreasing the model's likelihood to
68 /// repeat the same line verbatim.
69 #[serde(skip_serializing_if = "Option::is_none")]
70 pub frequency_penalty: Option<f32>,
71
72 /// Whether to return log probabilities of the output tokens or not. If true,
73 /// returns the log probabilities of each output token returned in the `content` of
74 /// `message`.
75 #[serde(skip_serializing_if = "Option::is_none")]
76 pub logprobs: Option<bool>,
77
78 /// An upper bound for the number of tokens that can be generated for a completion,
79 /// including visible output tokens and reasoning tokens.
80 #[serde(skip_serializing_if = "Option::is_none")]
81 pub max_completion_tokens: Option<u32>,
82
83 /// The maximum number of tokens that can be generated in the chat completion.
84 /// Deprecated according to OpenAI's Python SDK in favour of
85 /// `max_completion_tokens`.
86 #[serde(skip_serializing_if = "Option::is_none")]
87 pub max_tokens: Option<u32>,
88
89 /// A list of messages comprising the conversation so far.
90 pub messages: Vec<Message>,
91
92 /// Set of 16 key-value pairs that can be attached to an object. This can be useful
93 /// for storing additional information about the object in a structured format, and
94 /// querying for objects via API or the dashboard.
95 ///
96 /// Keys are strings with a maximum length of 64 characters. Values are strings with
97 /// a maximum length of 512 characters.
98 #[serde(skip_serializing_if = "Option::is_none")]
99 pub metadata: Option<HashMap<String, String>>,
100
101 /// Output types that you would like the model to generate. Most models are capable
102 /// of generating text, which is the default:
103 ///
104 /// `["text"]`
105 ///
106 /// The `gpt-4o-audio-preview` model can also be used to
107 /// [generate audio](https://platform.openai.com/docs/guides/audio). To request that
108 /// this model generate both text and audio responses, you can use:
109 ///
110 /// `["text", "audio"]`
111 #[serde(skip_serializing_if = "Option::is_none")]
112 pub modalities: Option<Vec<Modality>>,
113
114 /// Name of the model to use to generate the response.
115 pub model: String, // The type of this attribute needs improvements.
116
117 /// How many chat completion choices to generate for each input message. Note that
118 /// you will be charged based on the number of generated tokens across all of the
119 /// choices. Keep `n` as `1` to minimize costs.
120 #[serde(skip_serializing_if = "Option::is_none")]
121 pub n: Option<u32>,
122
123 /// Whether to enable
124 /// [parallel function calling](https://platform.openai.com/docs/guides/function-calling#configuring-parallel-function-calling)
125 /// during tool use.
126 #[serde(skip_serializing_if = "Option::is_none")]
127 pub parallel_tool_calls: Option<bool>,
128
129 /// Static predicted output content, such as the content of a text file that is
130 /// being regenerated.
131 #[serde(skip_serializing_if = "Option::is_none")]
132 pub prediction: Option<ChatCompletionPredictionContentParam>,
133
134 /// Number between -2.0 and 2.0. Positive values penalize new tokens based on
135 /// whether they appear in the text so far, increasing the model's likelihood to
136 /// talk about new topics.
137 #[serde(skip_serializing_if = "Option::is_none")]
138 pub presence_penalty: Option<f32>,
139
140 /// Used by OpenAI to cache responses for similar requests to optimize your cache
141 /// hit rates. Replaces the `user` field.
142 /// [Learn more](https://platform.openai.com/docs/guides/prompt-caching).
143 #[serde(skip_serializing_if = "Option::is_none")]
144 pub prompt_cache_key: Option<String>,
145
146 /// Constrains effort on reasoning for
147 /// [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently
148 /// supported values are `minimal`, `low`, `medium`, and `high`. Reducing reasoning
149 /// effort can result in faster responses and fewer tokens used on reasoning in a
150 /// response.
151 #[serde(skip_serializing_if = "Option::is_none")]
152 pub reasoning_effort: Option<String>,
153
154 /// specifying the format that the model must output.
155 ///
156 /// Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured
157 /// Outputs which ensures the model will match your supplied JSON schema. Learn more
158 /// in the
159 /// [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs).
160 /// Setting to `{ "type": "json_object" }` enables the older JSON mode, which
161 /// ensures the message the model generates is valid JSON. Using `json_schema` is
162 /// preferred for models that support it.
163 #[serde(skip_serializing_if = "Option::is_none")]
164 pub response_format: Option<ResponseFormat>,
165
166 /// A stable identifier used to help detect users of your application that may be
167 /// violating OpenAI's usage policies. The IDs should be a string that uniquely
168 /// identifies each user. It is recommended to hash their username or email address, in
169 /// order to avoid sending any identifying information.
170 #[serde(skip_serializing_if = "Option::is_none")]
171 pub safety_identifier: Option<String>,
172
173 /// If specified, the system will make a best effort to sample deterministically. Determinism
174 /// is not guaranteed, and you should refer to the `system_fingerprint` response parameter to
175 /// monitor changes in the backend.
176 #[serde(skip_serializing_if = "Option::is_none")]
177 pub seed: Option<i64>,
178
179 /// Specifies the processing type used for serving the request.
180 ///
181 /// - If set to 'auto', then the request will be processed with the service tier
182 /// configured in the Project settings. Unless otherwise configured, the Project
183 /// will use 'default'.
184 /// - If set to 'default', then the request will be processed with the standard
185 /// pricing and performance for the selected model.
186 /// - If set to '[flex](https://platform.openai.com/docs/guides/flex-processing)' or
187 /// '[priority](https://openai.com/api-priority-processing/)', then the request
188 /// will be processed with the corresponding service tier.
189 /// - When not set, the default behavior is 'auto'.
190 ///
191 /// When the `service_tier` parameter is set, the response body will include the
192 /// `service_tier` value based on the processing mode actually used to serve the
193 /// request. This response value may be different from the value set in the
194 /// parameter.
195 #[serde(skip_serializing_if = "Option::is_none")]
196 pub service_tier: Option<ServiceTier>,
197
198 /// Up to 4 sequences where the API will stop generating further tokens. The
199 /// returned text will not contain the stop sequence.
200 #[serde(skip_serializing_if = "Option::is_none")]
201 pub stop: Option<StopKeywords>,
202
203 /// Whether or not to store the output of this chat completion request for use in
204 /// our [model distillation](https://platform.openai.com/docs/guides/distillation)
205 /// or [evals](https://platform.openai.com/docs/guides/evals) products.
206 ///
207 /// Supports text and image inputs. Note: image inputs over 8MB will be dropped.
208 #[serde(skip_serializing_if = "Option::is_none")]
209 pub store: Option<bool>,
210
211 /// Although it is optional, you should explicitly designate it
212 /// for an expected response.
213 pub stream: bool,
214
215 /// Options for streaming response. Only set this when you set `stream: true`
216 #[serde(skip_serializing_if = "Option::is_none")]
217 pub stream_options: Option<StreamOptions>,
218
219 /// What sampling temperature to use, between 0 and 2. Higher values like 0.8 will
220 /// make the output more random, while lower values like 0.2 will make it more
221 /// focused and deterministic. It is generally recommended to alter this or `top_p` but
222 /// not both.
223 pub temperature: Option<f32>,
224
225 /// Controls which (if any) tool is called by the model. `none` means the model will
226 /// not call any tool and instead generates a message. `auto` means the model can
227 /// pick between generating a message or calling one or more tools. `required` means
228 /// the model must call one or more tools. Specifying a particular tool via
229 /// `{"type": "function", "function": {"name": "my_function"}}` forces the model to
230 /// call that tool.
231 #[serde(skip_serializing_if = "Option::is_none")]
232 pub tool_choice: Option<ToolChoice>,
233
234 /// A list of tools the model may call.
235 #[serde(skip_serializing_if = "Option::is_none")]
236 pub tools: Option<Vec<RequestTool>>,
237
238 /// An integer between 0 and 20 specifying the number of most likely tokens to
239 /// return at each token position, each with an associated log probability.
240 /// `logprobs` must be set to `true` if this parameter is used.
241 #[serde(skip_serializing_if = "Option::is_none")]
242 pub top_logprobs: Option<u32>,
243
244 /// An alternative to sampling with temperature, called nucleus sampling, where the
245 /// model considers the results of the tokens with top_p probability mass. So 0.1
246 /// means only the tokens comprising the top 10% probability mass are considered.
247 ///
248 /// It is generally recommended to alter this or `temperature` but not both.
249 pub top_p: Option<f32>,
250
251 /// This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use
252 /// `prompt_cache_key` instead to maintain caching optimizations. A stable
253 /// identifier for your end-users. Used to boost cache hit rates by better bucketing
254 /// similar requests and to help OpenAI detect and prevent abuse.
255 /// [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers).
256 #[serde(skip_serializing_if = "Option::is_none")]
257 pub user: Option<String>,
258
259 /// Constrains the verbosity of the model's response. Lower values will result in
260 /// more concise responses, while higher values will result in more verbose
261 /// responses. Currently supported values are `low`, `medium`, and `high`.
262 #[serde(skip_serializing_if = "Option::is_none")]
263 pub verbosity: Option<LowMediumHighEnum>,
264
265 /// This tool searches the web for relevant results to use in a response. Learn more
266 /// about the
267 /// [web search tool](https://platform.openai.com/docs/guides/tools-web-search?api-mode=chat).
268 #[serde(skip_serializing_if = "Option::is_none")]
269 pub web_search: Option<WebSearchOptions>,
270}
271
272#[derive(Serialize, Debug, Clone)]
273#[serde(tag = "role", rename_all = "lowercase")]
274pub enum Message {
275 /// In this case, the role of the message author is `system`.
276 /// The field `{ role = "system" }` is added automatically.
277 System {
278 /// The contents of the system message.
279 content: String,
280 /// An optional name for the participant.
281 ///
282 /// Provides the model information to differentiate between
283 /// participants of the same role.
284 #[serde(skip_serializing_if = "Option::is_none")]
285 name: Option<String>,
286 },
287 /// In this case, the role of the message author is `user`.
288 /// The field `{ role = "user" }` is added automatically.
289 User {
290 /// The contents of the user message.
291 content: String,
292 /// An optional name for the participant.
293 ///
294 /// Provides the model information to differentiate between
295 /// participants of the same role.
296 #[serde(skip_serializing_if = "Option::is_none")]
297 name: Option<String>,
298 },
299 /// In this case, the role of the message author is `assistant`.
300 /// The field `{ role = "assistant" }` is added automatically.
301 ///
302 /// Unimplemented params:
303 /// - _audio_: Data about a previous audio response from the model.
304 Assistant {
305 /// The contents of the assistant message. Required unless `tool_calls`
306 /// or `function_call` is specified. (Note that `function_call` is deprecated
307 /// in favour of `tool_calls`.)
308 content: Option<String>,
309 /// The refusal message by the assistant.
310 #[serde(skip_serializing_if = "Option::is_none")]
311 refusal: Option<String>,
312 #[serde(skip_serializing_if = "Option::is_none")]
313 name: Option<String>,
314 /// Set this to true for completion
315 #[serde(skip_serializing_if = "is_false")]
316 prefix: bool,
317 /// Used for the deepseek-reasoner model in the Chat Prefix
318 /// Completion feature as the input for the CoT in the last
319 /// assistant message. When using this feature, the prefix
320 /// parameter must be set to true.
321 #[serde(skip_serializing_if = "Option::is_none")]
322 reasoning_content: Option<String>,
323
324 /// The tool calls generated by the model, such as function calls.
325 #[serde(skip_serializing_if = "Option::is_none")]
326 tool_calls: Option<Vec<AssistantToolCall>>,
327 },
328 /// In this case, the role of the message author is `assistant`.
329 /// The field `{ role = "tool" }` is added automatically.
330 Tool {
331 /// The contents of the tool message.
332 content: String,
333 /// Tool call that this message is responding to.
334 tool_call_id: String,
335 },
336 /// In this case, the role of the message author is `function`.
337 /// The field `{ role = "function" }` is added automatically.
338 Function {
339 /// The contents of the function message.
340 content: String,
341 /// The name of the function to call.
342 name: String,
343 },
344 /// In this case, the role of the message author is `developer`.
345 /// The field `{ role = "developer" }` is added automatically.
346 Developer {
347 /// The contents of the developer message.
348 content: String,
349 /// An optional name for the participant.
350 ///
351 /// Provides the model information to differentiate between
352 /// participants of the same role.
353 name: Option<String>,
354 },
355}
356
357#[derive(Debug, Serialize, Clone)]
358#[serde(tag = "role", rename_all = "lowercase")]
359pub enum AssistantToolCall {
360 Function {
361 /// The ID of the tool call.
362 id: String,
363 /// The function that the model called.
364 function: ToolCallFunction,
365 },
366 Custom {
367 /// The ID of the tool call.
368 id: String,
369 /// The custom tool that the model called.
370 custom: ToolCallCustom,
371 },
372}
373
374#[derive(Debug, Serialize, Clone)]
375pub struct ToolCallFunction {
376 /// The arguments to call the function with, as generated by the model in JSON
377 /// format. Note that the model does not always generate valid JSON, and may
378 /// hallucinate parameters not defined by your function schema. Validate the
379 /// arguments in your code before calling your function.
380 arguments: String,
381 /// The name of the function to call.
382 name: String,
383}
384
385#[derive(Debug, Serialize, Clone)]
386pub struct ToolCallCustom {
387 /// The input for the custom tool call generated by the model.
388 input: String,
389 /// The name of the custom tool to call.
390 name: String,
391}
392
393#[derive(Debug, Serialize, Clone)]
394#[serde(tag = "type", rename_all = "snake_case")]
395pub enum ResponseFormat {
396 /// The type of response format being defined. Always `json_schema`.
397 JsonSchema {
398 /// Structured Outputs configuration options, including a JSON Schema.
399 json_schema: JSONSchema,
400 },
401 /// The type of response format being defined. Always `json_object`.
402 JsonObject,
403 /// The type of response format being defined. Always `text`.
404 Text,
405}
406
407#[derive(Debug, Serialize, Clone)]
408pub struct JSONSchema {
409 /// The name of the response format. Must be a-z, A-Z, 0-9, or contain
410 /// underscores and dashes, with a maximum length of 64.
411 pub name: String,
412 /// A description of what the response format is for, used by the model to determine
413 /// how to respond in the format.
414 pub description: String,
415 /// The schema for the response format, described as a JSON Schema object. Learn how
416 /// to build JSON schemas [here](https://json-schema.org/).
417 pub schema: serde_json::Map<String, serde_json::Value>,
418 /// Whether to enable strict schema adherence when generating the output. If set to
419 /// true, the model will always follow the exact schema defined in the `schema`
420 /// field. Only a subset of JSON Schema is supported when `strict` is `true`. To
421 /// learn more, read the
422 /// [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs).
423 pub strict: Option<bool>,
424}
425
426#[derive(Serialize, Debug, Clone)]
427#[serde(rename_all = "snake_case")]
428pub enum Modality {
429 Text,
430 Audio,
431}
432
433#[derive(Serialize, Debug, Clone)]
434pub struct ChatCompletionPredictionContentParam {
435 /// The content that should be matched when generating a model response. If
436 /// generated tokens would match this content, the entire model response can be
437 /// returned much more quickly.
438 pub content: ChatCompletionPredictionContentParamContent,
439
440 /// The type of the predicted content you want to provide.
441 /// This type is currently always `content`.
442 pub type_: ChatCompletionPredictionContentParamType,
443}
444
445#[derive(Serialize, Debug, Clone)]
446#[serde(untagged)]
447pub enum ChatCompletionPredictionContentParamContent {
448 Text(String),
449 ChatCompletionContentPartTextParam {
450 /// The text content.
451 text: String,
452 /// The type of the content part.
453 #[serde(rename = "type")]
454 type_: ChatCompletionContentPartTextParamType,
455 },
456}
457
458#[derive(Serialize, Debug, Clone)]
459#[serde(rename_all = "snake_case")]
460pub enum ChatCompletionContentPartTextParamType {
461 Text,
462}
463
464#[derive(Serialize, Debug, Clone)]
465#[serde(rename_all = "snake_case")]
466pub enum ChatCompletionPredictionContentParamType {
467 Content,
468}
469
470fn is_false(value: &bool) -> bool {
471 !value
472}
473
474#[derive(Serialize, Debug, Clone)]
475#[serde(untagged)]
476pub enum StopKeywords {
477 Word(String),
478 Words(Vec<String>),
479}
480
481#[derive(Serialize, Debug, Clone)]
482#[serde(rename_all = "snake_case")]
483pub enum LowMediumHighEnum {
484 Low,
485 Medium,
486 High,
487}
488
489#[derive(Serialize, Debug, Clone)]
490pub struct WebSearchOptions {
491 /// High level guidance for the amount of context window space to use for the
492 /// search. One of `low`, `medium`, or `high`. `medium` is the default.
493 pub search_context_size: LowMediumHighEnum,
494
495 pub user_location: Option<WebSearchOptionsUserLocation>,
496}
497
498#[derive(Serialize, Debug, Clone)]
499#[serde(tag = "type", rename_all = "snake_case")]
500pub enum WebSearchOptionsUserLocation {
501 /// The type of location approximation. Always `approximate`.
502 Approximate(WebSearchOptionsUserLocationApproximate),
503}
504
505#[derive(Serialize, Debug, Clone)]
506pub struct WebSearchOptionsUserLocationApproximate {
507 /// Free text input for the city of the user, e.g. `San Francisco`.
508 pub city: String,
509
510 /// The two-letter [ISO country code](https://en.wikipedia.org/wiki/ISO_3166-1) of
511 /// the user, e.g. `US`.
512 pub country: String,
513
514 /// Free text input for the region of the user, e.g. `California`.
515 pub region: String,
516
517 /// The [IANA timezone](https://timeapi.io/documentation/iana-timezones) of the
518 /// user, e.g. `America/Los_Angeles`.
519 pub timezone: String,
520}
521
522#[derive(Serialize, Debug, Clone)]
523pub struct StreamOptions {
524 /// If set, an additional chunk will be streamed before the `data: [DONE]` message.
525 ///
526 /// The `usage` field on this chunk shows the token usage statistics for the entire
527 /// request, and the `choices` field will always be an empty array.
528 ///
529 /// All other chunks will also include a `usage` field, but with a null value.
530 /// **NOTE:** If the stream is interrupted, you may not receive the final usage
531 /// chunk which contains the total token usage for the request.
532 pub include_usage: bool,
533}
534
535#[derive(Serialize, Debug, Clone)]
536#[serde(tag = "type", rename_all = "snake_case")]
537pub enum RequestTool {
538 /// The type of the tool. Currently, only `function` is supported.
539 Function { function: ToolFunction },
540 /// The type of the custom tool. Always `custom`.
541 Custom {
542 /// Properties of the custom tool.
543 custom: ToolCustom,
544 },
545}
546
547#[derive(Serialize, Debug, Clone)]
548#[serde(rename_all = "lowercase")]
549pub enum ReasoningEffort {
550 Minimal,
551 Low,
552 Medium,
553 High,
554}
555
556#[derive(Serialize, Debug, Clone)]
557pub struct ToolFunction {
558 /// The name of the function to be called. Must be a-z, A-Z, 0-9, or
559 /// contain underscores and dashes, with a maximum length
560 /// of 64.
561 pub name: String,
562 /// A description of what the function does, used by the model to choose when and
563 /// how to call the function.
564 pub description: String,
565 /// The parameters the functions accepts, described as a JSON Schema object.
566 ///
567 /// See the
568 /// [openai function calling guide](https://platform.openai.com/docs/guides/function-calling)
569 /// for examples, and the
570 /// [JSON Schema reference](https://json-schema.org/understanding-json-schema/) for
571 /// documentation about the format.
572 ///
573 /// Omitting `parameters` defines a function with an empty parameter list.
574 pub parameters: serde_json::Map<String, serde_json::Value>,
575 /// Whether to enable strict schema adherence when generating the function call.
576 ///
577 /// If set to true, the model will follow the exact schema defined in the
578 /// `parameters` field. Only a subset of JSON Schema is supported when `strict` is
579 /// `true`. Learn more about Structured Outputs in the
580 /// [openai function calling guide](https://platform.openai.com/docs/guides/function-calling).
581 #[serde(skip_serializing_if = "Option::is_none")]
582 pub strict: Option<bool>,
583}
584
585#[derive(Serialize, Debug, Clone)]
586pub struct ToolCustom {
587 /// The name of the custom tool, used to identify it in tool calls.
588 pub name: String,
589 /// Optional description of the custom tool, used to provide more context.
590 pub description: String,
591 /// The input format for the custom tool. Default is unconstrained text.
592 pub format: String,
593}
594
595#[derive(Serialize, Debug, Clone)]
596#[serde(rename_all = "snake_case", tag = "type")]
597pub enum ToolCustomFormat {
598 /// Unconstrained text format. Always `text`.
599 CustomFormatText,
600 /// Grammar format. Always `grammar`.
601 CustomFormatGrammar {
602 /// Your chosen grammar.
603 grammar: ToolCustomFormatGrammarGrammar,
604 },
605}
606
607#[derive(Debug, Serialize, Clone)]
608pub struct ToolCustomFormatGrammarGrammar {
609 /// The grammar definition.
610 pub definition: String,
611 /// The syntax of the grammar definition. One of `lark` or `regex`.
612 pub syntax: ToolCustomFormatGrammarGrammarSyntax,
613}
614
615#[derive(Debug, Serialize, Clone)]
616#[serde(rename_all = "snake_case")]
617pub enum ToolCustomFormatGrammarGrammarSyntax {
618 Lark,
619 Regex,
620}
621
622#[derive(Debug, Serialize, Clone)]
623#[serde(rename_all = "snake_case")]
624pub enum ToolChoice {
625 None,
626 Auto,
627 Required,
628 #[serde(untagged)]
629 Specific(ToolChoiceSpecific),
630}
631
632#[derive(Debug, Serialize, Clone)]
633#[serde(rename_all = "snake_case", tag = "type")]
634pub enum ToolChoiceSpecific {
635 /// Allowed tool configuration type. Always `allowed_tools`.
636 AllowedTools {
637 /// Constrains the tools available to the model to a pre-defined set.
638 allowed_tools: ToolChoiceAllowedTools,
639 },
640 /// For function calling, the type is always `function`.
641 Function { function: ToolChoiceFunction },
642 /// For custom tool calling, the type is always `custom`.
643 Custom { custom: ToolChoiceCustom },
644}
645
646#[derive(Debug, Serialize, Clone)]
647pub struct ToolChoiceAllowedTools {
648 /// Constrains the tools available to the model to a pre-defined set.
649 ///
650 /// - `auto` allows the model to pick from among the allowed tools and generate a
651 /// message.
652 /// - `required` requires the model to call one or more of the allowed tools.
653 pub mode: ToolChoiceAllowedToolsMode,
654 /// A list of tool definitions that the model should be allowed to call.
655 ///
656 /// For the Chat Completions API, the list of tool definitions might look like:
657 ///
658 /// ```json
659 /// [
660 /// { "type": "function", "function": { "name": "get_weather" } },
661 /// { "type": "function", "function": { "name": "get_time" } }
662 /// ]
663 /// ```
664 pub tools: serde_json::Map<String, serde_json::Value>,
665}
666
667/// The mode for allowed tools in tool choice.
668///
669/// Controls how the model should handle the set of allowed tools:
670///
671/// - `auto` allows the model to pick from among the allowed tools and generate a
672/// message.
673/// - `required` requires the model to call one or more of the allowed tools.
674#[derive(Debug, Serialize, Clone)]
675#[serde(rename_all = "lowercase")]
676pub enum ToolChoiceAllowedToolsMode {
677 /// The model can choose whether to use the allowed tools or not.
678 Auto,
679 /// The model must use at least one of the allowed tools.
680 Required,
681}
682
683#[derive(Debug, Serialize, Clone)]
684pub struct ToolChoiceFunction {
685 /// The name of the function to call.
686 pub name: String,
687}
688
689#[derive(Debug, Serialize, Clone)]
690pub struct ToolChoiceCustom {
691 /// The name of the custom tool to call.
692 pub name: String,
693}
694
695#[derive(Debug, Serialize, Clone)]
696pub struct ExtraBody {
697 /// Make sense only for Qwen API.
698 #[serde(skip_serializing_if = "Option::is_none")]
699 pub enable_thinking: Option<bool>,
700 /// Make sense only for Qwen API.
701 #[serde(skip_serializing_if = "Option::is_none")]
702 pub thinking_budget: Option<u32>,
703 ///The size of the candidate set for sampling during generation.
704 ///
705 /// Make sense only for Qwen API.
706 #[serde(skip_serializing_if = "Option::is_none")]
707 pub top_k: Option<u32>,
708}
709
710impl Post for RequestBody {
711 fn is_streaming(&self) -> bool {
712 self.stream
713 }
714}
715
716impl PostNoStream for RequestBody {
717 type Response = super::response::no_streaming::ChatCompletion;
718}
719
720impl PostStream for RequestBody {
721 type Response = super::response::streaming::ChatCompletionChunk;
722}
723
724#[cfg(test)]
725mod request_test {
726 use std::sync::LazyLock;
727
728 use futures_util::StreamExt;
729
730 use super::*;
731
732 const DEEPSEEK_API_KEY: LazyLock<&str> =
733 LazyLock::new(|| include_str!("../../../keys/deepseek_domestic_key").trim());
734 const DEEPSEEK_CHAT_URL: &'static str = "https://api.deepseek.com/chat/completions";
735 const DEEPSEEK_MODEL: &'static str = "deepseek-chat";
736
737 #[tokio::test]
738 async fn test_deepseek_no_stream() {
739 let request = RequestBody {
740 messages: vec![
741 Message::System {
742 content: "This is a request of test purpose. Reply briefly".to_string(),
743 name: None,
744 },
745 Message::User {
746 content: "What's your name?".to_string(),
747 name: None,
748 },
749 ],
750 model: DEEPSEEK_MODEL.to_string(),
751 stream: false,
752 ..Default::default()
753 };
754
755 let response = request
756 .get_response_string(DEEPSEEK_CHAT_URL, &*DEEPSEEK_API_KEY)
757 .await
758 .unwrap();
759
760 println!("{}", response);
761
762 assert!(response.to_ascii_lowercase().contains("deepseek"));
763 }
764
765 #[tokio::test]
766 async fn test_deepseek_stream() {
767 let request = RequestBody {
768 messages: vec![
769 Message::System {
770 content: "This is a request of test purpose. Reply briefly".to_string(),
771 name: None,
772 },
773 Message::User {
774 content: "What's your name?".to_string(),
775 name: None,
776 },
777 ],
778 model: DEEPSEEK_MODEL.to_string(),
779 stream: true,
780 ..Default::default()
781 };
782
783 let mut response = request
784 .get_stream_response_string(DEEPSEEK_CHAT_URL, *DEEPSEEK_API_KEY)
785 .await
786 .unwrap();
787
788 while let Some(chunk) = response.next().await {
789 println!("{}", chunk.unwrap());
790 }
791 }
792}