Warunki zakończenia — kiedy agent ma przestać

przez Łukasz | sie 24, 2026

W poprzednim artykule w kodzie pętli siedziała linia obrot < 8. Wpisana na sztywno, bez komentarza, żeby przykład się nie rozjechał.

Ta jedna linia jest tematem całego tego tekstu. Bez niej pętla kręci się dopóty, dopóki model prosi o narzędzia — a model potrafi prosić bardzo długo, powtarzając w kółko ten sam krok.

Cztery rodzaje warunków

Naturalny. Model zwrócił odpowiedź zamiast prośby o wywołanie narzędzia. To jedyny warunek wynikający z zachowania modelu i jedyny, na którym nie wolno poprzestać.

Limit kroków. Twarda górna granica liczby obrotów. To zabezpieczenie, nie mechanizm sterowania — jeśli agent regularnie w nie uderza, problem leży gdzie indziej.

Limit budżetu. Przerwanie po przekroczeniu zadanej liczby tokenów albo kosztu. W praktyce ważniejszy od limitu kroków, bo obroty mają bardzo różną cenę: krok, w którym narzędzie zwróciło pięćdziesiąt rekordów, kosztuje wielokrotnie więcej niż krok z trzema polami. Dziesięć kroków może oznaczać złotówkę albo dwadzieścia.

Limit czasu. Przerwanie po przekroczeniu czasu wykonania. Istotne wszędzie tam, gdzie na odpowiedź czeka człowiek albo system nadrzędny z własnym limitem.

Sensowna pętla ma wszystkie cztery. Trzy ostatnie nie są nadmiarowe — każdy łapie inny rodzaj wykolejenia.

Dlaczego model nie zatrzyma się sam

To wynika wprost z tego, co ustaliliśmy w Anatomii LLM: model nie ma dostępu do pętli i nie wie, ile kroków już wykonano, poza tym, co widzi w kontekście. Nie ma też pojęcia „poddaję się” — zawsze istnieje rozkład prawdopodobieństwa, a w nim kolejna prośba o narzędzie jest zwykle sensowną kontynuacją.

Model nie jest więc wykonawcą, który wie, kiedy skończyć. Jest funkcją, którą trzeba przestać wywoływać.

Zapętlenie i jego trzy postacie

Zapętlenie to najczęstsza awaria produkcyjna agentów i przybiera trzy formy.

Powtórzenie. To samo narzędzie, te same argumenty, raz za razem. Najłatwiejsze do wykrycia.

Oscylacja. Agent wywołuje na przemian dwa narzędzia, wracając wciąż w to samo miejsce. Sprawdzanie dwóch kolejnych kroków tego nie złapie, bo żadne dwa pod rząd nie są identyczne.

Dryf argumentów. Model wywołuje wciąż to samo narzędzie, minimalnie zmieniając zapytanie — raz z myślnikiem, raz bez, raz po polsku, raz po angielsku. Wygląda na postęp, nim nie jest.

Skąd się to bierze

Przyczyna jest mechaniczna. Model nie pamięta poprzedniego kroku — widzi tylko kontekst. Jeśli wynik narzędzia nie wniósł informacji, którą model szukał, sytuacja przy kolejnym obrocie wygląda niemal identycznie jak poprzednio. A niemal identyczne wejście daje niemal identyczną decyzję.

Najczęstszym wyzwalaczem jest narzędzie zwracające pustkę. Puste [] albo null to dla modelu sygnał nierozróżnialny: czy nic nie znaleziono, czy zapytanie było złe? Skoro nie wie, próbuje jeszcze raz.

To jest najtańsza naprawa w całym temacie i zarazem najczęściej pomijana: niech narzędzie mówi, co się stało. Nie [], tylko „nie znaleziono zamówień dla klienta 4471 w zakresie dat 2026-01-01 do 2026-06-30″. Model dostaje wtedy informację, która realnie zmienia kontekst, i może wybrać inną drogę zamiast powtarzać.

Jak wykrywać

Prosta wersja porównuje nazwę narzędzia i argumenty dwóch kolejnych wywołań. Łapie powtórzenie i nie łapie reszty.

Wersja użyteczna trzyma okno kilku ostatnich wywołań i liczy, ile razy powtórzyła się dana sygnatura. To wychwytuje także oscylację. Na dryf argumentów pomaga normalizacja przed porównaniem — sprowadzenie do małych liter, usunięcie spacji, uporządkowanie kluczy.

Co zrobić po wykryciu

Trzy sensowne reakcje.

Przerwać i zwrócić to, co udało się ustalić. Najbezpieczniejsze. Częściowy wynik z informacją o przerwaniu jest lepszy niż wynik pełny, ale zmyślony.

Wstrzyknąć do kontekstu jawną informację. „Krok wyszukiwania po numerze klienta został już wykonany i zwrócił zero wyników. Spróbuj innego podejścia albo zakończ.” To zmienia sytuację, którą model widzi — a więc może zmienić decyzję.

Eskalować do człowieka. Sensowne przy zadaniach o wysokiej stawce, gdzie przerwanie jest kosztowne.

Czwarta reakcja, najczęściej spotykana w praktyce, to milczące kontynuowanie do wyczerpania limitu. Kosztuje i nie kończy się niczym.

Zatrzymanie się to nie sukces

To rozróżnienie ginie w większości implementacji, a jest ważne.

Pętla może zakończyć się na cztery różne sposoby: model odpowiedział, wyczerpano limit kroków, wyczerpano budżet, wykryto zapętlenie. Tylko pierwszy oznacza, że zadanie prawdopodobnie zostało wykonane.

Agent przerwany limitem też coś zwróci — najczęściej odpowiedź niepełną, sformułowaną równie pewnie jak każda inna. Jeśli pętla nie odróżnia zakończenia normalnego od wymuszonego i nie przekazuje tego dalej, system nadrzędny potraktuje jedno i drugie tak samo.

Zwracaj więc nie samą odpowiedź, lecz odpowiedź razem z powodem zakończenia, liczbą kroków i zużytym budżetem. Bez tego nie da się ani sensownie monitorować, ani ocenić przebiegu — a ewaluacja trajektorii potrzebuje dokładnie tych danych.

Jak to sprawdzić u siebie

Pętla z poprzedniego artykułu, rozbudowana o cztery limity i wykrywanie zapętlenia. Narzędzie celowo zwraca pustkę, żeby zobaczyć mechanizm w działaniu.

python
import json, time
from openai import OpenAI

client = OpenAI()

def szukaj_zamowien(numer_klienta):
    return []                              # celowo: pustka bez wyjaśnienia

narzedzia = [{
    "type": "function",
    "function": {
        "name": "szukaj_zamowien",
        "description": "Wyszukuje zamówienia klienta po numerze",
        "parameters": {
            "type": "object",
            "properties": {"numer_klienta": {"type": "string"}},
            "required": ["numer_klienta"],
        },
    },
}]

MAX_KROKOW, MAX_TOKENOW, MAX_SEKUND, OKNO = 10, 20000, 30, 4

wiadomosci = [{"role": "user",
               "content": "Znajdź zamówienia klienta 4471 i podsumuj je."}]
historia, krok, tokeny, start = [], 0, 0, time.time()
powod = None

while True:
    if krok >= MAX_KROKOW:            powod = "limit kroków";  break
    if tokeny >= MAX_TOKENOW:         powod = "limit budżetu"; break
    if time.time() - start > MAX_SEKUND: powod = "limit czasu"; break

    krok += 1
    odp = client.chat.completions.create(
        model="gpt-4o-mini", messages=wiadomosci, tools=narzedzia
    )
    tokeny += odp.usage.total_tokens
    komunikat = odp.choices[0].message
    wiadomosci.append(komunikat)

    if not komunikat.tool_calls:
        powod = "model odpowiedział"
        break

    for w in komunikat.tool_calls:
        sygnatura = (w.function.name, w.function.arguments.strip().lower())
        historia.append(sygnatura)
        if historia[-OKNO:].count(sygnatura) >= 3:
            powod = "wykryto zapętlenie"

        wynik = szukaj_zamowien(**json.loads(w.function.arguments))
        wiadomosci.append({"role": "tool", "tool_call_id": w.id,
                           "content": json.dumps(wynik)})
    if powod:
        break

print(f"powód: {powod} | kroki: {krok} | tokeny: {tokeny}")

Uruchom i zobacz, ile obrotów agent wykonuje, zanim coś go zatrzyma. Prawdopodobnie kilka — i żaden nie przybliży go do celu.

Teraz najważniejsza część ćwiczenia. Zamień pustą listę na komunikat:

python
def szukaj_zamowien(numer_klienta):
    return {"znaleziono": 0,
            "komunikat": f"Brak zamówień dla klienta {numer_klienta}. "
                         f"Numer nie występuje w bazie."}

Uruchom ponownie. W większości przypadków agent zakończy po jednym wywołaniu, odpowiadając, że takiego klienta nie ma. Zmieniło się jedno zdanie w narzędziu — nie prompt, nie model, nie parametry.

To jest cała lekcja tego artykułu: zapętlenie zwykle nie jest problemem modelu, tylko projektu narzędzia.

Co dalej

Wszystko powyżej zakłada agenta, który dostaje zadanie i ma je skończyć. Istnieje jednak druga kategoria: agenci, którzy nie kończą wcale — działają w tle, budzą się co jakiś czas i sami decydują, czy jest coś do zrobienia.

Warunek zakończenia przestaje wtedy dotyczyć agenta, a zaczyna dotyczyć pojedynczego cyklu. Zmienia się też sposób liczenia budżetu i model nadzoru.

O tym jest artykuł trzeci: Agent wyzwalany kontra agent długo żyjący.

Stan poza kontekstem

Stan poza kontekstem

W pierwszym artykule padło zdanie, że agent nie kontynuuje pracy — zaczyna od zera z coraz dłuższą notatką. Notatką jest kontekst, a jego rosnąca długość to główny składnik rachunku. Pytanie, które z tego wynika, brzmi: co właściwie musi być w tej notatce, a co...

Kiedy narzędzie zawodzi

Kiedy narzędzie zawodzi

Wszystkie dotychczasowe przykłady zakładały, że narzędzie działa. W produkcji to założenie jest fałszywe częściej, niż się zakłada przy projektowaniu: API mają awarie, limity, przeciążenia i czasy oczekiwania. To, co pętla zrobi w takim momencie — i co powie o tym...

Ile narzędzi to za dużo

Ile narzędzi to za dużo

Dodanie narzędzia wygląda na czynność addytywną. Agent umie o jedną rzecz więcej, reszta zostaje jak była. Nie zostaje. Każde kolejne narzędzie pogarsza wybór wszystkich pozostałych i podnosi cenę każdego obrotu pętli — a oba efekty są niewidoczne, dopóki ktoś ich nie...

Anatomia definicji narzędzia

Anatomia definicji narzędzia

Kiedy agent wybiera niewłaściwe narzędzie, pierwszym odruchem jest poprawianie promptu systemowego. Drugim — zmiana modelu na mocniejszy. Zwykle problem leży gdzie indziej i naprawia się go bez dotykania jednego i drugiego. Model nie widzi kodu To warto powiedzieć...

Agent wyzwalany kontra agent długo żyjący

Agent wyzwalany kontra agent długo żyjący

Dwa poprzednie artykuły opisywały pętlę, która dostaje zadanie i ma je skończyć. Warunek zakończenia dotyczył całego agenta, budżet liczyliśmy na zadanie, a na wynik ktoś czekał. Istnieje druga kategoria, w której żadne z tych założeń nie obowiązuje. Dwa rytmy Agent...