Donner des données web fraîches à un LLM : API SERP pour le RAG et le grounding

Un pipeline Python minimal pour ancrer un LLM dans des résultats de recherche en direct, et pourquoi un coût fixe en crédits bat une facture imprévisible.

Chaque modèle a une date limite d’entraînement, et l’écart entre cette date et aujourd’hui ne fait que grandir. Le grounding corrige cela de la même façon que depuis que la génération augmentée par récupération est devenue un schéma standard : chercher d’abord, lire ce qui revient, puis répondre à partir de ça.

Le mécanisme est simple. Ce qui mérite réflexion, c’est ce que ça coûte à faire tourner, parce que ce chiffre se comporte très différemment selon qui contrôle l’étape de recherche.

L’étape de récupération, concrètement

Une requête en entrée, des résultats organiques en sortie, récupérez les pages qui valent la peine d’être lues, donnez au modèle une fenêtre de contexte au lieu d’une date limite d’entraînement :

import requests

API_KEY = "faf_live_..."

def ground(query, top_n=3):
    serp = requests.get(
        "https://api.fastaf.co/v1/serp",
        params={"query": query, "results": top_n},
        headers={"Authorization": f"Bearer {API_KEY}"},
        timeout=60,
    )
    serp.raise_for_status()
    links = [r["link"] for r in serp.json().get("organic_results", [])[:top_n]]

    context = []
    for link in links:
        page = requests.get(
            "https://api.fastaf.co/v1/scrape",
            params={"url": link},
            headers={"Authorization": f"Bearer {API_KEY}"},
            timeout=60,
        )
        if page.status_code == 200:
            context.append({"url": link, "text": page.json().get("text", "")[:4000]})

    return context


sources = ground("latest changes to the EU AI Act")
prompt = "\n\n".join(f"Source: {s['url']}\n{s['text']}" for s in sources)
# passez `prompt` à votre appel LLM comme contexte, aux côtés de la question originale

/serp coûte 5 crédits, /scrape coûte 1 crédit pour une page statique (5 avec dynamic=true si une source nécessite un rendu JS). Une réponse ancrée avec trois sources coûte 5 + 3 = 8 crédits, fixe, à chaque fois, quoi que le modèle fasse du résultat.

Pourquoi les outils de recherche intégrés coûtent quelque chose que vous ne pouvez pas dimensionner à l’avance

Les deux principaux fournisseurs proposent désormais un outil de recherche que le modèle peut appeler de lui-même. La documentation de l’outil de recherche web d’Anthropic, vérifiée en août 2026, le tarifie à « 10 $ pour 1 000 recherches, plus les coûts de tokens standards pour le contenu généré par la recherche ». La page de tarifs d’OpenAI, vérifiée le même mois, liste son outil de recherche web standard à « 10,00 $ / 1k appels + tokens de contenu de recherche facturés aux tarifs du modèle ».

Aucun des deux chiffres n’est le problème en soi. Le problème est que le modèle décide du nombre de recherches à lancer par requête, et de la quantité de chaque résultat à lire. Un modèle doté d’un outil de recherche face à une question difficile peut lancer une recherche ou dix, selon la façon dont il raisonne sur la tâche à ce moment-là. La documentation d’Anthropic le dit directement : « les requêtes factuelles simples utilisent typiquement 1 à 3 recherches ; les recherches comparatives ou multi-entités peuvent en utiliser 10 ou plus ». Multipliez cette variance par le contenu de page facturé aux tarifs de tokens du modèle, et la réponse honnête à « combien cela va-t-il coûter par requête » est une fourchette, pas un chiffre, jusqu’à ce que la facture arrive.

Vous pouvez plafonner le nombre de recherches par requête (les deux fournisseurs exposent un paramètre de type max-uses), ce qui limite le rayon d’impact d’un seul appel. Cela ne rend pas le coût par requête prévisible, parce que le coût en tokens continue de dépendre de la quantité de chaque page que le modèle décide de lire.

Faire tourner la récupération vous-même à la place

Appeler /serp et /scrape directement depuis votre propre code contourne la variance d’une manière précise : vous décidez top_n, vous décidez combien de chaque page conserver avant qu’elle n’atteigne le modèle, et le coût en crédits par appel ancré est fixé au moment où vous écrivez le pipeline, pas décidé par requête selon le jugement propre du modèle.

L’autre levier, c’est la clé elle-même. Les crédits sont finis sur une clé FastAF et réservés quand une requête démarre, donc un agent qui boucle (réessaie sur un mauvais résultat, décompose une question en plusieurs recherches, reste coincé à replanifier) atteint un arrêt net sur le solde de cette clé, pas une facture sans limite. Pour un agent IA qui appelle ce pipeline comme un outil, ce solde fini est le plafond de dépense : dimensionnez une clé au budget de cet agent, et le plafond est imposé par l’API, pas en espérant que le prompt lui disant de « chercher avec parcimonie » tienne bon.

def ground_with_budget(query, budget, top_n=3, cost_per_page=1):
    call_cost = 5 + top_n * cost_per_page  # /serp + /scrape par source
    if budget["spent"] + call_cost > budget["max_credits"]:
        return {"error": "grounding budget exhausted for this run"}

    budget["spent"] += call_cost
    return ground(query, top_n=top_n)

En bref

Ancrer un LLM dans une recherche en direct, c’est un appel de recherche, une poignée de récupérations de pages, et une chaîne de contexte, rien de plus exotique que ça. Ce qui détermine si c’est bon marché et prévisible ou une ligne désagréable en fin de mois, c’est qui choisit combien de recherches et de récupérations ont lieu : un modèle qui raisonne tour par tour, ou un pipeline que vous avez écrit avec un top_n fixe et une clé avec un solde fini derrière. Voir les tarifs pour le coût en crédits par endpoint, et la documentation pour la liste complète des paramètres de /serp et /scrape.