Pętla agenta w Pythonie od zera: narzędzia, limity kroków i kosztów

Piotr Dul

27-10-2026

(aktualizacja: 27-10-2026)

Jak napisać pętlę agenta w Pythonie bez frameworka: kolejne wywołania narzędzi, warunki zatrzymania, limit kroków i tokenów oraz wykrywanie zapętlenia.

Pętla agenta w Pythonie od zera: narzędzia, limity kroków i kosztów
Pętla agenta (ang. agent loop) to for albo while, w którym wywołujesz model z narzędziami, wykonujesz zwrócone tool_calls, dopisujesz wyniki do historii i powtarzasz, aż model odpowie tekstem zamiast prosić o kolejne narzędzie. Każda taka pętla potrzebuje twardych limitów: maksymalnej liczby kroków i budżetu tokenów, bo historia rośnie z każdym krokiem. Po przekroczeniu limitu zakończ pętlę kontrolowanie, na przykład ostatnim wywołaniem z tool_choice="none".

W części o tool callingu w OpenAI API model poprosił o narzędzia, my je wykonaliśmy, a on napisał odpowiedź. To jeden obieg. Problem w tym, że prawdziwe pytania często wymagają kilku kroków: najpierw sprawdzasz jedną rzecz, potem na jej podstawie drugą. Agent to dokładnie ten sam mechanizm powtarzany w pętli, aż model uzna, że ma komplet informacji. W angielskojęzycznej dokumentacji nazywa się to agent loop albo agentic loop.

Pętla jest prosta do napisania i łatwa do napisania źle. Bez limitów jeden błąd w opisie narzędzia potrafi zakończyć się dziesiątkami zapytań i zużytym budżetem. Dlatego większość tego artykułu dotyczy nie samej pętli, tylko tego, kiedy i jak ją zatrzymać. Korzystamy z narzędzi get_price i check_stock oraz funkcji run_tool z poprzedniej części, więc zacznij od niej.

Czym pętla agenta (agent loop) różni się od jednego wywołania z narzędziem?

W jednym wywołaniu z narzędziem to Ty z góry wiesz, że będą dwa zapytania do modelu: pierwsze z pytaniem, drugie z wynikiem. W agencie liczbę kroków wyznacza model. Raz wystarczy jedno narzędzie, innym razem trzy po kolei, bo wynik pierwszego wpływa na to, o co zapyta drugi.

CechaJedno wywołanie z narzędziemPętla agenta
Liczba zapytań do modeluStała, zwykle dwaZmienna, zależy od modelu
Kto decyduje o zakończeniuTwój kodModel (albo limit)
Ryzyko zapętleniaBrakRealne, potrzebne limity
KosztŁatwy do oszacowaniaTrzeba go ograniczać

Jak wygląda pętla agenta w Pythonie?

Poniżej kompletna pętla. Ma prosty kształt: w każdym kroku pytasz model, a jeśli nie poprosił o narzędzia, to znaczy, że skończył. W przeciwnym razie wykonujesz narzędzia i wracasz na początek.

from dataclasses import dataclass

from openai import OpenAI

SYSTEM_PROMPT = (
    "Jesteś asystentem sklepu. Używaj narzędzi, żeby sprawdzać ceny i stany magazynowe. "
    "Nie zgaduj. Gdy masz komplet informacji, odpowiedz użytkownikowi po polsku."
)


@dataclass
class AgentLimits:
    max_steps: int = 6
    max_tokens: int = 20_000


@dataclass
class AgentResult:
    answer: str
    steps: int
    tokens: int
    stop_reason: str  # "done", "max_steps" albo "max_tokens"


def run_agent(
    client: OpenAI,
    question: str,
    limits: AgentLimits = AgentLimits(),
    model: str = "gpt-4o-mini",
) -> AgentResult:
    messages = [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": question},
    ]
    tokens_used = 0
    seen_calls: set[tuple[str, str]] = set()

    for step in range(1, limits.max_steps + 1):
        if tokens_used >= limits.max_tokens:
            return finish(client, messages, model, step - 1, tokens_used, "max_tokens")

        response = client.chat.completions.create(
            model=model, messages=messages, tools=TOOLS
        )
        tokens_used += response.usage.total_tokens
        message = response.choices[0].message

        if not message.tool_calls:
            return AgentResult(message.content, step, tokens_used, "done")

        messages.append(message)
        for call in message.tool_calls:
            key = (call.function.name, call.function.arguments)
            if key in seen_calls:
                result = "To wywołanie już było. Użyj innych argumentów albo odpowiedz użytkownikowi."
            else:
                seen_calls.add(key)
                result = run_tool(call.function.name, call.function.arguments)
            messages.append(
                {"role": "tool", "tool_call_id": call.id, "content": result}
            )

    return finish(client, messages, model, limits.max_steps, tokens_used, "max_steps")

TOOLS i run_tool pochodzą z poprzedniej części. Funkcja finish jest opisana niżej. Zwróć uwagę, że wynik to nie sam tekst, tylko obiekt AgentResult z liczbą kroków, zużyciem tokenów i powodem zatrzymania. Dzięki temu wiesz nie tylko, co agent odpowiedział, ale też dlaczego skończył.

Kiedy pętla agenta powinna się zatrzymać?

Warunków zakończenia jest kilka i każdy z nich jest potrzebny:

Powód zatrzymaniaJak go wykryćCo zrobić
Model skończyłOdpowiedź bez tool_callsZwróć message.content. To normalne zakończenie.
Za dużo krokówLicznik pętli dobił do max_stepsZakończ kontrolowanie (patrz niżej).
Za dużo tokenówSuma z response.usage przekroczyła budżetZakończ kontrolowanie, zanim wydasz więcej.
ZapętlenieTe same narzędzie i argumenty po raz drugiNie wykonuj ponownie, podpowiedz modelowi, żeby zmienił podejście.

Bez żadnego z tych zabezpieczeń agent, któremu narzędzie ciągle zwraca „nie znaleziono”, może w nieskończoność próbować tego samego.

Dlaczego limit tokenów jest równie ważny jak limit kroków?

Bo koszt kroku nie jest stały. API nie ma pamięci, więc w każdym kroku wysyłasz całą dotychczasową historię: wiadomość systemową, pytanie, wszystkie poprzednie wywołania i ich wyniki. Im dalej w pętli, tym większe zapytanie. Pokazuje to prosty rachunek na przykładowych liczbach: wiadomość systemowa i pytanie zajmują razem 300 tokenów, a każdy krok dokłada 250 tokenów (wywołanie i wynik).

KrokTokenów wysyłanych do modelu
1300
2550
3800
41050
51300
61550
Razem5550

Gdyby historia nie rosła, sześć kroków kosztowałoby 1800 tokenów wejściowych. Przy rosnącej historii to 5550, czyli trzy razy więcej. Dłuższe pętle drożeją więc szybciej niż liniowo. Dlatego sumujemy response.usage.total_tokens po każdym kroku i przerywamy po przekroczeniu budżetu. Jak działa okno kontekstu i skąd te liczby, wyjaśniamy w artykule o tokenach, kontekście i kosztach w API OpenAI.

Jaki limit kroków ustawić na start?

Zacznij od max_steps=4 albo 5 i podnoś go dopiero wtedy, gdy realne pytania tego wymagają. Prosty agent z dwoma narzędziami rzadko potrzebuje więcej niż kilku kroków, a niski limit szybciej ujawnia błędy w opisach narzędzi.

Co zrobić, gdy limit zostanie przekroczony?

Najgorsze, co możesz zrobić, to rzucić wyjątek i pokazać użytkownikowi pustą stronę albo błąd. Lepszy sposób to kontrolowane zakończenie: zrób jeszcze jedno wywołanie z tool_choice="none", które zabrania modelowi używać narzędzi i zmusza go do napisania odpowiedzi na podstawie tego, co już wie.

def finish(client, messages, model, steps, tokens_used, reason) -> AgentResult:
    messages.append(
        {
            "role": "user",
            "content": "Limit kroków wyczerpany. Odpowiedz na podstawie tego, co wiesz.",
        }
    )
    response = client.chat.completions.create(
        model=model, messages=messages, tools=TOOLS, tool_choice="none"
    )
    tokens_used += response.usage.total_tokens
    return AgentResult(response.choices[0].message.content, steps, tokens_used, reason)

Użytkownik dostaje częściową, ale sensowną odpowiedź, a Ty w polu stop_reason widzisz, że agent nie skończył sam. Pamiętaj, że to wywołanie też kosztuje, więc budżet tokenów traktuj jako limit zasadniczy, nie co do jednego tokenu.

Dlaczego warto zwracać powód zatrzymania, a nie tylko odpowiedź?

Loguj stop_reason przy każdym uruchomieniu. Jeśli duża część odpowiedzi kończy się na max_steps, to sygnał, że opis narzędzia jest niejasny albo pytania są za szerokie, a nie, że trzeba podnieść limit. Podnoszenie limitu bez zrozumienia przyczyny tylko zwiększa rachunek.

Jak wykryć agenta, który kręci się w kółko?

W kodzie zapisujemy w zbiorze seen_calls pary (nazwa narzędzia, argumenty). Jeśli model prosi o dokładnie to samo po raz drugi, nie wykonujemy narzędzia ponownie, tylko odsyłamy komunikat z podpowiedzią. Model dostaje informację, że ten ślad już sprawdził, i zwykle zmienia podejście albo kończy odpowiedzią.

To proste zabezpieczenie, które nie zastępuje limitu kroków. Zapętlenie bywa też subtelniejsze: model może prosić o to samo z minimalnie innymi argumentami. Wtedy łapie je dopiero max_steps.

Co z błędami narzędzi i timeoutami?

Błędy dzielą się na dwa rodzaje i każdy obsługujemy inaczej:

Rodzaj błęduPrzykładObsługa
Błąd, który model może naprawićZłe argumenty, nieznany produktZwróć tekst jako wynik narzędzia (robi to run_tool).
Błąd, którego model nie naprawiBrak połączenia z bazą, wyjątek w kodziePrzerwij pętlę, zaloguj błąd i pokaż użytkownikowi komunikat.

Dla wywołań samego API ustaw timeout i liczbę ponowień w kliencie, żeby jedno zawieszone zapytanie nie blokowało całego agenta:

client = OpenAI(timeout=30, max_retries=2)

Ponowienia przy błędach sieciowych klient robi sam, więc w pętli nie musisz pisać własnej obsługi.

Jak przetestować pętlę agenta bez prawdziwego API?

Pętla ma ciekawą właściwość: jej zachowanie zależy od odpowiedzi modelu, a te możesz podstawić. W testach zastąp klienta atrapą, która zwraca z góry ustalone odpowiedzi: raz wywołanie narzędzia, raz tekst. Dzięki temu sprawdzisz bez kosztów i bez losowości, czy:

  • agent kończy po normalnym zakończeniu modelu,
  • po przekroczeniu max_steps zwraca stop_reason == "max_steps" i wykonuje ostatnie wywołanie z tool_choice="none",
  • powtórzone wywołanie nie uruchamia narzędzia po raz drugi,
  • budżet tokenów faktycznie przerywa pętlę.

Jak zbudować taką atrapę i użyć unittest.mock albo pytesta, pokazujemy w artykule o testowaniu kodu z API LLM.

Co dalej?

Masz działającą pętlę z limitami. W następnej części zbudujemy agenta z narzędziami wyszukiwania i odczytu dokumentów, czyli małego asystenta, który sam wybiera, gdzie szukać odpowiedzi. Jeśli interesuje Cię, jak wyszukiwanie po znaczeniu działa pod spodem, zajrzyj do artykułu o wyszukiwarce semantycznej w Pythonie.

Konsultacja i warsztaty devmentor.pl

Masz pytania?

Partnerzy

Firmy, z którymi współpracujemy

To partnerzy odpowiedzialni za staże po naszych kursach. Organizują je dla najlepszych absolwentów.

Zobacz wszystkich partnerów →
  • VR Medical
  • Sophona
  • Robot Force