Skip the AI rewrite entirely for single-source clusters
A cluster of one item still went through synthesizeArticle to be "lightly rewritten" — the only recent real-world example fabricated a fake two-outlet merge out of one genuine article (see the opaque-sourceId attribution fix). There's no actual synthesis to do with one source, so the rewrite step only added risk (hallucinated attribution, subtly altered facts) for no benefit. priorityQueue.ts's runSynthesisCycle now routes a 1-item cluster to publishDirect instead of publishCluster — same verbatim-text path already used for youtube/nitter/telegram items and AI-disabled categories. publishCluster is now only ever called with 2+ items, so its doc comment and synthesis.ts's system prompt no longer reference the single-source case. Verified directly: a 1-item cluster now publishes with the original body untouched and zero calls to the model, while a 2-item cluster still goes through the AI merge path unchanged.
This commit is contained in:
@@ -349,8 +349,9 @@ export async function publishDirect(
|
||||
|
||||
/**
|
||||
* Publishing is always automatic — there's no draft/review state (see schema doc).
|
||||
* A cluster of size 1 publishes as-is via the same path; synthesizeArticle lightly
|
||||
* rewrites rather than merges when there's only one source.
|
||||
* Callers should route a size-1 cluster to publishDirect instead — there's nothing to
|
||||
* merge, so an LLM rewrite would only add risk (hallucinated attribution, altered
|
||||
* facts) for no synthesis benefit. See priorityQueue.ts's runSynthesisCycle.
|
||||
*/
|
||||
export async function publishCluster(
|
||||
provider: InferenceProvider,
|
||||
|
||||
@@ -37,8 +37,6 @@ const SYSTEM_PROMPT = `You are a neutral news synthesis assistant. Given summari
|
||||
- Stays neutral and factual, without editorializing
|
||||
- Is 2-4 short paragraphs
|
||||
|
||||
If only one source is provided, lightly rewrite it in your own words rather than merging, and do not attribute it to any outlet other than that single given source.
|
||||
|
||||
After the article, on a new line, write exactly "${TAG_DELIMITER}" followed by 2-4 short comma-separated topic/entity tags (e.g. proper nouns, named events) that this article is about. If nothing salient qualifies, leave the tag line empty.`;
|
||||
|
||||
export interface SynthesisResult {
|
||||
|
||||
@@ -186,7 +186,14 @@ export async function runSynthesisCycle(provider: InferenceProvider, settings: G
|
||||
// in practice a cluster's items are all near-duplicate coverage of the same
|
||||
// story, so they'd all match the same event's filter anyway when they match at all.
|
||||
const eventId = cluster.items.map((i) => claimedEventId(i, activeEvents)).find((id) => id !== null) ?? undefined;
|
||||
const article = await publishCluster(provider, settings, cluster, { eventId });
|
||||
// A single-item cluster has nothing to merge — publish the source's own text
|
||||
// verbatim instead of asking the LLM to "lightly rewrite" it, which only risked
|
||||
// introducing errors (or fabricated attribution — see synthesis.ts) with no
|
||||
// actual synthesis to justify the risk.
|
||||
const article =
|
||||
cluster.items.length === 1
|
||||
? await publishDirect(cluster.items[0], settings, { eventId })
|
||||
: await publishCluster(provider, settings, cluster, { eventId });
|
||||
contentItemsDb.assignCluster(
|
||||
cluster.items.map((i) => i.id),
|
||||
cluster.id
|
||||
|
||||
Reference in New Issue
Block a user