fix(research): handle zero-hit retrieval — skip empty insert + graceful summary

Smoke-testing /api/v1/research/start with mana-search down surfaced a
crash: drizzle's .values([]) throws "values() must be called with at
least one value", which dropped the run into status='error' even though
the failure is a perfectly normal "no results" case.

Two changes:
- Guard the sources insert behind enriched.length > 0
- If retrieval returns nothing, short-circuit straight to status='done'
  with an explicit German "keine Quellen gefunden" summary instead of
  feeding an empty corpus to the synthesiser

The same path also triggers when every sub-query genuinely returns no
results (very specific question, niche domain) so this isn't just an
ops-failure case.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-08 22:19:17 +02:00
parent e82851985b
commit 83828e5a44

View file

@ -143,19 +143,41 @@ export async function runPipeline(
}
// Persist sources with stable rank order so citations [n] map to sources[n-1].
await db.insert(sources).values(
enriched.map((e, idx) => ({
researchResultId: id,
url: e.hit.url,
title: e.hit.title,
snippet: e.hit.snippet,
extractedContent: e.extractedText,
category: e.hit.category,
rank: idx + 1,
}))
);
// Drizzle's .values([]) throws — only insert when we actually have hits.
if (enriched.length > 0) {
await db.insert(sources).values(
enriched.map((e, idx) => ({
researchResultId: id,
url: e.hit.url,
title: e.hit.title,
snippet: e.hit.snippet,
extractedContent: e.extractedText,
category: e.hit.category,
rank: idx + 1,
}))
);
}
emit({ type: 'sources', count: enriched.length });
// If retrieval found nothing (all sub-queries failed or genuinely
// no hits), skip synthesis and surface an explicit "no sources"
// summary instead of asking the LLM to fabricate one.
if (enriched.length === 0) {
await db
.update(researchResults)
.set({
status: 'done',
summary:
'Keine Quellen gefunden. Die Web-Suche hat keine Treffer geliefert — entweder ist die Frage zu spezifisch oder die Suchdienste sind aktuell nicht erreichbar.',
keyPoints: [],
followUpQuestions: [],
finishedAt: new Date(),
})
.where(eq(researchResults.id, id));
emit({ type: 'done', researchResultId: id });
return;
}
// ─── Phase 3: Synthesise ───────────────────────────
await setStatus(id, 'synthesizing');
emit({ type: 'status', status: 'synthesizing' });