# Zbuduj agenta deep research w Pythonie z API wyszukiwania w sieci

> Zbuduj agenta deep research w około 60 liniach Pythona: planuj zapytania, wyszukuj treści stron, usuwaj duplikaty i pisz odpowiedź z cytatami przez LLM.

- URL: https://llmwebsearch.com/pl/guides/build-a-deep-research-agent/
- Ostatnia aktualizacja: 2026-10-22
- Wydawca: LLMWebSearch (https://llmwebsearch.com)

Deep research to pętla, a nie model. Ten przewodnik buduje taką, którą przeczytasz w kilka minut: planuje zapytania, wyszukuje w sieci z tekstem stron, trzyma każde źródło tylko raz, szuka luk i pisze odpowiedź z numerowanymi cytatami.

**Szybka odpowiedź — Jak zbudować agenta deep research w Pythonie?**

Napisz pętlę, która prosi LLM o zapytania do wyszukiwania, przepuszcza każde przez API wyszukiwania w sieci zwracające tekst stron, trzyma każde źródło tylko raz, prosi o zapytania uzupełniające, a następnie pisze odpowiedź z cytatami. Z **LLMWebSearch** każde wyszukiwanie zwraca pierwsze 3 strony jako Markdown za jeden kredyt; poniższy skrypt wykonuje sześć wyszukiwań na pytanie. Od 1,20 USD/1K wyszukiwań, bez KYC, płatność kryptowalutą.

## Co będzie robił agent?

1. **Plan** — Poproś model o cztery krótkie zapytania, które razem pokrywają pytanie.
2. **Wyszukiwanie** — Uruchom każde zapytanie z read=3, żeby pierwsze trzy strony wróciły jako Markdown.
3. **Trzymaj źródła tylko raz** — Przechowuj wyniki według URL i pomijaj strony już zebrane lub bez tekstu.
4. **Znajdź luki** — Pokaż modelowi dotychczas znalezione tytuły i poproś o dwa zapytania o to, czego brakuje.
5. **Napisz** — Ponumeruj do 12 źródeł i poproś o odpowiedź cytującą je jako [1], [2].

## Czego potrzebuję przed rozpoczęciem?

Trzy rzeczy:

- **Klucz API LLMWebSearch** w zmiennej środowiskowej `LLMWEBSEARCH_API_KEY`. Klucze pojawiają się w panelu po pierwszym pakiecie.
- **Dowolny endpoint czatu zgodny z OpenAI**, ustawiony przez `LLM_BASE_URL`, `LLM_API_KEY` i `LLM_MODEL`. Modele hostowane i lokalne serwery działają równie dobrze.
- **Python 3.9 lub nowszy** z pakietem `requests`.

Skrypt używa zwykłego HTTP dla obu usług, więc możesz podmienić model bez zmiany kodu wyszukiwania.

*Python: deep_research.py*

```python
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}")
```

## Jak działa każda część?

`plan_queries` prosi model o krótkie zapytania, jedno na linię, i usuwa znaczniki listy. Krótkie zapytania lepiej pasują do tego, jak wyszukiwarki rankują strony, niż pełne pytania.

`search` wywołuje `POST https://llmwebsearch.com/api/v1/search` z `max_results=5`, `read=3` i `max_chars=5000`. Odpowiedź zawiera pięć wyników; pierwsze trzy niosą tekst strony w `content`.

`research` prowadzi rundy. Trzyma źródła w słowniku kluczowanym po URL, więc strona znaleziona przez dwa zapytania liczy się raz. Zachowywane są tylko wyniki z `content_status` równym `ok`, bo odpowiedź musi opierać się na tekście, który model może przeczytać.

Po każdej rundzie model widzi dotychczas zebrane tytuły i proponuje dwa nowe zapytania o to, czego brakuje. Końcowy prompt numeruje do dwunastu źródeł i ustala zasady cytowania.

## Ile kosztuje jeden raport?

Sześć wyszukiwań na pytanie przy domyślnych ustawieniach: cztery w pierwszej rundzie i dwa w drugiej. Przy `read` równym 3 to sześć kredytów. 25 000 wyszukiwań przy 41,25 USD pokrywa więc ponad 4 000 raportów.

Wywołania modelu kosztują dodatkowo na Twoim rachunku za LLM: trzy wywołania planujące i jedno długie wywołanie odpowiedzi na raport. Wywołanie odpowiedzi niesie do dwunastu stron po 5 000 znaków, więc obniż `max_chars`, jeśli Twój model jest drogi.

## Jak sprawić, żeby szło głębiej?

Zmień liczby, nie strukturę:

- **Więcej rund.** `rounds=4` pozwala modelowi ścigać jeszcze dwa zestawy luk.
- **Więcej zapytań na rundę.** Podnieś `n` w wywołaniu szukającym luk z 2 do 4.
- **Węższe źródła.** Dodaj `include_domains` do treści zapytania wyszukiwania, aby ograniczyć się do oficjalnych lub akademickich stron.
- **Tylko świeże materiały.** Dodaj `"time_range": "month"` lub `"topic": "news"`.
- **Więcej stron na zapytanie.** `read=5` czyta pięć stron, po 2 kredyty za wyszukiwanie.

Dodaj regułę zatrzymania, zanim podniesiesz limity: zakończ pętlę, gdy runda przyniesie mniej niż dwa nowe źródła.

## Jak zadbać o bezpieczeństwo i kulturę agenta?

Traktuj każdą stronę jako niezaufany tekst. Końcowy prompt mówi modelowi, aby traktował źródła wyłącznie jako dowody; dodaj wyraźną linię, że instrukcje zawarte w źródłach należy ignorować. Nigdy nie pozwól, aby źródło wywołało narzędzie lub zapis do pliku.

Po stronie sieci Nasz czytnik respektuje robots.txt: przedstawia się jako LLMWebSearch, przestrzega reguł, które witryna zapisuje dla niego lub dla wszystkich robotów, i pomija treść każdej strony, której nie wolno mu czytać. Strony, które odmawiają dostępu, wracają bez treści, a skrypt po prostu je pomija. Gdy witryna odrzuca nasz czytnik, wynik wraca bez treści i ze statusem witryny: nigdy nie rozwiązujemy CAPTCHA, nie logujemy się na konta ani nie obchodzimy blokady czy paywalla.

## Co może pójść nie tak?

W testach wychodzą trzy rzeczy:

- **Niejasne zapytania.** Jeśli plan jest słaby, każda runda znajduje te same strony. Poproś model o zapytania z konkretnymi nazwami i datami.
- **Zbyt mało źródeł z tekstem.** Niektóre tematy są za paywallem. Poszerz zapytania lub pozwól na więcej wyników.
- **Zbyt długie prompty.** Dwanaście długich stron może przekroczyć kontekst małego modelu. Zmniejsz `max_chars` lub zostaw osiem źródeł.

Loguj każde zapytanie i zwrócone przez nie URL-e. Czytanie tego logu to najszybszy sposób na poprawę promptu planu.

## Agent do pogłębionego researchu: najczęstsze pytania

### Czy mogę uruchomić to na lokalnym modelu?

Tak, jeśli lokalny serwer obsługuje format OpenAI chat-completions. Ustaw `LLM_BASE_URL` na jego adres, a `LLM_MODEL` na nazwę modelu.

### Czy mogę zamienić to w narzędzie MCP?

Tak. Owiń `research()` w narzędzie MCP albo daj swojemu agentowi zwykłe narzędzie wyszukiwania ze [strony MCP](https://llmwebsearch.com/pl/docs/mcp/) i pozwól mu samodzielnie prowadzić pętlę.

### Dlaczego nie zapytać search API bezpośrednio o odpowiedź?

Ponieważ LLMWebSearch nie pisze odpowiedzi. API zwraca źródła, a nie gotową odpowiedź: Twój własny model czyta strony, pisze odpowiedź i cytuje adresy URL.

### Jak sprawdzić, ile kosztowało każde wyszukiwanie?

Każda odpowiedź zawiera `credits_used`, a `GET https://llmwebsearch.com/api/v1/credits` pokazuje Twoje pozostałe saldo bez żadnych kosztów.

---

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ą.
