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}