linger_openai_sdk/
moderations.rs1use crate::error::LingerError;
2use crate::RequestId;
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use std::collections::BTreeMap;
6
7#[derive(Clone, Debug, Serialize, PartialEq)]
10#[non_exhaustive]
11pub struct CreateModerationRequest {
12 #[serde(skip_serializing_if = "Option::is_none")]
15 pub model: Option<String>,
16 pub input: ModerationInput,
19 #[serde(flatten)]
22 pub extra: BTreeMap<String, Value>,
23}
24
25impl CreateModerationRequest {
26 pub fn builder() -> CreateModerationRequestBuilder {
29 CreateModerationRequestBuilder::default()
30 }
31}
32
33#[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 pub fn model(mut self, model: impl Into<String>) -> Self {
47 self.model = Some(model.into());
48 self
49 }
50
51 pub fn input(mut self, input: impl Into<ModerationInput>) -> Self {
54 self.input = Some(input.into());
55 self
56 }
57
58 pub fn extra(mut self, name: impl Into<String>, value: Value) -> Self {
61 self.extra.insert(name.into(), value);
62 self
63 }
64
65 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#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
92#[serde(untagged)]
93#[non_exhaustive]
94pub enum ModerationInput {
95 Text(String),
98 Texts(Vec<String>),
101 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#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
151#[serde(tag = "type")]
152#[non_exhaustive]
153pub enum ModerationInputItem {
154 #[serde(rename = "text")]
157 Text {
158 text: String,
161 },
162 #[serde(rename = "image_url")]
165 ImageUrl {
166 image_url: ModerationImageUrl,
169 },
170}
171
172impl ModerationInputItem {
173 pub fn text(text: impl Into<String>) -> Self {
176 Self::Text { text: text.into() }
177 }
178
179 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#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
198#[non_exhaustive]
199pub struct ModerationImageUrl {
200 pub url: String,
203}
204
205#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
208#[non_exhaustive]
209pub struct ModerationCreateResponse {
210 pub id: String,
213 pub model: String,
216 #[serde(default)]
219 pub results: Vec<ModerationResult>,
220 #[serde(skip)]
223 request_id: Option<RequestId>,
224 #[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 pub fn request_id(&self) -> Option<&RequestId> {
239 self.request_id.as_ref()
240 }
241}
242
243#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
246#[non_exhaustive]
247pub struct ModerationResult {
248 pub flagged: bool,
251 #[serde(default)]
254 pub categories: BTreeMap<String, bool>,
255 #[serde(default)]
258 pub category_scores: BTreeMap<String, f64>,
259 #[serde(default)]
262 pub category_applied_input_types: BTreeMap<String, Vec<String>>,
263 #[serde(flatten)]
266 pub extra: BTreeMap<String, Value>,
267}