Tool calling przy lokalnych modelach: jak sprawić, by agenty przestały się wykładać na popsutym JSON-ie

Autor: Jakub Rusinowski · Ostatnia aktualizacja 2026-08-04 · Wartości sprzętowe wyliczone naszym silnikiem VRAM

Tool calling to miejsce, w którym lokalne agenty kodujące psują się najczęściej — nie dlatego, że model nie potrafi rozumować, tylko dlatego, że emituje JSON, którego harness nie sparsuje. Trzy poprawki w kolejności skuteczności: weź model, którego szablon czatu ma natywną obsługę wywołań narzędzi; ogranicz dekodowanie gramatyką ze schematu JSON, żeby niepoprawne wyjście było niemożliwe z konstrukcji; i trzymaj małą powierzchnię narzędziową. Razem zamieniają niestabilnego agenta w przewidywalnego, bez zmiany progu modelu.

Agent to model, który potrafi działać. Mechanizm jest mało efektowny: model emituje ustrukturyzowane wywołanie — nazwę i obiekt JSON z argumentami — a harness je wykonuje. Każda zdolność, na której ci zależy, od odczytania pliku po uruchomienie zestawu testów, przechodzi przez ten jeden wąski interfejs.\n\nModele z czołówki sprawiają, że wygląda to trywialnie. Lokalne obnażają, jak wąski ten interfejs naprawdę jest. Model 8–32B poproszony o wywołanie narzędzia z pewną częstotliwością owinie JSON wyjaśnieniem, użyje file tam, gdzie schemat mówił path, wyemituje dwa wywołania tam, gdzie dozwolone było jedno, albo zacznie wywołanie i zjedzie w prozę. Harness widzi akcję nie do sparsowania, a twój agent staje, ponawia albo umiera. To nie jest porażka rozumowania i większy model nie jest jedynym lekarstwem.

Trzy sposoby, na jakie lokalny model może wywołać narzędzie

Świadomość, którego mechanizmu używa twój stack, wyjaśnia większość jego awarii.\n\n1 · Natywne wywoływanie narzędzi przez szablon czatu. Model został dostrojony do emitowania wywołań w konkretnym formacie, a szablon czatu w GGUF-ie ten format koduje. Serwer parsuje go i zwraca ustrukturyzowane pole tool_calls w odpowiedzi zgodnej z OpenAI. To ścieżka niezawodna — i dlatego modele reklamowane jako warianty *agentic* albo *coder* zachowują się w agentach nieporównanie lepiej niż ogólne modele czatowe tej samej wielkości.\n\n2 · Promptowany JSON. Harness opisuje narzędzia w prompcie systemowym, prosi o JSON i sam go parsuje. Działa z każdym modelem; zawodzi znacznie częściej, bo nic nie wymusza kształtu. Większość zgłoszeń „dlaczego mój agent stanął?” mieszka właśnie tutaj.\n\n3 · Formaty edycji zamiast wywołań narzędzi. Podejście Aidera: zamiast schematu narzędzi poproś o diff albo blok szukaj-i-zamień. To format *tekstowy*, więc słabsze modele radzą sobie z nim lepiej niż z zagnieżdżonym JSON-em, a błąd jest widoczny (łatka się nie nakłada), nie cichy. To rzeczywisty powód, dla którego Aider bije ponad swoją wagę przy małych modelach.\n\nPreferuj 1, w razie czego schodź do 3, a 2 traktuj jako to, co trzeba ograniczyć.

Dekodowanie ograniczone gramatyką: poprawka, którą większość pomija

Najmocniejsza dostępna poprawka nie prosi modelu, żeby się zachowywał — czyni złe zachowanie niemożliwym. Przy każdym tokenie dekodowanie ograniczone gramatyką maskuje wszystkie tokeny, które nie mogłyby kontynuować poprawnego wyjścia. Niepoprawny JSON nie jest naprawiany po fakcie; on nigdy nie powstaje.

llama.cpp używa GBNF (własnego formatu gramatyk w stylu BNF) i automatycznie zamienia JSON Schema w gramatykę, więc w praktyce podajesz jej ten sam schemat, który dałbyś w definicji narzędzia. Zbudowane na niej serwery zgodne z OpenAI wystawiają to jako ustrukturyzowane wyjście albo format odpowiedzi zgodny ze schematem JSON; vLLM i SGLang mają swoje odpowiedniki.

# serwer llama.cpp: wymuś, by każda odpowiedź pasowała do schematu wywołania narzędzia
llama-server -m qwen3-coder-8b-q4_k_m.gguf --port 8080 --ctx-size 16384

curl http://localhost:8080/v1/chat/completions -H "Content-Type: application/json" -d '{
  "messages": [{"role":"user","content":"Read src/auth.py"}],
  "response_format": {
    "type": "json_schema",
    "json_schema": {"name":"tool_call","schema":{
      "type":"object",
      "properties":{
        "tool":{"type":"string","enum":["read_file","edit_file","run_tests"]},
        "args":{"type":"object"}
      },
      "required":["tool","args"],
      "additionalProperties": false
    }}
  }
}'

Dwa zastrzeżenia, które warto znać, zanim włączysz to wszędzie:

Wybierz model, który był pod to trenowany

Niezawodność wywołań narzędzi jest własnością treningu, nie czymś, co się samo wyłania. Dwa modele identycznej wielkości potrafią różnić się ogromnie, a różnica idzie za tym, czy model był dotrenowany na przebiegach agentowych i różnorodnych formatach wywołań.\n\nNa co patrzeć, po kolei:\n\n1. Jawne dostrojenie agentowe albo koderskie. Devstral-2 22B jest trenowany pod agentową inżynierię oprogramowania; linia Qwen3-Coder pod pracę agentów kodujących i wywoływanie narzędzi. Oba zachowują się w harnessie dużo lepiej niż ogólny model czatowy o tej samej liczbie parametrów.\n2. Szablon czatu z obsługą narzędzi. Sprawdź, czy GGUF faktycznie taki ma — ollama show --modelfile <tag> pokaże szablon. Brak tokenów narzędziowych w szablonie oznacza, że jesteś na ścieżce promptowanego JSON-a, chciałeś tego czy nie.\n3. W obrębie progu — świeżość ponad rozmiar. Jakość wywoływania narzędzi poprawiała się w latach 2025–2026 dużo szybciej niż samo rozumowanie. Aktualny 22B zwykle bije dwuletniego 34B wewnątrz pętli agenta.\n\nWybory na każdy próg i dokładne wartości VRAM znajdziesz w najlepszych lokalnych modelach do kodowania. Krótko, pod agentów: 8B da radę z ograniczeniami i małym zestawem narzędzi; 22B to pierwszy wygodny próg; przy 27–32B przestajesz o tym myśleć.

Modele, które zachowują się w pętli agenta

Progi, na których wywoływanie narzędzi jest na tyle niezawodne, że można zostawić agenta w ruchu. Wartości VRAM to wagi Q4_K_M z silnika obliczeniowego tej strony — cache KV pod twój kontekst dolicz ponad to.

ModelVRAM (Q4)Uruchomi się naKontekstLicencja
Qwen3-Coder 8B
Wejście — ogranicz go — Nadaje się do pętli przy małym zestawie narzędzi i ograniczeniach gramatycznych. Trzymaj zadania w obrębie jednego pliku.
ollama pull qwen3-coder:8b
5.6 GB8 GB GPU (RTX 3060/4060)
Mac: 16 GB pamięci zunifikowanej
125KApache-2.0
Devstral-2 22B
Pierwszy wygodny próg — Wprost dostrojony agentowo pod inżynierię oprogramowania; niezawodnie trzyma się formatów edycji i schematów narzędzi.
ollama pull devstral:22b
14.1 GB16 GB GPU (RTX 4060 Ti 16GB / 5060 Ti)
Mac: 24 GB pamięci zunifikowanej
125KApache-2.0
Qwen 3.6 27B
Autonomia godna zaufania — Próg, na którym zepsute wywołania przestają być czymś, o czym myślisz na co dzień.
ollama pull qwen3.6:27b
17.6 GB24 GB GPU (RTX 3090/4090)
Mac: 24 GB pamięci zunifikowanej
256KApache-2.0
Qwen3-Coder 80B-A3B (MoE)
Stacja robocza / Mac od 96 GB — Szybkie dekodowanie MoE plus mocny trening pod wywoływanie narzędzi — lokalny sufit dla długich przebiegów agenta.
ollama pull qwen3-coder:80b-a3b-q4
49.1 GB2×48 GB GPUs / big unified memory
Mac: 96 GB pamięci zunifikowanej
125KApache-2.0

Trzymaj małą powierzchnię narzędziową

Każde wystawione narzędzie jest opisywane w każdym żądaniu, a każda dodatkowa opcja to kolejna szansa, że słabszy model wybierze źle. Oba koszty są lokalnie dotkliwsze: tokenów brakuje, bo okno jest małe, a trafność wyboru i tak jest niższa na starcie.\n\n- Sześć do ośmiu narzędzi w zupełności wystarczy do pracy z kodem: odczyt, edycja, listowanie i wyszukiwanie, uruchomienie polecenia, uruchomienie testów, ewentualnie pobranie zasobu. Jeśli twój rejestr ma dwadzieścia pozycji, tnij.\n- Podpięte serwery MCP to też narzędzia. Kilka serwerów potrafi wstrzyknąć dziesiątki tysięcy tokenów schematów, zanim napiszesz choć słowo — jedną trzecią dużego okna, a więcej niż całe małe. Podpinaj je per projekt, nie globalnie (MCP przy lokalnych agentach).\n- Dawaj narzędziom rozłączne zadania. search_files i grep_repo robiące prawie to samo kosztują tokeny i fundują modelowi rzut monetą. Scal je.\n- Opisy to prompty. Jedna linijka, w trybie rozkazującym, mówiąca dokładnie, kiedy tego użyć i co to zwraca. Jeśli model uparcie używa narzędzia źle, dopisz, czego ono *nie* robi.

Strategie naprawy, gdy mimo wszystko się psuje

Nawet z ograniczeniami pojedyncze wywołania będą padać. Obsłuż je w harnessie, zamiast eskalować do człowieka:\n\n1. Ponowienie naprawiające parsowanie. Po błędzie parsowania poproś ponownie, podając surowe wyjście i schemat, i prosząc wyłącznie o poprawiony JSON. Zwykle udaje się za pierwszym razem i *nie* powinno liczyć się do limitu iteracji pętli — to błąd formatowania, nie nieudana próba.\n2. Waliduj argumenty, nie tylko parsuj. Poprawny JSON ze zmyśloną ścieżką jest gorszy od błędu parsowania, bo się wykona. Sprawdź, czy ścieżka istnieje i mieści się w dozwolonym katalogu, czy wartości należą do enumów i czy są wszystkie wymagane pola — a potem zwróć modelowi komunikat błędu jako wynik narzędzia. Modele poprawiają się po konkretnym sprzężeniu zwrotnym zaskakująco dobrze.\n3. Obniż temperaturę na tury z akcją. Ustrukturyzowane wyjście chce dekodowania bliskiego zachłannemu. 0,0–0,2 na tury z wywołaniem narzędzia; wyższą temperaturę zostaw dla prozy.\n4. Loguj każde zepsute wywołanie. Wskaźnik powyżej mniej więcej jednego na dwadzieścia to problem konfiguracji — zły szablon, nieograniczone dekodowanie, za dużo narzędzi — a nie pech. Log powie ci który.\n5. Ogranicz pętlę naprawczą. Dwie nieudane naprawy oznaczają: zatrzymaj się i pokaż to człowiekowi. Trzy modele kłócące się ze schematem to zmarnowany wieczór.

Diagnoza pięciu typowych awarii

| Objaw | Przyczyna | Poprawka |\n|---|---|---|\n| tool_calls zawsze puste, model zamiast tego opowiada swój plan | Szablon czatu nie obsługuje narzędzi albo harness jest na ścieżce promptowanego JSON-a | Przejdź na dostrojenie agentowe lub koderskie z tokenami narzędziowymi w szablonie; sprawdź to przez ollama show --modelfile |\n| JSON poprawny, nazwy argumentów złe | Schemat nie jest wymuszany, model zgaduje z opisu | Ogranicz schematem JSON; w opisach podawaj nazwy pól wprost |\n| Wywołanie ucięte w połowie JSON-a | Za niski limit tokenów odpowiedzi albo okno się zapełniło i na odpowiedź nie zostało miejsca | Zarezerwuj miejsce na odpowiedź w budżecie kontekstu; podnieś maksymalną liczbę tokenów |\n| Poprawne wywołanie, zła ścieżka pliku | To nie problem wywoływania narzędzi — modelowi brakuje kontekstu repozytorium | Mapa repozytorium albo krok wyszukiwania przed edycją (context engineering) |\n| Działa pojedynczo, psuje się po dwudziestu turach | Degradacja kontekstu, nie formatowanie | Kompaktuj wcześniej albo zrestartuj sesję |\n\nWzorzec wart zapamiętania: awarie formatowania to błędy konfiguracji, a błędy konfiguracji naprawia się tanio. Dopiero gdy wskaźnik poprawnych wywołań jest czysty, przeskok na wyższy próg modelu mówi ci cokolwiek wiążącego.

Najczęstsze pytania

Dlaczego lokalne modele produkują zepsute wywołania narzędzi?
Zwykle z jednej z trzech przyczyn konfiguracyjnych: model nie ma natywnej obsługi wywołań w szablonie czatu, więc harness wyłuskuje JSON z prozy; dekodowanie nie jest ograniczone, więc nic nie zapobiega niepoprawnemu wyjściu; albo wystawionych jest za dużo narzędzi i model wybiera źle. Wszystkie trzy da się naprawić bez zmiany progu modelu — dekodowanie ograniczone gramatyką plus model dostrojony agentowo usuwają większość tej klasy problemów.
Czym jest dekodowanie ograniczone gramatyką?
To technika dekodowania maskująca każdy token, który nie mógłby kontynuować poprawnego wyjścia zgodnego z formalną gramatyką, dzięki czemu niepoprawne wyjście jest niemożliwe, a nie poprawiane po fakcie. llama.cpp używa GBNF i automatycznie zamienia JSON Schema w gramatykę; vLLM i SGLang mają odpowiedniki. Kosztuje kilka procent przepustowości i całkowicie usuwa cykl walidacji i ponowień.
Które lokalne modele najlepiej radzą sobie z wywoływaniem narzędzi?
Te dotrenowane pod agentowe kodowanie, a nie pod ogólny czat. Devstral-2 22B jest wprost trenowany pod agentową inżynierię oprogramowania i jest pierwszym wygodnym progiem; rodzina Qwen3-Coder jest trenowana pod pracę agentów kodujących; przy Qwen 3.6 27B zepsute wywołania przestają być codziennym zmartwieniem. W obrębie progu wybieraj model nowszy — jakość wywoływania narzędzi poprawiała się szybciej niż samo rozumowanie.
Ile narzędzi dawać lokalnemu agentowi kodującemu?
Sześć do ośmiu wystarcza do kodowania: odczyt, edycja, listowanie, wyszukiwanie, uruchomienie polecenia, uruchomienie testów. Każda definicja to tokeny w każdym żądaniu i kolejna szansa na zły wybór, a oba koszty biją mocniej przy małym oknie i mniejszym modelu. Podpięte serwery MCP też się liczą — kilka z nich potrafi zająć więcej lokalnego okna niż samo zadanie.
Czy dekodowanie ograniczone gramatyką szkodzi jakości modelu?
Może, jeśli przesadzisz. Wymuszanie sztywnej struktury przy każdym tokenie tłumi swobodne rozumowanie, które wiele modeli przeprowadza przed działaniem. Praktycznym kompromisem jest pozwolić modelowi rozumować tekstem, a ograniczyć wyłącznie końcowy blok akcji, albo umieścić w schemacie jawne pole na rozumowanie przed polem narzędzia, tak by struktura dopuszczała myślenie.
Czy harness powinien ponawiać zepsute wywołanie narzędzia?
Tak — raz albo dwa, prosząc ponownie z surowym wyjściem i schematem i żądając wyłącznie poprawionego JSON-a. Traktuj to jako naprawę formatowania, nie nieudaną próbę, żeby nie zjadało budżetu iteracji pętli. Waliduj też argumenty, a nie tylko parsuj: poprawne wywołanie ze zmyśloną ścieżką się wykona, co jest gorsze od błędu parsowania.

Co dalej