Compare commits
4 Commits
Fev
...
refactor/o
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14c974d07f | ||
|
|
e8dffd35f3 | ||
|
|
30db34e737 | ||
|
|
d3f549ab7b |
1
api/cache/getLogStores.js
vendored
1
api/cache/getLogStores.js
vendored
@@ -79,6 +79,7 @@ const namespaces = {
|
|||||||
[ViolationTypes.ILLEGAL_MODEL_REQUEST]: createViolationInstance(
|
[ViolationTypes.ILLEGAL_MODEL_REQUEST]: createViolationInstance(
|
||||||
ViolationTypes.ILLEGAL_MODEL_REQUEST,
|
ViolationTypes.ILLEGAL_MODEL_REQUEST,
|
||||||
),
|
),
|
||||||
|
[ViolationTypes.MODERATION]: createViolationInstance(ViolationTypes.MODERATION),
|
||||||
logins: createViolationInstance('logins'),
|
logins: createViolationInstance('logins'),
|
||||||
[CacheKeys.ABORT_KEYS]: abortKeys,
|
[CacheKeys.ABORT_KEYS]: abortKeys,
|
||||||
[CacheKeys.TOKEN_CONFIG]: tokenConfig,
|
[CacheKeys.TOKEN_CONFIG]: tokenConfig,
|
||||||
|
|||||||
@@ -1,41 +1,148 @@
|
|||||||
const axios = require('axios');
|
const OpenAI = require('openai');
|
||||||
const { ErrorTypes } = require('librechat-data-provider');
|
const { ErrorTypes, ViolationTypes } = require('librechat-data-provider');
|
||||||
|
const { getCustomConfig } = require('~/server/services/Config');
|
||||||
|
const { isEnabled } = require('~/server/utils');
|
||||||
const denyRequest = require('./denyRequest');
|
const denyRequest = require('./denyRequest');
|
||||||
|
const { logViolation } = require('~/cache');
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
|
||||||
|
const DEFAULT_ACTIONS = Object.freeze({
|
||||||
|
violation: 2,
|
||||||
|
blockMessage: true,
|
||||||
|
log: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pre-compile threshold map for faster lookups
|
||||||
|
const DEFAULT_THRESHOLDS = new Map();
|
||||||
|
|
||||||
|
function formatViolation(violation) {
|
||||||
|
return {
|
||||||
|
category: violation.category,
|
||||||
|
score: Math.round(violation.score * 100) / 100,
|
||||||
|
threshold: violation.threshold,
|
||||||
|
severity: getSeverityLevel(violation.score),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSeverityLevel(score) {
|
||||||
|
if (score >= 0.9) {
|
||||||
|
return 'HIGH';
|
||||||
|
}
|
||||||
|
if (score >= 0.7) {
|
||||||
|
return 'MEDIUM';
|
||||||
|
}
|
||||||
|
return 'LOW';
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatViolationsLog(violations, userId = 'unknown') {
|
||||||
|
const violationsStr = violations
|
||||||
|
.map((v) => `${v.category}:${v.score}>${v.threshold}`)
|
||||||
|
.join(' | ');
|
||||||
|
|
||||||
|
return `userId=${userId} violations=[${violationsStr}]`;
|
||||||
|
}
|
||||||
|
|
||||||
async function moderateText(req, res, next) {
|
async function moderateText(req, res, next) {
|
||||||
if (process.env.OPENAI_MODERATION === 'true') {
|
if (!isEnabled(process.env.OPENAI_MODERATION)) {
|
||||||
try {
|
return next();
|
||||||
const { text } = req.body;
|
}
|
||||||
|
|
||||||
const response = await axios.post(
|
const moderationKey = process.env.OPENAI_MODERATION_API_KEY;
|
||||||
process.env.OPENAI_MODERATION_REVERSE_PROXY || 'https://api.openai.com/v1/moderations',
|
if (!moderationKey) {
|
||||||
{
|
logger.error('Missing OpenAI moderation API key');
|
||||||
input: text,
|
return denyRequest(req, res, { message: 'Moderation configuration error' });
|
||||||
},
|
}
|
||||||
{
|
|
||||||
headers: {
|
const { text } = req.body;
|
||||||
'Content-Type': 'application/json',
|
if (!text?.length || typeof text !== 'string') {
|
||||||
Authorization: `Bearer ${process.env.OPENAI_MODERATION_API_KEY}`,
|
return denyRequest(req, res, { type: ErrorTypes.VALIDATION, message: 'Invalid text input' });
|
||||||
},
|
}
|
||||||
},
|
|
||||||
|
try {
|
||||||
|
const customConfig = await getCustomConfig();
|
||||||
|
|
||||||
|
if (!moderateText.openai) {
|
||||||
|
moderateText.openai = new OpenAI({ apiKey: moderationKey });
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await moderateText.openai.moderations.create({
|
||||||
|
model: 'omni-moderation-latest',
|
||||||
|
input: text,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!Array.isArray(response.results)) {
|
||||||
|
throw new Error('Invalid moderation API response format');
|
||||||
|
}
|
||||||
|
|
||||||
|
const violations = checkViolations(response.results, customConfig).map(formatViolation);
|
||||||
|
|
||||||
|
if (violations.length === 0) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
const actions = Object.assign({}, DEFAULT_ACTIONS, customConfig?.moderation?.actions);
|
||||||
|
|
||||||
|
if (actions.log) {
|
||||||
|
const userId = req.user?.id || 'anonymous';
|
||||||
|
logger.warn(
|
||||||
|
'[moderateText] Content moderation violations: ' + formatViolationsLog(violations, userId),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const results = response.data.results;
|
if (!actions.blockMessage) {
|
||||||
const flagged = results.some((result) => result.flagged);
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
if (flagged) {
|
if (actions.violation > 0) {
|
||||||
const type = ErrorTypes.MODERATION;
|
logViolation(req, res, ViolationTypes.MODERATION, { violations }, actions.violation);
|
||||||
const errorMessage = { type };
|
}
|
||||||
return await denyRequest(req, res, errorMessage);
|
|
||||||
|
return denyRequest(req, res, {
|
||||||
|
type: ErrorTypes.MODERATION,
|
||||||
|
message: `Content violates moderation policies: ${violations
|
||||||
|
.map((v) => v.category)
|
||||||
|
.join(', ')}`,
|
||||||
|
violations: violations,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
const errorDetails =
|
||||||
|
process.env.NODE_ENV === 'production'
|
||||||
|
? { message: error.message }
|
||||||
|
: {
|
||||||
|
error: error.message,
|
||||||
|
stack: error.stack,
|
||||||
|
status: error.response?.status,
|
||||||
|
};
|
||||||
|
|
||||||
|
logger.error('Moderation error:', errorDetails);
|
||||||
|
|
||||||
|
return denyRequest(req, res, {
|
||||||
|
type: ErrorTypes.MODERATION,
|
||||||
|
message: 'Content moderation check failed',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkViolations(results, customConfig) {
|
||||||
|
const violations = [];
|
||||||
|
const categories = customConfig?.moderation?.categories || {};
|
||||||
|
|
||||||
|
for (const result of results) {
|
||||||
|
for (const [category, score] of Object.entries(result.category_scores)) {
|
||||||
|
const categoryConfig = categories[category];
|
||||||
|
|
||||||
|
if (categoryConfig?.enabled === false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const threshold = categoryConfig?.threshold || DEFAULT_THRESHOLDS.get(category) || 0.7;
|
||||||
|
|
||||||
|
if (score >= threshold) {
|
||||||
|
violations.push({ category, score, threshold });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error in moderateText:', error);
|
|
||||||
const errorMessage = 'error in moderation check';
|
|
||||||
return await denyRequest(req, res, errorMessage);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
next();
|
return violations;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = moderateText;
|
module.exports = moderateText;
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ const { logger } = require('~/config');
|
|||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.use(moderateText);
|
|
||||||
router.post('/abort', handleAbort());
|
router.post('/abort', handleAbort());
|
||||||
|
router.use(moderateText);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/',
|
'/',
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ const {
|
|||||||
} = require('~/server/middleware');
|
} = require('~/server/middleware');
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
router.use(moderateText);
|
|
||||||
router.post('/abort', handleAbort());
|
router.post('/abort', handleAbort());
|
||||||
|
router.use(moderateText);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/',
|
'/',
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ const { logger } = require('~/config');
|
|||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.use(moderateText);
|
|
||||||
router.post('/abort', handleAbort());
|
router.post('/abort', handleAbort());
|
||||||
|
router.use(moderateText);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/',
|
'/',
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ const {
|
|||||||
} = require('~/server/middleware');
|
} = require('~/server/middleware');
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
router.use(moderateText);
|
|
||||||
router.post('/abort', handleAbort());
|
router.post('/abort', handleAbort());
|
||||||
|
router.use(moderateText);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/',
|
'/',
|
||||||
|
|||||||
@@ -1,120 +1,140 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const axios = require('axios');
|
||||||
|
const yaml = require('js-yaml');
|
||||||
const { CacheKeys, configSchema, EImageOutputType } = require('librechat-data-provider');
|
const { CacheKeys, configSchema, EImageOutputType } = require('librechat-data-provider');
|
||||||
const getLogStores = require('~/cache/getLogStores');
|
const getLogStores = require('~/cache/getLogStores');
|
||||||
const loadYaml = require('~/utils/loadYaml');
|
const loadYaml = require('~/utils/loadYaml');
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
const axios = require('axios');
|
|
||||||
const yaml = require('js-yaml');
|
|
||||||
|
|
||||||
const projectRoot = path.resolve(__dirname, '..', '..', '..', '..');
|
const CONFIG = {
|
||||||
const defaultConfigPath = path.resolve(projectRoot, 'librechat.yaml');
|
PROJECT_ROOT: path.resolve(__dirname, '..', '..', '..', '..'),
|
||||||
|
CACHE_TTL: 1000 * 60 * 5, // 5 minutes
|
||||||
|
HTTP_TIMEOUT: 5000, // 5 seconds
|
||||||
|
MAX_RETRIES: 3,
|
||||||
|
};
|
||||||
|
|
||||||
let i = 0;
|
const defaultConfigPath = path.resolve(CONFIG.PROJECT_ROOT, 'librechat.yaml');
|
||||||
|
const CONFIG_URL_REGEX = /^https?:\/\//;
|
||||||
|
const IMAGE_OUTPUT_ERROR = `Please specify a correct \`imageOutputType\` value (case-sensitive).
|
||||||
|
Available options: ${Object.values(EImageOutputType).join(', ')}
|
||||||
|
See: https://www.librechat.ai/docs/configuration/librechat_yaml`;
|
||||||
|
|
||||||
/**
|
// Cache management
|
||||||
* Load custom configuration files and caches the object if the `cache` field at root is true.
|
class ConfigCache {
|
||||||
* Validation via parsing the config file with the config schema.
|
constructor() {
|
||||||
* @function loadCustomConfig
|
this.data = null;
|
||||||
* @returns {Promise<TCustomConfig | null>} A promise that resolves to null or the custom config object.
|
this.timestamp = null;
|
||||||
* */
|
|
||||||
async function loadCustomConfig() {
|
|
||||||
// Use CONFIG_PATH if set, otherwise fallback to defaultConfigPath
|
|
||||||
const configPath = process.env.CONFIG_PATH || defaultConfigPath;
|
|
||||||
|
|
||||||
let customConfig;
|
|
||||||
|
|
||||||
if (/^https?:\/\//.test(configPath)) {
|
|
||||||
try {
|
|
||||||
const response = await axios.get(configPath);
|
|
||||||
customConfig = response.data;
|
|
||||||
} catch (error) {
|
|
||||||
i === 0 && logger.error(`Failed to fetch the remote config file from ${configPath}`, error);
|
|
||||||
i === 0 && i++;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
customConfig = loadYaml(configPath);
|
|
||||||
if (!customConfig) {
|
|
||||||
i === 0 &&
|
|
||||||
logger.info(
|
|
||||||
'Custom config file missing or YAML format invalid.\n\nCheck out the latest config file guide for configurable options and features.\nhttps://www.librechat.ai/docs/configuration/librechat_yaml\n\n',
|
|
||||||
);
|
|
||||||
i === 0 && i++;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (customConfig.reason || customConfig.stack) {
|
|
||||||
i === 0 && logger.error('Config file YAML format is invalid:', customConfig);
|
|
||||||
i === 0 && i++;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof customConfig === 'string') {
|
set(data) {
|
||||||
try {
|
this.data = data;
|
||||||
customConfig = yaml.load(customConfig);
|
this.timestamp = Date.now();
|
||||||
} catch (parseError) {
|
|
||||||
i === 0 && logger.info(`Failed to parse the YAML config from ${configPath}`, parseError);
|
|
||||||
i === 0 && i++;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = configSchema.strict().safeParse(customConfig);
|
get() {
|
||||||
if (result?.error?.errors?.some((err) => err?.path && err.path?.includes('imageOutputType'))) {
|
if (!this.data || !this.timestamp) {
|
||||||
throw new Error(
|
return null;
|
||||||
`
|
}
|
||||||
Please specify a correct \`imageOutputType\` value (case-sensitive).
|
if (Date.now() - this.timestamp > CONFIG.CACHE_TTL) {
|
||||||
|
this.clear();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.data;
|
||||||
|
}
|
||||||
|
|
||||||
The available options are:
|
clear() {
|
||||||
- ${EImageOutputType.JPEG}
|
this.data = null;
|
||||||
- ${EImageOutputType.PNG}
|
this.timestamp = null;
|
||||||
- ${EImageOutputType.WEBP}
|
}
|
||||||
|
}
|
||||||
Refer to the latest config file guide for more information:
|
|
||||||
https://www.librechat.ai/docs/configuration/librechat_yaml`,
|
const configCache = new ConfigCache();
|
||||||
|
|
||||||
|
// Error handling
|
||||||
|
class ConfigError extends Error {
|
||||||
|
constructor(message, type) {
|
||||||
|
super(message);
|
||||||
|
this.name = 'ConfigError';
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validation
|
||||||
|
const validateConfig = (config, configPath) => {
|
||||||
|
const result = configSchema.strict().safeParse(config);
|
||||||
|
|
||||||
|
if (result?.error?.errors?.some((err) => err?.path?.includes('imageOutputType'))) {
|
||||||
|
throw new ConfigError(IMAGE_OUTPUT_ERROR, 'invalid_image_type');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
throw new ConfigError(
|
||||||
|
`Invalid config at ${configPath}:\n${JSON.stringify(result.error, null, 2)}`,
|
||||||
|
'validation_error',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!result.success) {
|
|
||||||
let errorMessage = `Invalid custom config file at ${configPath}:
|
|
||||||
${JSON.stringify(result.error, null, 2)}`;
|
|
||||||
|
|
||||||
if (i === 0) {
|
return result;
|
||||||
logger.error(errorMessage);
|
};
|
||||||
const speechError = result.error.errors.find(
|
|
||||||
(err) =>
|
|
||||||
err.code === 'unrecognized_keys' &&
|
|
||||||
(err.message?.includes('stt') || err.message?.includes('tts')),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (speechError) {
|
// HTTP config loading with retries
|
||||||
logger.warn(`
|
const fetchConfig = async (url, retries = CONFIG.MAX_RETRIES) => {
|
||||||
The Speech-to-text and Text-to-speech configuration format has recently changed.
|
try {
|
||||||
If you're getting this error, please refer to the latest documentation:
|
const { data } = await axios.get(url, { timeout: CONFIG.HTTP_TIMEOUT });
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
if (retries > 0) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
return fetchConfig(url, retries - 1);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
https://www.librechat.ai/docs/configuration/stt_tts`);
|
// Main function
|
||||||
}
|
async function loadCustomConfig() {
|
||||||
|
try {
|
||||||
i++;
|
const cachedConfig = configCache.get();
|
||||||
|
if (cachedConfig) {
|
||||||
|
return cachedConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const configPath = process.env.CONFIG_PATH || defaultConfigPath;
|
||||||
|
let customConfig;
|
||||||
|
|
||||||
|
if (CONFIG_URL_REGEX.test(configPath)) {
|
||||||
|
customConfig = await fetchConfig(configPath);
|
||||||
|
} else {
|
||||||
|
customConfig = loadYaml(configPath);
|
||||||
|
if (!customConfig) {
|
||||||
|
throw new ConfigError('Config file missing or invalid YAML format', 'invalid_yaml');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof customConfig === 'string') {
|
||||||
|
customConfig = yaml.load(customConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = validateConfig(customConfig, configPath);
|
||||||
|
|
||||||
|
if (customConfig.cache) {
|
||||||
|
const cache = getLogStores(CacheKeys.CONFIG_STORE);
|
||||||
|
await cache.set(CacheKeys.CUSTOM_CONFIG, customConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.data.modelSpecs) {
|
||||||
|
customConfig.modelSpecs = result.data.modelSpecs;
|
||||||
|
}
|
||||||
|
|
||||||
|
configCache.set(customConfig);
|
||||||
|
logger.info('Config loaded successfully');
|
||||||
|
logger.debug('Config details:', customConfig);
|
||||||
|
|
||||||
|
return customConfig;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Config loading failed: ${error.message}`);
|
||||||
return null;
|
return null;
|
||||||
} else {
|
|
||||||
logger.info('Custom config file loaded:');
|
|
||||||
logger.info(JSON.stringify(customConfig, null, 2));
|
|
||||||
logger.debug('Custom config:', customConfig);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (customConfig.cache) {
|
|
||||||
const cache = getLogStores(CacheKeys.CONFIG_STORE);
|
|
||||||
await cache.set(CacheKeys.CUSTOM_CONFIG, customConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.data.modelSpecs) {
|
|
||||||
customConfig.modelSpecs = result.data.modelSpecs;
|
|
||||||
}
|
|
||||||
|
|
||||||
return customConfig;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = loadCustomConfig;
|
module.exports = loadCustomConfig;
|
||||||
|
|||||||
@@ -419,6 +419,100 @@ export const rateLimitSchema = z.object({
|
|||||||
.optional(),
|
.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const moderationSchema = z
|
||||||
|
.object({
|
||||||
|
categories: z
|
||||||
|
.object({
|
||||||
|
sexual: z
|
||||||
|
.object({
|
||||||
|
enabled: z.boolean().default(true),
|
||||||
|
threshold: z.number().min(0).max(1).default(0.7),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
'sexual/minors': z
|
||||||
|
.object({
|
||||||
|
enabled: z.boolean().default(true),
|
||||||
|
threshold: z.number().min(0).max(1).default(0),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
harassment: z
|
||||||
|
.object({
|
||||||
|
enabled: z.boolean().default(true),
|
||||||
|
threshold: z.number().min(0).max(1).default(0.7),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
'harassment/threatening': z
|
||||||
|
.object({
|
||||||
|
enabled: z.boolean().default(true),
|
||||||
|
threshold: z.number().min(0).max(1).default(0.7),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
hate: z
|
||||||
|
.object({
|
||||||
|
enabled: z.boolean().default(true),
|
||||||
|
threshold: z.number().min(0).max(1).default(0.7),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
'hate/threatening': z
|
||||||
|
.object({
|
||||||
|
enabled: z.boolean().default(true),
|
||||||
|
threshold: z.number().min(0).max(1).default(0.7),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
illicit: z
|
||||||
|
.object({
|
||||||
|
enabled: z.boolean().default(true),
|
||||||
|
threshold: z.number().min(0).max(1).default(0.7),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
'illicit/violent': z
|
||||||
|
.object({
|
||||||
|
enabled: z.boolean().default(true),
|
||||||
|
threshold: z.number().min(0).max(1).default(0.7),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
'self-harm': z
|
||||||
|
.object({
|
||||||
|
enabled: z.boolean().default(true),
|
||||||
|
threshold: z.number().min(0).max(1).default(0.7),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
'self-harm/intent': z
|
||||||
|
.object({
|
||||||
|
enabled: z.boolean().default(true),
|
||||||
|
threshold: z.number().min(0).max(1).default(0.7),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
'self-harm/instructions': z
|
||||||
|
.object({
|
||||||
|
enabled: z.boolean().default(true),
|
||||||
|
threshold: z.number().min(0).max(1).default(0.7),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
violence: z
|
||||||
|
.object({
|
||||||
|
enabled: z.boolean().default(true),
|
||||||
|
threshold: z.number().min(0).max(1).default(0.7),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
'violence/graphic': z
|
||||||
|
.object({
|
||||||
|
enabled: z.boolean().default(true),
|
||||||
|
threshold: z.number().min(0).max(1).default(0.7),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
actions: z
|
||||||
|
.object({
|
||||||
|
violation: z.number().default(2),
|
||||||
|
blockMessage: z.boolean().default(true),
|
||||||
|
log: z.boolean().default(false),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
})
|
||||||
|
.optional();
|
||||||
|
|
||||||
export enum EImageOutputType {
|
export enum EImageOutputType {
|
||||||
PNG = 'png',
|
PNG = 'png',
|
||||||
WEBP = 'webp',
|
WEBP = 'webp',
|
||||||
@@ -471,6 +565,7 @@ export const configSchema = z.object({
|
|||||||
agents: true,
|
agents: true,
|
||||||
}),
|
}),
|
||||||
fileStrategy: fileSourceSchema.default(FileSources.local),
|
fileStrategy: fileSourceSchema.default(FileSources.local),
|
||||||
|
moderation: moderationSchema.optional(),
|
||||||
actions: z
|
actions: z
|
||||||
.object({
|
.object({
|
||||||
allowedDomains: z.array(z.string()).optional(),
|
allowedDomains: z.array(z.string()).optional(),
|
||||||
@@ -929,6 +1024,10 @@ export enum ViolationTypes {
|
|||||||
* Verify Conversation Access violation.
|
* Verify Conversation Access violation.
|
||||||
*/
|
*/
|
||||||
CONVO_ACCESS = 'convo_access',
|
CONVO_ACCESS = 'convo_access',
|
||||||
|
/**
|
||||||
|
* Verify moderation LLM violation.
|
||||||
|
*/
|
||||||
|
MODERATION = 'moderation',
|
||||||
/**
|
/**
|
||||||
* Tool Call Limit Violation.
|
* Tool Call Limit Violation.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user