ToolCallingAgent kontra CodeAgent w lokalnych modelach Qwen2.5 14B
ToolCallingAgent kontra CodeAgent w smolagents na Macu z procesorem M4: szybkość i dokładność routingu przy użyciu lokalnych modeli Qwen2.5 14B do generowania poleceń i kodowania przy różnych temperaturach.
Źródła: ToolCallingAgent vs. CodeAgent: Which Performs Better on a Local LLM?
Konfiguracje lokalnych agentów szybko ujawniają słabe punkty. Urządzenie to było zwykłym komputerem Apple M4 z 16 GB pamięci zintegrowanej, bez możliwości przetwarzania w chmurze ani dodatkowej pamięci VRAM do wykorzystania, więc nawet umiarkowanie duży okno kontekstowe może spowodować wymianę danych w pamięci. Dwa style agentów z smolagents, ToolCallingAgent oraz CodeAgent, zostały przetestowane na dwóch modelach uruchamianych lokalnie: qwen2.5-coder:14b i qwen2.5:14b-instruct. Oba modele mają pojemność 14 miliardów parametrów i tę samą architekturę podstawową, a oba działały na tym samym urządzeniu.
- Qwen2.5:14b-instruct to model uniwersalny z serii 14B, dostosowany na podstawie szerokich zbiorów danych instrukcji i dialogów, dzięki czemu dobrze wykonuje polecenia.
- qwen2.5-coder:14b wykorzystuje tę samą architekturę podstawową, ale przeszedł dodatkowe szkolenie wstępne na dużych zbiorach kodu oraz dalsze dostosowanie skierowane na ten cel.
Biorąc pod uwagę tak ograniczony budżet na sprzęt, nie oczekiwano, że żadna z konfiguracji będzie wyjątkowa. Bardziej interesujące było to, w którym momencie każda z nich zawiedzie, oraz czy wybór innego typu agenta mógłby złagodzić te ograniczenia.
Konfiguracja
Przeprowadzono pomiar temperatury w wartościach 0, 0,1, 0,3 i 0,6 dla obu typów agentów przy jednym zadaniu: znalezienie pięciu najlepszych pizzerii w Seattle, sprawdzenie czasu oczekiwania w każdej z nich oraz polecenie tylko tych, których czas oczekiwania jest poniżej ustalonego progu przez klienta. Funkcje obliczające czas oczekiwania i porównujące go z progiem są deterministyczne.
@tool
def get_wait_time(restaurant_name: str) -> int:
"""Current wait in minutes for a restaurant. Deterministic, not a live lookup."""
@tool
def restaurants_in_wait_limit(wait_time: int, wait_threshold: int) -> bool:
"""True if the wait is within the customer's threshold."""
Projekt deterministyczny jest celowy. Szukanie w czasie rzeczywistym pozwoliłoby modelowi oszacować prawdopodobny czas oczekiwania i kontynuować pracę. Przymusowe wywołanie funkcji sprawdza to, co jest tutaj ważne: czy agent kieruje się danymi dostarczonymi przez narzędzie zamiast samemu wymyślać wartość, oraz czy to nadal zachodzi, gdy zmienia się temperatura lub typ agenta.
Śledzono trzy aspekty: opóźnienie, to, czy proces został zakończony, oraz dokładność routingu, określaną jako udział wszystkich elementów łańcucha narzędzi wykonywanych poprawnie przy każdym wywołaniu. Traktuj te liczby jako narzędzie pomiarowe, ponieważ nie jest to formalny benchmark.
Jak różnią się dwa agenci
W przypadku ToolCallingAgent każdy krok generuje pojedyncze wywołanie narzędzia w formacie JSON. Model dostarcza argumenty, framework wykonywa narzędzie, a następnie rozpoczyna się kolejny krok. Pięć restauracji i dwie funkcje dają łącznie dziesięć oddzielnych wywołań, a każde z nich stanowi nową szansę na zepsucie ustrukturyzowanego wyniku.
CodeAgent wybiera inną drogę i generuje fragment kodu w Pythonie, który działa w jednym kroku. Jeden pętla może objąć wszystkie restauracje i wywołać obie funkcje znajdujące się w jej obrębie. Poprawna pętla jest skuteczna dla wszystkich z nich podczas tego jednego wykonywania, więc decyzja jest podejmowana raz zamiast dziesięć razy.
Wyniki
Na modelu Qwen2.5:14b-instruct
Oba agenty radziły sobie dobrze. CodeAgent utrzymywał dokładność planowania na poziomie 1,00 przy każdej temperaturze, natomiast ToolCallingAgent oscylował między 0,95 a 1,00. Nie zaobserwowano żadnej znaczącej różnicy pod względem poprawności.
Szybkość pokazywała inny obraz. ToolCallingAgent działał mniej więcej dwa razy wolniej – około pięć minut na jedną próbę w porównaniu z około dwoma i pół minutą u CodeAgent. Przyczyną jest struktura działania. ToolCallingAgent musi za każdym razem wywołać nowy model, ponownie wysyłając kontekst rozmowy i generując treść od zera. CodeAgent robi to tylko raz. Na lokalnym komputerze, gdzie każda generacja jest już wolna, ten dodatkowy obciążenie nie może być zignorowane.
Na Qwen2.5-coder:14b
Tutaj oba agenty znacznie się różniły. ToolCallingAgent nigdy nie osiągnął 50 procent dokładności w kierowaniu procesem, pozostając w przedziale od 0,40 do 0,50. Działał szybciej niż na modelu instrukcyjnym, ale to nie było dobrym znakiem. Był szybki, ponieważ się poddał. Gdy stracił wątek, przestał wysyłać żądania i zakończył pracę wcześniej, wykonawszy mniej zadań.
CodeAgent wygrał bezkonkurencyjnie, osiągając dokładność routowania 1,00, co odpowiada najlepszym wynikom w modelu instruct. Jest to spodziewane, ponieważ prosi model kodujący o kod w formacie Python, a nigdy o strukturyzowany JSON.
Co można z tego wyciągnąć
Przewaga CodeAgent w obsłudze pętli jest rzeczywista. W modelu instruct, gdzie oba agenty uzyskały wynik 1,00 w zakresie routowania, grupowanie zadań sprawiło, że CodeAgent działał dwa razy szybciej niż ToolCallingAgent.
Qwen2.5:14b-instruct sprawdza się jako niezawodny uniwersalista, dobrze radzący sobie zarówno z wywołaniami strukturyzowanego JSON, jak i z prostym kodem. Qwen2.5-coder:14b lepiej nadaje się do złożonych zadań programistycznych, ponieważ może być niestabilny przy generowaniu strukturyzowanego JSON i utrzymywaniu tej jakości w ciągu serii wywołań.
Otwarte pytanie
Pozostaje jeszcze jedno pytanie. Dwie rzeczy odróżniają te agenty: CodeAgent grupuje swoją pracę w jeden program i pisze kod w języku Python zamiast strukturyzowanego JSON. Następne eksperymenty mogłyby to rozdzielić, sprawiając, że CodeAgent będzie wysyłał strukturyzowane żądania do narzędzi, nadal działając w trybie zbiorczym. Wynik wskazałby, czy przewaga wynika głównie z pętli, czy też z faktu, że modele uważają tworzenie kodu za łatwiejsze niż realizację długiej serii żądań JSON.