Add error reporting to bulk AI summary generation

Surface actual API error messages in the admin UI instead of just
showing a generic failure count. This helps diagnose issues with
the OpenRouter API configuration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Terence Ong
2026-04-07 15:21:49 +07:00
parent 99aa2ca06d
commit 67f932a9e5
4 changed files with 48 additions and 2 deletions

View File

@@ -626,7 +626,11 @@ class Peptide_News_Admin {
if (remaining === 0) { if (remaining === 0) {
$result.text('Done! ' + totalProcessed + ' article(s) summarized.'); $result.text('Done! ' + totalProcessed + ' article(s) summarized.');
} else { } else {
$result.text(totalProcessed + ' summarized. ' + remaining + ' could not be processed — check the debug log.'); var errMsg = totalProcessed + ' summarized. ' + remaining + ' could not be processed.';
if (response.data.errors && response.data.errors.length > 0) {
errMsg += ' Error: ' + response.data.errors[0];
}
$result.text(errMsg);
} }
} }
} else { } else {

View File

@@ -58,6 +58,7 @@ class Peptide_News_LLM {
'keywords' => '', 'keywords' => '',
'summary' => '', 'summary' => '',
'success' => false, 'success' => false,
'errors' => array(),
); );
$api_key = get_option( 'peptide_news_openrouter_api_key', '' ); $api_key = get_option( 'peptide_news_openrouter_api_key', '' );
@@ -75,9 +76,12 @@ class Peptide_News_LLM {
$results['keywords'] = self::sanitize_keywords( $keywords_result ); $results['keywords'] = self::sanitize_keywords( $keywords_result );
$any_success = true; $any_success = true;
} else { } else {
$err_msg = 'Keywords (' . $keywords_model . '): ' . $keywords_result->get_error_message();
$results['errors'][] = $err_msg;
self::log_error( 'Keyword extraction failed for article ' . $article->id . ': ' . $keywords_result->get_error_message() ); self::log_error( 'Keyword extraction failed for article ' . $article->id . ': ' . $keywords_result->get_error_message() );
} }
} elseif ( $need_keywords ) { } elseif ( $need_keywords ) {
$results['errors'][] = 'Invalid keywords model ID: ' . $keywords_model;
self::log_error( 'Invalid keywords model ID: ' . $keywords_model ); self::log_error( 'Invalid keywords model ID: ' . $keywords_model );
} }
@@ -94,9 +98,12 @@ class Peptide_News_LLM {
$results['summary'] = sanitize_textarea_field( wp_strip_all_tags( $summary_result ) ); $results['summary'] = sanitize_textarea_field( wp_strip_all_tags( $summary_result ) );
$any_success = true; $any_success = true;
} else { } else {
$err_msg = 'Summary (' . $summary_model . '): ' . $summary_result->get_error_message();
$results['errors'][] = $err_msg;
self::log_error( 'Summarization failed for article ' . $article->id . ': ' . $summary_result->get_error_message() ); self::log_error( 'Summarization failed for article ' . $article->id . ': ' . $summary_result->get_error_message() );
} }
} elseif ( $need_summary ) { } elseif ( $need_summary ) {
$results['errors'][] = 'Invalid summary model ID: ' . $summary_model;
self::log_error( 'Invalid summary model ID: ' . $summary_model ); self::log_error( 'Invalid summary model ID: ' . $summary_model );
} }
@@ -150,6 +157,7 @@ class Peptide_News_LLM {
) ); ) );
$processed = 0; $processed = 0;
$last_errors = array();
$start_time = time(); $start_time = time();
foreach ( $articles as $article ) { foreach ( $articles as $article ) {
@@ -166,6 +174,11 @@ class Peptide_News_LLM {
$processed++; $processed++;
} }
// Track errors for debugging.
if ( ! empty( $result['errors'] ) ) {
$last_errors = $result['errors'];
}
// Small delay to avoid rate limiting. // Small delay to avoid rate limiting.
if ( next( $articles ) !== false ) { if ( next( $articles ) !== false ) {
usleep( 500000 ); // 0.5s between articles. usleep( 500000 ); // 0.5s between articles.
@@ -182,6 +195,7 @@ class Peptide_News_LLM {
'time' => current_time( 'mysql' ), 'time' => current_time( 'mysql' ),
'processed' => $processed, 'processed' => $processed,
'attempted' => count( $articles ), 'attempted' => count( $articles ),
'errors' => $last_errors,
) ); ) );
return $processed; return $processed;
@@ -431,10 +445,15 @@ class Peptide_News_LLM {
AND ( ai_summary = '' OR ai_summary IS NULL )" AND ( ai_summary = '' OR ai_summary IS NULL )"
); );
// Include error details from the last processing run for debugging.
$last_run = get_option( 'peptide_news_last_llm_process', array() );
$errors = isset( $last_run['errors'] ) ? $last_run['errors'] : array();
wp_send_json_success( array( wp_send_json_success( array(
'processed' => $processed, 'processed' => $processed,
'remaining' => $still_remaining, 'remaining' => $still_remaining,
'message' => sprintf( '%d article(s) summarized, %d remaining.', $processed, $still_remaining ), 'message' => sprintf( '%d article(s) summarized, %d remaining.', $processed, $still_remaining ),
'errors' => $errors,
) ); ) );
} }

View File

@@ -626,7 +626,11 @@ class Peptide_News_Admin {
if (remaining === 0) { if (remaining === 0) {
$result.text('Done! ' + totalProcessed + ' article(s) summarized.'); $result.text('Done! ' + totalProcessed + ' article(s) summarized.');
} else { } else {
$result.text(totalProcessed + ' summarized. ' + remaining + ' could not be processed — check the debug log.'); var errMsg = totalProcessed + ' summarized. ' + remaining + ' could not be processed.';
if (response.data.errors && response.data.errors.length > 0) {
errMsg += ' Error: ' + response.data.errors[0];
}
$result.text(errMsg);
} }
} }
} else { } else {

View File

@@ -58,6 +58,7 @@ class Peptide_News_LLM {
'keywords' => '', 'keywords' => '',
'summary' => '', 'summary' => '',
'success' => false, 'success' => false,
'errors' => array(),
); );
$api_key = get_option( 'peptide_news_openrouter_api_key', '' ); $api_key = get_option( 'peptide_news_openrouter_api_key', '' );
@@ -75,9 +76,12 @@ class Peptide_News_LLM {
$results['keywords'] = self::sanitize_keywords( $keywords_result ); $results['keywords'] = self::sanitize_keywords( $keywords_result );
$any_success = true; $any_success = true;
} else { } else {
$err_msg = 'Keywords (' . $keywords_model . '): ' . $keywords_result->get_error_message();
$results['errors'][] = $err_msg;
self::log_error( 'Keyword extraction failed for article ' . $article->id . ': ' . $keywords_result->get_error_message() ); self::log_error( 'Keyword extraction failed for article ' . $article->id . ': ' . $keywords_result->get_error_message() );
} }
} elseif ( $need_keywords ) { } elseif ( $need_keywords ) {
$results['errors'][] = 'Invalid keywords model ID: ' . $keywords_model;
self::log_error( 'Invalid keywords model ID: ' . $keywords_model ); self::log_error( 'Invalid keywords model ID: ' . $keywords_model );
} }
@@ -94,9 +98,12 @@ class Peptide_News_LLM {
$results['summary'] = sanitize_textarea_field( wp_strip_all_tags( $summary_result ) ); $results['summary'] = sanitize_textarea_field( wp_strip_all_tags( $summary_result ) );
$any_success = true; $any_success = true;
} else { } else {
$err_msg = 'Summary (' . $summary_model . '): ' . $summary_result->get_error_message();
$results['errors'][] = $err_msg;
self::log_error( 'Summarization failed for article ' . $article->id . ': ' . $summary_result->get_error_message() ); self::log_error( 'Summarization failed for article ' . $article->id . ': ' . $summary_result->get_error_message() );
} }
} elseif ( $need_summary ) { } elseif ( $need_summary ) {
$results['errors'][] = 'Invalid summary model ID: ' . $summary_model;
self::log_error( 'Invalid summary model ID: ' . $summary_model ); self::log_error( 'Invalid summary model ID: ' . $summary_model );
} }
@@ -150,6 +157,7 @@ class Peptide_News_LLM {
) ); ) );
$processed = 0; $processed = 0;
$last_errors = array();
$start_time = time(); $start_time = time();
foreach ( $articles as $article ) { foreach ( $articles as $article ) {
@@ -166,6 +174,11 @@ class Peptide_News_LLM {
$processed++; $processed++;
} }
// Track errors for debugging.
if ( ! empty( $result['errors'] ) ) {
$last_errors = $result['errors'];
}
// Small delay to avoid rate limiting. // Small delay to avoid rate limiting.
if ( next( $articles ) !== false ) { if ( next( $articles ) !== false ) {
usleep( 500000 ); // 0.5s between articles. usleep( 500000 ); // 0.5s between articles.
@@ -182,6 +195,7 @@ class Peptide_News_LLM {
'time' => current_time( 'mysql' ), 'time' => current_time( 'mysql' ),
'processed' => $processed, 'processed' => $processed,
'attempted' => count( $articles ), 'attempted' => count( $articles ),
'errors' => $last_errors,
) ); ) );
return $processed; return $processed;
@@ -431,10 +445,15 @@ class Peptide_News_LLM {
AND ( ai_summary = '' OR ai_summary IS NULL )" AND ( ai_summary = '' OR ai_summary IS NULL )"
); );
// Include error details from the last processing run for debugging.
$last_run = get_option( 'peptide_news_last_llm_process', array() );
$errors = isset( $last_run['errors'] ) ? $last_run['errors'] : array();
wp_send_json_success( array( wp_send_json_success( array(
'processed' => $processed, 'processed' => $processed,
'remaining' => $still_remaining, 'remaining' => $still_remaining,
'message' => sprintf( '%d article(s) summarized, %d remaining.', $processed, $still_remaining ), 'message' => sprintf( '%d article(s) summarized, %d remaining.', $processed, $still_remaining ),
'errors' => $errors,
) ); ) );
} }