feat: enhance AI content generation with logging and fallbacks
All checks were successful
Deploy to Production / deploy (push) Successful in 1m48s
All checks were successful
Deploy to Production / deploy (push) Successful in 1m48s
- Added comprehensive logging for OpenAI API calls and responses in both AltTextGenerator and MetadataGenerator - Updated OpenAI model from gpt-5-2025-08-07 to gpt-4o and standardized completion parameters - Implemented fallback mechanisms for handling empty or invalid AI responses - Added buildFallback methods to generate reasonable defaults from input text - Enhanced MetadataGenerator with smart title/tag derivation and URL slugification
This commit is contained in:
parent
36de987b5e
commit
a9b009685c
@ -36,8 +36,12 @@ export class AltTextGenerator {
|
||||
? ALT_TEXT_WITH_CAPTION_PROMPT
|
||||
: ALT_TEXT_ONLY_PROMPT;
|
||||
|
||||
const completion = await this.openai.chat.completions.create({
|
||||
model: 'gpt-5-2025-08-07',
|
||||
console.log('[AltTextGenerator] Context length:', context.length);
|
||||
console.log('[AltTextGenerator] Include caption:', includeCaption);
|
||||
console.log('[AltTextGenerator] Calling OpenAI with model: gpt-4o');
|
||||
|
||||
const completionParams: any = {
|
||||
model: 'gpt-4o',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
@ -48,13 +52,24 @@ export class AltTextGenerator {
|
||||
content: context,
|
||||
},
|
||||
],
|
||||
max_completion_tokens: includeCaption ? 200 : 100,
|
||||
});
|
||||
temperature: 0.7,
|
||||
max_tokens: includeCaption ? 200 : 100,
|
||||
};
|
||||
|
||||
if (includeCaption) {
|
||||
completionParams.response_format = { type: 'json_object' };
|
||||
}
|
||||
|
||||
const completion = await this.openai.chat.completions.create(completionParams);
|
||||
|
||||
console.log('[AltTextGenerator] Response:', completion.choices[0]?.message?.content);
|
||||
|
||||
const response = completion.choices[0]?.message?.content?.trim() || '';
|
||||
|
||||
if (!response) {
|
||||
throw new Error('No content generated');
|
||||
console.log('[AltTextGenerator] Empty response, using fallback');
|
||||
const fallback = this.buildFallback(params.placeholderDescription, includeCaption);
|
||||
return fallback;
|
||||
}
|
||||
|
||||
if (includeCaption) {
|
||||
@ -69,6 +84,7 @@ export class AltTextGenerator {
|
||||
};
|
||||
} catch (parseErr) {
|
||||
console.error('[AltTextGenerator] JSON parse error:', parseErr);
|
||||
console.error('[AltTextGenerator] Raw response was:', response);
|
||||
// Fallback: treat as alt text only
|
||||
return { altText: response, caption: '' };
|
||||
}
|
||||
@ -78,4 +94,10 @@ export class AltTextGenerator {
|
||||
return { altText: response, caption: '' };
|
||||
}
|
||||
}
|
||||
|
||||
private buildFallback(description: string, includeCaption: boolean): GenerateAltTextResponse {
|
||||
const altText = description.replace(/_/g, ' ').replace(/-/g, ' ').trim();
|
||||
const caption = includeCaption ? `Image showing ${altText}` : '';
|
||||
return { altText, caption };
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,9 +15,13 @@ export class MetadataGenerator {
|
||||
// Strip HTML and get preview
|
||||
const textContent = stripHtmlTags(params.contentHtml);
|
||||
const preview = textContent.slice(0, 2000);
|
||||
|
||||
console.log('[MetadataGenerator] Input preview length:', preview.length);
|
||||
console.log('[MetadataGenerator] Input preview:', preview.slice(0, 200) + '...');
|
||||
|
||||
console.log('[MetadataGenerator] Calling OpenAI with model: gpt-4o');
|
||||
const completion = await this.openai.chat.completions.create({
|
||||
model: 'gpt-5-2025-08-07',
|
||||
model: 'gpt-4o',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
@ -28,13 +32,27 @@ export class MetadataGenerator {
|
||||
content: `Generate metadata for this article:\n\n${preview}`,
|
||||
},
|
||||
],
|
||||
max_completion_tokens: 300,
|
||||
temperature: 0.7,
|
||||
max_tokens: 300,
|
||||
response_format: { type: 'json_object' as any },
|
||||
});
|
||||
|
||||
console.log('[MetadataGenerator] OpenAI completion object:', JSON.stringify(completion, null, 2));
|
||||
console.log('[MetadataGenerator] Choices:', completion.choices);
|
||||
console.log('[MetadataGenerator] First choice:', completion.choices[0]);
|
||||
console.log('[MetadataGenerator] Message:', completion.choices[0]?.message);
|
||||
console.log('[MetadataGenerator] Content:', completion.choices[0]?.message?.content);
|
||||
|
||||
const response = completion.choices[0]?.message?.content || '';
|
||||
|
||||
console.log('[MetadataGenerator] Response length:', response.length);
|
||||
console.log('[MetadataGenerator] Response is empty:', !response);
|
||||
|
||||
if (!response) {
|
||||
throw new Error('No metadata generated');
|
||||
console.log('[MetadataGenerator] Empty response from OpenAI, using fallback');
|
||||
const fallback = this.buildFallbackMetadata(textContent);
|
||||
console.log('[MetadataGenerator] Fallback metadata:', fallback);
|
||||
return fallback;
|
||||
}
|
||||
|
||||
console.log('[MetadataGenerator] Raw response:', response);
|
||||
@ -45,13 +63,49 @@ export class MetadataGenerator {
|
||||
console.log('[MetadataGenerator] Generated:', metadata);
|
||||
|
||||
return {
|
||||
title: metadata.title || '',
|
||||
tags: metadata.tags || '',
|
||||
canonicalUrl: metadata.canonicalUrl || '',
|
||||
title: metadata.title || this.buildFallbackMetadata(textContent).title,
|
||||
tags: metadata.tags || this.buildFallbackMetadata(textContent).tags,
|
||||
canonicalUrl: metadata.canonicalUrl || this.buildFallbackMetadata(textContent).canonicalUrl,
|
||||
};
|
||||
} catch (parseErr) {
|
||||
console.error('[MetadataGenerator] JSON parse error:', parseErr);
|
||||
throw new Error('Failed to parse metadata response');
|
||||
const fallback = this.buildFallbackMetadata(textContent);
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
private buildFallbackMetadata(text: string): GenerateMetadataResponse {
|
||||
const title = this.deriveTitle(text);
|
||||
const tags = this.deriveTags(text).join(', ');
|
||||
const canonicalUrl = this.slugify(title);
|
||||
return { title, tags, canonicalUrl };
|
||||
}
|
||||
|
||||
private deriveTitle(text: string): string {
|
||||
if (!text) return '';
|
||||
const sentence = text.split(/[.!?]/)[0] || text;
|
||||
const words = sentence.trim().split(/\s+/).slice(0, 12).join(' ');
|
||||
return words.length > 60 ? words.slice(0, 60).trim() : words;
|
||||
}
|
||||
|
||||
private deriveTags(text: string): string[] {
|
||||
const stop = new Set(['the','and','or','for','with','your','from','this','that','are','you','our','into','into','about','into','on','in','to','of','a','an','it','is','as','by','at','be','can','will','should','how','what','why']);
|
||||
const counts: Record<string, number> = {};
|
||||
text.toLowerCase().replace(/[^a-z0-9\s-]/g, ' ').split(/\s+/).forEach(w => {
|
||||
if (!w || stop.has(w) || w.length < 3) return;
|
||||
counts[w] = (counts[w] || 0) + 1;
|
||||
});
|
||||
const top = Object.entries(counts).sort((a,b)=>b[1]-a[1]).slice(0,5).map(([w])=>w);
|
||||
return top;
|
||||
}
|
||||
|
||||
private slugify(title: string): string {
|
||||
return (title || '')
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9\s-]/g, '')
|
||||
.trim()
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/-+/g, '-')
|
||||
.slice(0, 80);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user