Skip to main content

zai_rs/model/moderation/
models.rs

1//! Content moderation API models and types.
2//!
3//! This module provides data structures for content moderation requests and
4//! responses, supporting text, image, audio, and video content safety analysis.
5//!
6//! ## Features
7//!
8//! - **Multi-format support** - Text, image, audio, and video content
9//!   moderation
10//! - **Risk detection** - Identifies pornographic, violent, and illegal content
11//! - **Structured results** - Detailed risk level and type information
12//! - **Validation** - Input validation using the validator crate
13
14use serde::{Deserialize, Deserializer, Serialize};
15use validator::Validate;
16
17// Helper: accept string or number and always deserialize into Option<String>
18fn de_opt_string_from_number_or_string<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
19where
20    D: Deserializer<'de>,
21{
22    let v = serde_json::Value::deserialize(deserializer)?;
23    match v {
24        serde_json::Value::Null => Ok(None),
25        serde_json::Value::String(s) => Ok(Some(s)),
26        serde_json::Value::Number(n) => Ok(Some(n.to_string())),
27        other => Err(serde::de::Error::custom(format!(
28            "expected string or number, got {}",
29            other
30        ))),
31    }
32}
33
34/// Content moderation model type.
35#[derive(Debug, Clone, Serialize, Deserialize, Default)]
36pub enum ModerationModel {
37    /// Default moderation model
38    #[serde(rename = "moderation")]
39    #[default]
40    Moderation,
41}
42
43/// Moderation input content.
44#[derive(Debug, Clone, Serialize, Deserialize)]
45#[serde(untagged)]
46pub enum ModerationInput {
47    /// Text content for moderation
48    Text(String),
49    /// Multimedia content with type and URL
50    Multimedia(MultimediaInput),
51}
52
53/// Multimedia input for content moderation.
54#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
55pub struct MultimediaInput {
56    /// Content type (image, audio, video)
57    #[serde(rename = "type")]
58    pub content_type: MediaType,
59    /// URL to the multimedia content
60    #[validate(url)]
61    pub url: String,
62}
63
64/// Media types supported for moderation.
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub enum MediaType {
67    /// Image content
68    #[serde(rename = "image")]
69    Image,
70    /// Audio content
71    #[serde(rename = "audio")]
72    Audio,
73    /// Video content
74    #[serde(rename = "video")]
75    Video,
76}
77
78/// Content moderation request.
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct ModerationRequest {
81    /// Moderation model to use
82    #[serde(default)]
83    pub model: ModerationModel,
84    /// Content to moderate
85    pub input: ModerationInput,
86}
87
88impl ModerationRequest {
89    /// Create a new moderation request with text content.
90    pub fn new_text(text: impl Into<String>) -> Self {
91        Self {
92            model: ModerationModel::default(),
93            input: ModerationInput::Text(text.into()),
94        }
95    }
96
97    /// Create a new moderation request with multimedia content.
98    pub fn new_multimedia(content_type: MediaType, url: impl Into<String>) -> Self {
99        Self {
100            model: ModerationModel::default(),
101            input: ModerationInput::Multimedia(MultimediaInput {
102                content_type,
103                url: url.into(),
104            }),
105        }
106    }
107
108    /// Validates the moderation request parameters.
109    pub fn validate(&self) -> Result<(), validator::ValidationErrors> {
110        let mut errors = validator::ValidationErrors::new();
111
112        // Validate text input length
113        if let ModerationInput::Text(text) = &self.input
114            && text.len() > 2000
115        {
116            errors.add(
117                "input",
118                validator::ValidationError::new("text_length_exceeded"),
119            );
120        }
121
122        // Validate multimedia URL
123        if let ModerationInput::Multimedia(multimedia) = &self.input
124            && multimedia.url.parse::<url::Url>().is_err()
125        {
126            errors.add("input", validator::ValidationError::new("invalid_url"));
127        }
128
129        if errors.is_empty() {
130            Ok(())
131        } else {
132            Err(errors)
133        }
134    }
135}
136
137/// Risk level for moderated content.
138#[derive(Debug, Clone, Serialize, Deserialize)]
139pub enum RiskLevel {
140    /// Normal content, no risks detected
141    #[serde(rename = "PASS")]
142    Pass,
143    /// Suspicious content, requires review
144    #[serde(rename = "REVIEW")]
145    Review,
146    /// Violating content, should be rejected
147    #[serde(rename = "REJECT")]
148    Reject,
149}
150
151/// Risk types that can be detected.
152#[derive(Debug, Clone, Serialize, Deserialize)]
153pub enum RiskType {
154    /// Pornographic or adult content
155    #[serde(rename = "porn")]
156    Porn,
157    /// Violent or gory content
158    #[serde(rename = "violence")]
159    Violence,
160    /// Illegal or criminal content
161    #[serde(rename = "illegal")]
162    Illegal,
163    /// Political or sensitive content
164    #[serde(rename = "politics")]
165    Politics,
166    /// Other risk types
167    #[serde(rename = "other")]
168    Other,
169}
170
171/// Moderation result for a single content item.
172#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct ModerationResult {
174    /// Type of content that was moderated
175    #[serde(rename = "content_type")]
176    pub content_type: String,
177    /// Risk level assessment
178    #[serde(rename = "risk_level")]
179    pub risk_level: RiskLevel,
180    /// List of detected risk types
181    #[serde(rename = "risk_type")]
182    pub risk_types: Vec<String>,
183}
184
185/// Usage statistics for moderation API.
186#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct ModerationUsage {
188    /// Text moderation usage statistics
189    #[serde(rename = "moderation_text")]
190    pub moderation_text: ModerationTextUsage,
191}
192
193/// Text moderation usage statistics.
194#[derive(Debug, Clone, Serialize, Deserialize)]
195pub struct ModerationTextUsage {
196    /// Number of text moderation calls
197    #[serde(rename = "call_count")]
198    pub call_count: u32,
199}
200
201/// Content moderation response.
202#[derive(Debug, Clone, Serialize, Deserialize)]
203pub struct ModerationResponse {
204    /// Task ID
205    #[serde(skip_serializing_if = "Option::is_none")]
206    pub id: Option<String>,
207    /// Request creation time (Unix timestamp in seconds)
208    #[serde(skip_serializing_if = "Option::is_none")]
209    pub created: Option<u64>,
210    /// Request identifier
211    #[serde(
212        rename = "request_id",
213        skip_serializing_if = "Option::is_none",
214        deserialize_with = "de_opt_string_from_number_or_string"
215    )]
216    pub request_id: Option<String>,
217    /// List of moderation results
218    #[serde(rename = "result_list", skip_serializing_if = "Option::is_none")]
219    pub result_list: Option<Vec<ModerationResult>>,
220    /// Usage statistics
221    #[serde(skip_serializing_if = "Option::is_none")]
222    pub usage: Option<ModerationUsage>,
223}