zai_rs/model/traits.rs
1//! # Core Traits for AI Model Abstractions
2//!
3//! Defines the fundamental traits enabling type-safe interactions with
4//! different AI models and capabilities in the Zhipu AI ecosystem.
5//!
6//! # Trait Categories
7//!
8//! ## Model Identification
9//!
10//! - `ModelName` — Converts model types to string identifiers used in API
11//! requests
12//!
13//! ## Capability Markers
14//!
15//! These marker traits carry no runtime data but encode model capabilities at
16//! compile time:
17//!
18//! - `Chat` — Synchronous chat completion
19//! - `AsyncChat` — Asynchronous (queued) chat completion
20//! - `ChatRequestModel` — Frozen request limits shared by a chat model family
21//! - `ChatToolSupport` — Tool input accepted by a text or vision chat model
22//! - `ResponseFormatEnable` — Text-only structured response formatting
23//! - `WatermarkEnable` — Audio-chat watermark control
24//! - `ThinkEnable` — Thinking / reasoning mode
25//! - `ReasoningEffortEnable` — `reasoning_effort` depth control (GLM-5.2+)
26//! - `ToolStreamEnable` — Streaming tool-call output
27//! - `VideoGen` — Video generation
28//! - `ImageGen` — Image generation
29//! - `AudioToText` — Speech recognition
30//! - `TextToAudio` — Text-to-speech synthesis
31//! - `VoiceClone` — Voice cloning
32//!
33//! ## Type-Safety Traits
34//!
35//! - `Bounded` — Compile-time model ↔ message compatibility check
36//! - `StreamState` — Type-state pattern for streaming control
37//!
38//! # Type-State Pattern
39//!
40//! `StreamState` and its implementations (`StreamOn`, `StreamOff`)
41//! enforce streaming vs. non-streaming semantics at the type level, preventing
42//! invalid API usage without any runtime cost.
43//!
44/// Trait implemented by built-in, wire-serializable model identifiers.
45///
46/// [`NAME`](Self::NAME) avoids serializing or cloning a zero-sized model just
47/// to inspect the provider identifier during validation.
48pub trait ModelName: Into<String> + serde::Serialize {
49 /// Exact provider model identifier used on the wire.
50 const NAME: &'static str;
51}
52
53/// Marker trait for compile-time model-message compatibility checking.
54///
55/// This trait is used in conjunction with the type system to ensure that
56/// specific model types are only used with compatible message types,
57/// preventing invalid API calls at compile time.
58pub trait Bounded {}
59
60mod sealed {
61 pub trait Chat {}
62 pub trait AsyncChat {}
63 pub trait ChatRequestModel {}
64 pub trait AudioToText {}
65 pub trait TextToAudio {}
66 pub trait VideoGen {}
67 pub trait ImageGen {}
68 pub trait VoiceClone {}
69
70 impl AudioToText for crate::model::audio_to_text::GlmAsr {}
71 impl TextToAudio for crate::model::text_to_audio::GlmTts {}
72 impl VideoGen for crate::model::gen_video_async::CogVideoX3 {}
73 impl ImageGen for crate::model::gen_image::GlmImage {}
74 impl ImageGen for crate::model::gen_image::CogView4_250304 {}
75 impl ImageGen for crate::model::gen_image::CogView4 {}
76 impl ImageGen for crate::model::gen_image::CogView3Flash {}
77 impl VoiceClone for crate::model::voice_clone::GlmTtsClone {}
78}
79
80/// Indicates that a model supports synchronous chat completion.
81///
82/// Models implementing this trait can be used with the chat-completion API for
83/// real-time conversational interactions. This trait is sealed to the model
84/// ids in the frozen synchronous Chat request enum.
85pub trait Chat: sealed::Chat {}
86
87/// Indicates that a model supports asynchronous chat completion.
88///
89/// Models implementing this trait can be used with the asynchronous
90/// chat-completion API for queued conversational requests. This trait is
91/// sealed to frozen asynchronous model ids.
92pub trait AsyncChat: sealed::AsyncChat {}
93
94/// Frozen request constraints shared by every chat model schema.
95///
96/// The associated constants let validation enforce family-specific limits
97/// without exposing a catch-all request body that accepts invalid fields. The
98/// trait is sealed; downstream model identifiers cannot opt into frozen Chat
99/// operations by implementing capability markers themselves.
100///
101/// ```compile_fail
102/// use zai_rs::model::traits::{ChatRequestModel, ModelName};
103///
104/// #[derive(serde::Serialize)]
105/// struct UnofficialChat;
106///
107/// impl From<UnofficialChat> for String {
108/// fn from(_: UnofficialChat) -> Self {
109/// "unofficial-chat".to_owned()
110/// }
111/// }
112///
113/// impl ModelName for UnofficialChat {
114/// const NAME: &'static str = "unofficial-chat";
115/// }
116/// impl ChatRequestModel for UnofficialChat {
117/// const MAX_TOKENS: u32 = 1_024;
118/// }
119/// ```
120pub trait ChatRequestModel: ModelName + sealed::ChatRequestModel {
121 /// Largest `max_tokens` value accepted by this model's request schema.
122 const MAX_TOKENS: u32;
123}
124
125/// Indicates that a chat model accepts tools and defines their public input
126/// type.
127///
128/// Text models use [`Tools`](super::tools::Tools), while vision models use
129/// [`Function`](super::tools::Function). This prevents retrieval, web-search,
130/// and MCP tools from being attached to a vision request at compile time.
131pub trait ChatToolSupport: ChatRequestModel {
132 /// Public tool type accepted by `add_tool` and `add_tools`.
133 type Tool: Into<super::tools::Tools>;
134}
135
136/// Indicates that a text chat model accepts `response_format`.
137pub trait ResponseFormatEnable: ChatRequestModel {}
138
139/// Indicates that an audio chat model accepts `watermark_enabled`.
140pub trait WatermarkEnable: ChatRequestModel {}
141
142macro_rules! seal_chat_capability {
143 ($capability:ident: $($model:path),+ $(,)?) => {
144 $(impl sealed::$capability for $model {})+
145 };
146}
147
148seal_chat_capability!(Chat:
149 super::chat_models::GLM5_2,
150 super::chat_models::GLM5_1,
151 super::chat_models::GLM5_turbo,
152 super::chat_models::GLM5,
153 super::chat_models::GLM4_7,
154 super::chat_models::GLM4_7_flash,
155 super::chat_models::GLM4_7_flashx,
156 super::chat_models::GLM4_6,
157 super::chat_models::GLM4_5_flash,
158 super::chat_models::GLM4_5_air,
159 super::chat_models::GLM4_5_airx,
160 super::chat_models::GLM4_flash_250414,
161 super::chat_models::GLM4_flashx_250414,
162 super::chat_models::GLM5V_turbo,
163 super::chat_models::autoglm_phone,
164 super::chat_models::GLM4_6v,
165 super::chat_models::GLM4_6v_flash,
166 super::chat_models::GLM4_6v_flashx,
167 super::chat_models::GLM4v_flash,
168 super::chat_models::GLM4_1v_thinking_flash,
169 super::chat_models::GLM4_1v_thinking_flashx,
170 super::chat_models::GLM4_voice,
171);
172
173seal_chat_capability!(AsyncChat:
174 super::chat_models::GLM5_2,
175 super::chat_models::GLM5_1,
176 super::chat_models::GLM5_turbo,
177 super::chat_models::GLM5,
178 super::chat_models::GLM4_7,
179 super::chat_models::GLM4_6,
180 super::chat_models::GLM4_5_flash,
181 super::chat_models::GLM4_5_air,
182 super::chat_models::GLM4_5_airx,
183 super::chat_models::GLM4_flash_250414,
184 super::chat_models::GLM4_flashx_250414,
185 super::chat_models::GLM5V_turbo,
186 super::chat_models::GLM4_6v,
187 super::chat_models::GLM4_6v_flash,
188 super::chat_models::GLM4_6v_flashx,
189 super::chat_models::GLM4v_flash,
190 super::chat_models::GLM4_1v_thinking_flash,
191 super::chat_models::GLM4_1v_thinking_flashx,
192 super::chat_models::GLM4_voice,
193);
194
195seal_chat_capability!(ChatRequestModel:
196 super::chat_models::GLM5_2,
197 super::chat_models::GLM5_1,
198 super::chat_models::GLM5_turbo,
199 super::chat_models::GLM5,
200 super::chat_models::GLM4_7,
201 super::chat_models::GLM4_7_flash,
202 super::chat_models::GLM4_7_flashx,
203 super::chat_models::GLM4_6,
204 super::chat_models::GLM4_5_flash,
205 super::chat_models::GLM4_5_air,
206 super::chat_models::GLM4_5_airx,
207 super::chat_models::GLM4_flash_250414,
208 super::chat_models::GLM4_flashx_250414,
209 super::chat_models::GLM5V_turbo,
210 super::chat_models::autoglm_phone,
211 super::chat_models::GLM4_6v,
212 super::chat_models::GLM4_6v_flash,
213 super::chat_models::GLM4_6v_flashx,
214 super::chat_models::GLM4v_flash,
215 super::chat_models::GLM4_1v_thinking_flash,
216 super::chat_models::GLM4_1v_thinking_flashx,
217 super::chat_models::GLM4_voice,
218);
219
220/// Indicates that a model supports thinking/reasoning capabilities.
221///
222/// Models implementing this trait can enable the provider's extended reasoning
223/// mode. Whether reasoning text is returned is model- and endpoint-dependent.
224pub trait ThinkEnable: ChatRequestModel {}
225
226/// Indicates that a model supports the `reasoning_effort` parameter, which
227/// controls the depth of reasoning when thinking mode is enabled.
228///
229/// Currently supported only by GLM-5.2 and above. See
230/// [`ReasoningEffort`](super::tools::ReasoningEffort) for the available levels.
231pub trait ReasoningEffortEnable: ChatRequestModel {}
232
233/// Indicates that a model supports streaming tool calls (tool_stream
234/// parameter). Only models implementing this marker can enable tool_stream in
235/// requests.
236pub trait ToolStreamEnable: ChatToolSupport {}
237
238/// Indicates that an official model supports video generation.
239///
240/// This trait is sealed to the model ids documented by the frozen API schema.
241pub trait VideoGen: ModelName + sealed::VideoGen {}
242
243/// Indicates that an official model supports image generation.
244///
245/// This trait is sealed to the model ids documented by the frozen API schema.
246pub trait ImageGen: ModelName + sealed::ImageGen {}
247
248/// Indicates that an official model supports speech recognition.
249///
250/// This trait is sealed because the ASR request schema is frozen to the
251/// documented model ids. Downstream crates cannot opt arbitrary model names
252/// into that schema.
253///
254/// ```compile_fail
255/// use zai_rs::model::traits::{AudioToText, ModelName};
256///
257/// #[derive(serde::Serialize)]
258/// struct UnofficialAsr;
259///
260/// impl From<UnofficialAsr> for String {
261/// fn from(_: UnofficialAsr) -> Self {
262/// "unofficial-asr".to_owned()
263/// }
264/// }
265///
266/// impl ModelName for UnofficialAsr {
267/// const NAME: &'static str = "unofficial-asr";
268/// }
269/// impl AudioToText for UnofficialAsr {}
270/// ```
271pub trait AudioToText: ModelName + sealed::AudioToText {}
272
273/// Indicates that an official model supports text-to-speech synthesis.
274///
275/// This trait is sealed because the TTS request schema is frozen to the
276/// documented model ids. Downstream crates cannot opt arbitrary model names
277/// into that schema.
278///
279/// ```compile_fail
280/// use zai_rs::model::traits::{ModelName, TextToAudio};
281///
282/// #[derive(serde::Serialize)]
283/// struct UnofficialTts;
284///
285/// impl From<UnofficialTts> for String {
286/// fn from(_: UnofficialTts) -> Self {
287/// "unofficial-tts".to_owned()
288/// }
289/// }
290///
291/// impl ModelName for UnofficialTts {
292/// const NAME: &'static str = "unofficial-tts";
293/// }
294/// impl TextToAudio for UnofficialTts {}
295/// ```
296pub trait TextToAudio: ModelName + sealed::TextToAudio {}
297
298/// Indicates that an official model supports voice cloning.
299///
300/// This trait is sealed to the model ids documented by the frozen API schema.
301pub trait VoiceClone: ModelName + sealed::VoiceClone {}
302
303/// Type-state trait for compile-time streaming capability control.
304///
305/// This trait enables the type system to enforce whether a request
306/// supports streaming (`StreamOn`) or non-streaming (`StreamOff`) responses,
307/// preventing invalid API usage patterns.
308pub trait StreamState {}
309
310/// Type-state indicating that streaming is enabled.
311///
312/// Types parameterized with this marker support Server-Sent Events (SSE)
313/// streaming for real-time response processing.
314#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
315pub struct StreamOn;
316
317/// Type-state indicating that streaming is disabled.
318///
319/// Types parameterized with this marker receive complete responses
320/// rather than streaming chunks.
321#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
322pub struct StreamOff;
323
324impl StreamState for StreamOn {}
325impl StreamState for StreamOff {}
326
327// Generates a zero-sized built-in model id and its standard wire traits.
328macro_rules! define_model_type {
329 ($(#[$meta:meta])* $name:ident, $s:expr) => {
330 #[doc = concat!(" AI model type backed by the upstream id `", $s, "`.")]
331 #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
332 $(#[$meta])*
333 pub struct $name {}
334
335 impl ::core::convert::From<$name> for String {
336 fn from(_val: $name) -> Self { $s.to_string() }
337 }
338
339 impl ::serde::Serialize for $name {
340 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
341 where S: ::serde::Serializer {
342 serializer.serialize_str(&$s)
343 }
344 }
345
346 impl $crate::model::traits::ModelName for $name {
347 const NAME: &'static str = $s;
348 }
349 };
350}
351pub(crate) use define_model_type;
352
353// Binds the message schemas accepted by one built-in model.
354macro_rules! impl_message_binding {
355 ($name:ident, $message_type:ty) => {
356 impl $crate::model::traits::Bounded for ($name, $message_type) {}
357 };
358}
359pub(crate) use impl_message_binding;
360
361// Applies capability traits to one or more built-in model identifiers.
362macro_rules! impl_model_markers {
363 ($model:ident : $($marker:path),+ $(,)?) => {
364 $( impl $marker for $model {} )+
365 };
366}
367pub(crate) use impl_model_markers;