Skip to main content

zai_rs/model/
chat_base_request.rs

1//! Base types and structures for chat API models.
2//!
3//! This module provides the core data structures used for chat API requests,
4//! including the main request body structure.
5
6use serde::Serialize;
7use validator::*;
8
9use super::{tools::*, traits::*};
10
11/// Main request body structure for chat API calls.
12///
13/// This structure represents a complete chat request with all possible
14/// configuration options. It uses generic types to support different model
15/// names and message types while maintaining type safety through trait bounds.
16///
17/// # Type Parameters
18///
19/// * `N` - The model name type, must implement [`ModelName`]
20/// * `M` - The message type, must form a [`Bounded`] pair with `N`
21///
22/// # Examples
23///
24/// ```rust,ignore
25/// use crate::model::base::{ChatBody, TextMessage};
26///
27/// // Create a basic chat request
28/// let chat_body = ChatBody {
29///     model: "gpt-4".to_string(),
30///     messages: vec![
31///         TextMessage::user("Hello, how are you?"),
32///         TextMessage::assistant("I'm doing well, thank you!")
33///     ],
34///     temperature: Some(0.7),
35///     max_tokens: Some(1000),
36///     ..Default::default()
37/// };
38/// ```
39#[derive(Debug, Clone, Validate, Serialize)]
40pub struct ChatBody<N, M>
41where
42    N: ModelName,
43    (N, M): Bounded,
44{
45    /// The model to use for the chat completion.
46    pub model: N,
47
48    /// A list of messages comprising the conversation so far.
49    pub messages: Vec<M>,
50
51    /// A unique identifier for the request. Optional field that will be omitted
52    /// from serialization if not provided.
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub request_id: Option<String>,
55
56    /// Optional thinking prompt or reasoning text that can guide the model's
57    /// response. Only available for models that support thinking
58    /// capabilities.
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub thinking: Option<ThinkingType>,
61
62    /// Whether to use sampling during generation. When `true`, the model will
63    /// use probabilistic sampling; when `false`, it will use deterministic
64    /// generation.
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub do_sample: Option<bool>,
67
68    /// Whether to stream back partial message deltas as they are generated.
69    /// When `true`, responses will be sent as server-sent events.
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub stream: Option<bool>,
72
73    /// Whether to enable streaming of tool calls (streaming function call
74    /// parameters). Only supported by GLM-4.6 models. Defaults to false
75    /// when omitted.
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub tool_stream: Option<bool>,
78
79    /// Controls randomness in the output. Higher values (closer to 1.0) make
80    /// the output more random, while lower values (closer to 0.0) make it
81    /// more deterministic. Must be between 0.0 and 1.0.
82    #[serde(skip_serializing_if = "Option::is_none")]
83    #[validate(range(min = 0.0, max = 1.0))]
84    pub temperature: Option<f32>,
85
86    /// Controls diversity via nucleus sampling. Only tokens with cumulative
87    /// probability up to `top_p` are considered. Must be between 0.0 and
88    /// 1.0.
89    #[serde(skip_serializing_if = "Option::is_none")]
90    #[validate(range(min = 0.0, max = 1.0))]
91    pub top_p: Option<f32>,
92
93    /// The maximum number of tokens to generate in the completion.
94    /// Must be between 1 and 98304.
95    #[serde(skip_serializing_if = "Option::is_none")]
96    #[validate(range(min = 1, max = 98304))]
97    pub max_tokens: Option<u32>,
98
99    /// A list of tools the model may call. Currently supports function calling,
100    /// web search, and retrieval tools.
101    /// Note: server expects an array; we model this as a vector of tool items.
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub tools: Option<Vec<Tools>>,
104
105    // tool_choice: enum<string>, but we don't need it for now
106    /// A unique identifier representing your end-user, which can help monitor
107    /// and detect abuse. Must be between 6 and 128 characters long.
108    #[serde(skip_serializing_if = "Option::is_none")]
109    #[validate(length(min = 6, max = 128))]
110    pub user_id: Option<String>,
111
112    /// Up to 1 sequence where the API will stop generating further tokens.
113    #[serde(skip_serializing_if = "Option::is_none")]
114    #[validate(length(min = 1, max = 1))]
115    pub stop: Option<Vec<String>>,
116
117    /// An object specifying the format that the model must output.
118    /// Can be either text or JSON object format.
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub response_format: Option<ResponseFormat>,
121}
122
123impl<N, M> ChatBody<N, M>
124where
125    N: ModelName,
126    (N, M): Bounded,
127{
128    pub fn new(model: N, messages: M) -> Self {
129        Self {
130            model,
131            messages: vec![messages],
132            request_id: None,
133            thinking: None,
134            do_sample: None,
135            stream: None,
136            tool_stream: None,
137            temperature: None,
138            top_p: None,
139            max_tokens: None,
140            tools: None,
141            user_id: None,
142            stop: None,
143            response_format: None,
144        }
145    }
146
147    pub fn add_messages(mut self, messages: M) -> Self {
148        self.messages.push(messages);
149        self
150    }
151    pub fn with_request_id(mut self, request_id: impl Into<String>) -> Self {
152        self.request_id = Some(request_id.into());
153        self
154    }
155    pub fn with_do_sample(mut self, do_sample: bool) -> Self {
156        self.do_sample = Some(do_sample);
157        self
158    }
159    pub fn with_stream(mut self, stream: bool) -> Self {
160        self.stream = Some(stream);
161        self
162    }
163    pub fn with_temperature(mut self, temperature: f32) -> Self {
164        self.temperature = Some(temperature);
165        self
166    }
167    pub fn with_top_p(mut self, top_p: f32) -> Self {
168        self.top_p = Some(top_p);
169        self
170    }
171    pub fn with_max_tokens(mut self, max_tokens: u32) -> Self {
172        self.max_tokens = Some(max_tokens);
173        self
174    }
175    /// Deprecated: use `add_tools` (single) or `extend_tools` (Vec) on
176    /// ChatBody, or prefer ChatCompletion::add_tool / add_tools at the
177    /// client layer.
178    #[deprecated(note = "with_tools is deprecated; use add_tool/add_tools instead")]
179    pub fn with_tools(mut self, tools: impl Into<Vec<Tools>>) -> Self {
180        self.tools = Some(tools.into());
181        self
182    }
183    pub fn add_tools(mut self, tools: Tools) -> Self {
184        self.tools.get_or_insert(Vec::new()).push(tools);
185        self
186    }
187    pub fn extend_tools(mut self, tools: Vec<Tools>) -> Self {
188        self.tools.get_or_insert(Vec::new()).extend(tools);
189        self
190    }
191    pub fn with_user_id(mut self, user_id: impl Into<String>) -> Self {
192        self.user_id = Some(user_id.into());
193        self
194    }
195    pub fn with_stop(mut self, stop: String) -> Self {
196        self.stop.get_or_insert_with(Vec::new).push(stop);
197        self
198    }
199}
200
201impl<N, M> ChatBody<N, M>
202where
203    N: ModelName + ThinkEnable,
204    (N, M): Bounded,
205{
206    /// Adds thinking text to the chat body for models that support thinking
207    /// capabilities.
208    ///
209    /// This method is only available for models that implement the
210    /// [`ThinkEnable`] trait, ensuring type safety for thinking-enabled
211    /// models.
212    ///
213    /// # Arguments
214    ///
215    /// * `thinking` - The thinking prompt or reasoning text to add
216    ///
217    /// # Returns
218    ///
219    /// Returns `self` with the thinking field set, allowing for method
220    /// chaining.
221    ///
222    /// # Examples
223    ///
224    /// ```rust,ignore
225    /// let chat_body = ChatBody::new(model, messages)
226    ///     .with_thinking("Let me think step by step about this problem...");
227    /// ```
228    pub fn with_thinking(mut self, thinking: ThinkingType) -> Self {
229        self.thinking = Some(thinking);
230        self
231    }
232}
233
234// Only available when the model supports streaming tool calls (GLM-4.6)
235impl<N, M> ChatBody<N, M>
236where
237    N: ModelName + ToolStreamEnable,
238    (N, M): Bounded,
239{
240    /// Enables streaming tool calls (GLM-4.6 only). Default is false when
241    /// omitted.
242    pub fn with_tool_stream(mut self, tool_stream: bool) -> Self {
243        if tool_stream {
244            // Enabling tool_stream implies stream=true
245            self.stream = Some(true);
246        }
247        self
248    }
249}
250
251// 为方便使用,实现从单个Tools到Vec<Tools>的转换
252impl From<Tools> for Vec<Tools> {
253    fn from(tool: Tools) -> Self {
254        vec![tool]
255    }
256}