Rachunek eksplodował, a nic się nie zmieniło

przez Łukasz | sie 24, 2026

Objawy

Rachunek za bieżący miesiąc jest wielokrotnie wyższy niż za poprzedni. Liczba obsłużonych zadań się nie zmieniła. Nikt niczego nie wdrażał — albo wdrożył coś, co z kosztem nie miało nic wspólnego.

Sygnały, które warto sprawdzić od razu, bo od nich zaczyna się rozpoznanie:

Mediana kosztu na zadanie kontra percentyl dziewięćdziesiąty piąty. Jeśli mediana stoi w miejscu, a ogon odjechał — winne są pojedyncze przebiegi. Jeśli mediana wzrosła — winne są wszystkie.

Tokeny wejściowe na pojedynczy obrót. Rosną z krokami zawsze, ale ich poziom startowy jest stały. Jeśli podskoczył, coś doszło do stałego prefiksu.

Udział tokenów odczytanych z pamięci podręcznej. Spadek do zera przy niezmienionym kodzie promptu to jeden z najczęstszych i najbardziej cichych powodów.

Mechanizm w trzech zdaniach

Przy każdym obrocie pętli cały dotychczasowy kontekst jedzie do modelu od nowa — instrukcje, definicje narzędzi, historia kroków i wszystkie zebrane wyniki. Koszt zadania nie jest więc iloczynem liczby kroków i ceny wywołania, tylko sumą rosnącego ciągu.

Szczegóły tej arytmetyki opisuje artykuł o pętli od środka. Tutaj interesuje nas wyłącznie to, co się w niej zmieniło.

Diagnostyka różnicowa

Siedem przyczyn, w kolejności od najczęstszej. Pierwszy podział jest prosty: czy wzrosła mediana, czy tylko ogon.

1. Zapętlenia — rośnie tylko ogon

Jak rozpoznać: mediana liczby kroków bez zmian, maksimum i percentyl dziewięćdziesiąty piąty wielokrotnie wyższe. Kilka procent przebiegów robi większość rachunku.

Test: policz rozkład liczby kroków. Jeśli maksimum jest trzykrotnie wyższe od mediany, przejdź do poprzedniego syndromu — to ta sama choroba widziana od strony faktury.

2. Rozdęte wyniki narzędzi — rośnie mediana

Jak rozpoznać: liczba kroków bez zmian, ale tokeny wejściowe na krok rosną szybciej niż wcześniej. Zwykle po zmianie w API albo po dodaniu pola do odpowiedzi.

Dlaczego to boli nieproporcjonalnie: jednorazowo duży wynik podnosi koszt wszystkich kolejnych obrotów, bo od tego momentu jest wysyłany ponownie za każdym razem.

Test: znajdź w śladzie krok, po którym przyrost tokenów między obrotami wyraźnie się zwiększa. Zobacz, co zwróciło narzędzie.

3. Zbity cache promptu — rośnie mediana

Jak rozpoznać: udział tokenów z pamięci podręcznej spadł do zera albo blisko zera. Koszt wzrósł o kilkadziesiąt procent równomiernie na wszystkich przebiegach.

Dlaczego się dzieje: cache działa od początku sekwencji do pierwszej różnicy. Wystarczy znacznik czasu, identyfikator sesji albo niestabilna kolejność definicji narzędzi na początku promptu, żeby unieważnić wszystko dalej.

Test: odczytaj z odpowiedzi API liczbę tokenów odczytanych z cache przy dwóch kolejnych wywołaniach z tym samym prefiksem. Zero oznacza rozpoznanie.

To jest przyczyna, którą najłatwiej wprowadzić przypadkiem — jedna linia dopisana do logów potrafi podnieść rachunek, nie powodując żadnej widocznej awarii.

4. Ciche ponowienia — rośnie mediana

Jak rozpoznać: liczba wywołań modelu w rozliczeniu dostawcy jest wyraźnie wyższa niż w twoim logu aplikacji.

Dlaczego się dzieje: większość frameworków ponawia po cichu — przy niepoprawnej strukturze argumentów, przy timeoucie, przy przekroczonym limicie zapytań. Nieudane wywołania bywają rozliczane i nie zostawiają śladu.

Test: porównaj obie liczby za ten sam okres. Rozjazd jest rozpoznaniem.

5. Rozrost katalogu narzędzi — rośnie mediana

Jak rozpoznać: tokeny wejściowe pierwszego obrotu wzrosły. To odróżnia tę przyczynę od wszystkich pozostałych — koszt rośnie, zanim cokolwiek się wydarzy.

Dlaczego się dzieje: ktoś podłączył serwer MCP albo dodał kilka narzędzi. Katalog jedzie do modelu przy każdym obrocie, więc mnoży się przez liczbę kroków.

Test: policz długość samych definicji narzędzi i porównaj z poprzednim wdrożeniem.

6. Model rozumujący — rośnie mediana kosztu wyjścia

Jak rozpoznać: tokeny wyjściowe są wielokrotnie liczniejsze niż widoczna odpowiedź.

Dlaczego się dzieje: tokeny rozumowania są rozliczane po stawce wyjściowej, a w interfejsie zwykle ich nie widać. Jeśli ktoś przełączył model na rozumujący albo podniósł nakład rozumowania, koszt wzrośnie bez żadnej zmiany w kodzie.

Test: odczytaj z odpowiedzi API liczbę tokenów rozumowania i porównaj z długością odpowiedzi.

7. Zmiana po stronie dostawcy

Jak rozpoznać: nic z powyższego nie pasuje, a data wzrostu jest ostra.

Dlaczego się dzieje: aktualizacja modelu potrafi zmienić jego gadatliwość albo skłonność do dłuższego rozumowania. Zmienić mógł się też cennik.

Test: sprawdź, czy zapisujesz wersję modelu w śladzie przebiegu. Jeśli nie — to jest pierwsza rzecz do naprawienia, bo bez tego ta przyczyna jest niediagnozowalna.

Postępowanie

Natychmiast: limit budżetu na przebieg jako warunek zakończenia oraz alert przy przekroczeniu progu dziennego. To zatrzymuje krwawienie w godzinę.

Potem: przejdź podział mediana kontra ogon. Ogon prowadzi do zapętleń, mediana do przyczyn od drugiej do siódmej. Sprawdzenie cache i porównanie liczby wywołań z rozliczeniem zajmuje kilkanaście minut i zamyka trzy z siedmiu możliwości.

Nie zaczynaj od skracania promptu systemowego. To najczęstszy odruch i zwykle najmniejsza pozycja — do tego akurat prompt systemowy jest kandydatem idealnym do cache, więc skracanie go bywa optymalizacją odwrotną do potrzebnej. Najwięcej daje przycięcie wyników narzędzi.

Profilaktyka

Loguj per przebieg: tokeny wejściowe i wyjściowe osobno, tokeny odczytane z cache, tokeny rozumowania, liczbę kroków, liczbę ponowień i wersję modelu. Wszystko to jest w odpowiedzi API — wystarczy zapisać.

Monitoruj rozkłady, nie średnie. Alert warto wieszać na percentylu, bo średnia wygładza dokładnie to, co powinno zapalić lampkę.

Zwracaj z narzędzi uchwyt zamiast danych tam, gdzie wynik bywa obszerny. I trzymaj się zasady: stałe na początku promptu, zmienne na końcu.

Jak zdiagnozować u siebie

Analizator śladów wskazujący, w którą stronę iść.

python
import statistics as st

def diagnoza(przebiegi):
    """przebiegi: lista {'kroki':int,'tokeny_we':int,'tokeny_wy':int,
                         'z_cache':int,'rozumowanie':int,'model':str,
                         'we_pierwszy_obrot':int}"""
    kroki = [p["kroki"] for p in przebiegi]
    koszt = [p["tokeny_we"] + p["tokeny_wy"] for p in przebiegi]

    med_k, max_k = st.median(kroki), max(kroki)
    med_c = st.median(koszt)
    p95 = sorted(koszt)[int(len(koszt) * 0.95) - 1]

    print(f"kroki   mediana {med_k}  maks {max_k}")
    print(f"koszt   mediana {med_c}  p95 {p95}")

    if max_k > 3 * med_k:
        print("→ OGON: podejrzenie zapętleń (przyczyna 1)")
    if p95 > 3 * med_c and max_k <= 3 * med_k:
        print("→ OGON bez zapętleń: sprawdź pojedyncze duże wyniki narzędzi")

    cache = sum(p["z_cache"] for p in przebiegi)
    we = sum(p["tokeny_we"] for p in przebiegi)
    udzial = cache / we if we else 0
    print(f"cache   {udzial:.0%} tokenów wejściowych")
    if udzial < 0.05:
        print("→ MEDIANA: cache promptu nie działa (przyczyna 3)")

    roz = sum(p["rozumowanie"] for p in przebiegi)
    wy = sum(p["tokeny_wy"] for p in przebiegi)
    if wy and roz / wy > 0.5:
        print(f"→ MEDIANA: {roz/wy:.0%} wyjścia to rozumowanie (przyczyna 6)")

    pierwsze = [p["we_pierwszy_obrot"] for p in przebiegi]
    print(f"prefiks {st.median(pierwsze)} tokenów w pierwszym obrocie")
    print("   porównaj z poprzednim wdrożeniem — wzrost = katalog narzędzi (5)")

    modele = {p["model"] for p in przebiegi}
    if len(modele) > 1:
        print(f"→ UWAGA: różne wersje modelu w próbce: {modele} (przyczyna 7)")

przebiegi = [
    {"kroki": 4, "tokeny_we": 12000, "tokeny_wy": 400, "z_cache": 0,
     "rozumowanie": 0, "model": "gpt-4o-mini@2026-05", "we_pierwszy_obrot": 2400},
    {"kroki": 5, "tokeny_we": 15000, "tokeny_wy": 500, "z_cache": 0,
     "rozumowanie": 0, "model": "gpt-4o-mini@2026-05", "we_pierwszy_obrot": 2400},
    {"kroki": 22, "tokeny_we": 98000, "tokeny_wy": 900, "z_cache": 0,
     "rozumowanie": 0, "model": "gpt-4o-mini@2026-05", "we_pierwszy_obrot": 2400},
]
diagnoza(przebiegi)

Powyższa próbka pokazuje dwie przyczyny naraz: jeden przebieg z dwudziestoma dwoma krokami przy medianie cztery oraz zerowy udział cache. Pierwsze to zapętlenie, drugie to zbity prefiks — i akurat te dwie lubią występować razem, bo zapętlony przebieg dokłada do historii coraz to nowe wyniki, przez co żaden fragment sekwencji nie powtarza się między wywołaniami.

Ćwiczenie: puść to na trzydziestu realnych przebiegach z tygodnia przed wzrostem i trzydziestu po. Różnica w tych pięciu liczbach niemal zawsze wskazuje przyczynę bez zaglądania w kod.

Powiązane

Arytmetyka kosztu i cache promptu: Maszyneria agenta. Skąd bierze się koszt długiego kontekstu na poziomie modelu: Anatomia LLM.

Następny syndrom: Agent wybiera złe narzędzie.

Agent kończy, ale zadania nie wykonał

Agent kończy, ale zadania nie wykonał

Objawy Agent odpowiada: „Przygotowałem i wysłałem potwierdzenie do klienta." Potwierdzenie nie zostało wysłane. Albo: „Zaktualizowałem wszystkie rekordy" — zaktualizował trzy z dwunastu. Albo: „Nie znalazłem żadnych zamówień" — bo narzędzie zwróciło błąd, którego nikt...

Agent zrobił coś, o co nikt nie prosił

Agent zrobił coś, o co nikt nie prosił

Objawy Operacja, której nikt nie zlecił. Dane wysłane pod adres, którego nie ma w żadnej konfiguracji. Rekord zmieniony poza zakresem zadania. Odpowiedź, z której wynika, że agent dostał instrukcje od kogoś innego niż ty. Cecha wspólna: technicznie wszystko zadziałało...

Agent zrobił to samo dwa razy

Agent zrobił to samo dwa razy

Objawy Ten syndrom różni się od pozostałych jedną rzeczą: objaw widzi klient, nie ty. Trzy identyczne wiadomości w skrzynce. Dwa zamówienia zamiast jednego. Podwójne obciążenie. Duplikaty rekordów, które ktoś zauważa tydzień później. W twoim dzienniku wszystko wygląda...

Agent gubi wątek w długim zadaniu

Agent gubi wątek w długim zadaniu

Objawy Pierwsze kroki idą wzorowo. Po kilkunastu agent zaczyna się rozjeżdżać. Przestaje przestrzegać reguły z promptu systemowego, której trzymał się na początku. Zmienia format odpowiedzi w połowie zadania. Wraca do czegoś, co już ustalił, i ustala to inaczej....

Agent podaje dane, których nie ma

Agent podaje dane, których nie ma

Objawy Numer zamówienia w idealnym formacie, którego nie ma w bazie. Kwota, której w dokumencie nie ma. Nazwa pola API, które nigdy nie istniało. Cytat z regulaminu, brzmiący dokładnie jak reszta regulaminu i w nim nieobecny. Cecha wspólna wszystkich tych przypadków:...

Działał wczoraj, dziś nie działa

Działał wczoraj, dziś nie działa

Objawy Nic się nie wywala. Nie ma wyjątków, nie ma timeoutów, dziennik wygląda tak samo jak zawsze. Po prostu odpowiedzi są gorsze. Postacie, w jakich to się objawia: Jakość. Agent zaczyna pomijać kroki, które wcześniej wykonywał, albo odpowiada ogólniej. Format....

Agent wybiera złe narzędzie

Agent wybiera złe narzędzie

Objawy Agent odpowiada nie na to pytanie. Pobiera listę zamówień, gdy pytano o jedno konkretne. Odpowiada z pamięci, choć miał sprawdzić w bazie. Albo sięga po właściwe narzędzie i wpisuje w argumenty coś, czego nie da się użyć. Sygnał, który odróżnia ten syndrom od...

Agent kręci się w kółko

Agent kręci się w kółko

Objawy Agent wykonuje kolejne obroty pętli, nie zbliżając się do zakończenia zadania. W dzienniku widać jedną z trzech postaci. Powtórzenie. To samo narzędzie, te same argumenty, raz za razem — czasem dziesiątki razy pod rząd. Oscylacja. Agent wywołuje na przemian dwa...

Co logować w pętli agenta

Co logować w pętli agenta

W drugim artykule padło, że pętla powinna zwracać nie samą odpowiedź, lecz odpowiedź razem z powodem zakończenia, liczbą kroków i zużytym budżetem. To był zalążek. Tu jest pełna wersja. Typowy dziennik agenta zapisuje pytanie i odpowiedź. To akurat najmniej użyteczna...