From b5decc517f7deb7914b9c951da25ee8a463ceba4 Mon Sep 17 00:00:00 2001 From: Rene Fichtmueller Date: Tue, 28 Apr 2026 22:45:48 +0200 Subject: [PATCH] fix: hard-cap blog generation to 800-1100 words MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LLM_OPTS.maxTokens 8192 → 1600, LLM_REFINE 6144 → 1800, Step 4 master draft 8192 → 1600. Added explicit word-count constraint to STEP4_MASTER_DRAFT prompt: HARD LIMIT 800-1100 words. Root cause: no token ceiling → fo-blog-v6 produced 4000-5000w articles. Generated-by label updated to fo-blog-engine-v6-length-fix. --- packages/api/src/llm/fo-blog-pipeline.ts | 6 ++++++ packages/api/src/routes/blog.ts | 16 +++++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/api/src/llm/fo-blog-pipeline.ts b/packages/api/src/llm/fo-blog-pipeline.ts index 31a9517..efa7218 100644 --- a/packages/api/src/llm/fo-blog-pipeline.ts +++ b/packages/api/src/llm/fo-blog-pipeline.ts @@ -396,6 +396,12 @@ Write the flow plan (3-4 beats, as prose):`; export const STEP4_MASTER_DRAFT = `Write the full technical blog article based on the outline below. +═══════════════════════════════════════════════════════ +LENGTH: HARD LIMIT — 800–1,100 WORDS. STOP AT 1,100. +═══════════════════════════════════════════════════════ +Target: 800–1,100 words. If you hit 1,100 words, stop immediately and end the article. +This is not negotiable. Longer is not better. Dense and focused beats exhaustive. + ═══════════════════════════════════════════════════════ FORMAT: CONTINUOUS PROSE — NO EXCEPTIONS ═══════════════════════════════════════════════════════ diff --git a/packages/api/src/routes/blog.ts b/packages/api/src/routes/blog.ts index be409e7..a6a26fb 100644 --- a/packages/api/src/routes/blog.ts +++ b/packages/api/src/routes/blog.ts @@ -1017,8 +1017,8 @@ async function runLlmPipeline( withCalibration, } = await import("../llm/fo-blog-pipeline"); - const LLM_OPTS = { temperature: 0.7, maxTokens: 8192, timeoutMs: 480000 }; - const LLM_REFINE = { temperature: 0.4, maxTokens: 6144, timeoutMs: 480000 }; + const LLM_OPTS = { temperature: 0.7, maxTokens: 1600, timeoutMs: 480000 }; + const LLM_REFINE = { temperature: 0.4, maxTokens: 1800, timeoutMs: 480000 }; const TOTAL_STEPS = 16; // 10 original + 4b Narrative Control + 8b Reduction + 8c Style Lock + 8d Auto-Kill + Auto-Kill Score + LinkedIn let stepsCompleted = 0; @@ -1083,6 +1083,12 @@ async function runLlmPipeline( } } + if (additionalContext) { + contextLines.unshift( + `[HOT TOPIC BRIEFING — editorial direction, do not copy verbatim]\n${additionalContext.slice(0, 4000)}` + ); + } + const contextData = contextLines.length > 0 ? contextLines.join("\n") : "[NO PRODUCT DATA AVAILABLE — do NOT invent product names, part numbers, or prices]"; @@ -1131,7 +1137,7 @@ async function runLlmPipeline( STEP4_MASTER_DRAFT .replace("{{OUTLINE}}", step3.text) .replace("{{CONTEXT_DATA}}", contextData), - { ...LLM_OPTS, maxTokens: 8192 } + { ...LLM_OPTS, maxTokens: 1600 } ); stepsCompleted = 4; console.log(` Draft: ${step4.text.split(/\s+/).length} words`); @@ -1343,8 +1349,8 @@ async function runLlmPipeline( await pool.query( `UPDATE blog_drafts SET title = $9, draft_content = $1, word_count = $2, - generated_by = 'fo-blog-engine-v5-autokill', - pipeline_version = 'v5-auto-kill-layer', + generated_by = 'fo-blog-engine-v6-length-fix', + pipeline_version = 'v6-length-capped', pipeline_steps_completed = $3, auto_qa_score = $4, outline = $5,