Projektowanie harnessu agenta: od zadania do architektury
Tłumaczenie automatyczne
Ten artykuł został automatycznie przetłumaczony z angielskiego oryginału.
Harness agenta to kod aplikacji, który zamienia decyzje modelu w wykonaną pracę: dostarcza kontekst, uruchamia dozwolone akcje, utrzymuje stan i sprawdza, kiedy zadanie jest ukończone. Jeśli budujesz agenta dla własnej aplikacji, projektowanie tego kodu oznacza decyzję, ile swobody potrzebuje zadanie i co aplikacja musi zagwarantować niezależnie od odpowiedzi modelu.
Te decyzje poprzedzają wybór frameworka. Asystent wsparcia klienta, agent programistyczny i klasyfikator dokumentów potrzebują różnych sposobów działania, odzyskiwania po błędach i wykazywania sukcesu. Danie wszystkim trzem tej samej pętli, tego samego magazynu pamięci i tego samego zestawu narzędzi ukryłoby wymagania, które je różnią.
Ten artykuł prowadzi przez te decyzje od opisu zadania do pierwszej implementacji. Rozwija artykuł Inżynieria harnessu dla agentów AI, który przedstawia obowiązki harnessu. Tutaj przejdziemy przez to, jak dobrać je do konkretnego projektu.
Dlaczego model potrzebuje harnessu
Model może zaproponować zwrot pieniędzy. Coś innego musi zidentyfikować konto, wczytać zamówienie, zdecydować, czy proponowana operacja jest dozwolona, wywołać usługę płatności i zapisać, co się stało. Jeśli ta usługa przekroczy limit czasu, aplikacja musi też zdecydować, czy ponowna próba jest bezpieczna. Te obowiązki istnieją nawet wtedy, gdy model bardzo dobrze wybiera następny krok.
LangChain stosuje szeroką definicję harnessu, która obejmuje kod, konfigurację i logikę wykonania wokół modelu. W praktyce część z tego może już należeć do twojej aplikacji: uwierzytelnianie, dostęp do bazy danych, kolejka zadań albo system zatwierdzeń. Projektowanie harnessu obejmuje decyzję, jak agent korzysta z tych mechanizmów. Nie wymaga budowania ich od nowa wewnątrz frameworka agentowego.
Najmniejsza wersja może wykonać jedno wywołanie modelu, zwalidować jego wynik i zwrócić rezultat. Większa może pozwolić modelowi przeglądać pliki, uruchamiać programy i poprawiać swoją pracę przez wiele tur. Obie potrzebują odpowiedzi na to samo pytanie: które części wykonania tego zadania możemy delegować i skąd będziemy wiedzieć, że wykonano je poprawnie?
Dla konkretu rozważmy asystenta wsparcia klienta, który obsługuje zwroty, pytania o dostawę i zmiany konta. Użyjemy go jako przykładu projektowego. Jego wymagania doprowadzą nas do określonej architektury; inne zadanie powinno prowadzić do innego zestawu wyborów.
Opisz wynik, zanim opiszesz agenta
„Obsługuj prośby o zwrot” pozostawia większość inżynierii nieokreśloną. Załóżmy, że klient prosi o zwrot za dostarczone zamówienie. Udany wynik wymaga zwrotu dla właściwego zamówienia i kwoty, wyjaśnienia dla klienta i braku niezwiązanych zmian na koncie. Jeśli prośba wykracza poza zasady, poprawnym wynikiem może być użyteczna odmowa. Jeśli nie da się zidentyfikować zamówienia, asystent powinien poprosić o brakujące informacje.
To są różne wyniki. Traktowanie każdej rozmowy, która kończy się bez błędu, jako sukcesu zlałoby je w jeden mylący sygnał.
Musimy też wiedzieć, kto pyta. Identyfikator zamówienia w wiadomości nie dowodzi, że to zamówienie należy do nadawcy. Uwierzytelnione konto musi pochodzić z aplikacji, a usługa wykonująca zwrot musi sprawdzić, czy operacja należy do tego konta. Model może pomóc zinterpretować prośbę; nie może ustalić uprawnień wywołującego, podając wiarygodnie wyglądający identyfikator.
To daje nam początek kontraktu zadania: zmiany, które liczą się jako sukces, zmiany zabronione i sytuacje, które wymagają pytania albo przekazania sprawy. Ujawnia też wymagania, których prompt sam nie spełni. Maksymalna kwota zwrotu potrzebuje usługi, która ją egzekwuje. Docelowy czas odpowiedzi potrzebuje terminu. Wymóg wznowienia pracy jutro potrzebuje trwałego stanu.
W tym momencie mamy dość, by zapytać, czy pętla agenta w ogóle jest przydatna.
Ile procedury już znamy?
Jeśli każda prośba przebiega według tej samej sekwencji — wyodrębnij numer zamówienia, pobierz zamówienie, oblicz uprawnienie do zwrotu i wyjaśnij wynik — możemy zapisać tę sekwencję wprost. Model może obsłużyć język na początku i na końcu. Nie potrzebuje kolejnego wywołania, by zdecydować, czy pobrać zamówienie, którego procedura zawsze wymaga.
Jawny workflow staje się przydatny, gdy procedura ma obowiązkowe rozgałęzienia albo stany oczekiwania. Zwrot powyżej progu może wymagać zatwierdzenia. Zmiana adresu może być dozwolona tylko przed wysyłką. Te etapy możemy przedstawić jako maszynę stanów albo graf, w którym kod aplikacji decyduje, które przejścia są dozwolone.
Pętla agenta daje modelowi więcej swobody. W mieszanej rozmowie z działem wsparcia klient może zacząć od skargi na dostawę, ujawnić, że przyszedł niewłaściwy produkt, a potem poprosić o wymianę. Następne przydatne wyszukiwanie zależy od tego, co odkryje asystent. Pętla pozwala mu wybrać akcję, sprawdzić wynik i wybrać ponownie. Ta elastyczność oznacza więcej możliwych ścieżek do sprawdzenia, w tym niepotrzebne wywołania, powtarzane wyszukiwania i przedwczesne zakończenie.
Przewodnik LangGraph po workflow i agentach opisuje to rozróżnienie jako z góry ustalone ścieżki wykonania kontra dynamicznie wybierane kroki. Graf może wyrazić jedno i drugie. „Oparty na grafie” i „agentowy” to nie są wzajemnie wykluczające się architektury.
Przydatnym kompromisem dla naszego asystenta wsparcia jest pozwolić agentowi zebrać informacje i przygotować proponowaną zmianę, a potem przekazać tę propozycję do stałego kroku walidacji i wykonania. Model może eksplorować w obrębie rozmowy, a zwykły kod kontroluje warunki zapisu. Jeśli już wiemy, że ten podział jest wymagany, możemy najpierw zbudować zewnętrzny workflow.
Alternatywnie możemy zacząć od ograniczonej pętli agenta, której narzędzia egzekwują te warunki. To utrzymuje małą orkiestrację, a my w tym czasie poznajemy, których ścieżek zadanie faktycznie potrzebuje. Wybór zależy od tego, czy jawne etapy, oczekiwanie na zatwierdzenie i reguły odzyskiwania są już wymaganiami.
Nie trzeba wprowadzać wielu agentów tylko po to, by narysować więcej prostokątów. Oddzielni agenci stają się przydatni, gdy podzadania potrzebują innego kontekstu albo uprawnień albo mogą postępować niezależnie. Potrzebują też sposobu na połączenie wyników i rozstrzyganie sprzecznych akcji. Dwóch wykonawców, którzy mogą zwrócić pieniądze za to samo zamówienie, tworzy problem koordynacji, którego jeden wykonawca nie miał.
W jakim języku agent ma działać?
Po wyborze tego, kto kontroluje następny krok, musimy jeszcze wybrać, jak wyrażona jest akcja. To niezależne decyzje: workflow może zawierać agenta piszącego kod, a otwarta pętla może używać tylko kilku wąskich narzędzi.
Dla asystenta wsparcia nazwana operacja, taka jak issue_refund(order_id, amount_cents), jest naturalnym interfejsem. Jej schemat czyni żądaną akcję jawną. Narzędzie może zwrócić identyfikator zwrotu przy sukcesie albo ustrukturyzowane wyjaśnienie, dlaczego prośbę odrzucono. Ten kontrakt możemy sprawdzić i przetestować bez proszenia modelu o wygenerowanie implementacji zwrotu.
Niektóre zadania nie potrzebują narzędzi. Klasyfikator, który ma wszystkie istotne dane wejściowe w prompcie, może zwrócić zwalidowaną etykietę. Wyszukiwanie staje się przydatne, gdy zadanie potrzebuje informacji spoza tych danych. Zapisy stają się przydatne tylko wtedy, gdy ukończenie zadania wymaga zmiany czegoś. Każda możliwość powinna mieć powód, by istnieć.
Generowany kod oferuje inny język akcji. Wyobraź sobie, że prosisz asystenta o przejrzenie tabeli, pogrupowanie wierszy według klienta i obliczenie kilku sum. Python może wyrazić tę pracę w jednym programie, trzymając wartości pośrednie wewnątrz środowiska wykonania. Wyrażenie każdej małej operacji jako osobnego wywołania narzędzia mogłoby wymagać więcej wymian z modelem.
smolagents od Hugging Face ilustruje oba podejścia. ToolCallingAgent tworzy ustrukturyzowane wywołania narzędzi. CodeAgent tworzy kod Python, który może wywoływać dostarczone narzędzia, przechowywać wartości i składać operacje. Agent kodowy nadal używa narzędzi; wyraża ich złożenie jako program.
Ta ekspresyjność zmienia to, czym musimy zarządzać. Teraz musimy obsłużyć błędy składni, limity wykonania, zależności i dostęp do plików albo sieci. To, czy redukcja liczby wymian z modelem pokrywa te koszty, zależy od zadania i modelu. To porównanie do przeprowadzenia, a nie automatyczna przewaga kodu.
Kilka platform obsługuje podejście hybrydowe: wąskie narzędzia biznesowe plus narzędzie do wykonywania kodu w sandboxie. Responses API od OpenAI, Claude API, Gemini API i AWS Bedrock AgentCore oferują wykonywanie kodu jako wbudowane narzędzie, które działa obok twoich własnych funkcji. Programmatic tool calling od Anthropic idzie o krok dalej: kod w sandboxie może wywoływać narzędzia, na które pozwolisz, a do modelu wracają tylko wybrane wyniki. Wytyczne Anthropic zostawiają bezpośrednie wywołania narzędzi jako domyślne i używają ścieżki kodu dla dużych wyników i łańcuchów zależnych wywołań. Istnieją też projekty oparte wyłącznie na kodzie, takie jak CodeAgent z smolagents i Code Mode od Cloudflare.
Dla naszego asystenta zwrotów zacząłbym od typowanych operacji biznesowych. Jeśli późniejsze zadanie będzie wymagało znaczących obliczeń, możemy dodać ograniczone obliczenia bez dawania temu kodowi bezpośredniej władzy nad kontami klientów.
MCP odpowiada na kolejne, odrębne pytanie: jak aplikacja powinna łączyć się z narzędziami? Lokalna funkcja wystarcza, gdy jedna aplikacja jest właścicielem implementacji. MCP może pomóc, gdy narzędzia są serwowane osobno albo współdzielone przez wielu klientów. Dostarcza protokół integracji; walidacja i kontrola dostępu pozostają obowiązkami uczestniczącej aplikacji i serwera, jak opisuje specyfikacja narzędzi MCP.
Umieść ograniczenia tam, gdzie dzieją się akcje
Opis narzędzia mówi modelowi, kiedy operacja jest przydatna. Implementacja musi zdecydować, czy to konkretne wywołanie może zostać wykonane. Dla zwrotu oznacza to sprawdzenie uwierzytelnionego konta, zamówienia, kwoty, obowiązujących zasad i wymaganego zatwierdzenia w usłudze, która wykonuje zapis.
Niektóre kontrole wymagają interpretacji
Załóżmy, że klient pyta: „Czy mogę zwrócić to zamówienie?”, a agent proponuje zwrot pieniędzy. Zamówienie należy do klienta, kwota jest poprawna, a okno zwrotu jest otwarte. Wszystkie te kontrole mogą przejść, a zamierzona akcja nadal pozostaje niejasna: klient może pytać o to, czy przysługuje mu zwrot, a nie prosić o zwrot teraz. Musimy zinterpretować wiadomość, zanim zdecydujemy, co dalej.
To dobre miejsce, by rozdzielić trzy obowiązki. Zwykły kod sprawdza własność, kwoty i daty. Główny model językowy prowadzi rozmowę i wyjaśnia wynik. Osobny model decyzyjny może ocenić wąskie pytanie semantyczne, na przykład czy klient faktycznie poprosił o proponowaną zmianę. Ten podział daje nam decyzję, którą możemy testować niezależnie od reszty rozmowy.
Jev, model decyzyjny od TypeSafe, jest jednym z kandydatów do tej roli. Przyjmuje dostarczony stan i typowane pytania, a zwraca ustrukturyzowane odpowiedzi. W tym przykładzie jego typ pytania Noul zwraca prawdopodobieństwo dla twierdzenia tak/nie. Pytałbym osobno, czy klient wprost poprosił o zwrot i czy późniejsza wiadomość wycofała tę prośbę. TypeSafe zaleca, by każde pytanie było konkretne, a odpowiedzi łączyć w kodzie; kilka niezależnych pytań może współdzielić jedno wywołanie API. Rozdzielenie tych dwóch ocen ułatwia znalezienie błędnej interpretacji.
Aplikacja łączy następnie te wyniki ze swoimi obowiązkowymi kontrolami. Niepewne albo sprzeczne odpowiedzi mogą prowadzić do doprecyzowania albo przeglądu przez człowieka; przekroczenie limitu czasu przez klasyfikator też potrzebuje jawnej ścieżki błędu. Pozytywna ocena semantyczna nie może unieważnić kontroli własności ani zastąpić wymaganego potwierdzenia. Prawdopodobieństwo od Jev to wynik modelu, a nie zapis autoryzacji ani niezależna kontrola poprawności.
Do takiej kontroli dostarczyłbym proponowaną akcję, istotne wiadomości klienta w kolejności i wyniki narzędzi potrzebne do decyzji, tak by ich źródła dało się rozróżnić. Przekazanie tylko ostatniej wiadomości mogłoby zgubić anulowanie; przekazanie całej historii konta mogłoby zagrzebać istotną prośbę. Pytanie, kontekst i zachowanie awaryjne są częściami projektu. Innym kandydatem jest mały model ogólnego przeznaczenia z ustrukturyzowanymi danymi wyjściowymi. Ich przydatność i koszt trzeba porównać na tych samych decyzjach.
Ograniczaj dostęp niezależnie od ocen modelu
Źródło instrukcji ma znaczenie. Wiadomość klienta albo pobrany dokument mogą zawierać instrukcje. Taki tekst nie może nadawać nowych uprawnień ani zastępować autorytatywnych zasad aplikacji. Jeśli dokument każe agentowi wyeksportować wszystkie rekordy klientów, system nie powinien mieć do tego uprawnień, niezależnie od tego, jak przekonująco dokument formułuje prośbę.
To jest też miejsce na decyzję o sandboxie. Jeśli model może wywoływać tylko przejrzane funkcje z wąskimi uprawnieniami, sandbox kodu ogólnego przeznaczenia może niewiele dodać. Funkcje nadal potrzebują autoryzacji, walidacji, limitów czasu i bezpiecznych operacji na bazie danych.
Gdy pozwolimy na generowany kod Python, polecenia powłoki albo wykonywalne edycje plików, musimy zdecydować, do czego to wykonanie ma dostęp, zanim je włączymy. Które katalogi są zapisywalne? Czy dostęp do sieci jest potrzebny? Jakie poświadczenia są dostępne? Ile czasu CPU, pamięci i danych wyjściowych może zużyć jedno uruchomienie? Sam osobny proces nie odpowiada na te pytania.
Przewodnik Hugging Face po bezpiecznym wykonywaniu kodu opisuje ograniczone wykonanie lokalne i bardziej izolowane alternatywy. Ich ograniczenia się różnią. Kontener z szerokimi montowaniami hosta i silnymi poświadczeniami nadal może odsłonić dokładnie te zasoby, które chcieliśmy chronić. Obok udanego programu przetestuj próbę zabronionego odczytu, zapisu i żądania sieciowego.
Możemy odłożyć moduł uruchamiający kod, dopóki nasze zadanie go nie potrzebuje. Jego izolacja musi być gotowa, gdy przyznamy wykonywanie kodu. A izolacja nie zapobiegnie nadużyciu API, które celowo udostępniamy: usługa zwrotów nadal musi egzekwować własne reguły.
Co przetrwa, gdy wykonanie zatrzyma się w połowie?
Załóżmy teraz, że asystent wysyła zwrot, usługa go tworzy, a odpowiedź ginie. Model widzi przekroczenie limitu czasu. Powtórzenie wywołania może utworzyć kolejny zwrot; zgłoszenie porażki może przekazać klientowi informację, która już nie jest prawdziwa.
Ten problem łączy stan, ponowienia i odzyskiwanie. Potrzebujemy identyfikatora operacji należącego do aplikacji, zapisanego przed wysłaniem i używanego ponownie przy odzyskiwaniu tej samej zamierzonej akcji. Usługa musi rozpoznać ten identyfikator i zwrócić istniejący wynik zamiast wykonywać akcję ponownie. Wytyczne AWS dotyczące idempotentnych API wyjaśniają ten wzorzec. Jeśli usługa nie daje takiej gwarancji, odzyskiwanie potrzebuje sposobu na sprawdzenie, co się stało, albo na poproszenie człowieka o decyzję przed kolejną próbą.
Zapisanie rozmowy nie wystarcza. Może ono odnotować, że model poprosił o zwrot, nie mówiąc nic o tym, czy usługa zewnętrzna go wykonała. Checkpoint pomaga wznowić wykonanie; nie cofa zewnętrznych skutków ani nie czyni ich powtórzenia bezpiecznym.
Dla tego asystenta trzy rodzaje stanu mają różne zadania. Rozmowa przechowuje prośbę klienta i dotąd zebrane fakty. Rekordy zamówień i zwrotów opisują stan biznesowy. Trwały rekord wykonania śledzi oczekujące operacje i zatwierdzenia, by ponownie uruchomiony worker mógł bezpiecznie kontynuować. Mogą współdzielić infrastrukturę, ale nie powinny się wzajemnie zastępować.
Pamięć między sesjami to kolejny wybór. Jeśli asystent musi pamiętać preferencję w przyszłym tygodniu, potrzebujemy reguł określających, czyja to preferencja, kto może ją zaktualizować i jak można ją usunąć. Jeśli każde zadanie jest niezależne, usługa pamięci może być niepotrzebna. Więcej przechowywanego tekstu oznacza też więcej okazji do ponownego użycia nieaktualnych lub nieistotnych informacji.
To samo rozumowanie dotyczy podsumowań. Gdy rozmowa przerasta użyteczny kontekst, może być potrzebne wyszukiwanie albo streszczanie. Podsumowanie, które gubi identyfikator zamówienia, warunek zatwierdzenia albo nierozstrzygniętą operację, może zepsuć procedurę. Trzymaj niezbędny stan operacyjny w jawnych polach i przetestuj, co przetrwa kompresję, zanim zaczniesz na niej polegać.
Daj pętli sposób na zatrzymanie się
Elastyczna pętla może ciągle znajdować coś kolejnego do sprawdzenia. Potrzebujemy więc polityki zatrzymania, która obejmuje sukces, brakujące informacje, wyczerpane zasoby i porażkę. Osiągnięcie limitu powinno dać prawdziwy status: zwrot może być wykonany, niepodjęty albo nadal niepewny. „Agent się zatrzymał” to za mało informacji dla klienta albo dla kolejnego workera.
Limit wywołań modelu ogranicza tylko część wykonania. Narzędzie może się zawiesić. Ponowienie może powtórzyć pracę. Moduł streszczający albo strażnik może wykonać dodatkowe wywołania modelu. Aplikacja potrzebuje limitów czasu, liczby prób, rozmiaru danych wyjściowych i wydatków, które obejmują komponenty, jakie faktycznie wywołuje. Anulowanie musi też uwzględniać pracę już wysłaną do usługi zewnętrznej.
Wybór modelu należy do tego projektu, bo wpływa na to, co pętla może zrobić w tych limitach. Przetestuj wymagany format akcji na reprezentatywnych zadaniach. Weź pod uwagę użyteczny kontekst, opóźnienie, koszt i to, dokąd mogą trafiać dane zadania. Model serwowany lokalnie i hostowane API mogą implementować ten sam interfejs, a jednocześnie narzucać różne ograniczenia pojemności i operacyjne.
Wybierz początkowy model i endpoint, zapisz ich ustawienia, a potem trzymaj je bez zmian podczas ewaluacji zmiany w harnessie. W przeciwnym razie szybsze wykonanie może wynikać z innego dostawcy albo modelu, a nie ze zmiany, którą chcieliśmy przetestować.
Ewaluuj ukończenie, łącznie z przypadkami, w których nic nie powinno się stać
Możemy już wykonać prośbę, ale nadal musimy zdecydować, czy wynik jest użyteczny. W przykładzie ze zwrotem są co najmniej trzy obserwacje: co powiedział asystent, jakie akcje próbował wykonać i co zmieniło się w usłudze. Każda wyłapuje błędy, które pozostałe mogą przeoczyć.
„Zwróciłem pieniądze za twoje zamówienie” może iść w parze z niezmienioną bazą danych. Właściwy zwrot może iść w parze z niezwiązaną zmianą adresu. Niezmieniona baza danych może oznaczać poprawną odmowę, użyteczne pytanie doprecyzowujące, awarię albo asystenta, który nic nie zrobił. Sam stan końcowy nie rozróżni tych czterech ostatnich przypadków.
Symulacja wsparcia klienta dołączona do tego artykułu pokazuje to ograniczenie konkretnie. Jej dwanaście własnych zadań obejmuje siedem, które nie oczekują żadnej zmiany w bazie danych. Przekazanie niezmienionej początkowej bazy danych do każdego modułu sprawdzającego przechodzi więc 7 z 12 kontroli stanu. To właściwość kontroli, a nie zmierzony wskaźnik sukcesu wsparcia. Definicje zadań sprawdzają różnice w końcowej bazie danych; nie oceniają wyjaśnienia ani akcji pośrednich. Zapis, który później cofnięto, też może zniknąć z tego porównania.
Do swojego pierwszego zestawu ewaluacyjnego włącz udaną akcję, uzasadnioną odmowę, prośbę bez niezbędnych informacji i awarię podczas wykonania. Zdefiniuj oczekiwaną odpowiedź i dozwolone akcje, a także stan końcowy. Przypadek doprecyzowania powinien wymagać właściwego pytania; przypadek odmowy powinien wymagać użytecznego wyjaśnienia. Te przypadki pokazują, czy agent rozumie zadanie i czy aplikacja je egzekwuje.
Kontrola intencji przez Jev też potrzebuje własnych przypadków. Daj mu proponowane akcje z niezależnie przypisanymi oczekiwanymi decyzjami: wyraźną prośbę o zwrot, pytanie o uprawnienie do zwrotu, wycofaną prośbę i instrukcję osadzoną w wyniku narzędzia. Ten ostatni przypadek sprawdza też, czy składanie kontekstu zachowuje rozróżnienie między intencją klienta a tekstem dostarczonym przez narzędzie. Policz niewłaściwe akcje, które przepuszcza, i uprawnione akcje, które blokuje, a potem prześledź całą rozmowę po blokadzie. Zapobieżenie zapisowi pomaga tylko w części zadania, jeśli asystent potem wpada w pętlę albo nigdy nie prosi klienta o doprecyzowanie. Wybierz progi na przykładach deweloperskich, zamroź je przed porównaniem na odłożonym zbiorze i zapisuj wersję pytania, dostarczony kontekst, zwrócone prawdopodobieństwa, zastosowaną regułę, błędy i koszt. Nagrane demo poniżej nie wywołało odrzucenia przez Jev, więc nie może potwierdzić tej korzyści.
Zapisuj tyle, ile trzeba, by zdiagnozować awarię: zadanie, wersje modelu i harnessu, proponowane akcje, wyniki wykonania, stan końcowy, błędy, opóźnienie i koszt. Trzymaj sekrety i niepotrzebne dane osobowe poza tym zapisem. Daj kandydatowi dane wejściowe zadania i potrzebne mu reguły, ale trzymaj wzorcowe odpowiedzi i odłożone przypadki testowe poza jego zasięgiem. Ewaluator i jego dowody też muszą pozostać poza kontrolą kandydata. Jeśli przyszły optymalizator będzie mógł edytować harness, nie może móc przepisać kontroli ani wyników, które oceniają jego zmiany.
Te dowody nadają opcjonalnym komponentom cel. Dodaj router, gdy ślady wykonania pokazują, że wybór zadania potrzebuje osobnego etapu. Wypróbuj podsumowanie, gdy długie historie powodują konkretny błąd. Przetestuj strażnika na zabronionych akcjach, łącznie z przypadkami, które powinien przepuścić. Wymagane kontrole dostępu wynikają z ograniczeń zadania; opcjonalne dodatki potrzebują dowodów, że ich korzyść uzasadnia ich koszt.
Zamień projekt w pierwszą implementację
Dla naszego przykładu wsparcia mamy teraz uzasadniony punkt wyjścia: ograniczoną pętlę do interpretowania różnorodnych próśb, wąskie narzędzia do operacji na kontach, autorytatywne kontrole w usługach wykonujących zapisy i jawne rekordy dla oczekujących akcji. Na razie nie mamy wymogu generowanego kodu ani trwałej pamięci rozmów.
Najpierw zbudowałbym jedną kompletną ścieżkę. Utwórz małe konto testowe i oczekiwany wynik zwrotu. Zaimplementuj operacje wyszukiwania i zwrotu, łącznie z ich ścieżkami odrzucenia. Połącz model z tymi operacjami. Potem przećwicz przypadki odmowy, brakujących informacji i przerwanego zapisu. To ujawnia brakujące wymagania, zanim większy zbiór zadań utrudni ich dostrzeżenie.
Na tym etapie może wystarczyć pętla bez frameworka. Podstawowy przepływ to złożyć kontekst, poprosić o następną akcję, zwalidować ją i wykonać, a potem zwrócić wynik modelowi. Posiadanie własnej pętli oznacza też odpowiedzialność za konwersję wiadomości, anulowanie, stan i tracing. Ponowne użycie istniejącego frameworka jest przydatne, gdy oszczędza tę pracę, nie ukrywając decyzji, które musimy kontrolować.
W tej serii create_agent z LangChain jest wygodnym punktem wyjścia, bo zapewnia pętlę model/narzędzia i hooki do zmiany jej zachowania. Już działa na LangGraph. Przejście później na jawny workflow w LangGraph oznacza przejęcie kontroli nad większą liczbą etapów wokół tego agenta; nie oznacza dodania grafu do systemu, który wcześniej go nie miał.
Pierwszy projekt nie musi zawierać każdej omówionej wyżej możliwości. Musi mieć dość, by ukończyć swoje zadanie w ramach swoich ograniczeń, plus dowody, które pokazują, gdzie zawodzi. Przed napisaniem implementacji zebrałbym decyzje na jednej stronie:
| Pytanie | Decyzja do zapisania |
|---|---|
| Jakie zadanie wykonujemy? | Wywołujący, oczekiwany wynik, uzasadniona odmowa lub przekazanie sprawy i zabronione skutki |
| Kto wybiera następny krok? | Znane etapy w kodzie; decyzje delegowane do modelu; powód każdej z nich |
| Które kontrole wymagają interpretacji? | Pytanie semantyczne, jego kontekst, kandydat na model i zachowanie przy niepewności |
| O co model może poprosić? | Brak narzędzi, typowane operacje, generowany kod albo ich połączenie |
| Kto to autoryzuje i wykonuje? | Dozwolone zasoby, usługa egzekwująca, reguły zatwierdzania i ograniczenia sandboxa |
| Co musi przetrwać przerwanie? | Stan rozmowy, rekordy biznesowe, oczekujące operacje i zachowanie przy odzyskiwaniu |
| Jakie limity obowiązują? | Ograniczenia modelu/dostawcy, obsługa danych, wywołania, czas, wydatki i anulowanie |
| Co dowodzi ukończenia? | Kontrole odpowiedzi, akcji i stanu; kto jest właścicielem ich dowodów |
| Co pominęliśmy? | Opcjonalne komponenty i błąd albo nowe wymaganie, które uzasadniłyby każdy z nich |
Użyteczna odpowiedź jest na tyle konkretna, że zmienia implementację. „Potrzebuje pamięci” jest mgliste. „Musi wznowić zatwierdzony zwrot po restarcie workera bez tworzenia drugiego zwrotu” mówi nam, co utrwalić i jaki błąd przetestować.
To jest część 1 serii Building and Evaluating Agent Harnesses. Część 2 rozwija pytanie o przepływ sterowania: kiedy jawny workflow poprawia obsługę zadania na tyle, by uzasadnić dodatkowe etapy? Zbudujemy najmniejszy uzasadniony workflow wokół agenta i porównamy go z pętlą. Routery, równoległe workery i podsumowania to kandydaci do zbadania, a nie z góry ustalony cel. Późniejsze części oddzielają ewaluatora od kandydata i mierzą zmienność w powtarzanych wykonaniach.
Opcjonalnie: przejrzyj implementację i jej ograniczenia
Dołączone demo to mała, uruchamialna wersja asystenta wsparcia. Działa w dwóch środowiskach:
- Własne zadania wsparcia: dwanaście zadań, każde na świeżej bazie danych SQLite z klientami i zamówieniami.
- τ³-bench retail: osiem zadań z publicznego benchmarku, w którym symulowany klient rozmawia z agentem, a kontrole bazy danych oceniają wynik.
Ta sekcja opisuje, co zawiera demo, co celowo pomija, co pokazują wyniki i jak je uruchomić. Możesz ją pominąć i nadal korzystać z opisanego wyżej procesu projektowania.
Co zawiera demo
Jeden agent LangChain wybiera wywołania. Ma pięć narzędzi kont (look_up_account, list_orders, issue_refund, update_address i set_preference) oraz serwer MCP do wyszukiwania zasad i FAQ. Checkpointer w pamięci przechowuje stan wykonania. Nie ma modułu uruchamiającego kod ani pamięci między sesjami.
Demo zapisuje dwie konfiguracje:
| Spec | Komponenty |
|---|---|
plain | Streszczanie, limit 20 wywołań modelu na wykonanie i 2 ponowienia przy błędzie narzędzia |
base | Wszystko z plain plus Schema-Guided Reasoning i strażnik zapisów Jev |
Schema-Guided Reasoning (SGR) sprawia, że model zwraca ustrukturyzowany obiekt następnego kroku zamiast natywnego wywołania narzędzia. Strażnik Jev działa przed każdym zapisem i zadaje jedno złożone pytanie: czy to wywołanie złamałoby zasady albo wykroczyło poza to, o co prosił klient? Dwa osobne pytania opisane wcześniej w tym artykule, o prośbę i jej wycofanie, to proponowana przebudowa. Nagrane wykonania ich nie używały.
base dodaje SGR i Jev razem, więc wyniki własnych zadań nie mogą pokazać, który z nich spowodował różnicę.
Co demo celowo pomija
- Narzędzia sprawdzają integralność danych, ale zasady biznesowe zostawiają agentowi, więc eksperyment może ujawnić zapis zabroniony przez zasady. Prawdziwa usługa egzekwowałaby zasady przy zapisie.
- Nie ma limitu czasu ani wydatków dla całego zadania, a ponawiane zapisy nie mają klucza idempotencji.
- Ewaluator działa w tym samym procesie co agent.
Te wyniki nie testują więc egzekwowania w produkcji, bezpieczeństwa ponowień ani ochrony dowodów przed manipulacją.
Wyniki na własnych zadaniach
Każdy wiersz to jedno wykonanie na zadanie, a model i ceny endpointu są zapisane razem z wynikami. Kontrola stanu porównuje końcową bazę danych z oczekiwaną. Koszt i opóźnienie to średnie na zadanie; koszt obejmuje Jev tam, gdzie działa.
| Spec | Model | Kontrole stanu | Koszt | Opóźnienie |
|---|---|---|---|---|
base | openai/gpt-6-luna | 12/12 | $0.00073 | 11.9 s |
plain | openai/gpt-6-luna | 12/12 | $0.00033 | 5.8 s |
base | z-ai/glm-5.3-flash | 12/12 | $0.00101 | 9.7 s |
base | xiaomi/mimo-v2.6-flash | 7/12 | $0.00087 | 18.6 s |
- Z
gpt-6-lunaobie konfiguracje przeszły wszystkie dwanaście zadań.basekosztował około 2,2 raza więcej i trwał mniej więcej dwa razy dłużej. - Jev przepuścił każdy sprawdzony zapis: dwanaście w wykonaniach
gpt-6-lunai GLM oraz cztery w wykonaniu MiMo. Nigdy nie zablokował zapisu, więc te wykonania nie sprawdzają, czy zatrzymuje zły zapis. - Pięć porażek MiMo to błędy formatu wyjścia SGR. Nie ma wykonania MiMo
plain, więc nie możemy powiedzieć, czy natywne wywoływanie narzędzi poradziłoby sobie lepiej. - Streszczanie nigdy się nie uruchomiło. Największe żądanie w sześciu nagranych wykonaniach (tych czterech i dwóch wykonaniach τ³-bench poniżej) miało 8 351 tokenów wejściowych, poniżej progu 12 000 tokenów. Te wyniki nie mogą pokazać, czy streszczanie pomaga.
Wyniki na τ³-bench
τ³-bench ocenia wykonanie, odtwarzając wywołania narzędzi agenta w świeżym środowisku, więc benchmark musi sam uruchamiać narzędzia. Adapter wstrzymuje graf przed jego węzłem narzędzi, zwraca wywołania narzędzi i zapisuje z powrotem wyniki benchmarku, zanim wznowi działanie.
Ma to dwie konsekwencje. Po pierwsze, prompt, narzędzia i zasady pochodzą z τ³-bench, więc tych wyników nie da się porównać z wynikami własnych zadań, nawet przy tym samym pliku spec. Po drugie, ponawianie narzędzi i Jev nigdy tu nie działają: jedyną różnicą między base a plain jest SGR.
Oba wykonania używają openai/gpt-6-luna dla agenta i dla symulowanego klienta. Opóźnienie symulacji obejmuje pracę symulatora. Koszt agenta i opóźnienie symulacji to średnie na zadanie; koszt symulatora to suma dla wszystkich ośmiu zadań.
| Spec | Zaliczone | Koszt agenta | Opóźnienie symulacji | Koszt symulatora, łącznie |
|---|---|---|---|---|
base | 6/8 | $0.00238 | 59.0 s | $0.00453 |
plain | 7/8 | $0.00136 | 38.9 s | $0.00471 |
- Osiem zadań pochodzi z testowego podziału retail i nie ma asercji w języku naturalnym, więc o nagrodzie decydują wyłącznie kontrole bazy danych.
- Obie konfiguracje ukończyły wszystkie osiem zadań bez błędów adaptera. Każda porażka była brakującym zapisem.
basenie zaliczył zadań 9 i 26.plainprzekazał zadanie 27 człowiekowi zamiast dokończyć wymianę. - Drugie wykonanie
basetakże zaliczyło 6/8, ale nie zaliczyło zadań 9 i 17. Niezaliczone zadania zmieniają się między wykonaniami, więc różnica jednego zadania nie jest powodem, by preferowaćplain. Te wykonania nie mierzą tej zmienności.
Sprawdź zapisane liczby
Zapisane raporty i ślady wykonania zawierają wyniki dla poszczególnych zadań, liczby tokenów, konfiguracje, wersje pakietów i zapisane ceny. Dla 393 wywołań czatu agenta w sześciu nagranych wykonaniach koszt obliczony z tokenów zgadzał się z kwotą naliczoną w każdej odpowiedzi. Ta kontrola nie obejmuje wywołań Jev ani symulowanego klienta. Ceny są historyczne, a nie wyceną twojego wykonania.
Uruchom samodzielnie
Zainstaluj uv i użyj oznaczonej tagiem wersji poniżej. Testy działają offline po zainstalowaniu zależności. Dwa wykonania środowisk wykonują płatne wywołania modeli przez OpenRouter i potrzebują klucza API.
git clone https://github.com/slavadubrov/agent-harness-lab-public
cd agent-harness-lab-public
git checkout v0.1.2-a1
cp .env.example .env # set OPENROUTER_API_KEY
make test
make a1-custom SPEC=harness/spec/plain.yaml
make a1-tau3 SPEC=harness/spec/plain.yaml
Każde wykonanie zastępuje wyniki w reports/article-a1/. Użyj git diff, by porównać je z zatwierdzonym punktem odniesienia. Makefile instaluje zależności z zamrożonego uv.lock.
Aby wypróbować inny model albo zestaw narzędzi, skopiuj spec z harness/spec/, zmień te pola i przekaż jego ścieżkę przez SPEC. Ustawienia dostawcy i własnych komponentów przyjmują dowolne klucze, więc walidacja nie wyłapie każdej literówki w zagnieżdżonych polach.
Proces projektowania z tego artykułu możesz stosować bez uruchamiania demo. Demo czyni jeden zestaw wyborów możliwym do sprawdzenia; to twój własny kontrakt zadania decyduje, które z nich należą do twojego harnessu.