Szybka odpowiedź
Jak wywołać web search API dla agentów AI z Pythona?
Wyślij zapytanie metodą POST do https://llmwebsearch.com/api/v1/search z requests lub httpx i kluczem Bearer. LLMWebSearch zwraca ranking wyników z 3 najważniejszymi stronami jako Markdown, które możesz ponumerować i przekazać dowolnemu LLM, aby odpowiedział z cytowaniami. Jedno wyszukiwanie kosztuje 1 kredyt, od 1,20 USD/1K wyszukiwań według stanu na październik 2026. Python 3.9+ i bez SDK. Bez KYC; płatność kryptowalutą.
Co potrzebujesz
| Python | 3.9 lub nowszy |
|---|---|
| Pakiety | requests dla prostego skryptu, httpx dla asynchronicznego |
| Klucz | LLMWEBSEARCH_API_KEY w Twoim środowisku |
| Dla pętli odpowiedzi | Dowolny endpoint czatu zgodny z OpenAI: LLM_BASE_URL, LLM_API_KEY, LLM_MODEL |
| Rozliczanie | Jeden kredyt opłaca jedno wyszukiwanie: do 20 wyników z rankingiem, przy czym strony z pierwszych 3 są odczytywane i zwracane jako Markdown. Odczyt od 4 do 10 stron to 2 kredyty. Wywołanie zakończone błędem nic nie kosztuje |
Jak wykonać jedno wyszukiwanie za pomocą requests?
Zbuduj treść JSON z q i potrzebnymi opcjami, wyślij ją i przejdź w pętli po results. Sprawdź content_status, zanim sięgniesz do content: tylko wyniki oznaczone ok zawierają tekst strony. Poniższy skrypt zapisuje każdą odczytaną stronę jako plik Markdown nazwany według jej pozycji, z adresem źródła w pierwszej linii.
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"])
Jak wykonać wiele wyszukiwań naraz?
Użyj httpx.AsyncClient i asyncio.Semaphore. Semafor ogranicza liczbę wywołań w locie, co trzyma Cię poniżej limitu na minutę dla Twojego konta i rozkłada obciążenie. Cztery do ośmiu równoczesnych wywołań pasuje do większości agentów badawczych. Każde konto ma limit wywołań na minutę, widoczny w dashboardzie; poproś support, a można go podnieść.
Przykład wysyła jedno wyszukiwanie na podpytanie, typowy krok agenta badawczego, i zbiera adresy URL odczytanych stron. Każde wywołanie jest niezależne: jedna porażka nie zatrzymuje pozostałych.
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())
Jak uzyskać odpowiedź z cytowaniami od dowolnego LLM?
Najpierw wyszukaj, potem daj modelowi numerowane źródła i poproś, aby je zacytował. API zwraca źródła, a nie gotową odpowiedź: Twój własny model czyta strony, pisze odpowiedź i cytuje adresy URL, więc to Ty wybierasz model i styl cytowania. Poniższa pętla działa z dowolnym endpointem czatu, który mówi w formacie chat/completions zgodnym z OpenAI; ustaw jego adres, klucz i model w trzech zmiennych środowiskowych.
Kontroluj kontekst. Skrypt przycina każde źródło, aby całość mieściła się w budżecie około 6 000 tokenów, licząc mniej więcej cztery znaki na token. Wynik bez treści jest przekazywany jako jego snippet, odpowiednio oznaczony, żeby model nie udawał, że przeczytał stronę.
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?"))
Ile tekstu powinienem wysłać do modelu?
Tyle, ile wystarczy, by odpowiedzieć na pytanie. Trzy strony przycięte do 2 000 tokenów każda zwykle biją dziesięć pełnych stron: model lepiej się skupia, a każda odpowiedź kosztuje mniej. Zmniejsz max_chars w samym wyszukiwaniu, gdy wiesz, że będziesz przycinać, żeby mniej danych podróżowało.
Przycinanie od początku strony działa dla większości artykułów, bo główny punkt zwykle jest na początku. W przypadku długich stron referencyjnych podziel treść według nagłówków i zachowaj sekcje, których tytuły pasują do pytania.
Co powinien zrobić kod, gdy brakuje strony?
Kontynuuj z pozostałymi źródłami. Strona oznaczona blocked_by_robots, blocked_by_site_policy lub target_refused nie zostanie odczytana przy drugiej próbie, więc nie zapętlaj się na niej. Strona fetch_timeout lub fetch_failed może zadziałać później przez https://llmwebsearch.com/api/v1/extract.
Błędy HTTP są inne: 429, 502 i 504 zasługują na ponowną próbę z rosnącą pauzą. Strona błędów ma gotowy helper ponawiania.
Python: najczęstsze pytania
Czy istnieje pakiet pip dla LLMWebSearch?
Nie. API to zwykłe HTTPS i JSON, więc wystarczy Ci requests lub httpx. Skrypty na tej stronie to właśnie integracja: skopiuj je do swojego projektu i dostosuj.
Czy mogę używać tych skryptów z modelem lokalnym?
Tak. Każdy serwer, który udostępnia trasę chat/completions zgodną z OpenAI, działa z pętlą cytowania: ustaw LLM_BASE_URL na jego adres, a LLM_MODEL na nazwę modelu, której oczekuje.
Jak liczyć kredyty z Pythona?
Zsumuj credits_used z każdej odpowiedzi albo wywołaj GET https://llmwebsearch.com/api/v1/credits przed zadaniem i po nim; to wywołanie jest darmowe.