Skip to main content

otari/types/
moderation.rs

1//! Moderation types.
2//!
3//! These mirror the OpenAI `/v1/moderations` request/response shape and
4//! are used by the gateway provider's inherent `moderation` method.
5
6use std::collections::HashMap;
7
8use serde::{Deserialize, Serialize};
9
10/// Input to a moderation request.
11///
12/// Serialized as untagged JSON so the body matches the OpenAI-compatible
13/// shape: either a single string, an array of strings, or an array of
14/// multimodal content parts.
15#[derive(Debug, Clone, Serialize, Deserialize)]
16#[serde(untagged)]
17pub enum ModerationInput {
18    /// A single text string.
19    Text(String),
20    /// A batch of text strings.
21    Batch(Vec<String>),
22    /// Multimodal content parts (OpenAI `omni-moderation-*` only).
23    Parts(Vec<ModerationContentPart>),
24}
25
26impl Default for ModerationInput {
27    fn default() -> Self {
28        Self::Text(String::new())
29    }
30}
31
32/// A single multimodal content part for moderation input.
33#[derive(Debug, Clone, Serialize, Deserialize)]
34#[serde(tag = "type", rename_all = "snake_case")]
35pub enum ModerationContentPart {
36    /// A text part.
37    Text {
38        /// The text content.
39        text: String,
40    },
41    /// An image referenced by URL.
42    ImageUrl {
43        /// The image URL descriptor.
44        image_url: ModerationImageUrl,
45    },
46}
47
48/// An image URL reference for a moderation content part.
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct ModerationImageUrl {
51    /// The URL of the image (HTTP(S) or `data:` URI).
52    pub url: String,
53}
54
55/// Parameters for a moderation request.
56///
57/// `include_raw` is intentionally not serialized into the HTTP body; it
58/// is emitted as the `?include_raw=true` query string by the gateway
59/// client when `true`.
60#[derive(Debug, Clone, Serialize, Deserialize, Default)]
61pub struct ModerationParams {
62    /// Namespaced model identifier (e.g. `openai:omni-moderation-latest`).
63    pub model: String,
64
65    /// The input to moderate.
66    pub input: ModerationInput,
67
68    /// Optional end-user identifier for abuse monitoring.
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub user: Option<String>,
71
72    /// When `true`, the gateway should echo the upstream raw response
73    /// alongside its normalized output. Serialized as a query parameter
74    /// (`?include_raw=true`), not in the body.
75    #[serde(skip_serializing)]
76    pub include_raw: bool,
77}
78
79impl ModerationParams {
80    /// Create a new moderation request for the given model and input.
81    pub fn new(model: impl Into<String>, input: ModerationInput) -> Self {
82        Self {
83            model: model.into(),
84            input,
85            user: None,
86            include_raw: false,
87        }
88    }
89
90    /// Attach an end-user identifier.
91    #[must_use]
92    pub fn with_user(mut self, user: impl Into<String>) -> Self {
93        self.user = Some(user.into());
94        self
95    }
96
97    /// Ask the gateway to include the upstream provider's raw response.
98    #[must_use]
99    pub fn with_include_raw(mut self, include_raw: bool) -> Self {
100        self.include_raw = include_raw;
101        self
102    }
103}
104
105/// A single moderation result.
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct ModerationResult {
108    /// Whether the input was flagged by any category.
109    pub flagged: bool,
110
111    /// Map of category name to whether it tripped.
112    #[serde(default)]
113    pub categories: HashMap<String, bool>,
114
115    /// Map of category name to model confidence score.
116    #[serde(default)]
117    pub category_scores: HashMap<String, f64>,
118
119    /// Which input modalities contributed to each category (OpenAI
120    /// omni-moderation only).
121    #[serde(default, skip_serializing_if = "Option::is_none")]
122    pub category_applied_input_types: Option<HashMap<String, Vec<String>>>,
123
124    /// The upstream provider's raw payload when `include_raw=true` was
125    /// requested.
126    #[serde(default, skip_serializing_if = "Option::is_none")]
127    pub provider_raw: Option<serde_json::Value>,
128}
129
130/// Top-level moderation response (OpenAI-compatible shape).
131#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct ModerationResponse {
133    /// Opaque response identifier.
134    pub id: String,
135    /// Echoed model identifier.
136    pub model: String,
137    /// One result per input item.
138    pub results: Vec<ModerationResult>,
139}