# Przykłady w Pythonie: wyszukiwanie, równoległe zapytania, odpowiedź z cytowaniami

> Przykłady Python web search API: requests, async httpx z semaforem i pętla odpowiedzi z cytowaniami dla dowolnego LLM w ramach budżetu tokenów. Bez KYC.

- URL: https://llmwebsearch.com/pl/docs/python/
- Ostatnia aktualizacja: 2026-10-22
- Wydawca: LLMWebSearch (https://llmwebsearch.com)

Trzy skrypty pokrywają to, czego potrzebuje większość agentów w Pythonie: jedno wyszukiwanie z `requests`, wiele wyszukiwań naraz z `httpx` i semaforem oraz krótka pętla, która przekazuje źródła modelowi językowemu i dostaje odpowiedź z numerowanymi cytowaniami.

**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

| Atrybut | Wartość |
| --- | --- |
| 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.

*search_once.py*

```python
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.

*many_searches.py*

```python
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ę.

*answer_with_citations.py*

```python
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](https://llmwebsearch.com/pl/docs/errors/) 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.

---

LLMWebSearch: LLMWebSearch to web search API dla agentów AI, który zwraca uszeregowane wyniki wraz z treścią najlepszych stron w formacie Markdown, z płatnością kryptowalutą i bez przesyłania dokumentu tożsamości. Kredyty przedpłacone od 1,20 USD/1K wyszukiwań nigdy nie wygasają. Bez KYC — bez dokumentu, bez selfie, bez zaświadczeń. Płatność kryptowalutą.
