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}