content-moderation-sdk 0.0.1 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +275 -0
- package/content-moderation-sdk.jpg +0 -0
- package/dist/azure.d.ts +38 -0
- package/dist/azure.d.ts.map +1 -0
- package/dist/azure.js +114 -0
- package/dist/azure.js.map +1 -0
- package/dist/core.d.ts +3 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +313 -0
- package/dist/core.js.map +1 -0
- package/dist/errors.d.ts +41 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +77 -0
- package/dist/errors.js.map +1 -0
- package/dist/http.d.ts +9 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +87 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/mistral.d.ts +23 -0
- package/dist/mistral.d.ts.map +1 -0
- package/dist/mistral.js +81 -0
- package/dist/mistral.js.map +1 -0
- package/dist/openai.d.ts +28 -0
- package/dist/openai.d.ts.map +1 -0
- package/dist/openai.js +105 -0
- package/dist/openai.js.map +1 -0
- package/dist/testing.d.ts +18 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +44 -0
- package/dist/testing.js.map +1 -0
- package/dist/types.d.ts +164 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +26 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +229 -0
- package/dist/utils.js.map +1 -0
- package/package.json +76 -1
- package/src/azure.ts +191 -0
- package/src/core.ts +440 -0
- package/src/errors.ts +109 -0
- package/src/http.ts +94 -0
- package/src/index.ts +43 -0
- package/src/mistral.ts +124 -0
- package/src/openai.ts +162 -0
- package/src/testing.ts +85 -0
- package/src/types.ts +241 -0
- package/src/utils.ts +281 -0
package/dist/utils.js
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { ModerationAbortError, ModerationAdapterError, ModerationSdkError, ModerationValidationError, } from "./errors.js";
|
|
2
|
+
const MESSAGE_ROLES = new Set(["system", "user", "assistant", "tool"]);
|
|
3
|
+
const BASE64_PATTERN = /^(?:[A-Za-z\d+/]{4})*(?:[A-Za-z\d+/]{2}==|[A-Za-z\d+/]{3}=)?$/;
|
|
4
|
+
export function assertModerationInput(input) {
|
|
5
|
+
if (!input || typeof input !== "object") {
|
|
6
|
+
throw new ModerationValidationError("Moderation input must be an object.");
|
|
7
|
+
}
|
|
8
|
+
if (input.type === "text") {
|
|
9
|
+
if (typeof input.text !== "string" || input.text.trim().length === 0) {
|
|
10
|
+
throw new ModerationValidationError("Text moderation requires non-empty text.");
|
|
11
|
+
}
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (input.type === "image") {
|
|
15
|
+
assertImageSource(input.source);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (input.type === "conversation") {
|
|
19
|
+
if (!Array.isArray(input.messages) || input.messages.length === 0) {
|
|
20
|
+
throw new ModerationValidationError("Conversation moderation requires at least one message.");
|
|
21
|
+
}
|
|
22
|
+
for (const [index, message] of input.messages.entries()) {
|
|
23
|
+
if (!message || typeof message !== "object" || !MESSAGE_ROLES.has(message.role)) {
|
|
24
|
+
throw new ModerationValidationError(`Conversation message ${index} has an unsupported role.`);
|
|
25
|
+
}
|
|
26
|
+
if (typeof message.content !== "string" || message.content.trim().length === 0) {
|
|
27
|
+
throw new ModerationValidationError(`Conversation message ${index} requires non-empty string content.`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
throw new ModerationValidationError("Unsupported moderation input type.");
|
|
33
|
+
}
|
|
34
|
+
export function assertImageSource(source) {
|
|
35
|
+
if (!source || typeof source !== "object") {
|
|
36
|
+
throw new ModerationValidationError("Image moderation requires an image source.");
|
|
37
|
+
}
|
|
38
|
+
if (source.type === "url") {
|
|
39
|
+
let parsed;
|
|
40
|
+
try {
|
|
41
|
+
parsed = new URL(source.url);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
throw new ModerationValidationError("Image URL must be a valid HTTP(S) URL.");
|
|
45
|
+
}
|
|
46
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
47
|
+
throw new ModerationValidationError("Image URL must use HTTP or HTTPS.");
|
|
48
|
+
}
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (source.type === "base64") {
|
|
52
|
+
assertMediaType(source.mediaType);
|
|
53
|
+
if (typeof source.data !== "string") {
|
|
54
|
+
throw new ModerationValidationError("Image base64 data must be a string.");
|
|
55
|
+
}
|
|
56
|
+
const normalized = normalizeBase64(source.data);
|
|
57
|
+
if (normalized.length === 0 || !BASE64_PATTERN.test(normalized)) {
|
|
58
|
+
throw new ModerationValidationError("Image base64 data is invalid.");
|
|
59
|
+
}
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (source.type === "bytes") {
|
|
63
|
+
assertMediaType(source.mediaType);
|
|
64
|
+
if (!isBinaryData(source.data) || binaryByteLength(source.data) === 0) {
|
|
65
|
+
throw new ModerationValidationError("Image bytes must be a non-empty Uint8Array, ArrayBuffer, or Blob.");
|
|
66
|
+
}
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
throw new ModerationValidationError("Unsupported image source type.");
|
|
70
|
+
}
|
|
71
|
+
function assertMediaType(mediaType) {
|
|
72
|
+
if (typeof mediaType !== "string" || !/^image\/[A-Za-z\d.+-]+$/i.test(mediaType)) {
|
|
73
|
+
throw new ModerationValidationError('Image mediaType must look like "image/png".');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function isBinaryData(value) {
|
|
77
|
+
return value instanceof Uint8Array || value instanceof ArrayBuffer || value instanceof Blob;
|
|
78
|
+
}
|
|
79
|
+
export function flattenConversation(input) {
|
|
80
|
+
return JSON.stringify(input.messages);
|
|
81
|
+
}
|
|
82
|
+
export function normalizeHttpEndpoint(value, label) {
|
|
83
|
+
let endpoint;
|
|
84
|
+
try {
|
|
85
|
+
endpoint = new URL(value);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
throw new ModerationValidationError(`${label} must be a valid HTTP(S) URL.`);
|
|
89
|
+
}
|
|
90
|
+
if (endpoint.protocol !== "http:" && endpoint.protocol !== "https:") {
|
|
91
|
+
throw new ModerationValidationError(`${label} must use HTTP or HTTPS.`);
|
|
92
|
+
}
|
|
93
|
+
return endpoint.toString().replace(/\/+$/, "");
|
|
94
|
+
}
|
|
95
|
+
export function normalizeBase64(value) {
|
|
96
|
+
return value.replace(/\s+/g, "");
|
|
97
|
+
}
|
|
98
|
+
export function base64ByteLength(value) {
|
|
99
|
+
const normalized = normalizeBase64(value);
|
|
100
|
+
if (normalized.length === 0)
|
|
101
|
+
return 0;
|
|
102
|
+
const padding = normalized.endsWith("==") ? 2 : normalized.endsWith("=") ? 1 : 0;
|
|
103
|
+
return (normalized.length * 3) / 4 - padding;
|
|
104
|
+
}
|
|
105
|
+
export function binaryByteLength(value) {
|
|
106
|
+
if (value instanceof Blob)
|
|
107
|
+
return value.size;
|
|
108
|
+
return value.byteLength;
|
|
109
|
+
}
|
|
110
|
+
export function imageSourceByteLength(source) {
|
|
111
|
+
if (source.type === "url")
|
|
112
|
+
return undefined;
|
|
113
|
+
if (source.type === "base64")
|
|
114
|
+
return base64ByteLength(source.data);
|
|
115
|
+
return binaryByteLength(source.data);
|
|
116
|
+
}
|
|
117
|
+
export async function imageSourceToBase64(source) {
|
|
118
|
+
if (source.type === "base64")
|
|
119
|
+
return normalizeBase64(source.data);
|
|
120
|
+
const bytes = source.data instanceof Blob
|
|
121
|
+
? new Uint8Array(await source.data.arrayBuffer())
|
|
122
|
+
: source.data instanceof ArrayBuffer
|
|
123
|
+
? new Uint8Array(source.data)
|
|
124
|
+
: source.data;
|
|
125
|
+
return bytesToBase64(bytes);
|
|
126
|
+
}
|
|
127
|
+
function bytesToBase64(bytes) {
|
|
128
|
+
if (typeof Buffer !== "undefined")
|
|
129
|
+
return Buffer.from(bytes).toString("base64");
|
|
130
|
+
let binary = "";
|
|
131
|
+
for (const byte of bytes)
|
|
132
|
+
binary += String.fromCharCode(byte);
|
|
133
|
+
return btoa(binary);
|
|
134
|
+
}
|
|
135
|
+
export async function imageSourceToDataUrl(source) {
|
|
136
|
+
return `data:${source.mediaType};base64,${await imageSourceToBase64(source)}`;
|
|
137
|
+
}
|
|
138
|
+
export function normalizeAdapterResult(adapter, value) {
|
|
139
|
+
if (!value || typeof value !== "object") {
|
|
140
|
+
throw new ModerationAdapterError("Adapter returned an invalid moderation result.", {
|
|
141
|
+
adapter,
|
|
142
|
+
code: "invalid_response",
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
if (typeof value.flagged !== "boolean") {
|
|
146
|
+
throw new ModerationAdapterError('Adapter result requires a boolean "flagged" field.', {
|
|
147
|
+
adapter,
|
|
148
|
+
code: "invalid_response",
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
if (!value.categories || typeof value.categories !== "object") {
|
|
152
|
+
throw new ModerationAdapterError('Adapter result requires a "categories" object.', {
|
|
153
|
+
adapter,
|
|
154
|
+
code: "invalid_response",
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
return { ...value, adapter };
|
|
158
|
+
}
|
|
159
|
+
export function toAdapterError(adapter, error) {
|
|
160
|
+
if (error instanceof ModerationAdapterError) {
|
|
161
|
+
if (error.adapter === adapter)
|
|
162
|
+
return error;
|
|
163
|
+
return new ModerationAdapterError(error.message, {
|
|
164
|
+
adapter,
|
|
165
|
+
code: error.code,
|
|
166
|
+
status: error.status,
|
|
167
|
+
retryable: error.retryable,
|
|
168
|
+
details: error.details,
|
|
169
|
+
cause: error,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
if (error instanceof ModerationSdkError) {
|
|
173
|
+
return new ModerationAdapterError(error.message, {
|
|
174
|
+
adapter,
|
|
175
|
+
code: error.code,
|
|
176
|
+
status: error.status,
|
|
177
|
+
retryable: error.retryable,
|
|
178
|
+
details: error.details,
|
|
179
|
+
cause: error,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
return new ModerationAdapterError(error instanceof Error ? error.message : "Moderation adapter failed.", { adapter, cause: error });
|
|
183
|
+
}
|
|
184
|
+
export function normalizeSdkError(error) {
|
|
185
|
+
if (error instanceof ModerationSdkError)
|
|
186
|
+
return error;
|
|
187
|
+
return new ModerationSdkError(error instanceof Error ? error.message : "Moderation failed.", {
|
|
188
|
+
code: "internal_error",
|
|
189
|
+
cause: error,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
export function throwIfAborted(signal) {
|
|
193
|
+
if (signal?.aborted)
|
|
194
|
+
throw new ModerationAbortError(signal.reason);
|
|
195
|
+
}
|
|
196
|
+
export function isAbortLike(error) {
|
|
197
|
+
return (error instanceof ModerationAbortError ||
|
|
198
|
+
(error instanceof DOMException && error.name === "AbortError") ||
|
|
199
|
+
(error instanceof Error && error.name === "AbortError"));
|
|
200
|
+
}
|
|
201
|
+
export async function raceWithAbort(promise, signal) {
|
|
202
|
+
throwIfAborted(signal);
|
|
203
|
+
if (!signal)
|
|
204
|
+
return promise;
|
|
205
|
+
return new Promise((resolve, reject) => {
|
|
206
|
+
const abort = () => reject(new ModerationAbortError(signal.reason));
|
|
207
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
208
|
+
promise.then(resolve, reject).finally(() => signal.removeEventListener("abort", abort));
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
export function sleepWithAbort(ms, signal) {
|
|
212
|
+
throwIfAborted(signal);
|
|
213
|
+
if (ms <= 0)
|
|
214
|
+
return Promise.resolve();
|
|
215
|
+
return new Promise((resolve, reject) => {
|
|
216
|
+
const timer = setTimeout(done, ms);
|
|
217
|
+
const abort = () => {
|
|
218
|
+
clearTimeout(timer);
|
|
219
|
+
signal?.removeEventListener("abort", abort);
|
|
220
|
+
reject(new ModerationAbortError(signal?.reason));
|
|
221
|
+
};
|
|
222
|
+
function done() {
|
|
223
|
+
signal?.removeEventListener("abort", abort);
|
|
224
|
+
resolve();
|
|
225
|
+
}
|
|
226
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,4 +1,79 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "content-moderation-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "A provider-agnostic TypeScript SDK for text, image, and conversation moderation.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"azure",
|
|
7
|
+
"content-moderation",
|
|
8
|
+
"mistral",
|
|
9
|
+
"moderation",
|
|
10
|
+
"openai",
|
|
11
|
+
"safety",
|
|
12
|
+
"typescript"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://github.com/sophisticatedalterego/content-moderation-sdk#readme",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/sophisticatedalterego/content-moderation-sdk/issues"
|
|
17
|
+
},
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/sophisticatedalterego/content-moderation-sdk.git"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"src",
|
|
26
|
+
"README.md",
|
|
27
|
+
"content-moderation-sdk.jpg",
|
|
28
|
+
"LICENSE"
|
|
29
|
+
],
|
|
30
|
+
"type": "module",
|
|
31
|
+
"sideEffects": false,
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"import": "./dist/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./openai": {
|
|
38
|
+
"types": "./dist/openai.d.ts",
|
|
39
|
+
"import": "./dist/openai.js"
|
|
40
|
+
},
|
|
41
|
+
"./azure": {
|
|
42
|
+
"types": "./dist/azure.d.ts",
|
|
43
|
+
"import": "./dist/azure.js"
|
|
44
|
+
},
|
|
45
|
+
"./mistral": {
|
|
46
|
+
"types": "./dist/mistral.d.ts",
|
|
47
|
+
"import": "./dist/mistral.js"
|
|
48
|
+
},
|
|
49
|
+
"./testing": {
|
|
50
|
+
"types": "./dist/testing.d.ts",
|
|
51
|
+
"import": "./dist/testing.js"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"access": "public"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "rm -rf dist && tsc -p tsconfig.json",
|
|
59
|
+
"check-types": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.type-tests.json",
|
|
60
|
+
"test": "bun test",
|
|
61
|
+
"lint": "oxlint src type-tests",
|
|
62
|
+
"format:check": "oxfmt --check .",
|
|
63
|
+
"check": "bun run lint && bun run format:check && bun run check-types && bun run test",
|
|
64
|
+
"pack:check": "bun run build && npm pack --dry-run --ignore-scripts",
|
|
65
|
+
"prepack": "bun run build"
|
|
66
|
+
},
|
|
67
|
+
"devDependencies": {
|
|
68
|
+
"@types/bun": "^1.3.4",
|
|
69
|
+
"@types/node": "^25.9.4",
|
|
70
|
+
"oxfmt": "^0.46.0",
|
|
71
|
+
"oxlint": "^1.72.0",
|
|
72
|
+
"typescript": "^6.0.3"
|
|
73
|
+
},
|
|
74
|
+
"engines": {
|
|
75
|
+
"bun": ">=1.1.0",
|
|
76
|
+
"node": ">=20.0.0"
|
|
77
|
+
},
|
|
78
|
+
"packageManager": "bun@1.4.0"
|
|
4
79
|
}
|
package/src/azure.ts
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { ModerationAdapterError, ModerationValidationError } from "./errors.js";
|
|
2
|
+
import { requestJson } from "./http.js";
|
|
3
|
+
import type {
|
|
4
|
+
ModerationAdapter,
|
|
5
|
+
ModerationAdapterResult,
|
|
6
|
+
ModerationCategoryAssessment,
|
|
7
|
+
ModerationInput,
|
|
8
|
+
} from "./types.js";
|
|
9
|
+
import { imageSourceByteLength, imageSourceToBase64, normalizeHttpEndpoint } from "./utils.js";
|
|
10
|
+
|
|
11
|
+
const AZURE_MAX_TEXT_CODE_POINTS = 10_000;
|
|
12
|
+
const AZURE_MAX_IMAGE_BYTES = 4 * 1024 * 1024;
|
|
13
|
+
|
|
14
|
+
export type AzureModerationCategory = "Hate" | "SelfHarm" | "Sexual" | "Violence";
|
|
15
|
+
export type AzureSeverityThreshold = 2 | 4 | 6;
|
|
16
|
+
|
|
17
|
+
export type AzureCategoryAnalysis = {
|
|
18
|
+
category: AzureModerationCategory | (string & {});
|
|
19
|
+
severity: number;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type AzureBlocklistMatch = {
|
|
23
|
+
blocklistName: string;
|
|
24
|
+
blocklistItemId: string;
|
|
25
|
+
blocklistItemText: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type AzureTextModerationResponse = {
|
|
29
|
+
categoriesAnalysis: AzureCategoryAnalysis[];
|
|
30
|
+
blocklistsMatch?: AzureBlocklistMatch[];
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type AzureImageModerationResponse = {
|
|
34
|
+
categoriesAnalysis: AzureCategoryAnalysis[];
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type AzureModerationResponse = AzureTextModerationResponse | AzureImageModerationResponse;
|
|
38
|
+
|
|
39
|
+
export type AzureOptions = {
|
|
40
|
+
endpoint: string;
|
|
41
|
+
apiKey: string;
|
|
42
|
+
apiVersion?: string;
|
|
43
|
+
threshold?: AzureSeverityThreshold;
|
|
44
|
+
categories?: readonly AzureModerationCategory[];
|
|
45
|
+
blocklistNames?: readonly string[];
|
|
46
|
+
haltOnBlocklistHit?: boolean;
|
|
47
|
+
headers?: Readonly<Record<string, string>>;
|
|
48
|
+
fetch?: typeof fetch;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type AzureAdapter = ModerationAdapter<
|
|
52
|
+
"azure",
|
|
53
|
+
{ endpoint: string; apiVersion: string; threshold: AzureSeverityThreshold },
|
|
54
|
+
AzureModerationResponse
|
|
55
|
+
>;
|
|
56
|
+
|
|
57
|
+
export function azure(options: AzureOptions): AzureAdapter {
|
|
58
|
+
const endpoint = normalizeHttpEndpoint(options.endpoint, "Azure endpoint");
|
|
59
|
+
if (typeof options.apiKey !== "string" || options.apiKey.trim().length === 0) {
|
|
60
|
+
throw new ModerationValidationError("Azure apiKey is required.");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const apiVersion = options.apiVersion ?? "2024-09-01";
|
|
64
|
+
const threshold = options.threshold ?? 4;
|
|
65
|
+
if (typeof apiVersion !== "string" || apiVersion.trim().length === 0) {
|
|
66
|
+
throw new ModerationValidationError("Azure apiVersion must be a non-empty string.");
|
|
67
|
+
}
|
|
68
|
+
if (threshold !== 2 && threshold !== 4 && threshold !== 6) {
|
|
69
|
+
throw new ModerationValidationError("Azure threshold must be 2, 4, or 6.");
|
|
70
|
+
}
|
|
71
|
+
const fetcher = options.fetch ?? fetch;
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
name: "azure",
|
|
75
|
+
capabilities: {
|
|
76
|
+
text: true,
|
|
77
|
+
image: true,
|
|
78
|
+
conversation: "flattened",
|
|
79
|
+
},
|
|
80
|
+
raw: { endpoint, apiVersion, threshold },
|
|
81
|
+
validate(input) {
|
|
82
|
+
if (input.type === "text" && [...input.text].length > AZURE_MAX_TEXT_CODE_POINTS) {
|
|
83
|
+
throw new ModerationValidationError(
|
|
84
|
+
`Azure text input cannot exceed ${AZURE_MAX_TEXT_CODE_POINTS} Unicode code points.`,
|
|
85
|
+
{ adapter: "azure", limit: AZURE_MAX_TEXT_CODE_POINTS },
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
if (input.type === "image") {
|
|
89
|
+
const bytes = imageSourceByteLength(input.source);
|
|
90
|
+
if (bytes !== undefined && bytes > AZURE_MAX_IMAGE_BYTES) {
|
|
91
|
+
throw new ModerationValidationError("Azure image input cannot exceed 4 MB.", {
|
|
92
|
+
adapter: "azure",
|
|
93
|
+
limit: AZURE_MAX_IMAGE_BYTES,
|
|
94
|
+
actual: bytes,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
async moderate(input, context) {
|
|
100
|
+
if (input.type === "conversation") {
|
|
101
|
+
throw new ModerationValidationError(
|
|
102
|
+
"Azure receives conversations through the client's flattened text path.",
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const isText = input.type === "text";
|
|
107
|
+
const body = await requestJson<AzureModerationResponse>({
|
|
108
|
+
adapter: "azure",
|
|
109
|
+
provider: "Azure AI Content Safety",
|
|
110
|
+
fetcher,
|
|
111
|
+
url: `${endpoint}/contentsafety/${isText ? "text" : "image"}:analyze?api-version=${encodeURIComponent(apiVersion)}`,
|
|
112
|
+
init: {
|
|
113
|
+
method: "POST",
|
|
114
|
+
signal: context.signal,
|
|
115
|
+
headers: {
|
|
116
|
+
"Ocp-Apim-Subscription-Key": options.apiKey,
|
|
117
|
+
"Content-Type": "application/json",
|
|
118
|
+
...options.headers,
|
|
119
|
+
},
|
|
120
|
+
body: JSON.stringify(
|
|
121
|
+
isText ? textPayload(input, options) : await imagePayload(input, options),
|
|
122
|
+
),
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if (!Array.isArray(body.categoriesAnalysis)) {
|
|
127
|
+
throw new ModerationAdapterError("Azure returned an invalid moderation response.", {
|
|
128
|
+
adapter: "azure",
|
|
129
|
+
code: "invalid_response",
|
|
130
|
+
details: body,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const categories = toCategoryAssessments(body.categoriesAnalysis, threshold);
|
|
135
|
+
const blocklistsMatch =
|
|
136
|
+
"blocklistsMatch" in body && Array.isArray(body.blocklistsMatch)
|
|
137
|
+
? body.blocklistsMatch
|
|
138
|
+
: [];
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
adapter: "azure",
|
|
142
|
+
flagged:
|
|
143
|
+
blocklistsMatch.length > 0 ||
|
|
144
|
+
Object.values(categories).some((category) => category.flagged === true),
|
|
145
|
+
categories,
|
|
146
|
+
raw: body,
|
|
147
|
+
} satisfies ModerationAdapterResult<"azure", AzureModerationResponse>;
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function textPayload(input: Extract<ModerationInput, { type: "text" }>, options: AzureOptions) {
|
|
153
|
+
return {
|
|
154
|
+
text: input.text,
|
|
155
|
+
outputType: "FourSeverityLevels",
|
|
156
|
+
...(options.categories ? { categories: options.categories } : {}),
|
|
157
|
+
...(options.blocklistNames ? { blocklistNames: options.blocklistNames } : {}),
|
|
158
|
+
...(options.haltOnBlocklistHit !== undefined
|
|
159
|
+
? { haltOnBlocklistHit: options.haltOnBlocklistHit }
|
|
160
|
+
: {}),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function imagePayload(
|
|
165
|
+
input: Extract<ModerationInput, { type: "image" }>,
|
|
166
|
+
options: AzureOptions,
|
|
167
|
+
) {
|
|
168
|
+
return {
|
|
169
|
+
image:
|
|
170
|
+
input.source.type === "url"
|
|
171
|
+
? { blobUrl: input.source.url }
|
|
172
|
+
: { content: await imageSourceToBase64(input.source) },
|
|
173
|
+
outputType: "FourSeverityLevels",
|
|
174
|
+
...(options.categories ? { categories: options.categories } : {}),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function toCategoryAssessments(
|
|
179
|
+
analyses: readonly AzureCategoryAnalysis[],
|
|
180
|
+
threshold: AzureSeverityThreshold,
|
|
181
|
+
): Readonly<Record<string, ModerationCategoryAssessment>> {
|
|
182
|
+
return Object.fromEntries(
|
|
183
|
+
analyses.map(({ category, severity }) => [
|
|
184
|
+
category,
|
|
185
|
+
{
|
|
186
|
+
flagged: severity >= threshold,
|
|
187
|
+
severity: { value: severity, max: 6 },
|
|
188
|
+
},
|
|
189
|
+
]),
|
|
190
|
+
);
|
|
191
|
+
}
|