mirror of
https://github.com/HeyPuter/puter.git
synced 2025-02-03 07:48:46 +08:00
fix: add fallback moderation in case openai goes down
This commit is contained in:
parent
97a1616305
commit
c6e814daa8
@ -6,6 +6,7 @@ const { DB_WRITE } = require("../../services/database/consts");
|
|||||||
const { TypeSpec } = require("../../services/drivers/meta/Construct");
|
const { TypeSpec } = require("../../services/drivers/meta/Construct");
|
||||||
const { TypedValue } = require("../../services/drivers/meta/Runtime");
|
const { TypedValue } = require("../../services/drivers/meta/Runtime");
|
||||||
const { Context } = require("../../util/context");
|
const { Context } = require("../../util/context");
|
||||||
|
const { AsModeration } = require("./lib/AsModeration");
|
||||||
|
|
||||||
// Maximum number of fallback attempts when a model fails, including the first attempt
|
// Maximum number of fallback attempts when a model fails, including the first attempt
|
||||||
const MAX_FALLBACKS = 3 + 1; // includes first attempt
|
const MAX_FALLBACKS = 3 + 1; // includes first attempt
|
||||||
@ -489,11 +490,6 @@ class AIChatService extends BaseService {
|
|||||||
* Returns true if OpenAI service is unavailable or all messages pass moderation.
|
* Returns true if OpenAI service is unavailable or all messages pass moderation.
|
||||||
*/
|
*/
|
||||||
async moderate ({ messages }) {
|
async moderate ({ messages }) {
|
||||||
const svc_openai = this.services.get('openai-completion');
|
|
||||||
|
|
||||||
// We can't use moderation of openai service isn't available
|
|
||||||
if ( ! svc_openai ) return true;
|
|
||||||
|
|
||||||
for ( const msg of messages ) {
|
for ( const msg of messages ) {
|
||||||
const texts = [];
|
const texts = [];
|
||||||
if ( typeof msg.content === 'string' ) texts.push(msg.content);
|
if ( typeof msg.content === 'string' ) texts.push(msg.content);
|
||||||
@ -508,8 +504,39 @@ class AIChatService extends BaseService {
|
|||||||
|
|
||||||
const fulltext = texts.join('\n');
|
const fulltext = texts.join('\n');
|
||||||
|
|
||||||
const mod_result = await svc_openai.check_moderation(fulltext);
|
let mod_last_error = null;
|
||||||
if ( mod_result.flagged ) return false;
|
let mod_result = null;
|
||||||
|
try {
|
||||||
|
const svc_openai = this.services.get('openai-completion');
|
||||||
|
mod_result = await svc_openai.check_moderation(fulltext);
|
||||||
|
if ( mod_result.flagged ) return false;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
mod_last_error = e;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const svc_claude = this.services.get('claude');
|
||||||
|
const chat = svc_claude.as('puter-chat-completion');
|
||||||
|
const mod = new AsModeration({
|
||||||
|
chat,
|
||||||
|
model: 'claude-3-haiku-20240307',
|
||||||
|
})
|
||||||
|
if ( ! await mod.moderate(fulltext) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
mod_last_error = null;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
mod_last_error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( mod_last_error ) {
|
||||||
|
this.log.error('moderation error', {
|
||||||
|
fulltext,
|
||||||
|
mod_last_error,
|
||||||
|
});
|
||||||
|
throw new Error('no working moderation service');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
77
src/backend/src/modules/puterai/lib/AsModeration.js
Normal file
77
src/backend/src/modules/puterai/lib/AsModeration.js
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
const { default: dedent } = require("dedent");
|
||||||
|
|
||||||
|
class AsModeration {
|
||||||
|
constructor ({ chat, model }) {
|
||||||
|
this.chat = chat;
|
||||||
|
this.model = model;
|
||||||
|
}
|
||||||
|
|
||||||
|
async moderate (text) {
|
||||||
|
// copied from Anthropic documentation
|
||||||
|
// https://docs.anthropic.com/en/docs/about-claude/use-case-guides/content-moderation
|
||||||
|
const unsafe_categories = [
|
||||||
|
'Child Exploitation',
|
||||||
|
'Conspiracy Theories',
|
||||||
|
'Hate',
|
||||||
|
'Indiscriminate Weapons',
|
||||||
|
'Intellectual Property',
|
||||||
|
'Non-Violent Crimes',
|
||||||
|
'Privacy',
|
||||||
|
'Self-Harm',
|
||||||
|
'Sex Crimes',
|
||||||
|
'Sexual Content',
|
||||||
|
'Specialized Advice',
|
||||||
|
'Violent Crimes'
|
||||||
|
];
|
||||||
|
|
||||||
|
const unsafe_category_str = unsafe_categories.join('\n');
|
||||||
|
|
||||||
|
const assessment_prompt = dedent(`
|
||||||
|
Determine whether the following message warrants moderation,
|
||||||
|
based on the unsafe categories outlined below.
|
||||||
|
|
||||||
|
Message:
|
||||||
|
<message>
|
||||||
|
${text
|
||||||
|
.replace('<', '<')
|
||||||
|
.replace('>', '>')
|
||||||
|
}
|
||||||
|
</message>
|
||||||
|
|
||||||
|
Unsafe Categories:
|
||||||
|
<categories>
|
||||||
|
${unsafe_category_str}
|
||||||
|
</categories>
|
||||||
|
|
||||||
|
Respond with ONLY a JSON object, using the format below:
|
||||||
|
{{
|
||||||
|
"violation": <Boolean field denoting whether the message should be moderated>,
|
||||||
|
"categories": [Comma-separated list of violated categories],
|
||||||
|
"explanation": [Optional. Only include if there is a violation.]
|
||||||
|
}}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const result = await this.chat.complete({
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: assessment_prompt,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('result???', require('util').inspect(result, { depth: null }));
|
||||||
|
|
||||||
|
const str = result.message?.content?.[0]?.text ??
|
||||||
|
result.messages?.[0]?.content?.[0]?.text ??
|
||||||
|
'{ "violation": true }';
|
||||||
|
|
||||||
|
const parsed = JSON.parse(str);
|
||||||
|
console.log('parsed?', parsed);
|
||||||
|
return ! parsed.violation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
AsModeration,
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user