TruthBase: verifizierbares RAG mit Zitaten, die zur markierten Textstelle führen
Verifizierbares RAG über PDFs auf Nuxt 4: Antworten nennen ihre Quellen, und ein Klick auf ein Zitat springt im PDF zur markierten Passage. Seit dem 21. Oktober 2026 live auf Vercel Hobby, auf Free Tiers für 0 EUR/Monat.
- Stack:
- Nuxt 4 + Nitro · Supabase Postgres (pgvector + tsvector) · Groq gpt-oss-120b / gpt-oss-20b · Vercel AI SDK v7 · gemini-embedding-001 @ 768 dims · Gemini Flash-Lite (fallback) · Nuxt UI v4 · unpdf + PDF.js · promptfoo + Langfuse · Playwright · Vercel Hobby
- Zeitraum:
- 2026
- veröffentlicht:
Das Problem
Jede RAG-Demo kann eine Antwort mit einem Zitat ausgeben. Kaum eine kann das Zitat belegen. Das übliche Muster („[1] report.pdf, S. 12“) verlangt vom Leser, das Dokument zu öffnen und selbst zu suchen; in der Praxis überprüft deshalb niemand etwas, und das Zitat ist Dekoration. Meine These für TruthBase lautet: Vertrauen braucht Belege. Ein Klick auf ein Zitat soll im PDF-Viewer genau zur richtigen Seite springen und genau die Passage hervorheben, die den Satz stützt, den Sie gerade gelesen haben.
Diese eine Interaktion ist das ganze Projekt. Sie erzwingt den größten Teil der interessanten Technik: Positionen müssen schon bei der Ingestion erfasst werden (sie nachzurüsten hieße, den gesamten Korpus neu zu parsen), sie müssen skalierungsunabhängig gespeichert und über alle Zoomstufen hinweg exakt rekonstruiert werden. Und die Retrieval-Pipeline dahinter muss gut genug sein, dass die hervorgehobene Passage die Antwort wirklich stützt.
Architektur
TruthBase läuft live unter truthbase.elbaymalik.com: eine einzige Nuxt-4-App, deren Arbeit mit Secrets und hohem Rechenbedarf (Embedding, Retrieval, Generierung) in Nitro-Server-Routen liegt, deployt als eine einzige Vercel Function auf Node 24 in dub1, neben der Supabase-Datenbank in AWS eu-west-1. Nichts Geheimes erreicht den Client. Der Demo-Korpus besteht aus vier öffentlichen PDFs (NIST CSF 2.0, NIST AI RMF 1.0 sowie die Paper zu RAPTOR und Self-RAG), eingespielt über eine CLI des Betreibers und nicht über den Browser.
Die Ingestion liest die Textelemente jeder Seite mit unpdf und PDF.js aus und spielt dann die Operator-Liste der Seite mit der Textvorschub-Mathematik von PDF.js nach, um die x-Ausdehnung jeder Glyphe zu erhalten. Das Chunking packt Sätze seitengebunden zusammen: bis 800 Zeichen mit 15 % Overlap, die kleinste Größe, bei der alle 60 Golden-Spans in einem Chunk liegen, denn die Markierung, die man sieht, ist der Chunk. Jeder Chunk speichert ein Rect pro gerenderter Zeile im PDF-Raum (chunks.highlight jsonb), neben einem generierten tsvector und einem Vektor von gemini-embedding-001 mit 768 Dimensionen, von Hand L2-normalisiert, weil Google nur die volle 3072-dimensionale Ausgabe normalisiert. Der Viewer zeichnet die gespeicherten Rects so, wie sie sind, bei jedem Zoom mit viewport.convertToViewportPoint von PDF.js umgerechnet. Er leitet sie nie aus der Textebene des Browsers ab, die Text in einer Ersatzschrift setzt; passt der gerenderte Text nicht mehr zum Chunk, sucht er die Passage über den Text und zeigt den Hinweis „ungefähr“.
Eine Frage passiert zuerst consume_quotas, einen einzigen Postgres-Aufruf, der die Zähler des Clients pro Minute und pro Tag und das globale Tagesbudget der Demo prüft, alles oder nichts, und noch vor jedem Modellaufruf mit 429 antwortet. Die Frage wird eingebettet, während ein selbstgesteuerter Rewriter auf gpt-oss-20b von Groq entscheidet, ob sie mehrdeutig genug ist, um sie in bis zu drei Anfragen aufzuteilen. Das Retrieval ist hybrid und bleibt in Postgres: hybrid_search, ein einziger SQL-RPC, fusioniert einen Volltext-Arm und einen exakten Kosinus-Arm per gewichteter Reciprocal Rank Fusion. Ein Listwise-LLM-Reranker auf gpt-oss-20b sortiert die fusionierten 15 besten; seine ersten vier Treffer stehen vorn, die fusionierte Reihenfolge füllt den Rest bis zu acht Quellen auf. Umformulieren und Reranking sind zur Laufzeit optional: Bei einem Fehler oder Timeout gilt die fusionierte Reihenfolge. Der Server streamt zuerst die Quellen mit ihren gespeicherten Rects, dann die Antwort von gpt-oss-120b bei Groq (Gemini Flash-Lite, wenn Groq ausfällt) über das Vercel AI SDK v7; sie zitiert mit [n]-Markern oder antwortet NO_ANSWER. Jede Chat-Anfrage wird nach Langfuse getraced. Das Entscheidungsprotokoll weiter unten hält fest, was dahintersteht, jeweils mit der Alternative, gegen die es sich durchgesetzt hat.
Aufbau der Evaluierung
Die Referenz ist ein Golden Set aus 50 Fragen über die vier PDFs: 45 beantwortbare, jede mit dem exakten stützenden Zitat und der Seitenzahl, und 5 unbeantwortbare (TruthBase docs/golden/golden.json). Bei n = 45 verschiebt eine einzige Frage eine Kennzahl um 2,2 Punkte. Dieselben Datensätze treiben die Playwright-Tests der Markierung, denn „Hat die Eval den richtigen Chunk zitiert?“ und „Ist die Markierung auf dem richtigen Rect gelandet?“ sind dieselbe Frage auf zwei Ebenen.
Die Evaluierung läuft auf zwei Ebenen. Ein deterministisches Retrieval-Gate ist in der CI für jeden Pull Request eingerichtet (sein letzter CI-Lauf war am 21. Oktober 2026): das echte hybrid_search auf einer eingecheckten Korpus-Fixture, das bei jedem Rückgang gegenüber TruthBase docs/evals/retrieval-baseline.json scheitert. Die modellabhängigen Evals verbrauchen kostenloses Kontingent und laufen deshalb auf Abruf: ein Skript pro Retrieval-Technik und eine LLM-Eval mit promptfoo und einem eigenen Judge-Modell, wöchentlich eingeplant.
Der Plan benannte, was ihn widerlegen würde: Schlägt die hybride Suche beim Recall auf dem Golden Set die rein dichte Suche nicht, wird zuerst das Chunking überarbeitet, bevor weitere Retrieval-Kniffe dazukommen. In Phase 4 lagen beide gleichauf bei 73 % Recall@8 (TruthBase docs/evals/phase-4-baseline.json). Eine Prüfung ergab, dass der Keyword-Arm alle Begriffe per AND verknüpfte und allein auf 27 % kam. Mit OR über die Lexeme der Frage selbst, Gewichten und k aus einem Raster von 72 Konfigurationen mit Kreuzvalidierung, stieg der hybride Recall@8 auf 87 % gegenüber 76 % bei rein dichter Suche, auf 45 beantwortbaren Fragen über 564 Chunks. Mit den ausgelieferten 15 Kandidaten hob der Reranker MRR von 0,515 auf 0,735, nDCG@5 von 0,638 auf 0,811 und Recall@5 von 76 % auf 84 % (TruthBase docs/evals/phase-7-groq-20b-15.json). Das Umformulieren mit dem ausgelieferten gpt-oss-20b hob Recall@5 bei den vier mehrdeutigen Fragen von 75 % auf 100 % und insgesamt von 76 % auf 80 % und ließ Recall@8 bei 87 % (TruthBase docs/evals/phase-8-groq-20b-gated.json).
Die letzte Antwort-Eval prüft die ausgelieferte Pipeline Ende zu Ende auf allen 50 Fragen: Umformulieren und Reranking auf gpt-oss-20b, Antworten von gpt-oss-120b (TruthBase docs/evals/phase-8-groq.json). Sie verweigerte alle 5 unbeantwortbaren Fragen, beantwortete 93 % (42 von 45) der beantwortbaren und keine davon ohne Zitat. Mindestens ein Zitat war bei 86 % der beantworteten Fragen richtig, bei 80 % der beantwortbaren, gegenüber 69 % im Lauf aus Phase 5. Die Zitatpräzision lag bei 56 %: eine Untergrenze, denn ein Zitat auf eine richtige Passage, die nicht die Golden-Evidenz ist, zählt als falsch. Die falschen Verweigerungen sanken von 7 im Lauf aus Phase 5 auf 3 und liegen damit weiter über dem Ziel von höchstens 1: zwei Fragen, deren Evidenz das Retrieval nicht findet, und eine Tabelle, die die Extraktion zu Fließtext gemacht hat. Der erste vollständige promptfoo-Lauf bewertete danach die Faithfulness mit 95,2 % und die Zitatpräzision mit 92,5 % (TruthBase docs/evals/phase-9-llm.json, 23. Oktober 2026), mit einem qwen-Modell als Judge und Zielen von 0,90. Eine Prüfung von 5 Antworten von Hand zeigte, dass der Judge zu nachsichtig ist, wenn er Antworten in Aussagen zerlegt und ihnen Zitate zuordnet. Für diese Antworten korrigiert liegen die gepoolten Werte bei etwa 92–94 % und 88–90 %. Der Lauf scheitert weiter an seinem Gate mit 4 falschen Verweigerungen, daher verzeichnet das Repository noch keine LLM-Baseline.
Die Markierung selbst wird auf drei Ebenen geprüft. pnpm check:viewer steuert den echten Viewer in Headless Chromium über alle 60 Golden-Spans bei Zoom 50, 100 und 200 %: Seit dem 25. Oktober 2026 bestehen alle 180 Prüfungen gegen den gehosteten Korpus, mit einem Platzierungsfehler von höchstens 0,03 px. Vitest hält die Span-Kanten innerhalb von ±0,05 pt an MuPDF, einer unabhängigen PDF-Engine. Playwright wiederholt die Prüfungen von Geometrie und Farbdeckung in der CI, zuletzt am 21. Oktober 2026. Davor verursachten zwei Fälle vier Fehlschläge: ein großer Σ-Operator, der unter die Em-Box ragte, die das vertikale Modell abdeckt, und ein als Vektorgrafik gezeichneter Rahmen, der Seitengrafik ist und kein Text. Der Operator bekommt seine Box jetzt aus den Glyphen-Umrissen, und die Prüfung der Farbdeckung zählt nur Textfarbe.
Kosten und Latenz als Entwurfsvorgaben
Das ursprüngliche Budget war eine harte Obergrenze von 5 EUR/Monat. Die Live-Demo kostet 0 EUR/Monat: Jeder Dienst läuft im Free Tier, und die Grenzen der Free Tiers waren Eingangsgrößen für den Entwurf, keine Überraschungen (TruthBase README.md, Cost and free-tier limits). Groq erlaubt jedem Modell 8K Tokens pro Minute, 1K Anfragen und 200K Tokens pro Tag; deshalb laufen die Antworten auf gpt-oss-120b und Umformulieren und Reranking auf gpt-oss-20b, jedes gegen sein eigenes Kontingent. Postgres setzt 10 Chats pro Minute und 30 pro Tag je Client durch und ein Budget von 80 Chats pro Tag für die ganze Demo; nach rund 60 erweiterten Chats am Tag ist gpt-oss-20b erschöpft, und der Chat antwortet aus der fusionierten Reihenfolge. Die kostenlosen 500 MB von Supabase fassen die 564 Chunks in rund 2 MB an Vektoren, und ein Keep-alive-Workflow ist zweimal pro Woche gegen die Pause nach 7 Tagen Inaktivität eingeplant; sein letzter erfolgreicher Lauf war am 19. Oktober 2026. Vercel Hobby erlaubt 4 Active-CPU-Stunden im Monat, und das reicht, weil das Warten auf ein Modell nicht als aktive CPU zählt.
Auf einem lokalen Produktions-Build bewertet Lighthouse die Startseite mit 98 auf Mobilgeräten und 100 auf dem Desktop, mit 100 für Barrierefreiheit, einem mobilen LCP von 2,40 s und einem CLS von 0 (Lighthouse 13.5.0, Median aus 3 Läufen, 2026-10-20). Eine Stichprobe auf dem Live-Deployment am 21. Oktober 2026 ergab 1,7 s für eine Suche und 1,5 s bis zum ersten Token einer Chat-Antwort: eine Messung, kein Perzentil. Traces auf dem Dev-Server zeigen 1,35–1,6 s bis zum ersten Token ohne Umformulieren und 2,2–2,9 s, wenn die Frage umformuliert wird (TruthBase docs/TECH_NOTES.md §15); das p50 bei echtem Traffic hängt also davon ab, wie viele Fragen umformuliert werden, und bleibt ausstehend, bis Langfuse diesen Traffic hat. Auch der Tokenverbrauch pro Anfrage ist nicht veröffentlicht: Langfuse erfasst Tokens pro Modellaufruf, und allein der Rewriter kostet rund 650 Tokens pro Frage, aber ein Wert für die ganze Anfrage ist noch nicht erhoben.
Was steht, was als Nächstes kommt
TruthBase läuft seit dem 21. Oktober 2026 live unter truthbase.elbaymalik.com, ohne Anmeldung: Frage stellen, auf ein Zitat klicken, und das PDF öffnet sich auf der richtigen Seite mit markierter Passage. Die Phasen 0 bis 15 der Roadmap sind gebaut, vom Golden Set, der Ingestion und dem Viewer mit Markierung über hybride Suche, Reranking, Umformulieren, Tracing, Missbrauchsschutz und Barrierefreiheit bis zu den Testsuiten in Vitest und Playwright und dem Deployment, bis auf eine: Die Eval-Harness (Phase 9) bleibt offen, bis ihr erster vollständiger Lauf mit promptfoo vorliegt. Reranking und Umformulieren wurden am 21. Oktober 2026 mit ihren ausgelieferten Einstellungen nachgemessen, dazu die ganze Pipeline Ende zu Ende.
Die bekannten Lücken stehen im Repository. Sechs Fragen aus dem Golden Set werden unter den 15 besten nie gefunden (drei über mehrere Seiten oder Schritte, drei Zahlen- oder Faktendetails), und das Umformulieren erreicht sie nicht. Die Extraktion macht aus Tabellen Fließtext, was zu Verweigerungen führt, selbst wenn die Evidenz gefunden ist. Die falschen Verweigerungen sind in der Antwort-Eval Ende zu Ende auf 3 gesunken, liegen aber weiter über dem Ziel von 1. Prompt Injection über Dokumenttext ist erschwert, aber nicht verhindert. Die nächste Phase, 11b, sind eigene Uploads hinter Supabase Auth, mit Kontingenten pro Nutzerkonto und einer Prüfung auf Prompt Injection, bevor Uploads öffnen; sie ist zurückgestellt.
Zentrale Entscheidungen
01 Hybrides Retrieval in Postgres
pgvector und native tsvector-Volltextsuche, in einem einzigen SQL-RPC auf Supabase per gewichteter Reciprocal Rank Fusion fusioniert: ein Keyword-Arm, der die Lexeme der Frage per OR verknüpft (ts_rank_cd, Gewicht 0,5), ein exakter Kosinus-Arm (Gewicht 1), k = 60 und 40 Kandidaten je Arm.
Externe Vektordatenbanken sind nicht kostenlos und bei 564 Chunks unnötig, und das BM25 von ParadeDB steht auf gehostetem Supabase nicht zur Verfügung. Mit einer einzigen Datenbank bleibt die Fusion in SQL und der Retrieval-Pfad in der CI testbar. Die Gewichte stammen aus einem Raster von 72 Konfigurationen mit Kreuzvalidierung: 45 Fragen reichen nicht, um eine Fusion zu lernen.
Gemessen: Recall@8 von 87 % gegenüber 76 % bei rein dichter Suche, nachdem der Keyword-Arm von AND auf OR umgestellt war (TruthBase docs/adr/0006-hybrid-search.md).
verworfen: Pinecone · Weaviate · pg_search/BM25 (ParadeDB) · Eine gelernte Fusion
02 Auf 768 Dimensionen gekürzte Matryoshka-Embeddings
gemini-embedding-001, gekürzt auf 768 Dimensionen und vor dem Speichern von Hand L2-normalisiert.
Vektoren in voller Breite würden die kostenlosen 500 MB von Supabase schneller aufbrauchen, und Google L2-normalisiert nur die 3072-dimensionale Ausgabe; gekürzte Vektoren müssen für die Kosinus-Suche also von Hand normalisiert werden. 564 Chunks belegen rund 2 MB an Vektoren.
verworfen: Volle Gemini-Vektoren mit 3072 Dimensionen · OpenAI-Embeddings · Jina als primäres Modell
03 Groq primär, Gemini als Fallback für die Generierung
Groq gpt-oss-120b streamt die Antworten mit Temperatur 0 und höchstens 700 Output-Tokens; Gemini Flash-Lite übernimmt bei einem 429 oder 5xx automatisch.
Generierung bei OpenAI und Anthropic ist nicht kostenlos, und das Budget ist eine harte Vorgabe. Der Plan nannte llama-3.3-70b, das den Free Tier von Groq verließ, bevor der Bau dort ankam, und Gemini 2.5 Flash-Lite, das für neue Keys geschlossen ist; gpt-oss-120b und ein neueres Flash-Lite haben sie ersetzt.
Der Modellwechsel ist in TruthBase docs/adr/0001-stack-selection.md und docs/adr/0005-citations-and-grounding.md festgehalten.
verworfen: OpenAI · Anthropic
04 LLM als Reranker auf Groq
Ein Listwise-Reranking der fusionierten 15 besten auf gpt-oss-20b mit strukturierter Ausgabe: Die 4 besten Treffer des Rerankings stehen vorn, die fusionierte Reihenfolge füllt den Rest auf, und nach 4 s Timeout gilt die fusionierte Reihenfolge.
Dedizierte Rerank-APIs haben Testkontingente und bringen einen weiteren Anbieter, und ein Cross-Encoder braucht einen Host. Gemini Flash-Lite sortierte am besten, hatte aber ein p95 von 15,5 s; gpt-oss-120b würde sich das Kontingent mit dem Antwortmodell teilen. Die fusionierten 4 besten an ihrem Platz zu lassen, hält Recall@8 bei 87 %; ein vollständiges Reranking verlor eine dokumentübergreifende Frage.
Angenommen nach der Nachmessung mit den ausgelieferten 15 Kandidaten: MRR 0,515 → 0,735 und nDCG@5 0,638 → 0,811, gleichauf mit dem Lauf mit 20 Kandidaten bei rund 3,0K statt 3,9K Tokens pro Aufruf; mit Dokumentfilter MRR 0,547 → 0,791 (TruthBase docs/adr/0007-reranking.md, docs/evals/phase-7-groq-20b-15.json).
verworfen: Cohere Rerank · Jina Reranker · Selbst gehosteter Cross-Encoder · Gemini Flash-Lite als Reranker · gpt-oss-120b als Reranker
05 Seitengebundene Satz-Chunks mit 800 Zeichen
Sätze werden bis 800 Zeichen (rund 200 Tokens) mit 15 % Overlap zusammengepackt, nie über eine Seite hinweg; jedes Zeichen behält einen Verweis auf sein Textelement für die Highlight-Rects.
Die Markierung, die man sieht, ist der Chunk; die Chunk-Größe ist also eine Entscheidung über Zitate. 800 Zeichen ist die kleinste Einstellung, bei der alle 60 Golden-Spans in einem Chunk liegen; die 512 Tokens des Plans würden eine halbe Seite markieren. Semantisches Chunking war rund 14-mal langsamer und verbraucht Embedding-Kontingent; Contextual Retrieval kostet einen LLM-Aufruf pro Chunk.
Das ersetzte den rekursiven Splitter des Plans mit rund 512 Tokens, bevor irgendein Retrieval gemessen war (TruthBase docs/adr/0003-chunking.md).
verworfen: Rekursives Splitting mit 512 Tokens (Vorgabe des Plans) · Seitenübergreifende Chunks · Semantic chunking · Contextual retrieval
06 Zwei Eval-Ebenen: ein deterministisches Gate und eine LLM-Eval mit promptfoo
revisitingEin Retrieval-Gate bei jedem Pull Request (das echte hybrid_search auf einer eingecheckten Korpus-Fixture, jeder Rückgang lässt es scheitern), dazu eine LLM-Eval mit promptfoo, auf Abruf und wöchentlich, mit einem qwen-Modell als Judge und auf der Platte gecachten Modellaufrufen.
Eine LLM-Eval bei jedem Pull Request würde das kostenlose Kontingent verbrauchen und auf Rauschen reagieren; das Retrieval-Gate ist deterministisch, jeder Rückgang ist also echt. Ragas ist Python in einem TypeScript-Repository. Der Judge ist ein anderes Modell als das antwortende, weil ein Modell, das seine eigenen Antworten bewertet, sie bevorzugt.
Im Repository vorgeschlagen. Die erste vollständige LLM-Eval (23. Oktober 2026) maß eine Faithfulness von 95,2 % und eine bewertete Zitatpräzision von 92,5 %. Sie scheitert an ihrem Gate mit 4 falschen Verweigerungen, und eine Prüfung von Hand zeigte einen zu nachsichtigen Judge (korrigiert etwa 88–90 % Zitatpräzision), daher gibt es noch keine Baseline (TruthBase docs/adr/0008-evals-and-ci-gate.md, docs/evals/phase-9-llm.json).
verworfen: Die LLM-Eval bei jedem Pull Request · promptfoo für das Retrieval-Gate · Ragas · gpt-oss-120b als Judge
07 Zeilenweise Highlight-Rects im PDF-Raum aus einem Glyphen-Replay
Bei der Ingestion wird die Operator-Liste jeder Seite mit der Textvorschub-Mathematik von PDF.js nachgespielt, um die x-Ausdehnung jeder Glyphe zu erhalten; gespeichert wird ein Rect pro gerenderter Zeile im PDF-Raum, und der Viewer zeichnet diese Rects unverändert, mit einem Fallback über Textabgleich.
Die Textebene des Browsers setzt Text in einer Ersatzschrift und lag gemessen bis zu 18,6 pt daneben; Vorschubbreiten allein lagen bis zu 12 pt daneben; ein Rahmen pro Chunk überspannt Spalten. Das Replay brachte die Span-Kanten im Spike auf 0,001 pt an MuPDF heran.
99,6 % der Textelemente erhalten exakte Positionen pro Zeichen; der Rest fällt auf eine proportionale Teilung zurück (TruthBase docs/adr/0004-extraction-and-highlights.md).
verworfen: Kanten aus der Textebene des Browsers nachschärfen · Nur die Vorschubbreiten der Schrift · Canvas-Rendering auf dem Server · Ein Rahmen pro Chunk
08 Selbstgesteuertes Umformulieren der Anfrage
Ein Rewrite-Aufruf auf gpt-oss-20b, der null bis drei Anfragen liefert, mit einem Dokumentkatalog und unveränderten Entitäten bei Tokens mit Ziffern; die Listen werden per einfacher RRF fusioniert und gegen die ursprüngliche Frage neu sortiert.
Jede Frage zu erweitern kostete Präzision (Recall@8 87 % → 84 %). Selbstgesteuert stieg Recall@5 bei mehrdeutigen Fragen von 75 % auf 100 %, und keine Teilmenge nach Tag verlor Recall. Die flüssigen, falschen Antworten von HyDE zogen die falschen NIST-Controls heran.
Gewählt mit gpt-oss-120b, angenommen nach der Messung des ausgelieferten Rewriters gpt-oss-20b: Recall@5 bei mehrdeutigen Fragen 75 % → 100 %, insgesamt 76 % → 80 %, Recall@8 unverändert bei 87 %, 0,4 Umformulierungen pro Frage (TruthBase docs/adr/0009-query-rewriting.md, docs/evals/phase-8-groq-20b-gated.json).
verworfen: Jede Frage erweitern · HyDE · Ein regelbasierter Mehrdeutigkeitsdetektor · Ein eigener Klassifikator-Aufruf
09 Rate Limits und Tagesbudgets in Postgres
Obergrenzen pro Client von 10 Chats pro Minute und 30 pro Tag, dazu ein globales Budget von 80 Chats pro Tag, alles oder nichts gezählt von einem einzigen consume_quotas-Aufruf vor jedem Modellaufruf; ein sauberes 429 mit Retry-After; Client-IPs nur als HMAC gespeichert; fail open, wenn der Zählerspeicher ausfällt.
Das bindende Limit von Groq gilt pro Tag, also ist das Budget ein Kalendertag; ein Minutenlimit, das streng genug wäre, um es zu schützen, würde normale Besucher blockieren. Auf Serverless-Plattformen zählt ein In-Memory-Limiter pro Instanz und beginnt bei jedem Kaltstart neu. Postgres ist ohnehin da, zählt über Instanzen hinweg exakt, und das Minutenfenster läuft im selben Aufruf mit.
Das ersetzte die 150 Anfragen pro 5 Minuten über nuxt-security aus dem Plan; nuxt-security bleibt als günstige erste Linie pro Instanz davor (TruthBase docs/adr/0010-abuse-protection.md).
verworfen: Ein In-Memory-Limiter allein (nuxt-security) · Pflicht-Anmeldung · CAPTCHA · Eine Vercel-WAF-Regel als einzige Ebene · Tokens statt Anfragen zählen
10 Vercel Hobby, eine Funktion neben der Datenbank
Die ganze Nuxt-App als eine Vercel Function auf Node 24 in dub1 (Dublin, neben Supabase in AWS eu-west-1), statische Assets im CDN, Ingestion über die CLI des Betreibers, Langfuse-Spans per waitUntil geflusht.
Hobby ist kostenlos, braucht keine Kreditkarte und führt denselben Node-Servercode aus, samt PDF.js-Assets. Workers Paid braucht eine Karte und eine Portierung auf workerd; die 10 ms CPU der kostenlosen Workers sind schon ohne Ingestion knapp und bedeuten zwei Deploy-Ziele; der Kaltstart bei Render dauert rund eine Minute.
Live seit dem 21. Oktober 2026. Hobby ist für nicht kommerzielle Nutzung gedacht, und eine Portfolio-Demo ohne Werbung, Zahlungen oder Kundenarbeit ist das (TruthBase docs/adr/0011-deployment-topology.md).
verworfen: Cloudflare Workers Paid (5 $/Monat) · Kostenlose Workers plus ein Node-Host für die Ingestion · Google Cloud Run · Render im Free Tier · Eine VM in der Oracle Cloud
Evaluationsergebnisse
| Kennzahl | Wert | Status |
|---|---|---|
| Bestandene Prüfungen der Platzierung, 60 Golden-Spans × Zoom 50/100/200 % | 180/180 | gemessen Der echte Viewer in Headless Chromium; Platzierungsfehler ≤ 0,03 px. Bis zum 25. Oktober 2026 waren es 176/180: Große Operatoren bekommen jetzt Glyphen-Boxen aus ihren Umrissen, und die Prüfung der Farbdeckung zählt nur Textfarbe. |
| Kanten der Markierung gegenüber MuPDF, jeder Golden-Span | ±0,05 pt | gemessen Vitest prüft jede Span-Kante gegen MuPDF, eine unabhängige PDF-Engine. |
| Recall@8, nur dicht → hybrid | 76 % → 87 % | gemessen 45 beantwortbare Fragen, 564 Chunks; der hybride Wert ist die Untergrenze des Retrieval-Gates in der CI, das zuletzt am 21. Oktober 2026 lief. |
| MRR, fusioniert → nach Reranking | 0,515 → 0,735 | gemessen 45 Fragen mit den ausgelieferten 15 Kandidaten. Mit Dokumentfilter (41 Fragen): 0,547 → 0,791. Rerank-Aufruf p50 0,65 s / p95 1,15 s. |
| nDCG@5, fusioniert → nach Reranking | 0,638 → 0,811 | gemessen Derselbe Lauf: Recall@5 76 % → 84 %, Recall@8 unverändert bei 87 %. Mit Dokumentfilter: nDCG@5 0,627 → 0,825, Recall@5 78 % → 88 %. |
| Recall@5 bei mehrdeutigen Fragen, ohne → mit Multi-Query | 75 % → 100 % | gemessen 4 mehrdeutige Fragen, mit dem ausgelieferten Rewriter gpt-oss-20b. Recall@5 insgesamt 76 % → 80 %, Recall@8 unverändert bei 87 %; 0,4 Umformulierungen pro Frage. |
| Unbeantwortbare Fragen verweigert | 5/5 | gemessen Die ausgelieferte Pipeline Ende zu Ende, 50 Fragen: Umformulieren und Reranking auf gpt-oss-20b, Antworten von gpt-oss-120b. 93 % (42/45) der beantwortbaren Fragen beantwortet, keine ohne Zitat. 3 falsche Verweigerungen (Phase 5: 7) bei einem Ziel von höchstens 1: zwei Retrieval-Fehlschläge und eine zu Fließtext gewordene Tabelle. |
| Richtiges Zitat, bezogen auf beantwortbare Fragen | 69 % → 80 % | gemessen Mindestens ein Zitat auf Golden-Evidenz, Retrieval aus Phase 5 → ausgelieferte Pipeline, beide Male mit Antworten von gpt-oss-120b. 86 % der beantworteten Fragen. |
| Zitatpräzision, Untergrenze | 56 % | gemessen Die ausgelieferte Pipeline (Phase 5: 55 % Groq, 57 % Gemini). Es zählen nur Chunks mit der Golden-Evidenz; ein Zitat auf eine richtige Passage, die nicht die Golden-Passage ist, zählt als falsch. |
| Faithfulness und bewertete Zitatpräzision (LLM-Judge) | 95,2 % / 92,5 % | gemessen Erster vollständiger promptfoo-Lauf (23. Oktober 2026): die ausgelieferte Pipeline auf allen 50 Fragen, gepoolt über die 41 beantworteten und von einem qwen-Modell als Judge gegen die Ziele von 0,90 bewertet. Eine Prüfung von 5 Antworten von Hand zeigte, dass der Judge zu nachsichtig bewertet; für diese korrigiert etwa 92–94 % / 88–90 %. Der Lauf scheitert an seinem Gate mit 4 falschen Verweigerungen (Ziel höchstens 1), daher gibt es noch keine Baseline (ADR-0008). |
Gemessen: TruthBase ROADMAP.md, Phase 5 (pnpm check:viewer, 25. Oktober 2026, gegen den gehosteten Korpus) · TruthBase docs/adr/0004-extraction-and-highlights.md · TruthBase docs/evals/retrieval-baseline.json · TruthBase docs/evals/phase-7-groq-20b-15.json · TruthBase docs/evals/phase-8-groq-20b-gated.json · TruthBase docs/evals/phase-8-groq.json · TruthBase docs/evals/phase-8-groq.json (vorher: docs/evals/phase-5-groq.json) · TruthBase docs/evals/phase-9-llm.json
Kosten & Latenz
| Kennzahl | Wert | Status |
|---|---|---|
| Monatliche Infrastrukturkosten | 0 EUR/Monat | gemessen Jeder Dienst läuft im Free Tier; die ursprüngliche Obergrenze im Entwurf lag bei 5 EUR/Monat. |
| Lighthouse-Performance, mobil / Desktop | 98 / 100 | gemessen Barrierefreiheit auf beiden 100. |
| LCP mobil / CLS | 2,40 s / 0 | gemessen |
| Stichprobe in Produktion: Suche / erstes Token im Chat | 1,7 s / 1,5 s | gemessen Eine Messung auf dem Live-Deployment, kein Perzentil. |
| Time-to-First-Token, p50 bei echtem Traffic | k. A. | ausstehend Wird aus Langfuse-Traces des Produktions-Traffics gelesen (pnpm sample:traces). Ziel sind unter 2 s; umformulierte Fragen dauern länger. |
| Tokenverbrauch pro Anfrage | k. A. | ausstehend Langfuse erfasst die Tokens pro Modellaufruf, aber ein Wert für die ganze Anfrage ist noch nicht veröffentlicht. Allein der Rewriter braucht rund 650 Tokens pro Frage. |
Gemessen: TruthBase README.md, Cost and free-tier limits · TruthBase README.md, Quality (Lighthouse 13.5.0, lokaler Produktions-Build, Median aus 3 Läufen, 2026-10-20) · TruthBase ROADMAP.md, Phase 14 (2026-10-21)
Sicherheit & Fehlerfälle
Risiken
- Eine öffentliche Demo kann den Free Tier von Groq aufbrauchen (pro Modell: 8K Tokens pro Minute, 1K Anfragen und 200K Tokens pro Tag) und die Demo für alle bis Mitternacht UTC lahmlegen
- Halluzinierte Aussagen im Gewand von Zitat-Chips: falsche Sicherheit ist genau der Fehlerfall, den dieses Projekt verhindern soll
- Dokumente, die über den Free Tier von Gemini eingebettet werden, kann Google zur Verbesserung seiner Produkte nutzen
- Verrutschte Highlight-Rects könnten eine Antwort mit der falschen Textstelle „belegen“
Gegenmaßnahmen
- Obergrenzen pro Minute und pro Tag je Client plus ein globales Tagesbudget, atomar in Postgres gezählt, bevor ein Modell aufgerufen wird, mit sauberem 429 und Retry-After, dazu automatisches Failover auf Gemini Flash-Lite
- Quellen als nummerierte, zitierte Daten, eine Verweigerung per NO_ANSWER und nur gültige Marker als Chips; in der Antwort-Eval Ende zu Ende 5 von 5 unbeantwortbaren Fragen verweigert und keine ohne Zitat beantwortet; Faithfulness erstmals mit 95,2 % gemessen, von einem LLM-Judge, den eine Prüfung von Hand als nachsichtig einstufte (korrigiert etwa 92–94 %), in einem Lauf, der sein Gate an falschen Verweigerungen verfehlt
- Der Demo-Korpus besteht aus vier öffentlichen, weiterverbreitbaren PDFs, die der Betreiber einspielt; Uploads bleiben bis zu einer Prüfung auf Prompt Injection geschlossen (Phase 11b)
- Rects im PDF-Raum gespeichert und unverändert gezeichnet, ein Fallback über Textabgleich mit dem Hinweis „ungefähr“ und drei Prüfungen: Vitest gegen MuPDF, der echte Viewer über jeden Golden-Span und Playwright in der CI, zuletzt am 21. Oktober 2026