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_WITH_CAPTION_PROMPT
|
||||||
: ALT_TEXT_ONLY_PROMPT;
|
: ALT_TEXT_ONLY_PROMPT;
|
||||||
|
|
||||||
const completion = await this.openai.chat.completions.create({
|
console.log('[AltTextGenerator] Context length:', context.length);
|
||||||
model: 'gpt-5-2025-08-07',
|
console.log('[AltTextGenerator] Include caption:', includeCaption);
|
||||||
|
console.log('[AltTextGenerator] Calling OpenAI with model: gpt-4o');
|
||||||
|
|
||||||
|
const completionParams: any = {
|
||||||
|
model: 'gpt-4o',
|
||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
role: 'system',
|
role: 'system',
|
||||||
@ -48,13 +52,24 @@ export class AltTextGenerator {
|
|||||||
content: context,
|
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() || '';
|
const response = completion.choices[0]?.message?.content?.trim() || '';
|
||||||
|
|
||||||
if (!response) {
|
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) {
|
if (includeCaption) {
|
||||||
@ -69,6 +84,7 @@ export class AltTextGenerator {
|
|||||||
};
|
};
|
||||||
} catch (parseErr) {
|
} catch (parseErr) {
|
||||||
console.error('[AltTextGenerator] JSON parse error:', parseErr);
|
console.error('[AltTextGenerator] JSON parse error:', parseErr);
|
||||||
|
console.error('[AltTextGenerator] Raw response was:', response);
|
||||||
// Fallback: treat as alt text only
|
// Fallback: treat as alt text only
|
||||||
return { altText: response, caption: '' };
|
return { altText: response, caption: '' };
|
||||||
}
|
}
|
||||||
@ -78,4 +94,10 @@ export class AltTextGenerator {
|
|||||||
return { altText: response, caption: '' };
|
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
|
// Strip HTML and get preview
|
||||||
const textContent = stripHtmlTags(params.contentHtml);
|
const textContent = stripHtmlTags(params.contentHtml);
|
||||||
const preview = textContent.slice(0, 2000);
|
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({
|
const completion = await this.openai.chat.completions.create({
|
||||||
model: 'gpt-5-2025-08-07',
|
model: 'gpt-4o',
|
||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
role: 'system',
|
role: 'system',
|
||||||
@ -28,13 +32,27 @@ export class MetadataGenerator {
|
|||||||
content: `Generate metadata for this article:\n\n${preview}`,
|
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 || '';
|
const response = completion.choices[0]?.message?.content || '';
|
||||||
|
|
||||||
|
console.log('[MetadataGenerator] Response length:', response.length);
|
||||||
|
console.log('[MetadataGenerator] Response is empty:', !response);
|
||||||
|
|
||||||
if (!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);
|
console.log('[MetadataGenerator] Raw response:', response);
|
||||||
@ -45,13 +63,49 @@ export class MetadataGenerator {
|
|||||||
console.log('[MetadataGenerator] Generated:', metadata);
|
console.log('[MetadataGenerator] Generated:', metadata);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: metadata.title || '',
|
title: metadata.title || this.buildFallbackMetadata(textContent).title,
|
||||||
tags: metadata.tags || '',
|
tags: metadata.tags || this.buildFallbackMetadata(textContent).tags,
|
||||||
canonicalUrl: metadata.canonicalUrl || '',
|
canonicalUrl: metadata.canonicalUrl || this.buildFallbackMetadata(textContent).canonicalUrl,
|
||||||
};
|
};
|
||||||
} catch (parseErr) {
|
} catch (parseErr) {
|
||||||
console.error('[MetadataGenerator] JSON parse error:', 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