Skip to main content

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;