Dare a un LLM dati web freschi: SERP API per RAG e grounding

Una pipeline di retrieval Python minimale per ancorare un LLM a risultati di ricerca live, e perché un costo fisso in crediti batte una bolletta per query imprevedibile.

Ogni modello ha un cutoff di addestramento, e il divario tra quella data e oggi cresce e basta. Il grounding lo risolve nello stesso modo da quando il retrieval-augmented generation è diventato un pattern standard: cerca prima, leggi cosa torna, poi rispondi da lì.

La meccanica è semplice. La parte su cui vale la pena ragionare è quanto costa farla girare, perché quel numero si comporta in modo molto diverso a seconda di chi controlla il passo di ricerca.

Il passo di retrieval, concretamente

Query in ingresso, risultati organici in uscita, fai il fetch delle pagine che vale la pena leggere, dai al modello una finestra di contesto invece di un cutoff di addestramento:

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)
# passa `prompt` alla tua chiamata LLM come contesto, insieme alla domanda originale

/serp costa 5 crediti, /scrape costa 1 credito per una pagina statica (5 con dynamic=true se una fonte richiede rendering JS). Una risposta ancorata con tre fonti è 5 + 3 = 8 crediti, fissi, ogni volta, indipendentemente da cosa fa il modello col risultato.

Perché gli strumenti di ricerca integrati costano qualcosa che non puoi dimensionare in anticipo

Entrambi i principali provider ora spediscono uno strumento di ricerca che il modello può chiamare da solo. La documentazione dello strumento web search di Anthropic, verificata ad agosto 2026, lo prezza a “$10 per 1.000 ricerche, più i costi token standard per il contenuto generato dalla ricerca”. La pagina prezzi di OpenAI, verificata lo stesso mese, elenca il suo strumento web search standard a “$10,00 / 1k chiamate + token del contenuto di ricerca fatturati alle tariffe del modello”.

Nessuno dei due numeri è il problema di per sé. Il problema è che è il modello a decidere quante ricerche eseguire per richiesta, e quanto di ogni risultato leggere. Un modello con uno strumento di ricerca e una domanda difficile può eseguire una ricerca o dieci, a seconda di come ragiona sul task in quel turno. La documentazione di Anthropic lo dice direttamente: “query fattuali semplici in genere usano 1-3 ricerche; ricerche comparative o multi-entità possono usarne 10 o più”. Moltiplica quella varianza per il contenuto di pagina fatturato alle tariffe token del modello, e la risposta onesta a “quanto costerà questa query” è un intervallo, non un numero, finché non arriva la bolletta.

Puoi limitare il numero di ricerche per richiesta (entrambi i provider espongono un parametro tipo max-uses), il che limita il raggio d’azione di una singola chiamata. Non rende prevedibile il costo per query, perché il costo in token scala comunque in base a quanto di ogni pagina il modello decide di leggere.

Far girare il retrieval da solo

Chiamare /serp e /scrape direttamente dal tuo codice aggira la varianza in un modo specifico: decidi tu top_n, decidi tu quanto di ogni pagina tenere prima che arrivi al modello, e il costo in crediti per chiamata ancorata è fisso nel momento in cui scrivi la pipeline, non deciso per richiesta dal giudizio del modello stesso.

L’altra leva è la chiave stessa. I crediti sono finiti su una chiave FastAF e vengono riservati quando una richiesta parte, quindi un agente che va in loop (ritenta su un risultato negativo, scompone una domanda in molte ricerche, resta bloccato a ripianificare) sbatte contro uno stop netto al saldo di quella chiave, non contro una bolletta illimitata. Per un agente AI che chiama questa pipeline come strumento, quel saldo finito è il tetto di spesa: dimensiona una chiave sul budget per quell’agente, e il limite è imposto dall’API, non dalla speranza che il prompt che gli dice di “cercare con parsimonia” tenga.

def ground_with_budget(query, budget, top_n=3, cost_per_page=1):
    call_cost = 5 + top_n * cost_per_page  # /serp + /scrape per fonte
    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)

La versione breve

Ancorare un LLM alla ricerca live è una chiamata di ricerca, una manciata di fetch di pagine, e una stringa di contesto, niente di più esotico di così. La parte che decide se è economico e prevedibile o una voce spiacevole a fine mese è chi sceglie quante ricerche e fetch avvengono: un modello che ragiona turno per turno, o una pipeline che hai scritto tu con un top_n fisso e una chiave con un saldo finito dietro. Vedi i prezzi per il costo in crediti per endpoint, e la documentazione per la lista completa dei parametri di /serp e /scrape.