Une citation qui se charge n'est pas une preuve. Imaginez un assistant de recherche à qui l'on demande si l'eau bout à 50 degrés Celsius au niveau de la mer. Il répond par un paragraphe assuré et un lien. Le lien fonctionne. La page indique 100 degrés. L'URL était réelle ; la citation ne soutenait pas l'affirmation.

C'est la faille des contrôles d'hallucination qui n'examinent que la réponse générée. Une phrase peut être fluide, plausible et accompagnée d'une URL fonctionnelle alors que la source citée dit autre chose. Un lien n'est qu'un pointeur. Le vérifier, c'est récupérer ce qui se trouve derrière ce pointeur, le comparer à l'affirmation et conserver les preuves sur lesquelles repose la décision.

Le service présenté dans cet article automatise cette comparaison. Vous lui fournissez une affirmation et les URL citées par le modèle. Il récupère chaque page avec la Crawling API, conserve les passages les plus pertinents pour l'affirmation, demande un verdict à un modèle juge, vérifie que l'extrait fourni comme preuve par le juge figure réellement sur la page, et combine les résultats de chaque citation en une seule étiquette : supported, contradicted, not_found ou unreachable.

En bref
  • Un HTTP 200 n'est pas une preuve. Une page peut se charger et n'être pourtant qu'un mur anti-bot, une coquille vide, une soft 404 ou la page d'accueil du site.
  • Envoyez au juge trois passages pertinents, pas la page entière. Moins de bruit, un coût plus faible, de meilleurs verdicts.
  • Ne faites jamais confiance à l'extrait du juge. N'acceptez supported ou contradicted que si l'extrait est une véritable sous-chaîne de la page récupérée.
  • Agrégez avec des règles, pas avec un autre modèle : une seule contradiction l'emporte sur toutes les citations favorables.
  • Stockez chaque récupération, afin de pouvoir rejouer un verdict après la modification de la page.
Cinq étapes, dont une seule fait appel à un modèle de langage. La récupération, la recherche de passages, le contrôle de l'extrait et l'agrégation sont déterministes. Le juge interprète les preuves ; le code décide si cette interprétation peut être prise en compte.

Le code se trouve dans ScraperHub/llm-citation-verification-service-with-crawling-api, avec un point de contrôle par étape sous steps/ et le package complet sous final/citation_verifier/. Les extraits de code ci-dessous proviennent de final/ sauf mention contraire. Il vous faut un compte Crawlbase ; conservez ses tokens dans des variables d'environnement, jamais dans le dépôt.

Récupérer la page citée, pas seulement un HTTP 200

Pourquoi un simple GET ne suffit pas

Une requête réussie ne signifie pas que vous avez récupéré le contenu vers lequel pointe une citation. Une application monopage peut renvoyer 200 avec un conteneur vide <div id="root">. Un mur anti-bot peut renvoyer 200 tout en servant un défi. Une soft 404 renvoie 200 avec un modèle de page « cette page n'existe plus », et un lien profond peut rediriger vers la page d'accueil. L'étape de récupération doit établir plus que l'accessibilité : elle conserve le statut d'origine, repère les redirections et renvoie un texte utilisable comme preuve.

Du Markdown avec une passe de lisibilité

La Crawling API se charge de la récupération. Le vérificateur demande format=md, qui renvoie la page en GitHub Flavored Markdown, et md_readability=true, qui supprime la navigation, les barres latérales, les pieds de page et les publicités avant la conversion, afin que la recherche de passages parte de l'article lui-même. Il définit aussi store=true pour que chaque récupération puisse être auditée par la suite.

python
# 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

Un paramètre ne sert à rien ici. custom_success_codes existe pour les statuts d'origine que l'API traiterait autrement comme des échecs, mais 404 et 410 comptent déjà comme des crawls réussis : ils reviennent avec cb_status 200 et le véritable original_status, et ils ne sont pas relancés. Vous pouvez le supprimer sans changer le comportement.

La passe de lisibilité peut retirer trop de contenu sur les pages courtes ou atypiques ; le vérificateur considère donc moins de 200 caractères hors espaces comme une passe ratée et récupère à nouveau la même URL sans md_readability. Le seuil de 200 caractères est une règle de l'application, pas une limite de Crawlbase. Les deux appels sont facturés et, avec store=true les deux pages sont stockées ; réservez donc cette nouvelle tentative aux pages qui en ont besoin.

Liens morts, soft 404 et redirections vers la page d'accueil

La plupart des mauvaises citations sont détectées avant l'intervention de tout modèle. Les liens morts, les redirections vers la page d'accueil et les PDF sont étiquetés à l'étape de récupération, et seules les pages au texte exploitable passent à la recherche de passages.

Deux statuts accompagnent chaque réponse. original_status correspond à la réponse du site cité ; cb_status est le résultat du crawl côté Crawlbase. Le vérificateur étiquette unreachable toute citation dont le site a répondu 404 ou 410, avec une raison telle que http_404, et elle n'atteint jamais le juge. Ce crawl est tout de même facturé, car Crawlbase a bien récupéré une réponse : une 404 est le crawl réussi d'une page manquante.

Les redirections nécessitent leur propre contrôle. Un lien profond qui aboutit sur / renvoie une page parfaitement valide qui ne dit rien de l'affirmation, et un texte générique de page d'accueil pourrait ressembler à un soutien. Lorsque le chemin demandé est plus profond que / mais que le chemin final est /, la citation devient not_found avec la raison redirected_to_homepage, et le juge est ignoré.

python
# 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) == "/"

L'en-tête url contient l'URL finale après les redirections que Crawlbase suit lors d'une requête normale. Sur une requête rendue en JavaScript, une redirection effectuée par le navigateur lui-même peut laisser url égal à l'URL demandée ; considérez donc le contrôle de page d'accueil comme un signal fort sur le chemin normal et comme un contrôle au mieux sur le chemin rendu.

Les soft 404 sont traitées à part : une page 200 n'est considérée comme une soft 404 que si son texte lisible est un court modèle de type « la page n'existe pas » de moins de 800 caractères hors espaces, de sorte qu'un long article qui se contente de mentionner une 404 n'est pas écarté.

Quand recourir au JavaScript token

Le rendu coûte plus cher ; le vérificateur commence donc chaque citation avec le Normal token et ne passe au niveau supérieur que lorsque la première tentative montre que la page a besoin d'un navigateur. Une requête JavaScript est facturée deux fois plus de crédits qu'une requête normale. Vous pouvez passer au niveau supérieur avec un second client utilisant le JavaScript token, comme le fait le dépôt, ou garder un seul client avec le Normal token et ajouter javascript=true à la nouvelle tentative ; dans les deux cas, la requête est facturée comme une requête JavaScript.

Le contrôle d'escalade du dépôt, dans fetch.py, se déclenche sur des valeurs de cb_status égales à 520 ou 525. Aucun de ces signaux n'arrive en pratique. 520 n'est pas du tout un cb_status : c'est le statut HTTP que renvoie la Crawling API lorsqu'un crawl échoue, et le SDK crawlbase 1.0.0 épinglé renvoie une telle réponse avec un corps vide et sans en-têtes, si bien qu'il n'y a aucun cb_status à lire. Une page dont le serveur a répondu 200 sans aucun contenu revient avec un cb_status 207, et 525 est un code interne sans rapport avec les défis anti-bot. Déclenchez l'escalade sur les signaux que l'API envoie réellement :

python
# 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

La version du SDK épinglée par le dépôt expose le statut sous son ancien nom, pc_status, que l'API envoie également sous le nom cb_status ; les nouvelles intégrations doivent lire cb_status. Après la tentative JavaScript, relancez la même interprétation, y compris le repli sans lisibilité, afin qu'une page rendue passe exactement par les mêmes contrôles qu'une page statique.

Rechercher les preuves avant d'interroger le juge

Envoyer une page entière au juge est coûteux et bruité. Un article de 2 000 mots peut contenir deux phrases pertinentes, et le reste donne au modèle plus d'occasions de s'accrocher à la navigation, aux avertissements ou à du texte sans rapport. Chaque page est donc d'abord découpée en passages : découpage sur les lignes vides, regroupement des paragraphes courts jusqu'à 200 mots, et coupure des plus longs aux limites de mots. Le budget de 200 mots est compté avec split(), si bien qu'aucun tokenizer n'est nécessaire pour garder des passages de taille à peu près égale.

python
# 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)

Les passages sont ensuite classés par rapport à l'affirmation à l'aide des embeddings all-MiniLM-L6-v2 et de la similarité cosinus, et les trois premiers sont conservés. Le modèle se charge lors du premier appel de recherche, de sorte que l'import du package ne déclenche jamais de téléchargement.

python
# 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]]

Trois est une limite délibérée. Un seul passage rend la recherche fragile lorsque la meilleure preuve arrive en deuxième position ; dix ramènent l'essentiel du bruit que la recherche devait éliminer. Une preuve qui se trouve hors des trois premiers passages aboutit à not_found, ce qui est la réponse honnête pour une preuve que le vérificateur n'a jamais montrée au juge.

Exiger du juge des preuves, pas seulement un verdict

Le juge interprète les preuves ; il n'est pas la source de vérité. Un modèle peut renvoyer un supported assuré avec un extrait qui n'a jamais figuré sur la page, et un JSON strict rend la sortie prévisible sans la rendre honnête. Le prompt limite donc le juge à trois étiquettes et exige un extrait contigu copié des passages pour toute étiquette autre que not_found. unreachable n'atteint jamais le modèle ; c'est l'étape de récupération qui l'attribue.

python
# 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 est le juge par défaut, appelé via httpx avec un schéma JSON strict dans response_format; JUDGE_PROVIDER=anthropic et JUDGE_PROVIDER=ollama exécutent le même prompt ailleurs. Le fournisseur change la façon dont un verdict est produit, jamais les règles qui décident s'il est accepté.

Contrôler l'extrait par rapport à la page

Ce contrôle est l'étape qui fait passer le juge du statut d'oracle à celui d'interprète. Il redresse les guillemets typographiques, réduit les espaces et exige que l'extrait apparaisse comme une sous-chaîne exacte des passages récupérés. Un verdict supported ou contradicted sans extrait correspondant est rétrogradé en not_found avec une confiance de 0 et la raison evidence_quote_not_in_source.

Un verdict n'est retenu qu'avec un extrait que la page contient. Le juge peut avoir raison sur l'affirmation et pourtant inventer sa preuve ; le contrôle de sous-chaîne le détecte avant que le verdict ne compte.
python
# 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

Dans l'exemple du point d'ébullition, l'affirmation indique 50 degrés et la page 100 ; le verdict contradicted est donc retenu : son extrait figure sur la page. La confiance du modèle n'est conservée que lorsque la preuve passe le contrôle ; l'agrégation l'ignore et ne travaille qu'à partir des étiquettes.

Exposer le service par une API /verify dédiée

Le service final comporte deux routes : GET /health et POST /verify. Une requête contient une affirmation et une ou plusieurs URL citées ; la réponse contient l'étiquette au niveau de l'affirmation et un verdict par citation avec son extrait, sa confiance, l'URL finale, original_status, le rid de stockage et toute raison attribuée par le vérificateur.

python
# 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)

L'agrégation est une règle fixe, pas un nouvel appel à un modèle. Une citation contradicted l'emporte sur toutes les citations supported ; en l'absence de contradiction, le moindre soutien suffit pour l'affirmation ; l'affirmation n'est unreachable que si toutes les citations le sont ; tout le reste est not_found.

python
# 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

La couche HTTP reste légère : POST /verify transmet la requête au vérificateur et renvoie 503 lorsqu'un token requis est manquant, en nommant la variable d'environnement sans exposer sa valeur. Le dry run du dépôt, python steps/step_07_api/main.py --dry-run, produit cette réponse. Les pages sont des fixtures sur example.com, les deux requêtes Crawlbase ont été effectuées en conditions réelles, et le juge a utilisé une phrase de fixture faute de clé OpenAI configurée ; lisez-la donc comme une fixture et non comme une trace de modèle :

json
{
  "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"
    }
  ]
}

L'affirmation ressort supported, car une citation la soutient et l'autre a tout simplement disparu. Cette distinction est la raison d'être des quatre étiquettes. Une source morte n'est pas une preuve contre une affirmation ; une source active qui affirme le contraire, si.

Crawlbase Crawling API

Récupérez la page vers laquelle pointe une citation sous forme de Markdown propre, rendue lorsqu'elle a besoin d'un navigateur, avec le véritable code de statut du site. Les requêtes échouées ne sont pas facturées. Commencez avec jusqu'à 5 000 requêtes gratuites, sans carte bancaire.

Mesurer la précision des citations sur un lot

Une affirmation vérifiée illustre le pipeline ; elle ne vous dit pas avec quelle fiabilité un modèle cite ses sources. Pour cela, le vérificateur évalue un lot : un fichier JSONL d'enregistrements comportant un id, une affirmation et ses URL citées, où chaque paire affirmation-URL compte comme une citation. La précision des citations correspond aux citations soutenues divisées par l'ensemble des citations. La précision sur les seules citations accessibles garde le même numérateur et retire du dénominateur les citations inaccessibles, ce qui distingue « le modèle cite des liens morts » de « le modèle cite des pages actives qui ne disent pas ce qu'il affirme ».

python
# 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

Les exécutions sont limitées par un asyncio.Semaphore (BATCH_CONCURRENCY, 4 par défaut), et comme l'appel au SDK Crawlbase est synchrone, il s'exécute dans un thread de travail au lieu de bloquer la boucle d'événements. Sur la fixture d'exemple, le dry run signale neuf citations, dont deux soutenues et trois inaccessibles :

text
citation_precision=0.2222 (2/9)
reachable_precision=0.3333 (2/6)

Exécutez-le sur vos propres sorties avec python -m citation_verifier.batch your_outputs.jsonl --out citation_report.csv une fois les tokens et une clé de juge configurés. Commencez par quelques dizaines d'affirmations et lisez d'abord les lignes contradicted : une seule contradiction vérifiée peut révéler un problème qu'un score de précision global masque.

Conserver les preuves avec Cloud Storage

Un score de précision ne vaut que par votre capacité à l'examiner plus tard, et la page citée peut changer dès le lendemain de votre vérification. Avec store=true, Crawlbase conserve le Markdown renvoyé dans Cloud Storage pendant 30 jours, et la réponse contient une storage_url dont la chaîne de requête contient l'identifiant de la page, le rid. Le vérificateur ne stocke que le rid, car l'URL complète contient le token, et tient un petit index d'audit SQLite composé du rid, de l'affirmation, de l'URL, du verdict, de la confiance et de l'heure. Le stockage ajoute un demi-crédit par page au crawl ; relire une page stockée est gratuit.

python
# 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)

Les pages stockées appartiennent à votre compte, et non au token qui les a crawlées ; l'un ou l'autre token peut donc rejouer n'importe quel rid, quoi qu'en disent les commentaires du dépôt, et vous pouvez ignorer cette restriction. Dans le SDK crawlbase 1.0.0 épinglé, StorageAPI.get prend le rid comme argument positionnel. Une citation qui n'a jamais produit de page stockée, comme un PDF ignoré, obtient tout de même une ligne d'audit avec un champ vide pour le rid, de sorte que la décision est enregistrée même lorsqu'il n'y a aucun corps à rejouer.

Limites : paywalls, PDF et affirmations multi-sources

Paywalls. Une page derrière un paywall peut répondre 200 avec seulement une invitation à s'abonner. Si c'est ce que trouve la recherche de passages, le verdict correct est not_found, car la page accessible publiquement ne soutient pas l'affirmation. C'est plus restreint que « l'affirmation est fausse », et plus honnête que des heuristiques de paywall qui se trompent sur des articles qui se contentent de mentionner des abonnements.

PDF. Une URL se terminant par .pdf est marquée not_found avec la raison pdf_unsupported. Le paramètre pdf=true de Crawlbase convertit une page web en PDF ; il n'extrait pas le texte d'une URL qui est déjà un PDF, si bien que le vérificateur refuse plutôt que de juger un texte dont il ne peut garantir la fiabilité.

Affirmations multi-sources. Chaque URL est jugée isolément. Une affirmation qui ne découle que de la combinaison de deux documents reste not_found, car le contrôle de sous-chaîne peut prouver qu'un extrait provient d'une page, mais ne peut pas valider une inférence entre plusieurs pages. Le vérificateur n'établit pas qu'une affirmation est vraie ; il établit si la source citée et accessible la soutenait au moment de la vérification.

Conclusion

La vérification des citations a sa place dans le pipeline en tant qu'étape de validation des données, pas comme un prompt supplémentaire par-dessus le modèle. Récupérez la source citée, réduisez-la aux preuves pertinentes, laissez un modèle interpréter ces preuves, puis appliquez des contrôles déterministes avant que quoi que ce soit ne compte. Chaque échec porte alors un nom et une raison que vous pouvez tester et auditer.

Commencez par le flux /verify en dry run dans ScraperHub/llm-citation-verification-service-with-crawling-api, puis créez un compte Crawlbase gratuit, définissez CRAWLBASE_TOKEN, CRAWLBASE_JS_TOKEN et OPENAI_API_KEY, et exécutez-le sur les citations issues de vos propres sorties.

Foire aux questions (FAQ)

Comment le vérificateur contrôle-t-il les sources d'un LLM ?

Il récupère chaque URL citée avec la Crawling API au format Markdown, conserve les trois passages les plus pertinents pour l'affirmation et demande à un modèle juge une étiquette et un extrait justificatif. Un contrôle déterministe n'accepte ensuite le verdict que si l'extrait est une sous-chaîne exacte des passages récupérés.

Que se passe-t-il lorsqu'une page citée a disparu ?

Une citation dont le site répond 404 ou 410 est étiquetée unreachable et n'atteint jamais le juge. Un lien profond qui redirige vers la page d'accueil est étiqueté not_found à la place, car la page vers laquelle il pointait n'existe plus à cette adresse.

Pourquoi récupérer les pages au format Markdown ?

Le Markdown conserve la structure de la page sans le balisage ; il se découpe donc proprement en passages et coûte bien moins de tokens que le HTML. Avec md_readability=true, la navigation, les barres latérales, les pieds de page et les publicités sont supprimés avant la conversion, si bien que la recherche de passages part du contenu lui-même.

Quand une citation a-t-elle besoin du JavaScript token ?

Lorsque le crawl avec le Normal token échoue ou renvoie une coquille vide, ce à quoi ressemblent les pages construites dans le navigateur sans rendu. Relancez celles-ci avec le JavaScript token, ou avec javascript=true sur le Normal token. Une requête rendue est facturée deux fois plus de crédits ; c'est pourquoi elle sert de solution de repli et non de comportement par défaut.

Cette solution peut-elle vérifier n'importe quelle affirmation d'un LLM ?

Elle vérifie si une page citée accessible soutient ou contredit l'affirmation. Elle ne valide pas les affirmations qui nécessitent de combiner plusieurs documents, n'extrait pas le texte des URL de PDF et ne voit pas le contenu derrière un paywall.

Commencer à construire

Crawlez n'importe quel site à grande échelle, sans combattre l'infrastructure.

Crawlbase gère les proxies, les empreintes et les CAPTCHA afin que votre équipe livre des pipelines de données au lieu de maintenir la plomberie de crawl. 1 000 requêtes gratuites, sans carte requise.

En libre-service · Sans appel commercial requis · Volumes de crawl entreprise disponibles