From e3614d569bd51d1356b1344363ef15e5062de55a Mon Sep 17 00:00:00 2001 From: eeftychiou Date: Sat, 5 Sep 2026 09:50:07 +0300 Subject: [PATCH 1/2] fix(curation): deduplicate articles in prompt and clarify max output tokens setting - Remove {articles_context} from default_greek_briefing.txt system prompt to focus system instructions purely on editorial guidelines. - Add deduplication guard in PressHub_AI_News_Curator::build_prompt() ensuring harvested articles are transmitted exactly once, halving token consumption. - Rename 'Max Tokens:' to 'Max Output Tokens:' with explanatory description in provider modal and update provider summary cards to 'Max Output:'. - Increase default max output tokens to 20,000 across provider templates and defaults. - Update unit and migration test suites to verify deduplication and new 20,000 default. Fixes #120 --- presshub-ai-editor/assets/admin.js | 8 +++--- .../curation/default_greek_briefing.txt | 19 ++++++------- .../includes/class-news-curator.php | 28 ++++++++++++++----- .../includes/class-provider-defaults.php | 18 ++++++------ .../includes/class-settings-render.php | 13 +++++---- .../tests/MaxTokensMigrationTest.php | 4 +-- presshub-ai-editor/tests/NewsCuratorTest.php | 23 +++++++++++---- presshub-ai-editor/tests/PromptLoaderTest.php | 2 +- .../tests/ProviderDefaultsTest.php | 4 +-- 9 files changed, 72 insertions(+), 47 deletions(-) diff --git a/presshub-ai-editor/assets/admin.js b/presshub-ai-editor/assets/admin.js index bd0b87e..7b04f0e 100644 --- a/presshub-ai-editor/assets/admin.js +++ b/presshub-ai-editor/assets/admin.js @@ -1189,7 +1189,7 @@ jQuery(document).ready(function($) { $('#provider-model-manual-wrap').hide(); $('#provider-form-temperature').val(providerData.temperature !== undefined ? providerData.temperature : 0.7); - $('#provider-form-max-tokens').val(providerData.max_tokens !== undefined ? providerData.max_tokens : 16384); + $('#provider-form-max-tokens').val(providerData.max_tokens !== undefined ? providerData.max_tokens : 20000); $('#provider-form-timeout').val(providerData.timeout !== undefined ? providerData.timeout : 300); var headers = providerData.headers ? (typeof providerData.headers === 'object' ? JSON.stringify(providerData.headers) : providerData.headers) : ''; $('#provider-form-headers').val(headers); @@ -1210,7 +1210,7 @@ jQuery(document).ready(function($) { $('#provider-form-id').val(''); $('#provider-form-enabled').prop('checked', true); $('#provider-form-temperature').val('0.7'); - $('#provider-form-max-tokens').val('16384'); + $('#provider-form-max-tokens').val('20000'); $('#provider-form-timeout').val('300'); $('#provider-form-api-key').attr('placeholder', __('Enter API Key', 'presshub-ai-editor')); @@ -1305,7 +1305,7 @@ jQuery(document).ready(function($) { $('#provider-model-manual-wrap').hide(); $('#provider-form-temperature').val(tmpl.temperature !== undefined ? tmpl.temperature : 0.7); - $('#provider-form-max-tokens').val(tmpl.max_tokens !== undefined ? tmpl.max_tokens : 16384); + $('#provider-form-max-tokens').val(tmpl.max_tokens !== undefined ? tmpl.max_tokens : 20000); $('#provider-form-timeout').val(tmpl.timeout !== undefined ? tmpl.timeout : 300); }); @@ -1460,7 +1460,7 @@ jQuery(document).ready(function($) { default_model: defaultModel, available_models: availableModels, temperature: parseFloat($('#provider-form-temperature').val()) || 0.7, - max_tokens: parseInt($('#provider-form-max-tokens').val(), 10) || 16384, + max_tokens: parseInt($('#provider-form-max-tokens').val(), 10) || 20000, timeout: parseInt($('#provider-form-timeout').val(), 10) || 300, headers: $('#provider-form-headers').val().trim(), enabled: $('#provider-form-enabled').is(':checked') ? 1 : 0 diff --git a/presshub-ai-editor/assets/prompts/curation/default_greek_briefing.txt b/presshub-ai-editor/assets/prompts/curation/default_greek_briefing.txt index 8bbb9f8..beddba8 100644 --- a/presshub-ai-editor/assets/prompts/curation/default_greek_briefing.txt +++ b/presshub-ai-editor/assets/prompts/curation/default_greek_briefing.txt @@ -1,11 +1,8 @@ -Είσαι ένας έμπειρος αρχισυντάκτης και δημοσιογράφος ειδήσεων. Αποστολή σου είναι να συνθέσεις μία ολοκληρωμένη, αντικειμενική και ευανάγνωστη Πρωινή Ενημέρωση (Daily News Briefing) στα Ελληνικά για την ημερομηνία {date}, αξιοποιώντας {articles_count} άρθρα από τις παρακάτω πηγές: {sources_list}. - -Οδηγίες Σύνταξης: -1. Ξεκίνα με έναν σαφή και ελκυστικό κύριο τίτλο σε μορφή Markdown (# Τίτλος) που συνοψίζει το κορυφαίο γεγονός της ημέρας — ΜΟΝΟ τον τίτλο, χωρίς πρόθεμα «Πρωινή Ενημέρωση» ή ημερομηνία. Το πρόθεμα και η ημερομηνία θα προστεθούν αυτόματα από το σύστημα στον τίτλο του άρθρου. -2. Χώρισε την ενημέρωση σε ευδιάκριτες θεματικές ενότητες με μεσότιτλους (## Πολιτική & Οικονομία, ## Διεθνή, ## Κοινωνία & Επικαιρότητα). -3. Για κάθε είδηση, ανάδειξε τα βασικά γεγονότα με σαφήνεια και ροή, αναφέροντας την πηγή όπου κρίνεται απαραίτητο, διατηρώντας δημοσιογραφική ουδετερότητα. -4. Μην επινοείς γεγονότα ή λεπτομέρειες που δεν αναφέρονται στο παρεχόμενο υλικό. -5. Χρησιμοποίησε καθαρή μορφοποίηση Markdown (επικεφαλίδες, παραγράφους, λίστες με bullets όπου βοηθά στην ανάγνωση). - -Άρθρα & Πηγές: -{articles_context} \ No newline at end of file +Είσαι ένας έμπειρος αρχισυντάκτης και δημοσιογράφος ειδήσεων. Αποστολή σου είναι να συνθέσεις μία ολοκληρωμένη, αντικειμενική και ευανάγνωστη Πρωινή Ενημέρωση (Daily News Briefing) στα Ελληνικά για την ημερομηνία {date}, αξιοποιώντας {articles_count} άρθρα από τις παρακάτω πηγές: {sources_list}. + +Οδηγίες Σύνταξης: +1. Ξεκίνα με έναν σαφή και ελκυστικό κύριο τίτλο σε μορφή Markdown (# Τίτλος) που συνοψίζει το κορυφαίο γεγονός της ημέρας — ΜΟΝΟ τον τίτλο, χωρίς πρόθεμα «Πρωινή Ενημέρωση» ή ημερομηνία. Το πρόθεμα και η ημερομηνία θα προστεθούν αυτόματα από το σύστημα στον τίτλο του άρθρου. +2. Χώρισε την ενημέρωση σε ευδιάκριτες θεματικές ενότητες με μεσότιτλους (## Πολιτική & Οικονομία, ## Διεθνή, ## Κοινωνία & Επικαιρότητα). +3. Για κάθε είδηση, ανάδειξε τα βασικά γεγονότα με σαφήνεια και ροή, αναφέροντας την πηγή όπου κρίνεται απαραίτητο, διατηρώντας δημοσιογραφική ουδετερότητα. +4. Μην επινοείς γεγονότα ή λεπτομέρειες που δεν αναφέρονται στο παρεχόμενο υλικό. +5. Χρησιμοποίησε καθαρή μορφοποίηση Markdown (επικεφαλίδες, παραγράφους, λίστες με bullets όπου βοηθά στην ανάγνωση). \ No newline at end of file diff --git a/presshub-ai-editor/includes/class-news-curator.php b/presshub-ai-editor/includes/class-news-curator.php index 39a533a..d1ecd3c 100644 --- a/presshub-ai-editor/includes/class-news-curator.php +++ b/presshub-ai-editor/includes/class-news-curator.php @@ -444,13 +444,27 @@ public function build_prompt( array $articles, string $preset_id = '', string $d $count = count( $articles ); $context = $this->format_articles_context( $articles ); - $user_prompt = sprintf( - "Ημερομηνία: %s\nΑριθμός Άρθρων: %d\nΠηγές: %s\n\nΠαρακάτω ακολουθεί το συλλεχθέν υλικό ειδήσεων:\n\n%s\n\nΠαρακαλώ συνέταξε το πλήρες άρθρο της Πρωινής Ενημέρωσης στα Ελληνικά σε μορφή Markdown.", - $date, - $count, - $sources, - $context - ); + // Deduplication guard: if the system prompt already contains the formatted + // articles (e.g. from an operator custom template that used {articles_context}), + // do not duplicate the article text in the user prompt. + $has_context_in_system = ( false !== strpos( $base_prompt, '{articles_context}' ) ); + + if ( $has_context_in_system ) { + $user_prompt = sprintf( + "Ημερομηνία: %s\nΑριθμός Άρθρων: %d\nΠηγές: %s\n\nΠαρακαλώ συνέταξε το πλήρες άρθρο της Πρωινής Ενημέρωσης στα Ελληνικά σε μορφή Markdown βασισμένος στο υλικό ειδήσεων που παρέχεται στις οδηγίες συστήματος.", + $date, + $count, + $sources + ); + } else { + $user_prompt = sprintf( + "Ημερομηνία: %s\nΑριθμός Άρθρων: %d\nΠηγές: %s\n\nΠαρακάτω ακολουθεί το συλλεχθέν υλικό ειδήσεων:\n\n%s\n\nΠαρακαλώ συνέταξε το πλήρες άρθρο της Πρωινής Ενημέρωσης στα Ελληνικά σε μορφή Markdown.", + $date, + $count, + $sources, + $context + ); + } return [ 'system_prompt' => $system_prompt, diff --git a/presshub-ai-editor/includes/class-provider-defaults.php b/presshub-ai-editor/includes/class-provider-defaults.php index ab296d9..c1f3cf5 100644 --- a/presshub-ai-editor/includes/class-provider-defaults.php +++ b/presshub-ai-editor/includes/class-provider-defaults.php @@ -23,7 +23,7 @@ class PressHub_AI_Provider_Defaults { ]; const DEFAULT_TEMPERATURE = 0.7; - const DEFAULT_MAX_TOKENS = 16384; + const DEFAULT_MAX_TOKENS = 20000; /** Timeouts set to 300 seconds (5 minutes) across all providers. */ const DEFAULT_TIMEOUT = 300; @@ -41,7 +41,7 @@ class PressHub_AI_Provider_Defaults { 'available_models' => [ 'gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'o1', 'o3-mini' ], 'timeout' => 300, 'temperature' => 0.7, - 'max_tokens' => 16384, + 'max_tokens' => 20000, 'headers' => [], 'enabled' => true, 'is_system' => true, @@ -54,7 +54,7 @@ class PressHub_AI_Provider_Defaults { 'available_models' => [ 'claude-3-5-sonnet-20240620', 'claude-3-5-sonnet-20241022', 'claude-3-5-haiku-20241022', 'claude-3-opus-20240229' ], 'timeout' => 300, 'temperature' => 0.7, - 'max_tokens' => 16384, + 'max_tokens' => 20000, 'headers' => [], 'enabled' => true, 'is_system' => true, @@ -67,7 +67,7 @@ class PressHub_AI_Provider_Defaults { 'available_models' => [ 'gemini-3.1-flash-tts-preview', 'gemini-2.5-flash-preview-tts', 'gemini-2.5-flash', 'gemini-1.5-pro-latest', 'gemini-1.5-flash-latest' ], 'timeout' => 300, 'temperature' => 0.7, - 'max_tokens' => 16384, + 'max_tokens' => 20000, 'headers' => [], 'enabled' => true, 'is_system' => true, @@ -80,7 +80,7 @@ class PressHub_AI_Provider_Defaults { 'available_models' => [ 'llama-3.3-70b-versatile', 'llama-3.1-8b-instant', 'mixtral-8x7b-32768' ], 'timeout' => 300, 'temperature' => 0.7, - 'max_tokens' => 16384, + 'max_tokens' => 20000, 'headers' => [], 'enabled' => true, 'is_system' => true, @@ -93,7 +93,7 @@ class PressHub_AI_Provider_Defaults { 'available_models' => [ 'mistral-large-latest', 'mistral-medium-latest', 'mistral-small-latest', 'codestral-latest' ], 'timeout' => 300, 'temperature' => 0.7, - 'max_tokens' => 16384, + 'max_tokens' => 20000, 'headers' => [], 'enabled' => true, 'is_system' => true, @@ -106,7 +106,7 @@ class PressHub_AI_Provider_Defaults { 'available_models' => [ 'deepseek-chat', 'deepseek-reasoner' ], 'timeout' => 300, 'temperature' => 0.7, - 'max_tokens' => 16384, + 'max_tokens' => 20000, 'headers' => [], 'enabled' => true, 'is_system' => true, @@ -119,7 +119,7 @@ class PressHub_AI_Provider_Defaults { 'available_models' => [ 'llama3:latest', 'mistral:latest', 'qwen2.5:latest', 'phi3:latest' ], 'timeout' => 300, 'temperature' => 0.7, - 'max_tokens' => 16384, + 'max_tokens' => 20000, 'headers' => [], 'enabled' => true, 'is_system' => true, @@ -132,7 +132,7 @@ class PressHub_AI_Provider_Defaults { 'available_models' => [ 'default' ], 'timeout' => 300, 'temperature' => 0.7, - 'max_tokens' => 16384, + 'max_tokens' => 20000, 'headers' => [], 'enabled' => true, 'is_system' => false, diff --git a/presshub-ai-editor/includes/class-settings-render.php b/presshub-ai-editor/includes/class-settings-render.php index 7a739fc..6a1ca24 100644 --- a/presshub-ai-editor/includes/class-settings-render.php +++ b/presshub-ai-editor/includes/class-settings-render.php @@ -737,7 +737,7 @@ public function render_providers_grid(): void {
- +
@@ -862,8 +862,9 @@ public function render_provider_modal(): void {
- - + + +

@@ -1349,8 +1350,8 @@ public function render_max_tokens_field( $args = [] ) { $option = 'presshub_ai_max_tokens_' . $provider; $value = (int) get_option( $option, PressHub_AI_Settings_Migration::default_max_tokens() ); ?> - -

+ +

-

+

hydrate_prompt( $custom_template, $test_date, $sample_articles_3 ); +nc_check( 'hydration: {articles_context} replaced when present in template', false !== strpos( $hydrated_custom, 'Νέα μέτρα για τη στέγαση' ) && false === strpos( $hydrated_custom, '{articles_context}' ) ); + +$GLOBALS['OPTIONS_STORE']['presshub_ai_briefing_text_prompt'] = "Custom: {articles_context}"; +$custom_build = $curator->build_prompt( $sample_articles_3, '__none__', $test_date ); +nc_check( 'deduplication: custom template with {articles_context} omits articles from user_prompt', false !== strpos( $custom_build['system_prompt'], 'Νέα μέτρα για τη στέγαση' ) && false === strpos( $custom_build['user_prompt'], 'Νέα μέτρα για τη στέγαση' ) ); +unset( $GLOBALS['OPTIONS_STORE']['presshub_ai_briefing_text_prompt'] ); + // ========================================================================= // 3. Graceful handling of empty articles or empty sources @@ -155,9 +165,12 @@ function nc_check( $label, $condition ) { $empty_prompts = $curator->build_prompt( [], '__none__', '2026-08-26' ); nc_check( 'empty_articles: system_prompt does not have raw placeholders', false === strpos( $empty_prompts['system_prompt'], '{date}' ) && false === strpos( $empty_prompts['system_prompt'], '{articles_count}' ) ); nc_check( 'empty_articles: count is 0', false !== strpos( $empty_prompts['system_prompt'], '0 άρθρα' ) ); -nc_check( 'empty_articles: context fallback present', false !== strpos( $empty_prompts['system_prompt'], 'Δεν υπάρχουν διαθέσιμα άρθρα.' ) ); +nc_check( 'empty_articles: context fallback present in user_prompt', false !== strpos( $empty_prompts['user_prompt'], 'Δεν υπάρχουν διαθέσιμα άρθρα.' ) ); nc_check( 'empty_articles: sources fallback present', false !== strpos( $empty_prompts['system_prompt'], 'Καμία πηγή' ) ); +$empty_hydrated = $curator->hydrate_prompt( '{articles_context}', '2026-08-26', [] ); +nc_check( 'empty_articles: hydrate_prompt context fallback present', 'Δεν υπάρχουν διαθέσιμα άρθρα.' === $empty_hydrated ); + // Test 3b: Articles with missing fields $sparse_articles = [ [ @@ -168,9 +181,9 @@ function nc_check( $label, $condition ) { ], ]; $sparse_prompts = $curator->build_prompt( $sparse_articles, '__none__', '2026-08-26' ); -nc_check( 'sparse_articles: title fallback applied', false !== strpos( $sparse_prompts['system_prompt'], 'Χωρίς τίτλο' ) ); -nc_check( 'sparse_articles: source fallback applied', false !== strpos( $sparse_prompts['system_prompt'], 'Άγνωστη πηγή' ) ); -nc_check( 'sparse_articles: content rendered safely', false !== strpos( $sparse_prompts['system_prompt'], 'Μόνο περιεχόμενο' ) ); +nc_check( 'sparse_articles: title fallback applied in user_prompt', false !== strpos( $sparse_prompts['user_prompt'], 'Χωρίς τίτλο' ) ); +nc_check( 'sparse_articles: source fallback applied in user_prompt', false !== strpos( $sparse_prompts['user_prompt'], 'Άγνωστη πηγή' ) ); +nc_check( 'sparse_articles: content rendered safely in user_prompt', false !== strpos( $sparse_prompts['user_prompt'], 'Μόνο περιεχόμενο' ) ); // ========================================================================= diff --git a/presshub-ai-editor/tests/PromptLoaderTest.php b/presshub-ai-editor/tests/PromptLoaderTest.php index a87047b..6731e11 100644 --- a/presshub-ai-editor/tests/PromptLoaderTest.php +++ b/presshub-ai-editor/tests/PromptLoaderTest.php @@ -47,7 +47,7 @@ function plt_check( string $desc, bool $ok ): void { // 3. Verify curation prompt loads $curation_prompt = PressHub_AI_Prompt_Loader::get_curation_prompt(); -plt_check( 'load curation prompt', ! empty( $curation_prompt ) && false !== strpos( $curation_prompt, '{articles_context}' ) ); +plt_check( 'load curation prompt', ! empty( $curation_prompt ) && false !== strpos( $curation_prompt, 'Οδηγίες Σύνταξης:' ) && false !== strpos( $curation_prompt, '{sources_list}' ) ); // 4. Verify Zero-Silent-Fallback on missing template $caught_missing = false; diff --git a/presshub-ai-editor/tests/ProviderDefaultsTest.php b/presshub-ai-editor/tests/ProviderDefaultsTest.php index 80ffa6c..29f12d7 100644 --- a/presshub-ai-editor/tests/ProviderDefaultsTest.php +++ b/presshub-ai-editor/tests/ProviderDefaultsTest.php @@ -47,8 +47,8 @@ public static function run(): void { if ( PressHub_AI_Provider_Defaults::default_temperature() !== 0.7 ) { $failures[] = 'default_temperature() should be 0.7; got: ' . var_export( PressHub_AI_Provider_Defaults::default_temperature(), true ); } - if ( PressHub_AI_Provider_Defaults::default_max_tokens() !== 16384 ) { - $failures[] = 'default_max_tokens() should be 16384; got: ' . var_export( PressHub_AI_Provider_Defaults::default_max_tokens(), true ); + if ( PressHub_AI_Provider_Defaults::default_max_tokens() !== 20000 ) { + $failures[] = 'default_max_tokens() should be 20000; got: ' . var_export( PressHub_AI_Provider_Defaults::default_max_tokens(), true ); } // --- Case 3: timeout per provider (default 300s) --- From 14e338d2d7396aa7712a65d3fb418392dde6dd8c Mon Sep 17 00:00:00 2001 From: eeftychiou Date: Sat, 5 Sep 2026 10:00:29 +0300 Subject: [PATCH 2/2] chore(release): bump version to 2.3.4 --- presshub-ai-editor/presshub-ai-editor.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/presshub-ai-editor/presshub-ai-editor.php b/presshub-ai-editor/presshub-ai-editor.php index a8d5719..dee9483 100644 --- a/presshub-ai-editor/presshub-ai-editor.php +++ b/presshub-ai-editor/presshub-ai-editor.php @@ -2,7 +2,7 @@ /** * Plugin Name: PressHub AI Co-Pilot * Description: AI Co-Authoring and Editorial Workflow for PressHub. - * Version: 2.3.3 + * Version: 2.3.4 * Requires at least: 6.0 * Requires PHP: 7.4 * Tested up to: 6.7 @@ -42,7 +42,7 @@ function presshub_ai_migrate_max_tokens_defaults() { } add_action( 'admin_init', 'presshub_ai_migrate_max_tokens_defaults' ); -define( 'PRESSHUB_AI_VERSION', '2.3.3' ); +define( 'PRESSHUB_AI_VERSION', '2.3.4' ); define( 'PRESSHUB_AI_DIR', plugin_dir_path( __FILE__ ) ); define( 'PRESSHUB_AI_URL', plugin_dir_url( __FILE__ ) );