Eine Quellenangabe, deren Link lädt, ist noch kein Beleg. Stellen Sie sich einen Recherche-Assistenten vor, der gefragt wird, ob Wasser auf Meereshöhe bei 50 Grad Celsius kocht. Er antwortet mit einem selbstsicheren Absatz und einem Link. Der Link funktioniert. Die Seite nennt 100 Grad. Die URL war echt; als Beleg für die Aussage taugte die Quellenangabe nicht.
Genau hier liegt die Lücke bei Halluzinationsprüfungen, die nur die generierte Antwort betrachten. Ein Satz kann flüssig, plausibel und mit einer funktionierenden URL versehen sein, während die zitierte Quelle etwas anderes sagt. Ein Link ist nur ein Verweis. Ihn zu verifizieren heißt, abzurufen, was hinter dem Verweis steht, es mit der Aussage zu vergleichen und die Belege aufzubewahren, auf denen die Entscheidung beruhte.
Der Service in diesem Beitrag automatisiert diesen Vergleich. Sie übergeben ihm eine Aussage und die URLs, die das Modell zitiert hat. Er ruft jede Seite mit der Crawling API ab, behält die für die Aussage relevantesten Passagen, fordert von einem Judge-Modell ein Urteil an, prüft, ob das Belegzitat des Judge-Modells tatsächlich auf der Seite steht, und fasst die Ergebnisse pro Quellenangabe zu einem Label zusammen: supported, contradicted, not_found oder unreachable.
- HTTP 200 ist kein Beleg. Eine Seite kann laden und trotzdem eine Bot-Sperre, eine leere Hülle, ein Soft 404 oder die Startseite der Website sein.
- Senden Sie dem Judge-Modell drei relevante Passagen, nicht die ganze Seite. Weniger Rauschen, geringere Kosten, bessere Urteile.
- Vertrauen Sie dem Zitat des Judge-Modells nie. Akzeptieren Sie
supportedodercontradictednur, wenn das Zitat ein echter Teilstring der abgerufenen Seite ist. - Aggregieren Sie mit Regeln, nicht mit einem weiteren Modell: Ein Widerspruch wiegt schwerer als alle stützenden Quellenangaben.
- Speichern Sie jeden Abruf, damit sich ein Urteil auch nach einer Änderung der Seite erneut nachvollziehen lässt.
Der Code liegt in ScraperHub/llm-citation-verification-service-with-crawling-api, mit je einem Checkpoint pro Stufe unter steps/ und dem vollständigen Paket unter final/citation_verifier/. Die folgenden Snippets stammen aus final/ (sofern nicht anders gekennzeichnet). Sie benötigen ein Crawlbase-Konto; bewahren Sie dessen Tokens in Umgebungsvariablen auf, niemals im Repository.
Die zitierte Seite abrufen, nicht nur ein HTTP 200
Warum ein einfacher GET nicht ausreicht
Ein erfolgreicher Request bedeutet nicht, dass Sie den Inhalt abgerufen haben, auf den eine Quellenangabe verweist. Eine Single-Page-App kann 200 zurückgeben, mit einem leeren <div id="root">. Eine Bot-Sperre kann 200 zurückgeben, während sie eine Challenge ausliefert. Ein Soft 404 liefert 200 mit einer Vorlage im Stil von „diese Seite existiert nicht mehr“, und ein Deep Link kann auf die Startseite umleiten. Die Abrufstufe muss mehr feststellen als die Erreichbarkeit: Sie behält den ursprünglichen Status, erkennt Weiterleitungen und liefert Text, der sich als Beleg verwenden lässt.
Markdown mit Readability-Durchlauf
Die Crawling API übernimmt den Abruf. Der Verifier nutzt format=md, das die Seite als GitHub Flavored Markdown zurückgibt, sowie md_readability=true, das Navigation, Seitenleisten, Footer und Werbung vor der Konvertierung entfernt, sodass das Retrieval beim eigentlichen Artikel ansetzt. Außerdem ist store=true gesetzt, damit sich jeder Abruf später prüfen lässt.
# final/citation_verifier/fetch.py def crawl_options(readability: bool) -> dict[str, str]: """Query parameters for one Crawling API call. ``md_readability`` is omitted on the fallback request. The docs define it only as a switch on ``format=md``, not as a guarantee about thin pages. """ options = { "format": "md", "store": "true", "custom_success_codes": "404,410", } if readability: options["md_readability"] = "true" return options
Ein Parameter hier bewirkt nichts. custom_success_codes ist für Ursprungsstatus gedacht, die die API sonst als Fehler behandeln würde, aber 404 und 410 zählen bereits als erfolgreiche Crawls: Sie liefern cb_status 200 und den echten original_status, und sie werden nicht wiederholt. Sie können ihn entfernen, ohne das Verhalten zu ändern.
Readability kann bei kurzen oder ungewöhnlichen Seiten zu viel entfernen. Daher wertet der Verifier weniger als 200 Zeichen ohne Leerraum als gescheiterten Durchlauf und wiederholt den Abruf derselben URL ohne md_readability. Der Schwellenwert von 200 Zeichen ist eine Anwendungsregel, kein Crawlbase-Limit. Beide Aufrufe werden abgerechnet, und mit store=true werden beide Seiten gespeichert. Behalten Sie den Retry daher den Seiten vor, die ihn brauchen.
Tote Links, Soft 404s und Weiterleitungen auf die Startseite
Mit jeder Response kommen zwei Status. original_status ist die Antwort der zitierten Website; cb_status ist das Ergebnis des Crawls bei Crawlbase. Der Verifier kennzeichnet jede Quellenangabe, deren Website mit 404 oder 410 antwortete, als unreachable mit einem Grund wie http_404, und sie erreicht das Judge-Modell nie. Dieser Crawl wird trotzdem abgerechnet, denn Crawlbase hat tatsächlich eine Antwort abgerufen: Ein 404 ist ein erfolgreicher Crawl einer fehlenden Seite.
Weiterleitungen brauchen eine eigene Prüfung. Ein Deep Link, der auf / landet, liefert eine völlig intakte Seite, die nichts über die Aussage sagt, und allgemeiner Startseitentext könnte wie eine Bestätigung wirken. Wenn der angeforderte Pfad tiefer als / ist, der endgültige Pfad aber nur /, wird die Quellenangabe zu not_found mit dem Grund redirected_to_homepage, und das Judge-Modell wird übersprungen.
# final/citation_verifier/fetch.py def _normalized_path(url: str) -> str: path = urlsplit(url).path or "/" if len(path) > 1 and path.endswith("/"): path = path[:-1] return path or "/" def is_homepage_redirect(requested: str, final: str | None) -> bool: """True when a deep link landed on ``/``. The Crawling API returns the post-redirect URL in the ``url`` header. A citation that survives only as the site homepage did not survive. """ if not final: return False return _normalized_path(requested) != "/" and _normalized_path(final) == "/"
Der url Header enthält die endgültige URL nach den Weiterleitungen, denen Crawlbase bei einem normalen Request folgt. Bei einem mit JavaScript gerenderten Request kann eine Weiterleitung, die der Browser selbst ausführt, url als die angeforderte URL belassen. Werten Sie die Startseitenprüfung daher auf dem normalen Pfad als starkes Signal und auf dem gerenderten als Best-Effort-Prüfung.
Soft 404s werden gesondert behandelt: Eine Seite mit Status 200 gilt nur dann als Soft 404, wenn ihr lesbarer Text eine kurze Vorlage im Stil von „Seite existiert nicht“ mit weniger als 800 Zeichen ohne Leerraum ist, sodass ein langer Artikel, der lediglich einen 404 erwähnt, nicht verworfen wird.
Wann Sie den JavaScript token einsetzen sollten
Rendering kostet mehr, deshalb startet der Verifier jede Quellenangabe mit dem Normal token und eskaliert nur, wenn der erste Versuch zeigt, dass die Seite einen Browser benötigt. Ein JavaScript-Request wird mit doppelt so vielen Credits abgerechnet wie ein normaler. Sie können mit einem zweiten Client auf dem JavaScript token eskalieren, wie es das Repository tut, oder einen einzigen Client auf dem Normal token behalten und dem Retry javascript=true hinzufügen; in beiden Fällen wird der Request als JavaScript-Request abgerechnet.
Die Eskalationsprüfung des Repositorys, in fetch.py, greift, wenn cb_status den Wert 520 oder 525 hat. Keines der beiden Signale kommt in der Praxis an. 520 ist überhaupt kein Wert, der als cb_status ankommt: Es ist der HTTP-Status, den die Crawling API zurückgibt, wenn ein Crawl fehlschlägt, und das gepinnte crawlbase 1.0.0 SDK liefert eine solche Response mit leerem Body und ohne Header, sodass kein cb_status zum Auslesen vorhanden ist. Eine Seite, deren Server mit 200, aber ohne Inhalt antwortete, kommt als cb_status 207 zurück, und 525 ist ein interner Code ohne Bezug zu Anti-Bot-Challenges. Eskalieren Sie anhand der Signale, die die API tatsächlich sendet:
# Production shape, not from the repository: when to retry on the JavaScript token. def needs_javascript(response: dict, markdown: str, min_chars: int) -> bool: if response.get("status_code") == 520: return True # the crawl failed; failed requests are not billed headers = response.get("headers") or {} if str(headers.get("pc_status")) == "207": return True # the origin answered 200 with an empty page return len("".join(markdown.split())) < min_chars # still thin after the readability retry
Die vom Repository gepinnte SDK-Version stellt den Status unter seinem Legacy-Namen bereit, pc_status; die API sendet ihn auch als cb_status mit, und für neue Integrationen gilt: Lesen Sie cb_status. Führen Sie nach dem JavaScript-Versuch dieselbe Interpretation erneut aus, einschließlich des Readability-Fallbacks, damit eine gerenderte Seite exakt dieselben Prüfungen durchläuft wie eine statische.
Erst die Belege finden, dann das Judge-Modell fragen
Eine ganze Seite an das Judge-Modell zu senden ist teuer und verrauscht. Ein Artikel mit 2.000 Wörtern enthält vielleicht zwei relevante Sätze, und der Rest gibt dem Modell mehr Gelegenheiten, sich an Navigation, Hinweisen oder themenfremdem Text festzubeißen. Deshalb wird jede Seite zuerst in Passagen zerlegt: an Leerzeilen trennen, kurze Absätze bis zu 200 Wörter zusammenfassen und längere an Wortgrenzen umbrechen. Gezählt wird das Budget von 200 Wörtern mit split(), sodass kein Tokenizer nötig ist, um die Passagen ungefähr gleich lang zu halten.
# final/citation_verifier/passages.py (excerpt) def split_passages(markdown: str, max_words: int = DEFAULT_MAX_WORDS) -> list[str]: """Split on blank lines, then pack up to ``max_words`` words. A paragraph longer than the cap is cut on word boundaries. Shorter paragraphs share a passage until the next one would overflow. Blank blocks are dropped. """ if max_words < 1: raise ValueError("max_words must be at least 1") text = markdown.replace("\r\n", "\n").replace("\r", "\n") pieces: list[list[str]] = [] for paragraph in re.split(r"\n\s*\n", text): words = paragraph.split() if not words: continue if len(words) > max_words: for start in range(0, len(words), max_words): pieces.append(words[start : start + max_words]) else: pieces.append(words)
Anschließend werden die Passagen mit all-MiniLM-L6-v2 Embeddings und Kosinus-Ähnlichkeit gegen die Aussage gerankt, und die besten drei bleiben erhalten. Das Modell wird beim ersten Retrieval-Aufruf geladen, sodass der Import des Pakets nie einen Download auslöst.
# final/citation_verifier/retrieval.py def top_k_passages( claim: str, passages: list[str], embedder: Embedder, k: int = DEFAULT_TOP_K, ) -> list[str]: """Return up to ``k`` passages, highest cosine similarity first.""" if k < 1 or not passages: return [] vectors = embedder.embed([claim, *passages]) query = vectors[0] ranked = [ (cosine(query, vectors[index + 1]), index, passage) for index, passage in enumerate(passages) ] ranked.sort(key=lambda item: (-item[0], item[1])) return [passage for _, _, passage in ranked[:k]]
Drei ist eine bewusste Obergrenze. Mit nur einer Passage wird das Retrieval anfällig, wenn der beste Beleg auf Platz zwei landet; zehn bringen den Großteil des Rauschens zurück, das das Retrieval entfernen sollte. Belege außerhalb der besten drei enden als not_found, und das ist die ehrliche Antwort für Belege, die der Verifier dem Judge-Modell nie gezeigt hat.
Das Judge-Modell Belege liefern lassen, nicht nur ein Urteil
Das Judge-Modell interpretiert Belege; es ist nicht die Quelle der Wahrheit. Ein Modell kann ein selbstsicheres supported mit einem Zitat zurückgeben, das nie auf der Seite stand, und striktes JSON macht die Ausgabe vorhersagbar, ohne sie ehrlich zu machen. Deshalb beschränkt der Prompt das Judge-Modell auf drei Labels und verlangt ein zusammenhängendes, aus den Passagen kopiertes Zitat für alles außer not_found. unreachable erreicht das Modell nie; die Abrufstufe vergibt es.
# final/citation_verifier/judge.py SYSTEM_PROMPT = """You verify one claim against the passages in the user message. The passages are the only evidence you may use. Return JSON with these fields: - label: supported, contradicted, or not_found - evidence_quote: a contiguous quote copied from the passages, or null when the label is not_found - confidence: a number from 0 to 1 supported means the passages state the claim. contradicted means the passages state the opposite. not_found means the passages do not settle the claim. If you cannot copy a quote that appears in the passages, return not_found and null. Do not add words, numbers, or sources that are not in the passages.""" class RawJudgement(BaseModel): label: Label evidence_quote: str | None = None confidence: float = Field(ge=0, le=1)
OpenAI ist das Standard-Judge-Modell, aufgerufen über httpx mit einem strikten JSON-Schema in response_format; JUDGE_PROVIDER=anthropic und JUDGE_PROVIDER=ollama führen denselben Prompt anderswo aus. Der Provider ändert, wie ein Urteil entsteht, niemals die Regeln, die entscheiden, ob es akzeptiert wird.
Das Zitat gegen die Seite absichern
Der Guard ist der Schritt, der das Judge-Modell von einem Orakel zu einem Interpreten macht. Er begradigt typografische Anführungszeichen, fasst Leerraum zusammen und verlangt, dass das Zitat als exakter Teilstring in den abgerufenen Passagen vorkommt. Ein supported oder contradicted Urteil ohne passendes Zitat wird herabgestuft auf not_found mit Konfidenz 0 und dem Grund evidence_quote_not_in_source.
# final/citation_verifier/judge.py def guard_judgement( raw: RawJudgement, passages: list[str], ) -> tuple[Label, str | None, float, str | None]: """Drop a quote the passages do not contain. Supported and contradicted both need a real quote. A quote that fails the check downgrades the citation to not_found with confidence 0. """ quote = raw.evidence_quote if raw.label in {Label.supported, Label.contradicted}: if quote and quote_in_passages(quote, passages): return raw.label, quote, raw.confidence, None return Label.not_found, None, 0.0, REASON_QUOTE if quote and not quote_in_passages(quote, passages): return Label.not_found, None, 0.0, REASON_QUOTE return Label.not_found, None, raw.confidence, None
Im Siedepunkt-Beispiel nennt die Aussage 50 Grad und die Seite 100, daher hat das contradicted Urteil Bestand: Sein Zitat steht auf der Seite. Die Konfidenz des Modells bleibt nur erhalten, wenn der Beleg die Prüfung besteht; die Aggregation ignoriert sie und arbeitet ausschließlich mit Labels.
Bereitstellung als /verify API
Der fertige Service hat zwei Routen: GET /health und POST /verify. Ein Request enthält eine Aussage und eine oder mehrere zitierte URLs; die Response enthält das Label auf Ebene der Aussage sowie ein Urteil pro Quellenangabe mit Zitat, Konfidenz, finaler URL, original_status, der rid aus dem Storage und einem etwaigen vom Verifier vergebenen Grund.
# final/citation_verifier/schema.py class UrlVerdict(BaseModel): url: str label: Label evidence_quote: str | None = None confidence: float = Field(ge=0, le=1) final_url: str | None = None original_status: int | None = None rid: str | None = None reason: str | None = None class VerifyRequest(BaseModel): claim: str = Field(min_length=1) urls: list[str] = Field(min_length=1)
Die Aggregation folgt einer festen Regel, nicht einem weiteren Modellaufruf. Eine contradicted Quellenangabe wiegt schwerer als jede supported Quellenangabe; ohne Widerspruch trägt jede Stütze die Aussage; die Aussage ist unreachable nur dann, wenn jede Quellenangabe es ist; alles andere ist not_found.
# final/citation_verifier/aggregate.py def aggregate(verdicts: list[UrlVerdict]) -> Label: """One contradiction outweighs every supporting citation. Otherwise any supported citation carries the claim. The claim is unreachable only when every citation is unreachable. Every remaining mix, including an empty list, is not_found. """ labels = [verdict.label for verdict in verdicts] if any(label == Label.contradicted for label in labels): return Label.contradicted if any(label == Label.supported for label in labels): return Label.supported if labels and all(label == Label.unreachable for label in labels): return Label.unreachable return Label.not_found
Die HTTP-Schicht bleibt schlank: POST /verify übergibt den Request an den Verifier und liefert 503 zurück, wenn ein erforderlicher Token fehlt, und nennt dabei die Umgebungsvariable, ohne ihren Wert preiszugeben. Der Dry Run des Repositorys, python steps/step_07_api/main.py --dry-run, erzeugt diese Response. Die Seiten sind Fixtures auf example.com, die beiden Crawlbase-Requests liefen live, und das Judge-Modell nutzte einen Fixture-Satz, weil kein OpenAI-Key gesetzt war. Lesen Sie sie daher als Fixture und nicht als Modell-Trace:
{ "claim": "Water boils at 100 degrees Celsius at sea level.", "overall": "supported", "citations": [ { "url": "https://example.com/articles/water-boiling-point", "label": "supported", "evidence_quote": "Water boils at 100 degrees Celsius at sea level.", "confidence": 0.96, "final_url": "https://example.com/articles/water-boiling-point", "original_status": 200, "rid": "rid-boil", "reason": null }, { "url": "https://example.com/old-report", "label": "unreachable", "evidence_quote": null, "confidence": 1.0, "final_url": "https://example.com/old-report", "original_status": 404, "rid": "rid-404", "reason": "http_404" } ] }
Die Aussage ergibt supported: Eine Quellenangabe stützt sie, und die andere ist schlicht verschwunden. Genau diese Unterscheidung ist der Grund für vier Labels. Eine tote Quelle ist kein Beleg gegen eine Aussage; eine erreichbare Quelle, die das Gegenteil sagt, schon.
Rufen Sie die Seite, auf die eine Quellenangabe verweist, als sauberes Markdown ab, gerendert, wenn sie einen Browser braucht, und zusammen mit dem echten Statuscode der Website. Fehlgeschlagene Requests werden nicht abgerechnet. Zum Start erhalten Sie bis zu 5.000 kostenlose Requests, ohne Kreditkarte.
Zitierpräzision über einen Batch messen
Eine verifizierte Aussage demonstriert die Pipeline; sie sagt nichts darüber, wie zuverlässig ein Modell zitiert. Dafür bewertet der Verifier einen Batch: eine JSONL-Datei mit Datensätzen, die jeweils eine id, eine Aussage und deren zitierte URLs enthalten, wobei jedes Paar aus Aussage und URL als eine Quellenangabe zählt. Die Zitierpräzision ist die Zahl der gestützten Quellenangaben geteilt durch alle Quellenangaben. Die Präzision nur über erreichbare Quellen behält denselben Zähler und nimmt nicht erreichbare Quellenangaben aus dem Nenner. So lässt sich „das Modell zitiert tote Links“ von „das Modell zitiert erreichbare Seiten, die nicht sagen, was es behauptet“ trennen.
# final/citation_verifier/batch.py @dataclass(frozen=True) class Precision: """Citation precision over claim-URL pairs. citation precision is supported citations divided by every citation. Reachable-only precision uses the same numerator and drops unreachable citations from the denominator. It is None when nothing was reachable. """ supported: int total: int reachable: int
Durchläufe werden durch ein asyncio.Semaphore (BATCH_CONCURRENCY, Standard 4) begrenzt, und weil der Aufruf des Crawlbase-SDK synchron ist, läuft er in einem Worker-Thread, statt die Event-Loop zu blockieren. Auf der Beispiel-Fixture meldet der Dry Run neun Quellenangaben, davon zwei gestützt und drei nicht erreichbar:
citation_precision=0.2222 (2/9) reachable_precision=0.3333 (2/6)
Führen Sie ihn mit python -m citation_verifier.batch your_outputs.jsonl --out citation_report.csv gegen Ihre eigene Ausgabe aus, sobald die Tokens und ein Key für das Judge-Modell gesetzt sind. Beginnen Sie mit ein paar Dutzend Aussagen und lesen Sie zuerst die contradicted Zeilen: Ein einziger verifizierter Widerspruch kann ein Problem aufdecken, das ein aggregierter Genauigkeitswert verbirgt.
Die Belege mit Cloud Storage aufbewahren
Ein Präzisionswert ist nur so gut wie Ihre Möglichkeit, ihn später zu prüfen, und die zitierte Seite kann sich schon am Tag nach Ihrer Prüfung ändern. Setzen Sie store=true, bewahrt Crawlbase das zurückgegebene Markdown in Cloud Storage 30 Tage lang auf, und die Response enthält eine storage_url und in deren Query-String die zur Seite gehörende rid. Der Verifier speichert nur die rid, weil die vollständige URL den Token enthält, und führt einen kleinen SQLite-Audit-Index aus rid, Aussage, URL, Urteil, Konfidenz und Zeitpunkt. Das Speichern kostet pro Seite einen halben Credit zusätzlich zum Crawl; das Zurücklesen einer gespeicherten Seite ist kostenlos.
# final/citation_verifier/audit.py (docstring omitted) def replay_rid(rid: str, token: str | None = None) -> str: if not rid: raise ValueError("rid is required") settings = get_settings() used = token if token else settings.crawlbase_token if not used: raise MissingTokenError( "CRAWLBASE_TOKEN is not set. Replay needs the token that stored the page." ) client = StorageAPI({"token": used}) response = _storage_get(client, rid)
Gespeicherte Seiten gehören zu Ihrem Konto, nicht zu dem Token, mit dem sie gecrawlt wurden. Daher eignet sich jeder der beiden Tokens zum erneuten Abruf jeder beliebigen rid; die Kommentare im Repository legen etwas anderes nahe, und Sie können diese Einschränkung ignorieren. Im gepinnten crawlbase 1.0.0 SDK nimmt StorageAPI.get die rid als Positionsargument entgegen. Eine Quellenangabe, die nie eine gespeicherte Seite erzeugt hat, etwa ein übersprungenes PDF, erhält trotzdem eine Audit-Zeile mit leerer rid, sodass die Entscheidung auch dann festgehalten ist, wenn kein Body zum erneuten Abrufen vorhanden ist.
Grenzen: Paywalls, PDFs und Aussagen aus mehreren Quellen
Paywalls. Eine Seite hinter einer Paywall kann mit 200 antworten und nur eine Abo-Aufforderung zeigen. Findet das Retrieval genau das, lautet das korrekte Urteil not_found: Die öffentlich zugängliche Seite stützt die Aussage nicht. Das ist enger gefasst als „die Aussage ist falsch“ und ehrlicher als Paywall-Heuristiken, die bei Artikeln danebenliegen, in denen Abonnements lediglich erwähnt werden.
PDFs. Eine URL mit der Endung .pdf wird als not_found gekennzeichnet, mit dem Grund pdf_unsupported. Der Crawlbase-Parameter pdf=true rendert eine Webseite als PDF; er extrahiert keinen Text aus einer URL, die bereits ein PDF ist. Daher lehnt der Verifier ab, statt Text zu beurteilen, für den er nicht bürgen kann.
Aussagen aus mehreren Quellen. Jede URL wird einzeln beurteilt. Eine Aussage, die sich erst aus der Kombination zweier Dokumente ergibt, bleibt not_found, denn der Teilstring-Guard kann belegen, dass ein Zitat von einer Seite stammt, aber keinen Schluss über mehrere Seiten hinweg validieren. Der Verifier stellt nicht fest, ob eine Aussage wahr ist; er stellt fest, ob die zitierte, zugängliche Quelle sie zum Zeitpunkt der Prüfung gestützt hat.
Fazit
Die Verifikation von Quellenangaben gehört als Datenvalidierungsschritt in die Pipeline, nicht als weiterer Prompt über dem Modell. Rufen Sie die zitierte Quelle ab, reduzieren Sie sie auf relevante Belege, lassen Sie ein Modell diese Belege interpretieren und wenden Sie dann deterministische Prüfungen an, bevor irgendetwas zählt. So hat jeder Fehlschlag einen Namen und einen Grund, die Sie testen und auditieren können.
Beginnen Sie mit dem Dry Run des /verify Ablaufs in ScraperHub/llm-citation-verification-service-with-crawling-api, dann erstellen Sie ein kostenloses Crawlbase-Konto, setzen Sie CRAWLBASE_TOKEN, CRAWLBASE_JS_TOKEN und OPENAI_API_KEY, und führen Sie ihn mit Quellenangaben aus Ihrer eigenen Ausgabe aus.
Häufig gestellte Fragen (FAQs)
Wie prüft der Verifier die Quellen eines LLM?
Er ruft jede zitierte URL mit der Crawling API als Markdown ab, behält die drei für die Aussage relevantesten Passagen und fordert von einem Judge-Modell ein Label und ein stützendes Zitat an. Ein deterministischer Guard akzeptiert das Urteil anschließend nur, wenn das Zitat ein exakter Teilstring der abgerufenen Passagen ist.
Was passiert, wenn eine zitierte Seite nicht mehr existiert?
Eine Quellenangabe, deren Website mit 404 oder 410 antwortet, wird als unreachable gekennzeichnet und erreicht das Judge-Modell nie. Ein Deep Link, der auf die Startseite umleitet, wird stattdessen als not_found gekennzeichnet, weil die Seite, auf die er verwies, unter dieser Adresse nicht mehr existiert.
Warum Seiten als Markdown abrufen?
Markdown bewahrt die Struktur der Seite ohne das Markup, lässt sich daher sauber in Passagen zerlegen und kostet weit weniger Tokens als HTML. Setzen Sie md_readability=true, werden Navigation, Seitenleisten, Footer und Werbung vor der Konvertierung entfernt, sodass das Retrieval beim eigentlichen Inhalt ansetzt.
Wann braucht eine Quellenangabe den JavaScript token?
Wenn der Crawl mit dem Normal token fehlschlägt oder als leere Hülle zurückkommt, und genau so sehen im Browser aufgebaute Seiten ohne Rendering aus. Wiederholen Sie diese Requests mit dem JavaScript token oder mit javascript=true auf dem Normal token. Ein gerenderter Request wird mit doppelt so vielen Credits abgerechnet, deshalb ist er ein Fallback und nicht der Standard.
Kann das jede Aussage eines LLM verifizieren?
Es prüft, ob eine zugängliche zitierte Seite die Aussage stützt oder ihr widerspricht. Es validiert keine Aussagen, für die mehrere Dokumente kombiniert werden müssen, extrahiert keinen Text aus PDF-URLs und sieht keine Inhalte hinter einer Paywall.
Crawlen Sie jede Website im großen Maßstab, ohne gegen die Infrastruktur zu kämpfen.
Crawlbase übernimmt Proxys, Fingerprints und CAPTCHAs, damit Ihr Team Datenpipelines ausliefert, statt Crawl-Infrastruktur zu pflegen. 1.000 Anfragen kostenlos, keine Karte erforderlich.
