Skip to main content

linger_openai_sdk/
moderations.rs

1use crate::error::LingerError;
2use crate::RequestId;
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use std::collections::BTreeMap;
6
7/// EN: Request body for `POST /v1/moderations`.
8/// 中文:`POST /v1/moderations` 的请求体。
9#[derive(Clone, Debug, Serialize, PartialEq)]
10#[non_exhaustive]
11pub struct CreateModerationRequest {
12    /// EN: Optional moderation model id.
13    /// 中文:可选的 moderation 模型 ID。
14    #[serde(skip_serializing_if = "Option::is_none")]
15    pub model: Option<String>,
16    /// EN: Text, text list, or multimodal input to classify.
17    /// 中文:要分类的文本、文本列表或多模态输入。
18    pub input: ModerationInput,
19    /// EN: Forward-compatible optional fields not yet covered by handwritten types.
20    /// 中文:手写类型尚未覆盖的前向兼容可选字段。
21    #[serde(flatten)]
22    pub extra: BTreeMap<String, Value>,
23}
24
25impl CreateModerationRequest {
26    /// EN: Starts building a moderation request.
27    /// 中文:开始构建 moderation 请求。
28    pub fn builder() -> CreateModerationRequestBuilder {
29        CreateModerationRequestBuilder::default()
30    }
31}
32
33/// EN: Builder for create-moderation requests.
34/// 中文:创建 moderation 请求的构建器。
35#[derive(Clone, Debug, Default)]
36#[non_exhaustive]
37pub struct CreateModerationRequestBuilder {
38    model: Option<String>,
39    input: Option<ModerationInput>,
40    extra: BTreeMap<String, Value>,
41}
42
43impl CreateModerationRequestBuilder {
44    /// EN: Sets the optional moderation model id.
45    /// 中文:设置可选的 moderation 模型 ID。
46    pub fn model(mut self, model: impl Into<String>) -> Self {
47        self.model = Some(model.into());
48        self
49    }
50
51    /// EN: Sets the moderation input.
52    /// 中文:设置 moderation 输入。
53    pub fn input(mut self, input: impl Into<ModerationInput>) -> Self {
54        self.input = Some(input.into());
55        self
56    }
57
58    /// EN: Adds a forward-compatible JSON field.
59    /// 中文:添加前向兼容的 JSON 字段。
60    pub fn extra(mut self, name: impl Into<String>, value: Value) -> Self {
61        self.extra.insert(name.into(), value);
62        self
63    }
64
65    /// EN: Builds and validates the request.
66    /// 中文:构建并校验请求。
67    pub fn build(self) -> Result<CreateModerationRequest, LingerError> {
68        if self
69            .model
70            .as_ref()
71            .is_some_and(|value| value.trim().is_empty())
72        {
73            return Err(LingerError::invalid_config("model must not be empty"));
74        }
75        let input = self
76            .input
77            .ok_or_else(|| LingerError::invalid_config("input is required"))?;
78        if input.is_empty() {
79            return Err(LingerError::invalid_config("input must not be empty"));
80        }
81        Ok(CreateModerationRequest {
82            model: self.model,
83            input,
84            extra: self.extra,
85        })
86    }
87}
88
89/// EN: Moderations API input value.
90/// 中文:Moderations API 输入值。
91#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
92#[serde(untagged)]
93#[non_exhaustive]
94pub enum ModerationInput {
95    /// EN: Single text input.
96    /// 中文:单条文本输入。
97    Text(String),
98    /// EN: Multiple text inputs.
99    /// 中文:多条文本输入。
100    Texts(Vec<String>),
101    /// EN: Multimodal input items.
102    /// 中文:多模态输入项。
103    Items(Vec<ModerationInputItem>),
104}
105
106impl ModerationInput {
107    fn is_empty(&self) -> bool {
108        match self {
109            Self::Text(value) => value.is_empty(),
110            Self::Texts(values) => values.is_empty() || values.iter().any(String::is_empty),
111            Self::Items(items) => {
112                items.is_empty() || items.iter().any(ModerationInputItem::is_empty)
113            }
114        }
115    }
116}
117
118impl From<&str> for ModerationInput {
119    fn from(value: &str) -> Self {
120        Self::Text(value.to_string())
121    }
122}
123
124impl From<String> for ModerationInput {
125    fn from(value: String) -> Self {
126        Self::Text(value)
127    }
128}
129
130impl From<Vec<String>> for ModerationInput {
131    fn from(value: Vec<String>) -> Self {
132        Self::Texts(value)
133    }
134}
135
136impl From<Vec<&str>> for ModerationInput {
137    fn from(value: Vec<&str>) -> Self {
138        Self::Texts(value.into_iter().map(str::to_string).collect())
139    }
140}
141
142impl From<Vec<ModerationInputItem>> for ModerationInput {
143    fn from(value: Vec<ModerationInputItem>) -> Self {
144        Self::Items(value)
145    }
146}
147
148/// EN: Multimodal moderation input item.
149/// 中文:多模态 moderation 输入项。
150#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
151#[serde(tag = "type")]
152#[non_exhaustive]
153pub enum ModerationInputItem {
154    /// EN: Text input item.
155    /// 中文:文本输入项。
156    #[serde(rename = "text")]
157    Text {
158        /// EN: Text content.
159        /// 中文:文本内容。
160        text: String,
161    },
162    /// EN: Image URL input item.
163    /// 中文:图片 URL 输入项。
164    #[serde(rename = "image_url")]
165    ImageUrl {
166        /// EN: Image URL object.
167        /// 中文:图片 URL 对象。
168        image_url: ModerationImageUrl,
169    },
170}
171
172impl ModerationInputItem {
173    /// EN: Creates a text input item.
174    /// 中文:创建文本输入项。
175    pub fn text(text: impl Into<String>) -> Self {
176        Self::Text { text: text.into() }
177    }
178
179    /// EN: Creates an image URL input item.
180    /// 中文:创建图片 URL 输入项。
181    pub fn image_url(url: impl Into<String>) -> Self {
182        Self::ImageUrl {
183            image_url: ModerationImageUrl { url: url.into() },
184        }
185    }
186
187    fn is_empty(&self) -> bool {
188        match self {
189            Self::Text { text } => text.is_empty(),
190            Self::ImageUrl { image_url } => image_url.url.is_empty(),
191        }
192    }
193}
194
195/// EN: Image URL object used by moderation input items.
196/// 中文:moderation 输入项使用的图片 URL 对象。
197#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
198#[non_exhaustive]
199pub struct ModerationImageUrl {
200    /// EN: Publicly accessible image URL.
201    /// 中文:可公开访问的图片 URL。
202    pub url: String,
203}
204
205/// EN: Response object returned by the Moderations API.
206/// 中文:Moderations API 返回的响应对象。
207#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
208#[non_exhaustive]
209pub struct ModerationCreateResponse {
210    /// EN: Moderation response id.
211    /// 中文:moderation 响应 ID。
212    pub id: String,
213    /// EN: Model used to classify the input.
214    /// 中文:用于分类输入的模型。
215    pub model: String,
216    /// EN: Moderation results.
217    /// 中文:moderation 结果。
218    #[serde(default)]
219    pub results: Vec<ModerationResult>,
220    /// EN: OpenAI request id from response headers.
221    /// 中文:响应头中的 OpenAI 请求 ID。
222    #[serde(skip)]
223    request_id: Option<RequestId>,
224    /// EN: Additional fields preserved for forward compatibility.
225    /// 中文:为前向兼容保留的额外字段。
226    #[serde(flatten)]
227    pub extra: BTreeMap<String, Value>,
228}
229
230impl ModerationCreateResponse {
231    pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
232        self.request_id = request_id;
233        self
234    }
235
236    /// EN: Returns the OpenAI request id, when present.
237    /// 中文:返回 OpenAI 请求 ID,如存在。
238    pub fn request_id(&self) -> Option<&RequestId> {
239        self.request_id.as_ref()
240    }
241}
242
243/// EN: Single moderation result.
244/// 中文:单个 moderation 结果。
245#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
246#[non_exhaustive]
247pub struct ModerationResult {
248    /// EN: Whether the input was flagged by any moderation category.
249    /// 中文:输入是否被任一 moderation 类别标记。
250    pub flagged: bool,
251    /// EN: Category decisions keyed by OpenAI category name.
252    /// 中文:以 OpenAI 类别名为键的类别判定。
253    #[serde(default)]
254    pub categories: BTreeMap<String, bool>,
255    /// EN: Category scores keyed by OpenAI category name.
256    /// 中文:以 OpenAI 类别名为键的类别分数。
257    #[serde(default)]
258    pub category_scores: BTreeMap<String, f64>,
259    /// EN: Input types considered for each category.
260    /// 中文:每个类别适用的输入类型。
261    #[serde(default)]
262    pub category_applied_input_types: BTreeMap<String, Vec<String>>,
263    /// EN: Additional fields preserved for forward compatibility.
264    /// 中文:为前向兼容保留的额外字段。
265    #[serde(flatten)]
266    pub extra: BTreeMap<String, Value>,
267}