173 lines
5.8 KiB
JavaScript
173 lines
5.8 KiB
JavaScript
/**
|
|
* Name Validation Task
|
|
*
|
|
* Validates a product name for spelling, grammar, and naming conventions.
|
|
* Uses Groq with the smaller model for fast response times.
|
|
* Loads all prompts from the database (no hardcoded prompts).
|
|
*/
|
|
|
|
const { MODELS } = require('../providers/groqProvider');
|
|
const {
|
|
loadNameValidationPrompts,
|
|
validateRequiredPrompts
|
|
} = require('../prompts/promptLoader');
|
|
const {
|
|
buildNameUserPrompt,
|
|
parseNameResponse
|
|
} = require('../prompts/namePrompts');
|
|
|
|
const TASK_ID = 'validate.name';
|
|
|
|
/**
|
|
* Create the name validation task
|
|
*
|
|
* @returns {Object} Task definition
|
|
*/
|
|
function createNameValidationTask() {
|
|
return {
|
|
id: TASK_ID,
|
|
description: 'Validate product name for spelling, grammar, and conventions',
|
|
|
|
/**
|
|
* Run the name validation
|
|
*
|
|
* @param {Object} payload
|
|
* @param {Object} payload.product - Product data
|
|
* @param {string} payload.product.name - Product name to validate
|
|
* @param {string} [payload.product.company_name] - Company name
|
|
* @param {string} [payload.product.company_id] - Company ID for loading specific rules
|
|
* @param {string} [payload.product.line_name] - Product line
|
|
* @param {string} [payload.product.description] - Description for context
|
|
* @param {Object} payload.provider - Groq provider instance
|
|
* @param {Object} payload.pool - PostgreSQL pool
|
|
* @param {Object} [payload.logger] - Logger instance
|
|
* @returns {Promise<Object>}
|
|
*/
|
|
async run(payload) {
|
|
const { product, provider, pool, logger } = payload;
|
|
const log = logger || console;
|
|
|
|
// Validate required input
|
|
if (!product?.name) {
|
|
return {
|
|
isValid: true,
|
|
suggestion: null,
|
|
issues: [],
|
|
skipped: true,
|
|
reason: 'No name provided'
|
|
};
|
|
}
|
|
|
|
if (!provider) {
|
|
throw new Error('Groq provider not available');
|
|
}
|
|
|
|
if (!pool) {
|
|
throw new Error('Database pool not available');
|
|
}
|
|
|
|
try {
|
|
// Load prompts from database
|
|
const companyKey = product.company_id || product.company_name || product.company;
|
|
const prompts = await loadNameValidationPrompts(pool, companyKey);
|
|
|
|
// Debug: Log loaded prompts
|
|
log.info('[NameValidation] Loaded prompts:', {
|
|
hasSystem: !!prompts.system,
|
|
systemLength: prompts.system?.length || 0,
|
|
hasGeneral: !!prompts.general,
|
|
generalLength: prompts.general?.length || 0,
|
|
generalPreview: prompts.general?.substring(0, 100) || '(empty)',
|
|
hasCompanySpecific: !!prompts.companySpecific,
|
|
companyKey
|
|
});
|
|
|
|
// Validate required prompts exist
|
|
validateRequiredPrompts(prompts, 'name_validation');
|
|
|
|
// Build the user prompt with database-loaded prompts
|
|
const userPrompt = buildNameUserPrompt(product, prompts);
|
|
|
|
// Debug: Log the full user prompt being sent
|
|
log.info('[NameValidation] User prompt:', userPrompt.substring(0, 500));
|
|
|
|
let response;
|
|
let result;
|
|
|
|
try {
|
|
// Try with JSON mode first
|
|
response = await provider.chatCompletion({
|
|
messages: [
|
|
{ role: 'system', content: prompts.system },
|
|
{ role: 'user', content: userPrompt }
|
|
],
|
|
model: MODELS.LARGE, // openai/gpt-oss-120b - reasoning model
|
|
temperature: 0.2, // Low temperature for consistent results
|
|
maxTokens: 3000, // Reasoning models need extra tokens for thinking
|
|
responseFormat: { type: 'json_object' }
|
|
});
|
|
|
|
// Log full raw response for debugging
|
|
log.info('[NameValidation] Raw AI response:', {
|
|
parsed: response.parsed,
|
|
content: response.content,
|
|
contentLength: response.content?.length
|
|
});
|
|
|
|
// Parse the response
|
|
result = parseNameResponse(response.parsed, response.content);
|
|
} catch (jsonError) {
|
|
// If JSON mode failed, check if we have failedGeneration to parse
|
|
if (jsonError.failedGeneration) {
|
|
log.warn('[NameValidation] JSON mode failed, attempting to parse failed_generation:', {
|
|
failedGeneration: jsonError.failedGeneration
|
|
});
|
|
result = parseNameResponse(null, jsonError.failedGeneration);
|
|
response = { latencyMs: 0, usage: {}, model: MODELS.SMALL };
|
|
} else {
|
|
// Retry without JSON mode
|
|
log.warn('[NameValidation] JSON mode failed, retrying without JSON mode');
|
|
response = await provider.chatCompletion({
|
|
messages: [
|
|
{ role: 'system', content: prompts.system },
|
|
{ role: 'user', content: userPrompt }
|
|
],
|
|
model: MODELS.SMALL,
|
|
temperature: 0.2,
|
|
maxTokens: 1500 // Reasoning models need extra tokens for thinking
|
|
// No responseFormat - let the model respond freely
|
|
});
|
|
log.info('[NameValidation] Raw AI response (no JSON mode):', {
|
|
parsed: response.parsed,
|
|
content: response.content,
|
|
contentLength: response.content?.length
|
|
});
|
|
result = parseNameResponse(response.parsed, response.content);
|
|
}
|
|
}
|
|
|
|
log.info(`[NameValidation] Validated "${product.name}" in ${response.latencyMs}ms`, {
|
|
isValid: result.isValid,
|
|
hassuggestion: !!result.suggestion,
|
|
issueCount: result.issues.length
|
|
});
|
|
|
|
return {
|
|
...result,
|
|
latencyMs: response.latencyMs,
|
|
usage: response.usage,
|
|
model: response.model
|
|
};
|
|
} catch (error) {
|
|
log.error('[NameValidation] Error:', error.message);
|
|
throw error;
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
module.exports = {
|
|
TASK_ID,
|
|
createNameValidationTask
|
|
};
|