fix(livia): ein Totalausfall der Beurteilung wird nicht mehr verschwiegen

Beim Messen des Tokenverbrauchs fiel auf, dass ein Lauf «49 Informationen
analysiert · 0 nicht relevant · 0 Watchlist · 0 relevante Entwicklungen»
meldete — was wie ein sauberer Lauf ohne Treffer aussieht. Tatsächlich war
jeder einzelne Beurteilungsaufruf mit «Your credit balance is too low»
gescheitert. Der `catch` um den Teilstapel fing den Fehler ab, damit ein
Ausfall nicht die übrigen mitreisst, und verlor dabei den Grund.

Scheitern alle Teilstapel, ist das kein leeres Ergebnis, sondern ein Ausfall.
Der Grund wird jetzt weitergereicht und erscheint als Warnung über der Liste.
Scheitern nur einzelne, bleibt es beim bisherigen Verhalten: die übrigen Leads
werden gezeigt.

Das ist die unangenehmste Fehlerklasse überhaupt — eine, die wie ein Ergebnis
aussieht. In einer Präsentation hätte niemand gemerkt, dass Livia gar nicht
gearbeitet hat.

Geprüft: tsc 0 Fehler, eslint 0 Fehler/0 Warnungen; der Lauf meldet den Fehler
jetzt sichtbar statt eine leere Liste.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin SutterandClaude Opus 5 committed 2026-08-30 19:14:36 +02:00
1 parent a028d1a21d
commit e986e9f274
1 file changed
+17 -2
+17 -2
View File
@@ -162,15 +162,18 @@ export async function analyzeResearchItems(items: ResearchItem[]): Promise<Analy
// Anbieter drosselt.
const teilstapel = chunk(items, BATCH_SIZE)
const ergebnisse: Awaited<ReturnType<typeof analyzeBatch>>[] = new Array(teilstapel.length)
const fehler: string[] = []
let next = 0
const worker = async (): Promise<void> => {
for (;;) {
const i = next++
if (i >= teilstapel.length) return
// Ein gescheiterter Teilstapel darf die übrigen nicht mitreissen.
// Ein gescheiterter Teilstapel darf die übrigen nicht mitreissen — aber
// sein Grund darf auch nicht verlorengehen, siehe unten.
try {
ergebnisse[i] = await analyzeBatch(client, teilstapel[i])
} catch {
} catch (err) {
fehler.push(err instanceof Error ? err.message : String(err))
ergebnisse[i] = []
}
}
@@ -178,6 +181,18 @@ export async function analyzeResearchItems(items: ResearchItem[]): Promise<Analy
await Promise.all(
Array.from({ length: Math.min(ANALYSIS_CONCURRENCY, teilstapel.length) }, worker),
)
/*
* Scheitern alle Teilstapel, ist das kein leeres Ergebnis, sondern ein
* Ausfall — und der muss gesagt werden. Vorher wurde er verschluckt und die
* Oberfläche meldete «49 analysiert · 0 relevante Entwicklungen», was wie ein
* sauberer Lauf ohne Treffer aussieht. Aufgefallen ist das erst, als das
* Guthaben aufgebraucht war: der Fehler «credit balance is too low» erschien
* nirgends, die Zahlen sahen bloss unauffällig aus.
*/
if (fehler.length === teilstapel.length) {
throw new Error(fehler[0])
}
const batches = ergebnisse
const leads: ResearchLead[] = []