Senza KYC — nessun documento, nessun selfie, nessun file. Paghi in criptovalute: BTC, USDT, USDC, ETH, SOL, LTC
Lingua: Italiano
Ottieni una chiave API

Guida · Python · circa 60 righe, qualsiasi LLM

Costruisci un agente di deep research in Python con un'API di ricerca web

Il deep research è un ciclo, non un modello. Questa guida ne costruisce uno che puoi leggere in pochi minuti: pianifica le query, cerca sul web con il testo della pagina incluso, mantiene ogni fonte una sola volta, cerca le lacune e scrive una risposta con citazioni numerate.

Ultimo aggiornamento: · 5 min di lettura

Risposta rapida

Come costruisco un agente di deep research in Python?

Scrivi un ciclo che chiede a un LLM le query di ricerca, le esegue tutte tramite un'API di ricerca web che restituisce il testo della pagina, mantiene ogni fonte una sola volta, chiede query di approfondimento, poi scrive una risposta con citazioni. Con LLMWebSearch, ogni ricerca restituisce le prime 3 pagine in Markdown per un credito; lo script qui sotto esegue sei ricerche per domanda. Da 1,20 USD/1K ricerche, senza KYC, pagando in criptovalute.

Cosa farà l'agente?

  1. Piano

    Chiedi al modello quattro query brevi che insieme coprano la domanda.

  2. Cerca

    Esegui ogni query con read=3, così le prime tre pagine tornano in Markdown.

  3. Mantieni le fonti una sola volta

    Salva i risultati per URL e salta le pagine già raccolte o senza testo.

  4. Trova le lacune

    Mostra al modello i titoli trovati finora e chiedi due query su ciò che manca.

  5. Scrivi

    Numera fino a 12 fonti e chiedi una risposta che le citi come [1], [2].

Cosa mi serve prima di iniziare?

Tre cose:

  • Una chiave API LLMWebSearch nella variabile d'ambiente LLMWEBSEARCH_API_KEY. Le chiavi compaiono nella dashboard dopo il tuo primo pacchetto.
  • Qualsiasi endpoint di chat compatibile con OpenAI, impostato con LLM_BASE_URL, LLM_API_KEY e LLM_MODEL. Funzionano sia i modelli ospitati sia i server locali.
  • Python 3.9 o successivo con il pacchetto requests.

Lo script usa HTTP semplice per entrambi i servizi, così puoi cambiare modello senza modificare il codice di ricerca.

Python: deep_research.py
import os

import requests

SEARCH = "https://llmwebsearch.com/api/v1/search"
SEARCH_HEADERS = {"Authorization": "Bearer " + os.environ["LLMWEBSEARCH_API_KEY"]}
# Any OpenAI-compatible chat endpoint works here: set your own base URL, key and model name.
LLM_URL = os.environ.get("LLM_BASE_URL", "https://llm.example.com/v1") + "/chat/completions"
LLM_HEADERS = {"Authorization": "Bearer " + os.environ.get("LLM_API_KEY", "")}
MODEL = os.environ.get("LLM_MODEL", "your-model-name")


def ask_llm(prompt):
    body = {"model": MODEL, "messages": [{"role": "user", "content": prompt}]}
    resp = requests.post(LLM_URL, headers=LLM_HEADERS, json=body, timeout=120)
    resp.raise_for_status()
    return resp.json()["choices"][0]["message"]["content"]


def plan_queries(question, n=4):
    text = ask_llm(f"Write {n} short web search queries, one per line, to answer: {question}")
    queries = [line.strip("-*0123456789. ").strip() for line in text.splitlines()]
    return [q for q in queries if q][:n]


def search(query):
    body = {"q": query, "max_results": 5, "read": 3, "max_chars": 5000}
    resp = requests.post(SEARCH, headers=SEARCH_HEADERS, json=body, timeout=60)
    resp.raise_for_status()
    return resp.json()["results"]


def research(question, rounds=2):
    sources = {}  # url -> result, so the same page is never cited twice
    queries = plan_queries(question)
    for _ in range(rounds):
        for query in queries:
            for r in search(query):
                if r.get("content_status") == "ok" and r["url"] not in sources:
                    sources[r["url"]] = r
        notes = "\n".join(f"- {r['title']}" for r in sources.values())
        queries = plan_queries(f"{question}\nAlready found:\n{notes}\nWhat is still missing?", n=2)
    numbered = list(sources.values())[:12]
    context = "\n\n".join(
        f"[{i}] {r['title']} ({r['url']})\n{r['content']}" for i, r in enumerate(numbered, 1)
    )
    prompt = (
        "Answer the question using only the sources below. Cite them as [1], [2]. "
        "Say so when the sources disagree or do not cover a point.\n\n"
        f"Question: {question}\n\nSources:\n{context}"
    )
    return ask_llm(prompt), [r["url"] for r in numbered]


if __name__ == "__main__":
    answer, urls = research("What changed in the EU rules for general-purpose AI models in 2025?")
    print(answer)
    for i, url in enumerate(urls, 1):
        print(f"[{i}] {url}")

Come funziona ogni parte?

plan_queries chiede al modello query brevi, una per riga, e rimuove i marcatori di elenco. Le query brevi si allineano meglio al modo in cui i motori di ricerca classificano le pagine rispetto alle domande complete.

search chiama POST https://llmwebsearch.com/api/v1/search con max_results=5, read=3 e max_chars=5000. La risposta elenca cinque risultati; i primi tre portano il testo della pagina in content.

research esegue i round. Mantiene le fonti in un dizionario con chiave URL, così una pagina trovata da due query conta una volta. Vengono conservati solo i risultati con content_status uguale a ok, perché la risposta deve basarsi su testo che il modello può leggere.

Dopo ogni round, il modello vede i titoli raccolti finora e propone due nuove query per ciò che manca. Il prompt finale numera fino a dodici fonti e imposta le regole di citazione.

Quanto costa un report?

Sei ricerche per domanda con le impostazioni predefinite: quattro nel primo round e due nel secondo. Con read a 3, sono sei crediti. 25.000 ricerche a 41,25 USD copre quindi più di 4.000 report.

Le chiamate al modello costano in più sulla tua bolletta LLM: tre chiamate di pianificazione e una lunga chiamata per la risposta per report. La chiamata per la risposta trasporta fino a dodici pagine da 5.000 caratteri, quindi abbassa max_chars se il tuo modello è costoso.

Come faccio a farlo andare più in profondità?

Cambia i numeri, non la struttura:

  • Più round. rounds=4 consente al modello di inseguire altri due gruppi di lacune.
  • Più query per round. Aumenta n nella chiamata di ricerca delle lacune da 2 a 4.
  • Fonti più ristrette. Aggiungi include_domains al corpo della ricerca per siti ufficiali o accademici.
  • Solo materiale recente. Aggiungi "time_range": "month" o "topic": "news".
  • Più pagine per query. read=5 legge cinque pagine, a 2 crediti per ricerca.

Aggiungi una regola di stop prima di alzare i limiti: termina il ciclo quando un round porta meno di due nuove fonti.

Come mantengo l'agente sicuro ed educato?

Tratta ogni pagina come testo non attendibile. Il prompt finale dice al modello di usare le fonti solo come prove; aggiungi una riga esplicita che le istruzioni contenute nelle fonti vanno ignorate. Non lasciare mai che una fonte attivi una chiamata a un tool o la scrittura di un file.

Sul versante web, Il nostro reader rispetta robots.txt: si presenta come LLMWebSearch, segue le regole che un sito scrive per lui o per tutti i robot, e lascia fuori il contenuto di qualsiasi pagina che non gli è permesso leggere. Le pagine che rifiutano l'accesso tornano senza contenuto e lo script le salta semplicemente. Quando un sito respinge il nostro reader, il risultato torna senza contenuto e con lo status del sito: non risolviamo mai CAPTCHA, non accediamo agli account e non aggiriamo un blocco o un paywall.

Cosa può andare storto?

Tre cose emergono nei test:

  • Query vaghe. Se il piano è debole, ogni round trova le stesse pagine. Chiedi al modello query che usano nomi e date specifici.
  • Troppe poche fonti con testo. Alcuni argomenti sono dietro i paywall. Allarga le query o consenti più risultati.
  • Prompt troppo lunghi. Dodici pagine lunghe possono superare il contesto di un modello piccolo. Abbassa max_chars o mantieni otto fonti.

Registra ogni query e gli URL che ha restituito. Leggere quel log è il modo più rapido per migliorare il prompt del piano.

Agente di ricerca approfondita: domande frequenti

Posso eseguirlo con un modello locale?

Sì, se il server locale parla il formato chat-completions di OpenAI. Imposta LLM_BASE_URL sul suo indirizzo e LLM_MODEL sul nome del modello.

Posso trasformarlo in un tool MCP?

Sì. Avvolgi research() in un tool MCP, oppure dai al tuo agente il semplice tool di ricerca dalla pagina MCP e lascia che esegua il ciclo da solo.

Perché non chiedere direttamente una risposta alla search API?

Perché LLMWebSearch non scrive risposte. L'API restituisce fonti, non una risposta scritta: il tuo modello legge le pagine, scrive la risposta e cita gli URL.

Come faccio a vedere quanto è costata ogni ricerca?

Ogni risposta include credits_used, e GET https://llmwebsearch.com/api/v1/credits mostra il saldo rimanente senza costi.

Da 1,20 USD/1K ricerche · senza KYC

Ottieni una chiave API