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