Risposta rapida
Come chiamo un'API di ricerca web per agenti AI da Python?
Invia la query in POST a https://llmwebsearch.com/api/v1/search con requests o httpx e una chiave Bearer. LLMWebSearch restituisce risultati ordinati con le prime 3 pagine in Markdown, che puoi numerare e passare a qualsiasi LLM per rispondere con citazioni. Una ricerca costa 1 credito, da 1,20 USD/1K ricerche a partire da ottobre 2026. Python 3.9+ e nessun SDK. Senza KYC; paghi in criptovalute.
Cosa ti serve
| Python | 3.9 o superiore |
|---|---|
| Pacchetti | requests per lo script semplice, httpx per quello asincrono |
| Chiave | LLMWEBSEARCH_API_KEY nel tuo ambiente |
| Per il ciclo di risposta | Qualsiasi endpoint chat compatibile con OpenAI: LLM_BASE_URL, LLM_API_KEY, LLM_MODEL |
| Fatturazione | Un credito paga una ricerca: fino a 20 risultati classificati, con le pagine dei primi 3 lette e restituite come Markdown. Leggere da 4 a 10 pagine costa 2 crediti. Una chiamata che termina in errore non costa nulla |
Come eseguo una ricerca con requests?
Costruisci un body JSON con q e le opzioni che ti servono, invialo in POST e cicla su results. Controlla content_status prima di toccare content: solo i risultati marcati ok contengono il testo della pagina. Lo script qui sotto salva ogni pagina letta come file Markdown con il nome della sua posizione, con l'URL di origine sulla prima riga.
import os
import pathlib
import requests
resp = requests.post(
"https://llmwebsearch.com/api/v1/search",
headers={"Authorization": "Bearer " + os.environ["LLMWEBSEARCH_API_KEY"]},
json={"q": "how to size a postgres connection pool", "max_results": 5, "read": 3},
timeout=60,
)
resp.raise_for_status()
data = resp.json()
out = pathlib.Path("sources")
out.mkdir(exist_ok=True)
for r in data["results"]:
if r["content_status"] != "ok":
print("skip", r["position"], r["content_status"], r["url"])
continue
path = out / f"{r['position']:02d}.md"
path.write_text("Source: " + r["url"] + "\n\n" + r["content"], encoding="utf-8")
print("saved", path, r["word_count"], "words")
print("credits used:", data["credits_used"])
Come eseguo molte ricerche insieme?
Usa httpx.AsyncClient e un asyncio.Semaphore. Il semaforo limita quante chiamate sono in corso, il che ti tiene sotto il limite al minuto del tuo account e distribuisce il carico. Da quattro a otto chiamate concorrenti vanno bene per la maggior parte degli agenti di ricerca. Ogni account ha un limite di chiamate al minuto, mostrato nella dashboard; chiedi al supporto e può essere alzato.
L'esempio invia una ricerca per sotto-domanda, un passo tipico di un agente di ricerca, e raccoglie gli URL delle pagine lette. Ogni chiamata è indipendente: un fallimento non ferma le altre.
import asyncio
import os
import httpx
API = "https://llmwebsearch.com/api/v1/search"
HEADERS = {"Authorization": "Bearer " + os.environ["LLMWEBSEARCH_API_KEY"]}
QUESTIONS = [
"what is connection pooling in postgres",
"recommended pool size per cpu core",
"pgbouncer transaction mode limitations",
"connection pool exhaustion symptoms",
]
async def search(client, sem, q):
async with sem:
resp = await client.post(API, json={"q": q, "read": 2, "max_chars": 4000})
if resp.status_code != 200:
return q, resp.json().get("error", {}).get("code"), []
data = resp.json()
read = [r["url"] for r in data["results"] if r["content_status"] == "ok"]
return q, "ok", read
async def main():
sem = asyncio.Semaphore(4)
async with httpx.AsyncClient(headers=HEADERS, timeout=60) as client:
for q, status, urls in await asyncio.gather(*(search(client, sem, q) for q in QUESTIONS)):
print(status, q, urls)
asyncio.run(main())
Come ottengo una risposta con citazioni da qualsiasi LLM?
Prima cerca, poi dai al modello le fonti numerate e chiedigli di citarle. L'API restituisce fonti, non una risposta scritta: il tuo modello legge le pagine, scrive la risposta e cita gli URL, così scegli tu il modello e lo stile di citazione. Il ciclo qui sotto funziona con qualsiasi endpoint chat che parla il formato chat/completions compatibile con OpenAI; imposta indirizzo, chiave e modello in tre variabili d'ambiente.
Tieni il contesto sotto controllo. Lo script riduce ogni fonte affinché il totale resti entro un budget di circa 6.000 token, contando all'incirca quattro caratteri per token. Un risultato senza contenuto viene passato come snippet, etichettato come tale, così il modello non finge di aver letto la pagina.
import os
import requests
SEARCH = "https://llmwebsearch.com/api/v1/search"
KEY = os.environ["LLMWEBSEARCH_API_KEY"]
BUDGET_CHARS = 6000 * 4 # about 6,000 tokens of sources
def sources_for(question):
resp = requests.post(SEARCH, headers={"Authorization": "Bearer " + KEY},
json={"q": question, "max_results": 5, "read": 3}, timeout=60)
resp.raise_for_status()
results = resp.json()["results"]
share = BUDGET_CHARS // max(len(results), 1)
blocks = []
for i, r in enumerate(results, start=1):
if r["content_status"] == "ok":
body = r["content"][:share]
else:
body = "(snippet only) " + r["snippet"]
blocks.append(f"[{i}] {r['title']} - {r['url']}\n{body}")
return "\n\n".join(blocks)
def answer(question):
prompt = ("Answer the question using only the sources below. "
"Cite them as [1], [2] and so on. Say so if the sources do not answer it.\n\n"
+ sources_for(question) + "\n\nQuestion: " + question)
resp = requests.post(
os.environ["LLM_BASE_URL"].rstrip("/") + "/chat/completions",
headers={"Authorization": "Bearer " + os.environ["LLM_API_KEY"]},
json={"model": os.environ["LLM_MODEL"], "messages": [{"role": "user", "content": prompt}]},
timeout=120,
)
resp.raise_for_status()
return resp.json()["choices"][0]["message"]["content"]
print(answer("How should I size a Postgres connection pool?"))
Quanto testo devo inviare al modello?
Il minimo che basta a rispondere alla domanda. Tre pagine ridotte a 2.000 token ciascuna di solito battono dieci pagine intere: il modello si concentra meglio e ogni risposta costa meno. Abbassa max_chars nella ricerca stessa quando sai che ridurrai, così viaggiano meno dati.
Ridurre dall'inizio di una pagina funziona per la maggior parte degli articoli, perché il punto principale tende a venire prima. Per le pagine di riferimento lunghe, dividi il contenuto per intestazioni e tieni le sezioni i cui titoli corrispondono alla domanda.
Cosa deve fare il codice quando manca una pagina?
Prosegui con le altre fonti. Una pagina marcata blocked_by_robots, blocked_by_site_policy o target_refused non verrà letta a un secondo tentativo, quindi non ciclare su di essa. Una pagina fetch_timeout o fetch_failed potrebbe funzionare più tardi tramite https://llmwebsearch.com/api/v1/extract.
Gli errori HTTP sono diversi: 429, 502 e 504 meritano un retry con una pausa crescente. La pagina degli errori ha un helper di retry pronto all'uso.
Python: domande frequenti
Esiste un pacchetto pip per LLMWebSearch?
No. L'API è semplice HTTPS e JSON, quindi ti bastano requests o httpx. Gli script in questa pagina sono l'integrazione: copiali nel tuo progetto e adattali.
Posso usare questi script con un modello locale?
Sì. Qualsiasi server che espone una route chat/completions compatibile con OpenAI funziona con il ciclo di citazioni: imposta LLM_BASE_URL sul suo indirizzo e LLM_MODEL sul nome del modello che si aspetta.
Come conto i crediti da Python?
Somma credits_used di ogni risposta, oppure chiama GET https://llmwebsearch.com/api/v1/credits prima e dopo un job; quella chiamata è gratuita.