Wszystkie wpisy

AI agents w testowaniu - nagłówek, nie owner suite (styczeń 2024)
AI agents w testowaniu - nagłówek, nie owner suite (styczeń 2024)

English

AI agents w testowaniu - nagłówek, nie owner suite (styczeń 2024)

Styczeń 2024: Auto-GPT jest nagłówkiem od wiosny 2023, Copilot Chat jest wreszcie GA, a suite nadal nie ma autonomicznego właściciela. Merguje człowiek.

Zdanie, które zestarzało się w dwa tygodnie

Jest 15 stycznia. Miesiąc temu zamykałem podsumowanie 2023 akapitem, który wtedy wyglądał na najtrwalszy wniosek roku: piszę te słowa 15 grudnia, Copilot Chat nadal nie jest ogólnie dostępny, mam obietnicę z listopadowego Universe i mam betę w edytorze. To zdanie przeżyło dokładnie czternaście dni. 29 grudnia czat wszedł w ogólną dostępność w stabilnym VS Code i w Visual Studio, więc pierwszy raz od maja piszę o narzędziu, którego nie muszę tłumaczyć przez waitlistę, kanał Insiders i rozszerzenie Nightly. Grudniowy wpis nie był pomyłką, tylko datą: 15 grudnia GA nie było, 29 grudnia jest.

Cieszy mnie to mniej, niż wyglądałoby w nagłówku, bo styczeń przyniósł drugą rzecz i to ona jest tematem tego wpisu. Odkąd wróciłem po świętach, w każdej rozmowie o testach pada słowo “agent”, i nie w znaczeniu maszyny, na której chodzi pipeline. Chodzi o pętlę: dajesz modelowi cel, model sam dobiera narzędzia, sam czyta wynik, sam planuje kolejny krok i teoretycznie sam dochodzi do końca. W wersji sprzedażowej brzmi to tak, że suite testów dostaje wreszcie właściciela, który nie chodzi na urlop: agent rano czyta czerwony przebieg, poprawia lokator, wypycha commit i zamyka zgłoszenie.

Moja teza na styczeń 2024 jest węższa i będę jej bronił przez cały wpis. Agenci są nagłówkiem, nie właścicielem suite. Pętla potrafi być szybszym szkicownikiem hipotez niż czat, i to jest realny zysk. Nie potrafi zdecydować, czy czerwony test jest złym testem, czy dobrym testem, który właśnie złapał zmianę w produkcie, a ta decyzja jest całą wartością mojej pracy. Zostaję więc przy recenzji, dokładnie tak jak przy szarym tekście przed kursorem od czerwca 2022, tylko obiekt recenzji jest większy: nie jedna podpowiedź w linii, ale kilkanaście kroków, które pętla wykonała, zanim pokazała mi diff.

Zapowiadam też od razu, czego tu nie będzie, żeby nikt nie czytał tego wpisu jako tutoriala. Nie będzie agenta podpiętego do mojego CI. Nie będzie procesu, w którym cokolwiek poza człowiekiem zakłada, zamyka albo merguje. Nie będzie tematu samonaprawiających się lokatorów, bo leczenie selektora to osobna kategoria narzędzi, starsza od tegorocznej fali agentów, i wrócę do niej osobno. Będzie jedno syntetyczne demo na moim własnym repozytorium z przykładami do bloga, trzy skreślenia i lista pytań, które i tak muszę zadać sam.

Co już umiem bez pętli

Zanim ocenię pętlę, wypiszę, co mam bez niej, bo w styczniowych rozmowach to znika najszybciej. Rok 2023 zostawił mi trzy kanały i każdy z nich ma dziś nazwane zadanie.

Pierwszy to czat w przeglądarce. Od stycznia zeszłego roku wklejam tam kryteria akceptacji i dostaję listę przypadków testowych, z której wykreślam mniej więcej połowę. Reszta idzie do zgłoszenia, nie do commita. Wejściem jest ticket albo kod, nie pamięć modelu o typowej aplikacji, i to jedno założenie odpowiada za większość wartości tego kanału.

Drugi to czat w edytorze i on właśnie zmienił status. Od 29 grudnia mam go w stabilnym VS Code, bez Insidersa i bez Nightly, na tej samej subskrypcji Copilota co ghost text. Zadanie ma wąskie: rozmawiam z nim o pliku, który mam otwarty. Wyjaśnij mi ten fixture. Zaproponuj drugi wariant asercji. Nazwij mi te trzy testy tak, żeby dało się je odróżnić w raporcie. Nie widzi logu z nocnego przebiegu i nie widzi mojego środowiska testowego.

Trzeci to triage. W lipcu prowadziłem przez dziewięć dni notes, w którym każdy niezrozumiały czerwony przebieg szedł do modelu po trzy hipotezy. Wniosek z tamtego notesu jest kontekstem dla całego dzisiejszego wpisu: z 33 hipotez dziewięć potwierdziłem odtworzeniem, ale tylko trzy były pomysłem, którego sam bym nie postawił, a hipoteza postawiona przez model jako najbardziej prawdopodobna w moim głównym przykładzie okazała się nieprawdziwa. Model poszerzył listę podejrzanych i nie skrócił ani o minutę drogi od listy do dowodu.

Wspólny mianownik tych trzech kanałów jest taki, że żaden z nich niczego za mnie nie zatwierdza. To jest różnica, którą agent ma skasować, i dlatego warto ją nazwać, zanim zobaczymy, jak wygląda skasowana.

Stan warsztatu na dziś, żeby za rok było wiadomo, o czym mówię. Runner to Playwright 1.40.1 z 28 listopada, ten sam pin, na którym zamknąłem grudzień. Modele mam od DevDay z 6 listopada, czyli GPT-4 i GPT-4 Turbo, do tego GPTs i Assistants, o których 15 grudnia w podsumowaniu 2023 napisałem, że odnotowuję datę, a nie proces, i przez dwa miesiące nic tego nie zmieniło. Do tego jedno zdanie o rzeczy, o którą pytają mnie od roku: edytor Anysphere o nazwie Cursor istnieje i nadal nie jest moim codziennym narzędziem.

Auto-GPT jako nagłówek, nie proces

Fala, o której dziś wszyscy mówią jak o nowości stycznia 2024, jest z wiosny zeszłego roku. Repozytorium Auto-GPT powstało 16 marca 2023, a pierwszy tag v0.1.0 ma datę 12 kwietnia 2023. Piszę obie daty, bo w rozmowach krążą wersje zaokrąglone do “końca marca”, a różnica prawie miesiąca ma znaczenie, kiedy ktoś opowiada, że agenty są z nami “od dawna” albo “dopiero od chwili”. To eksperyment open source, który w kwietniu przez dwa tygodnie był najgłośniejszą rzeczą w mojej bańce, a potem wrócił do bycia repozytorium z issue’ami. W moim procesie QA nie ma go od tamtej pory ani przez minutę.

Pętla wygląda tak samo dziś jak wtedy. Podajesz cel w jednym zdaniu. Model rozpisuje plan. Wybiera narzędzie: powłoka, zapis pliku, wyszukiwarka. Wykonuje krok, czyta wyjście, dopisuje sobie krytykę własnego planu i przechodzi dalej, aż uzna cel za osiągnięty albo aż skończy mu się budżet. Cały mechanizm jest sensowny i cała trudność siedzi w tym, że każdy z tych kroków ma prawo być zmyślony z równą pewnością siebie co krok trafny.

Postanowiłem to sprawdzić na sobie, na czterech wieczorach między 9 a 12 stycznia. Reguły ustawiłem tak, żeby nie było wątpliwości, czym to jest: repozytorium z przykładami do bloga, nie projekt, za który ktokolwiek mi płaci; sandbox w kontenerze, który poza aplikacją-fixture podniesioną obok tym samym plikiem compose nie ma dostępu do niczego w mojej sieci; własny klucz do API; osobna gałąź, którą i tak wyrzuciłem. Ważny jest tu jeden szczegół, do którego wrócę na końcu tej sekcji: pętla dostała katalog z testami i nic więcej, a plik compose i zmienne, którymi konfiguruję tę aplikację, leżą poza nim. Sześć przebiegów pętli, dwa doprowadzone do końca, cztery zatrzymane przeze mnie, kiedy było jasne, że pętla kręci się wokół własnego ogona. Rachunek za tokeny zmieścił się w kilkunastu dolarach, co jest tu najmniej ciekawą liczbą.

Cel podałem najbardziej oczywisty z możliwych, taki, który w styczniowych rozmowach pada jako pierwszy:

GOAL: Napraw padający test logowania w tym repozytorium.
CONSTRAINTS: nie zmieniaj kodu aplikacji; test ma przechodzić.

Padał auth/login.spec.ts:18:3, czyli “logowanie wpuszcza użytkownika z poprawnym hasłem”, z timeoutem na kliknięciu w przycisk “Zaloguj”. Oto ścieżka, którą przeszła pętla, w skrócie i w jej własnym formacie:

THOUGHTS:     Test nie znajduje przycisku logowania. Prawdopodobnie zmienił się
              selektor. Sprawdzę plik testu i komponent formularza.
NEXT ACTION:  execute_shell {"cmd": "npx playwright test auth/login.spec.ts"}

THOUGHTS:     Timeout 15000ms na locatorze. Formularz ma zapewne stabilny
              atrybut testowy. Zamienię locator po tekście na data-testid.
CRITICISM:    Nie sprawdziłem, czy ten atrybut istnieje w komponencie.
NEXT ACTION:  write_to_file {"file": "auth/login.spec.ts",
              "text": "await page.getByTestId('login-submit').click()"}

THOUGHTS:     Nadal timeout. Strona może potrzebować więcej czasu na hydrację.
              Dodam oczekiwanie przed kliknięciem i podniosę timeout testu.
NEXT ACTION:  write_to_file {"file": "auth/login.spec.ts",
              "text": "await page.waitForTimeout(5000)"}

THOUGHTS:     Zmiany wyglądają na kompletne. Utrwalę je w repozytorium.
NEXT ACTION:  execute_shell {"cmd": "git commit -am 'fix: flaky login test'"}

Cztery kroki, z czego trzy do skreślenia, i to nie są skreślenia z gatunku “model się nie postarał”. Każde z nich jest osobnym błędem kategorii, więc rozpiszę je po kolei.

Skreślenie pierwsze: data-testid, którego nie ma. Atrybut login-submit nie istnieje w moim formularzu, nigdy nie istniał i nie pojawił się w żadnym wyjściu, które pętla przeczytała. Model dopisał go, bo w kodzie, na którym się uczył, formularze logowania mają stabilne atrybuty testowe, i to jest statystycznie świetna zgadywanka. Efekt jest taki, że test przestał sprawdzać cokolwiek i zaczął czekać na element, który nigdy się nie pojawi. Do samokrytyki pętla wpisała sobie dokładnie ten zarzut, po czym wykonała krok mimo niego, i to jest najbardziej pouczający fragment całego przebiegu.

Skreślenie drugie: waitForTimeout jako odruch. Znam ten wzorzec z lipca, kiedy zapomniałem dopisać do promptu zakazu proponowania poprawek i dostałem sleepa w pakiecie z podniesionym timeoutem globalnym. Tym razem doszło jeszcze coś gorszego: sleep nie zadziałał. Test nadal padał, tylko każda próba trwała pięć sekund dłużej, a przy --repeat-each na całym pliku zrobiła się z tego minuta wall-clocku dołożona do niczego. Sleep jest zły nie dlatego, że nie działa; jest zły dlatego, że kiedy przypadkiem zadziała, nikt już nigdy nie zapyta, na co właściwie czekaliśmy.

Skreślenie trzecie: commit bez odtworzenia i bez pull requesta. Pętla zamknęła cel poleceniem git commit -am. Nie odtworzyła awarii, nie sprawdziła, czy test po zmianie w ogóle jest zielony, nie otworzyła pull requesta i nie zostawiła po sobie zdania, dlaczego uznała sprawę za załatwioną. W sandboxie nie miała gdzie tego wypchnąć i o to chodziło w konfiguracji sandboxa. Gdyby miała, dostałbym w repozytorium diff, pod którym nie podpisuje się nikt.

Prawdziwa przyczyna zajęła mi potem dwadzieścia minut i nie było w niej nic z lokatora. 8 stycznia, dzień przed pierwszym przebiegiem pętli, sam włączyłem w konfiguracji tej lokalnej aplikacji flagę, która pokazuje modal zgody nad formularzem logowania. Robiłem wtedy zupełnie inny przykład i zostawiłem ją włączoną. Przycisk był więc tam, gdzie zawsze, z tym samym tekstem, tylko przykryty. Test miał rację, padając. Złapał zmianę w zachowaniu aplikacji, o której sam zapomniałem, i to jest dokładnie to, po co go napisałem. Poprawka mieści się w konfiguracji środowiska i w jednym fixture, który zamyka modal przed scenariuszem, a nie w pliku login.spec.ts.

I tu jest sedno tego wpisu, w jednym zdaniu: agent, który “naprawiłby” ten test, skasowałby sygnał. Trzy jego kroki różniły się kosztem, ale wszystkie trzy prowadziły w tę samą stronę, czyli do zielonego wyniku bez wiedzy, dlaczego był czerwony. Pętla nie miała jak wiedzieć o fladze z 8 stycznia, bo tej informacji nie ma ani w testach, które widziała, ani w wyjściu, które przeczytała: siedzi w konfiguracji środowiska, czyli w jedynym miejscu tego demo, do którego z założenia nie miała dostępu. Ja też cztery wieczory nie skojarzyłem, więc nie chodzi o to, że byłem mądrzejszy. Różnica polega na tym, że ja mogę wyjść z repozytorium i sprawdzić, czym ta aplikacja jest w ogóle uruchomiona, a pętla ma do dyspozycji wyłącznie to, co widzi, i pod presją celu wypełni brakującą wiedzę czymś, co wygląda wiarygodnie.

Gdzie agent kłamie w QA

Te trzy skreślenia nie są przypadkiem jednego przebiegu, więc uogólnię je na trzy klasy, które widziałem w każdym z sześciu.

Pierwsza to konkret zależny od rzeczywistości, której model nie widzi. Nazwa atrybutu, nazwa joba, nazwa pliku konfiguracyjnego, istnienie opcji w mojej wersji runnera. Zapisałem to zdanie w lipcu i po pętli powtarzam je bez zmian, tylko z gorszą konsekwencją: czat, który zmyśli nazwę, zostawia mi tekst do przeczytania, a agent, który zmyśli nazwę, zostawia mi zmieniony plik i idzie dalej, budując następne trzy kroki na fikcji.

Druga to mylenie zielonego z poprawnym. Cel “test ma przechodzić” jest dla pętli funkcją celu, a nie umową o to, co ten test sprawdza. Każde narzędzie optymalizujące pod zielone dojdzie prędzej czy później do wniosku, że najprostszą drogą jest usunąć asercję. Nie zobaczyłem tego u siebie, ale zobaczyłem trzy kroki w tę stronę i nie mam powodu sądzić, że czwarty by się nie pojawił. Sformułowanie celu tak, żeby nie dało się go osiągnąć oszukiwaniem, jest dużo trudniejsze niż napisanie celu, i to jest praca, której nikt w styczniowych demach nie pokazuje.

Trzecia to złudzenie powtarzalności. Ten sam cel puściłem dwa razy i dostałem dwie różne ścieżki, jedną z nich krótszą i sensowniejszą. To ta sama właściwość, którą opisywałem jeszcze przy Completions i modelu davinci w 2021, tylko rozłożona teraz na kilkanaście kroków, z których każdy mnoży rozrzut przez kolejny. Narzędzie, którego dwa uruchomienia na tym samym wejściu dają dwa różne diffy, może być szkicownikiem. Nie może być krokiem w pipelinie, od którego zależy merge.

Warto też powiedzieć, czego pętla nie zmienia. Nie zmienia kształtu mojego zestawu. We wrześniu sprawdzałem, czy po dziewięciu miesiącach z modelami zmieniły mi się proporcje piramidy, i nie zmieniły; dziś, po czterech wieczorach z agentem, nadal nie. Dół przyspiesza, bo kolejny helper powstaje szybciej. Środek nie ma skrótu, bo model nie stoi za schematem kontraktu. Góra kosztuje wall-clock w nocy i żaden agent tego nie negocjuje. W moim CI nadal nie ma joba “LLM” i nadal nie ma powodu, żeby był.

Kto jest właścicielem merge’a

Odpowiedź brzmi: ja, i to nie jest deklaracja światopoglądowa, tylko opis tego, kto ma w tej sprawie informacje.

Zestaw pytań, które zadaję nad każdym czerwonym testem, mieści się w czterech linijkach i po styczniowym eksperymencie nie zmieniłem w nim ani słowa. Czy ten test w ogóle coś sprawdza, czy tylko czeka na element. Czy zmiana jest w produkcie, czy w środowisku. Czy potrafię odtworzyć awarię, zanim zaproponuję poprawkę. Czy pod tym diffem podpisuje się osoba, która umie wyjaśnić, dlaczego było czerwono. Pętla z mojego demo odpowiedziałaby “nie” na wszystkie cztery, a mimo to zamknęłaby cel jako osiągnięty, bo cel brzmiał inaczej niż te pytania.

Drugie pytanie z tej listy jest w tym miesiącu najważniejsze, bo dokładnie na nim wywróciło się demo. Rozróżnienie “produkt czy środowisko” to ta sama granica, którą wpisałem w proces testowy na długo przed pierwszym modelem w warsztacie, i jest ono niedostępne dla czegokolwiek, co widzi wyłącznie repozytorium z testami. Flaga włączona w konfiguracji środowiska nie zostawia śladu w kodzie testu. Zostawia go o poziom obok, tam gdzie trzeba świadomie zajrzeć, i w moim demo ten poziom był moim własnym plikiem compose.

Jest jeszcze rzecz, o którą pytają mnie ludzie pamiętający listopadowe Universe. Owszem, pokazano tam zapowiedź środowiska, w którym asystent prowadzi zmianę przez wiele plików, i owszem, powiedziano, że wyjdzie w tym roku. Dziś go nie mam, więc nie mam też o czym pisać poza odnotowaniem obietnicy. To samo dotyczy narzędzi, które reklamują się jako leczące lokatory bez udziału człowieka: to inna kategoria niż agent z pętlą i zasługuje na własny test, a nie na akapit w cudzym wpisie.

Co z tego wszystkiego zostaje w moim warsztacie po czterech wieczorach? Jedna rzecz, i szczerze mówiąc spodziewałem się mniej. Pętla jest szybszym generatorem hipotez niż czat, do którego wklejam log ręcznie, bo sama sobie dobiera wejścia: odpala test, czyta wyjście, zagląda do pliku. W dwóch przebiegach z sześciu dostałem w pierwszej minucie listę miejsc do sprawdzenia, którą sam składałbym kwadrans. To jest ten sam zysk, który zmierzyłem w lipcu, tylko tańszy w obsłudze. Zmienia się narzędzie do robienia listy podejrzanych, nie zmienia się to, kto zdobywa dowód.

Podsumowanie

Agenci są tematem tego roku i nie zamierzam udawać, że jest inaczej. W styczniu 2024 zostają jednak nagłówkiem, a nie właścicielem czegokolwiek w moim procesie.

Trzy zdania, które chcę móc przeczytać za rok. Pętla z celem “napraw padający test” wygenerowała mi w czterech krokach trzy rzeczy do skreślenia: nieistniejący atrybut testowy, sleepa, który nawet nie zadziałał, i commit bez odtworzenia i bez pull requesta. Prawdziwa przyczyna leżała poza repozytorium, które pętla mogła edytować, w fladze modalu zgody, którą sam włączyłem 8 stycznia w konfiguracji lokalnego środowiska, więc test, który agent miał naprawić, był jedyną rzeczą w tej historii, która działała poprawnie. Zysk, który zostaje, to szybsza lista podejrzanych, czyli dokładnie to, co miałem w lipcu, tylko z krótszym klikaniem.

Czego z tego nie wynika. Nie wynika, że pętle są bez wartości, bo dwa z sześciu przebiegów dały mi realnie lepszy start do szukania. Nie wynika, że to się nie zmieni, bo od marca zeszłego roku zmieniło się w tej dziedzinie więcej niż w poprzednich pięciu latach. Nie wynika też, że mam już odpowiedź na pytanie, kiedy oddam pętli pierwszy prawdziwy krok w pipelinie, bo nie mam.

Copilot Chat wszedł tymczasem do stabilnego edytora i to jest najspokojniejsza dobra wiadomość tego miesiąca, dokładnie dlatego, że nie próbuje być właścicielem niczego. Miesiąc temu w podsumowaniu 2023 zapisałem, po czym poznaję, że narzędzie weszło do zespołu, a nie do hype’u, i dwie z tych czterech rzeczy to nazwane zadanie i człowiek, który odpowiada za wynik przed resztą zespołu. Pętla z mojego demo ma pierwszą i nie ma drugiej: cel był nazwany, a pod diffem nie podpisywał się nikt. W podsumowaniu 2022 zapisałem z kolei o Copilocie, że po wyjściu z preview zmieniła się bramka dostępu, a nie sposób pracy, i że nadal nie ma miejsca, w którym mógłbym o cokolwiek zapytać; ten jeden brak zniknął 29 grudnia. A oś izolacji środowisk, którą streściłem w podsumowaniu 2021, nie przestała być większą częścią odpowiedzi na pytanie o stabilność zestawu.

Warunek, po którym poznam, że temat dojrzał, formułuję dziś tak: agent będzie mógł dotknąć mojego pipeline’u wtedy, kiedy dwa uruchomienia na tym samym wejściu dadzą ten sam diff, a jego propozycja będzie umiała powiedzieć “nie wiem, zapytaj człowieka o środowisko”. Do tego czasu suite nie ma autonomicznego właściciela. Ma mnie, i to ja podpisuję się pod zdaniem, że coś jest naprawione.