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