feat(livia): Modell und Textlänge über Umgebungsvariablen einstellbar

Das Modell stand fest im Code, obwohl es die grösste Kostenstellschraube ist —
ein Lauf über fünfzig Meldungen kostet mit Opus 5 rund 0.40 USD, mit Haiku 4.5
etwa 0.08. Wer die Anwendung betreibt, soll diese Abwägung treffen können, ohne
den Quelltext anzufassen und neu zu deployen.

`LIVIA_RESEARCH_MODEL` wählt das Modell, `LIVIA_RESEARCH_TEXT_LIMIT` die Menge
Artikeltext je Meldung — der zweite grosse Hebel, weil der Immobilienbezug einer
Firmenmeldung fast immer im ersten Absatz steht und jede Halbierung die
Eingabekosten halbiert. Beide haben Vorgabewerte; ohne gesetzte Variablen
verhält sich alles wie bisher.

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:18:11 +02:00
1 parent e986e9f274
commit c19f071acc
1 file changed
+25 -2
+25 -2
View File
@@ -90,6 +90,29 @@ function extractJson(text: string): unknown {
*/ */
const BATCH_SIZE = 5 const BATCH_SIZE = 5
/**
* Welches Modell die Einordnung vornimmt.
*
* Über `LIVIA_RESEARCH_MODEL` umstellbar, ohne den Code anzufassen — die
* Wahl ist eine Kosten- und Qualitätsabwägung und gehört damit dem Betreiber,
* nicht dem Quelltext.
*
* Grössenordnung je Lauf über rund fünfzig Meldungen:
* claude-opus-5 ≈ 0.40 USD stärkstes Urteil
* claude-sonnet-5 ≈ 0.20 USD
* claude-haiku-4-5 ≈ 0.08 USD für eine Ja/Nein-Einordnung meist ausreichend
*/
const MODEL = process.env.LIVIA_RESEARCH_MODEL ?? 'claude-opus-5'
/**
* Wie viel Artikeltext das Modell zu lesen bekommt.
*
* Ebenfalls umstellbar, weil es der zweite grosse Kostenhebel ist: Der
* Immobilienbezug einer Firmenmeldung steht fast immer im ersten Absatz, und
* jede Halbierung halbiert die Eingabekosten.
*/
const TEXT_LIMIT = Number(process.env.LIVIA_RESEARCH_TEXT_LIMIT ?? 2000)
/** /**
* Wie viele Teilstapel gleichzeitig beurteilt werden. * Wie viele Teilstapel gleichzeitig beurteilt werden.
* *
@@ -116,12 +139,12 @@ async function analyzeBatch(
`Quelle: ${it.source}`, `Quelle: ${it.source}`,
`Titel: ${it.title}`, `Titel: ${it.title}`,
it.publishedAt ? `Publiziert: ${it.publishedAt}` : '', it.publishedAt ? `Publiziert: ${it.publishedAt}` : '',
`Text: ${it.text.slice(0, 2000)}`, `Text: ${it.text.slice(0, TEXT_LIMIT)}`,
].filter(Boolean).join('\n')) ].filter(Boolean).join('\n'))
.join('\n\n') .join('\n\n')
const response = await client.messages.create({ const response = await client.messages.create({
model: 'claude-opus-5', model: MODEL,
max_tokens: 8000, max_tokens: 8000,
system: SYSTEM_PROMPT, system: SYSTEM_PROMPT,
// Einordnen ist bei fünf Einträgen eine Routineaufgabe — die niedrige Stufe // Einordnen ist bei fünf Einträgen eine Routineaufgabe — die niedrige Stufe