Use full article body, not just the RSS blurb, in synthesis prompts

buildPrompt() only ever sent ContentItem.summary (a ~500-char RSS
description) to the model, never .body (the full article text when
the feed provides <content:encoded>) — even though publishDirect
already preferred body over summary for the no-AI-merge path. A
single-source cluster was effectively asking the model to "lightly
rewrite" a one-paragraph blurb, which it did almost verbatim,
producing a short repeated synopsis instead of an actual article.

Now mirrors publishDirect's item.body || item.summary fallback. Body
is already HTML-stripped at ingestion (ingestion/adapters/base.ts),
so no new sanitization needed. The per-entry character budget added
in the previous truncation fix now does real work here, since full
bodies can be much longer than summaries.
This commit is contained in:
Claude
2026-07-27 03:52:42 +00:00
parent ee585ea65c
commit 53ebb68339
+9 -4
View File
@@ -50,12 +50,17 @@ function buildPrompt(items: ContentItem[]): string {
const budgetPerItem = Math.max(MIN_ENTRY_CHARS, Math.floor(MAX_INPUT_CHARS / items.length));
let truncated = 0;
const entries = items.map((item, i) => {
const summary = capEntryText(item.summary, budgetPerItem);
if (summary !== item.summary) truncated++;
return `Source ${i + 1} (${item.sourceId}):\nTitle: ${item.title}\nSummary: ${summary}`;
// Same fallback publishDirect uses (publish.ts) — body is the full article text
// when the feed supplies it (e.g. RSS <content:encoded>), summary is a ~500-char
// blurb. Using summary alone starved the model of real content to synthesize
// from, so a single-source cluster just echoed the blurb back nearly verbatim.
const full = item.body || item.summary;
const text = capEntryText(full, budgetPerItem);
if (text !== full) truncated++;
return `Source ${i + 1} (${item.sourceId}):\nTitle: ${item.title}\nSummary: ${text}`;
});
if (truncated > 0) {
logger.warn('synthesis', `Trimmed ${truncated}/${items.length} source summar${truncated === 1 ? 'y' : 'ies'} to fit the model's context window`);
logger.warn('synthesis', `Trimmed ${truncated}/${items.length} source article${truncated === 1 ? '' : 's'} to fit the model's context window`);
}
return entries.join('\n\n');
}