Files
prompts.chat/src/lib/ai/quality-check.ts
T
2025-12-17 20:41:05 +03:00

209 lines
6.4 KiB
TypeScript

import OpenAI from "openai";
// DelistReason enum values (matches Prisma schema)
export type DelistReason = "TOO_SHORT" | "NOT_ENGLISH" | "LOW_QUALITY" | "NOT_LLM_INSTRUCTION" | "MANUAL";
let openai: OpenAI | null = null;
function getOpenAIClient(): OpenAI {
if (!openai) {
const apiKey = process.env.OPENAI_API_KEY;
if (!apiKey) {
throw new Error("OPENAI_API_KEY is not set");
}
openai = new OpenAI({
apiKey,
baseURL: process.env.OPENAI_BASE_URL || undefined,
});
}
return openai;
}
const GENERATIVE_MODEL = process.env.OPENAI_GENERATIVE_MODEL || "gpt-4o";
// Minimum character count for prompt content
const MIN_CONTENT_LENGTH = 50;
// Minimum word count for prompt content
const MIN_WORD_COUNT = 10;
export interface QualityCheckResult {
shouldDelist: boolean;
reason: DelistReason | null;
confidence: number;
details: string;
}
/**
* Performs basic length checks on prompt content
*/
function checkLength(content: string): QualityCheckResult | null {
const trimmed = content.trim();
const wordCount = trimmed.split(/\s+/).filter(w => w.length > 0).length;
if (trimmed.length < MIN_CONTENT_LENGTH) {
return {
shouldDelist: true,
reason: "TOO_SHORT",
confidence: 1.0,
details: `Content is too short (${trimmed.length} chars, minimum ${MIN_CONTENT_LENGTH})`,
};
}
if (wordCount < MIN_WORD_COUNT) {
return {
shouldDelist: true,
reason: "TOO_SHORT",
confidence: 1.0,
details: `Content has too few words (${wordCount} words, minimum ${MIN_WORD_COUNT})`,
};
}
return null;
}
/**
* AI-powered quality check for prompt content
* Returns quality assessment with high precision to avoid false positives
*/
export async function checkPromptQuality(
title: string,
content: string,
description?: string | null
): Promise<QualityCheckResult> {
console.log(`[Quality Check] Checking: "${title}" (${content.length} chars)`);
// First, run basic length checks (no AI needed)
const lengthCheck = checkLength(content);
if (lengthCheck) {
console.log(`[Quality Check] Length check failed:`, lengthCheck);
return lengthCheck;
}
// Check if OpenAI is available
const apiKey = process.env.OPENAI_API_KEY;
if (!apiKey) {
console.log(`[Quality Check] No OpenAI API key - skipping AI check`);
// If no AI available, pass the check (avoid false positives)
return {
shouldDelist: false,
reason: null,
confidence: 0,
details: "AI quality check skipped - no API key configured",
};
}
console.log(`[Quality Check] Running AI check...`);
try {
const client = getOpenAIClient();
const systemPrompt = `You are a quality assurance system for an AI prompt sharing platform.
Your job is to evaluate if a submitted prompt meets quality standards.
IMPORTANT: You must be VERY conservative. Only flag prompts that are CLEARLY problematic.
When in doubt, APPROVE the prompt. False positives are worse than false negatives.
Evaluate the prompt for these issues:
1. NOT_ENGLISH: The prompt is primarily in a language other than English.
- Allow prompts with some non-English words if the main instructions are in English
- Allow technical terms, code, or proper nouns in any language
- Only flag if >70% of the meaningful text is non-English
2. LOW_QUALITY: The prompt is extremely low quality.
- Random characters, keyboard mashing, or gibberish
- Completely meaningless or incoherent text
- NOT low quality: Simple prompts, informal language, or beginner-level prompts
3. NOT_LLM_INSTRUCTION: The content is NOT an instruction for an LLM.
- This platform is for LLM prompts/instructions only
- Flag if it's clearly NOT a prompt (e.g., a recipe, personal note, random text, advertisement)
- APPROVE if it could reasonably be used to instruct an AI, even if unusual
- APPROVE roleplay scenarios, persona definitions, and creative prompts
- APPROVE technical, coding, or specialized domain prompts
Respond with a JSON object:
{
"shouldDelist": boolean,
"reason": "NOT_ENGLISH" | "LOW_QUALITY" | "NOT_LLM_INSTRUCTION" | null,
"confidence": number (0.0 to 1.0),
"details": "Brief explanation"
}
CRITICAL RULES:
- Only set shouldDelist: true if confidence >= 0.85
- When uncertain, set shouldDelist: false
- Creative, roleplay, and unusual prompts are VALID
- Short but clear prompts are VALID
- Prompts in specialized domains are VALID`;
const userMessage = `Title: ${title}
${description ? `Description: ${description}\n` : ""}
Content:
${content}`;
const response = await client.chat.completions.create({
model: GENERATIVE_MODEL,
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: userMessage }
],
temperature: 0.1, // Low temperature for consistent results
max_tokens: 300,
response_format: { type: "json_object" },
});
const responseText = response.choices[0]?.message?.content || "{}";
console.log(`[Quality Check] AI response:`, responseText);
try {
const result = JSON.parse(responseText);
// Extra safety: only delist if confidence is high enough
if (result.shouldDelist && result.confidence < 0.85) {
return {
shouldDelist: false,
reason: null,
confidence: result.confidence,
details: `Below confidence threshold: ${result.details}`,
};
}
return {
shouldDelist: !!result.shouldDelist,
reason: result.reason as DelistReason | null,
confidence: result.confidence || 0,
details: result.details || "Quality check completed",
};
} catch {
console.error("Failed to parse AI quality check response:", responseText);
// On parse error, don't delist (avoid false positives)
return {
shouldDelist: false,
reason: null,
confidence: 0,
details: "Failed to parse AI response - defaulting to approve",
};
}
} catch (error) {
console.error("AI quality check error:", error);
// On error, don't delist (avoid false positives)
return {
shouldDelist: false,
reason: null,
confidence: 0,
details: "AI quality check failed - defaulting to approve",
};
}
}
/**
* Check if auto-delist feature is enabled
*/
export async function isAutoDelistEnabled(): Promise<boolean> {
// Auto-delist requires OpenAI API key for AI checks
// Basic length checks will still work without it
return true;
}