fix: hard-cap blog generation to 800-1100 words

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.
This commit is contained in:
Rene Fichtmueller 2026-04-28 22:45:48 +02:00
parent ab6888fec8
commit b5decc517f
2 changed files with 17 additions and 5 deletions

View File

@ -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. export const STEP4_MASTER_DRAFT = `Write the full technical blog article based on the outline below.
LENGTH: HARD LIMIT 8001,100 WORDS. STOP AT 1,100.
Target: 8001,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 FORMAT: CONTINUOUS PROSE NO EXCEPTIONS

View File

@ -1017,8 +1017,8 @@ async function runLlmPipeline(
withCalibration, withCalibration,
} = await import("../llm/fo-blog-pipeline"); } = await import("../llm/fo-blog-pipeline");
const LLM_OPTS = { temperature: 0.7, maxTokens: 8192, timeoutMs: 480000 }; const LLM_OPTS = { temperature: 0.7, maxTokens: 1600, timeoutMs: 480000 };
const LLM_REFINE = { temperature: 0.4, maxTokens: 6144, 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 const TOTAL_STEPS = 16; // 10 original + 4b Narrative Control + 8b Reduction + 8c Style Lock + 8d Auto-Kill + Auto-Kill Score + LinkedIn
let stepsCompleted = 0; 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 const contextData = contextLines.length > 0
? contextLines.join("\n") ? contextLines.join("\n")
: "[NO PRODUCT DATA AVAILABLE — do NOT invent product names, part numbers, or prices]"; : "[NO PRODUCT DATA AVAILABLE — do NOT invent product names, part numbers, or prices]";
@ -1131,7 +1137,7 @@ async function runLlmPipeline(
STEP4_MASTER_DRAFT STEP4_MASTER_DRAFT
.replace("{{OUTLINE}}", step3.text) .replace("{{OUTLINE}}", step3.text)
.replace("{{CONTEXT_DATA}}", contextData), .replace("{{CONTEXT_DATA}}", contextData),
{ ...LLM_OPTS, maxTokens: 8192 } { ...LLM_OPTS, maxTokens: 1600 }
); );
stepsCompleted = 4; stepsCompleted = 4;
console.log(` Draft: ${step4.text.split(/\s+/).length} words`); console.log(` Draft: ${step4.text.split(/\s+/).length} words`);
@ -1343,8 +1349,8 @@ async function runLlmPipeline(
await pool.query( await pool.query(
`UPDATE blog_drafts `UPDATE blog_drafts
SET title = $9, draft_content = $1, word_count = $2, SET title = $9, draft_content = $1, word_count = $2,
generated_by = 'fo-blog-engine-v5-autokill', generated_by = 'fo-blog-engine-v6-length-fix',
pipeline_version = 'v5-auto-kill-layer', pipeline_version = 'v6-length-capped',
pipeline_steps_completed = $3, pipeline_steps_completed = $3,
auto_qa_score = $4, auto_qa_score = $4,
outline = $5, outline = $5,