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}