Kiedy wdrażaliśmy u klienta z branży energetycznej agenta do automatyzacji raportów z systemu SCADA, pierwsze dni przebiegły bez zakłóceń. Potem zaczęły się ciche awarie: agent wywoływał właściwe narzędzie, ale wysyłał argumenty w nieprawidłowym formacie — liczba zamiast oczekiwanego ciągu znaków, null zamiast oczekiwanej wartości. Efekt? Narzędzie zwracało błąd, agent go ignorował i działał dalej, jakby nic się nie stało. Raport był generowany, wyglądał na poprawny, ale liczby były niedokładne. Odkrycie przyczyny zajęło nam kilka dni.
Tool calling — wywoływanie zewnętrznych narzędzi i systemów przez LLM — to podstawowa zdolność każdego agenta produkcyjnego. A zarazem największe źródło cichych awarii. Ten artykuł skupia się na konkretnych wzorcach: jak projektować schematy narzędzi, walidować argumenty, obsługiwać błędy i budować logikę ponownych prób, która działa w rzeczywistych warunkach.
Dlaczego tool calling zawodzi — nie tak, jak się Państwo spodziewają
Gdy programiści rozwiązują problem z niezawodnością agentów, zwykle szukają przyczyny w modelu. Złe rozumowanie, niedostateczne wnioskowanie, błędna interpretacja zadania. W praktyce przekonaliśmy się, że większość incydentów produkcyjnych ma inne źródło: zły projekt schematu narzędzia, brak walidacji argumentów, nieobsłużone błędy narzędzia.
Cztery najczęstsze wzorce awarii, które obserwujemy:
- Agent wywołuje złe narzędzie — ponieważ opisy narzędzi są zbyt podobne, niejasne lub nierozróżnialne
- Agent wywołuje właściwe narzędzie z błędnymi argumentami — ponieważ schemat nie opisuje dokładnie, jakie wartości są prawidłowe
- Agent ignoruje błąd narzędzia — ponieważ nikt nie obsłużył przypadku, w którym narzędzie zwraca wyjątek
- Agent wpada w pętlę ponownych prób — ponieważ logika retry nie jest ograniczona i nie stosuje wykładniczego backoff
Żaden z tych problemów nie wiąże się z inteligencją modelu. Wszystkie można rozwiązać na poziomie inżynieryjnym. I wszystkie powtarzają się niezależnie od zastosowanej architektury agenta.
Projekt schematu narzędzia: precyzja ponad zwięzłość
Schemat narzędzia to to, co agent widzi, podejmując decyzję, co wywołać. Jeśli schemat jest nieprecyzyjny, agent zgaduje — a zgadywanie w systemie deterministycznym to problem.
Podstawowe zasady projektowania schematu, sprawdzone w praktyce:
Nazwa narzędzia musi być czasownikiem czynności + obiektem. get_sensor_reading jest dobre. sensor jest złe. data jest bardzo złe. LLM orientuje się według nazwy — jeśli dwa narzędzia brzmią podobnie, agent będzie je mylił bez względu na jakość opisu.
Opis musi rozróżniać, kiedy narzędzie należy użyć, a kiedy nie. Nie wystarczy napisać „zwraca dane z czujnika". Napiszcie: „Wywołuj tylko wtedy, gdy potrzebujesz bieżących wartości z konkretnego urządzenia według ID. Nie używaj do danych historycznych — do tego służy get_sensor_history." Wyraźna negacja jest równie ważna jak opis pozytywny.
Każdy parametr musi mieć typ, opis i przykład. Nie wystarczy sensor_id: string. Napiszcie: „ID czujnika w formacie PLANT_ZONE_NNN, np. A_LINE1_042. Nigdy nie podawaj samej liczby bez prefiksu." Model nie domyśli się formatu — musicie mu go pokazać.
Parametry opcjonalne oznaczajcie wyraźnie — i podajcie wartość domyślną oraz to, co się stanie, jeśli je pominiecie. Bez tego agent wielokrotnie generuje null lub zakłada nieprawidłową wartość.
Przykład schematu przed i po:
# Przed (typowe złe schema)
{
"name": "sensor_data",
"description": "Gets sensor data",
"parameters": {
"id": {"type": "string"},
"from": {"type": "string"},
"to": {"type": "string"}
}
}
# Po (produkcyjne schema)
{
"name": "get_sensor_reading",
"description": "Reads current live value from a field sensor.
Use ONLY for real-time values. For historical ranges use get_sensor_history instead.
Returns {value, unit, timestamp, status}. Throws SensorOfflineError if device unreachable.",
"parameters": {
"sensor_id": {
"type": "string",
"description": "Sensor identifier in format PLANT_ZONE_NNN, e.g. A_LINE1_042"
},
"unit": {
"type": "string",
"enum": ["raw", "normalized"],
"description": "Output unit type. Default: 'raw'",
"default": "raw"
}
},
"required": ["sensor_id"]
}Różnica nie leży w elegancji — lecz w tym, jak często agent wywołuje właściwe narzędzie z prawidłowymi argumentami. Z praktyki: po naprawieniu schematów zmniejszyliśmy wskaźnik błędnych wywołań o ponad połowę, nie zmieniając niczego w modelu.
Walidacja argumentów: nie należy wierzyć modelowi na słowo
Nawet gdy schemat jest dobry, model może czasem wygenerować argumenty, które są syntaktycznie poprawne, ale semantycznie błędne. from_date: "2026-13-45" to prawidłowy ciąg znaków, ale nieprawidłowa data. count: -5 to prawidłowa liczba, ale bez sensu.
Walidacja argumentów przed samym wywołaniem narzędzia to obowiązek, nie optymalizacja.
Trzy warstwy walidacji, które zalecamy:
1. Walidacja JSON Schema — najprostsza warstwa. Sprawdzamy, że agent przesłał właściwy typ dla każdego parametru. Większość frameworków (LangGraph, LangChain) robi to automatycznie przy dostępnym schemacie, ale jawna warstwa jest pewniejsza.
2. Walidacja domenowa — sprawdzenie, że wartości mają sens w kontekście Państwa domeny. Daty w rozsądnym zakresie, ID w prawidłowym formacie, wartości liczbowe w dopuszczalnym zakresie. Tego musicie dokonać sami — żaden generyczny framework nie zrobi tego za was.
3. Integralność referencyjna — weryfikacja, że wskazana encja istnieje. sensor_id: "A_LINE1_042" musicie sprawdzić względem rzeczywistego rejestru czujników — model nie ma możliwości wiedzieć, które ID są prawidłowe.
Gdy walidacja zawodzi, nie rzucajcie po prostu wyjątku. Zwróćcie strukturalny komunikat błędu, który informuje model, *co* było nie tak i *jak* to naprawić:
# Źle
raise ValueError("Invalid sensor_id")
# Dobrze
return {
"error": "INVALID_ARGUMENT",
"parameter": "sensor_id",
"received": "A_LINE_42",
"message": "Format must be PLANT_ZONE_NNN. Did you mean A_LINE1_042?",
"hint": "Call list_sensors() to get valid IDs for this plant"
}Ten komunikat błędu jest wejściem do kolejnego kroku agenta — im bardziej informatywny, tym większa szansa, że agent poprawi się sam bez zbędnej eskalacji.
Obsługa błędów: błędy narzędzia to normalna sytuacja, nie wyjątek
Jeden z największych problemów koncepcyjnych, które obserwujemy w agentach produkcyjnych: kod został napisany pod happy path. Gdy narzędzie zwraca błąd, agent zachowuje się nieprzewidywalnie — albo się blokuje, albo działa dalej z błędnym stanem.
Rzeczywistość produkcyjna: narzędzia zawodzą. API mają awarie. Bazy danych są chwilowo niedostępne. Systemy SCADA zwracają timeout. To nie są przypadki brzegowe — to codzienne zdarzenia w środowisku przemysłowym.
Podstawowy wzorzec obsługi błędów dla każdego narzędzia:
def call_tool_safely(tool_fn, args, max_retries=3):
for attempt in range(max_retries):
try:
result = tool_fn(**args)
return {"status": "ok", "data": result}
except TransientError as e:
if attempt == max_retries - 1:
return {
"status": "error",
"type": "TRANSIENT",
"message": str(e),
"hint": "Service temporarily unavailable, retry later"
}
time.sleep(2 ** attempt) # exponential backoff
except PermanentError as e:
return {
"status": "error",
"type": "PERMANENT",
"message": str(e),
"hint": "Do not retry. Escalate to human operator."
}
except Exception as e:
return {
"status": "error",
"type": "UNKNOWN",
"message": str(e),
"hint": "Unexpected error. Log and escalate."
}Kluczowe zasady:
- Rozróżniajcie błędy przejściowe i trwałe. Timeout to błąd przejściowy (retry). Błąd autoryzacji to błąd trwały (bez retry, eskaluj). Model musi wiedzieć, co zrobić dalej.
- Wykładniczy backoff — nie stała przerwa. Każda ponowna próba czeka 2× dłużej (1 s, 2 s, 4 s…). Zapobiega przeciążeniu systemów podczas awarii.
- Maksymalna liczba prób — zawsze ograniczona. Bez limitu agent może wpaść w nieskończoną pętlę. Z praktyki: wskaźnik ponownych prób na poziomie ok. 12 % jest normalny w systemach produkcyjnych — jeśli nie macie go pod kontrolą, możecie podwoić łączną liczbę wywołań i koszty.
- Strukturalny komunikat błędu — agent musi otrzymać informację o typie błędu i zalecanym następnym kroku.
Więcej o kosztowych konsekwencjach logiki retry znajdziecie w artykule o kosztach agenta AI w produkcji.
Idempotentność: zapomniana cecha niezawodności
Gdy agent ponawia wywołanie narzędzia, musi być bezpieczne wywołanie go wielokrotnie z tymi samymi argumentami. Jeśli nie — macie problem.
Wywołanie idempotentne: wielokrotne wywołanie get_sensor_reading(sensor_id="A_LINE1_042") zwraca tę samą (lub równoważną) wartość bez efektów ubocznych. Bezpieczne przy retry.
Wywołanie nieidempotentne: wielokrotne wywołanie create_work_order(equipment_id="P-042", type="inspection") tworzy wiele zleceń roboczych. Katastrofa przy retry.
Zasada dla systemów produkcyjnych: każde narzędzie wykonujące zapis, mutację lub akcję z efektem ubocznym musi być zabezpieczone przed duplikatami wywołań.
Wzorce zabezpieczeń:
Klucz idempotentności — agent generuje unikalny klucz dla każdego zadania i przesyła go z wywołaniem. Backend rejestruje klucz, a drugie wywołanie z tym samym kluczem zwraca wynik pierwszego bez ponownego wykonania akcji:
def create_work_order(equipment_id, type, idempotency_key):
existing = db.find_by_key(idempotency_key)
if existing:
return existing # Zwracamy poprzedni wynik
result = actually_create_work_order(equipment_id, type)
db.store(idempotency_key, result)
return resultSprawdzenie stanu przed akcją — przed operacją zapisu należy sprawdzić, czy stan już odpowiada celowi. Jeśli zlecenie robocze dla urządzenia P-042 na dziś już istnieje, nie tworzyć nowego. To prostsze niż klucz idempotentności i odpowiednie dla domen biznesowych z naturalną unikalnością.
Jawna dokumentacja w schemacie — każde narzędzie z efektem ubocznym musi mieć w opisie: „Ta akcja jest nieodwracalna / tworzy rekord. Wywołaj tylko gdy masz pewność, że warunki są spełnione." Model, który otrzyma tę informację, działa ostrożniej.
Dlaczego agent wywołuje złe narzędzie — i jak to ograniczyć
Jeden z bardziej tajemniczych problemów: agent konsekwentnie wywołuje tool_A zamiast tool_B, mimo że właściwe jest tool_B. Przyczyn jest kilka i każda wymaga innego rozwiązania.
Zbyt podobne nazwy i opisy. Jeśli macie get_equipment_data i fetch_equipment_info, agent będzie te narzędzia mylił. Rozwiązanie: przejrzyjcie cały zestaw narzędzi i sprawdźcie, czy nazwy i opisy są jednoznacznie rozróżnialne. Jeśli dwóch współpracowników nie potrafi wyjaśnić różnicy bez czytania kodu, model też tego nie potrafi.
Zbyt wiele narzędzi naraz. LLM ma ograniczoną pojemność uwagi. Gdy agentowi podrzucicie 30 narzędzi w jednym prompcie, prawdopodobieństwo błędnego wyboru rośnie. Wzorzec rozwiązania: toolset routing — agent najpierw wybiera grupę narzędzi (np. „narzędzia czujnikowe" vs „narzędzia raportowe"), a następnie z tej grupy konkretne narzędzie. To związane z protokołem MCP — serwery służą jako naturalne grupy narzędzi.
Brak kontekstu w prompcie. Jeśli prompt systemowy nie mówi agentowi, w jakim kontekście działa, model zgaduje odpowiednie narzędzie na podstawie historii konwersacji. Wyraźny kontekst w prompcie systemowym — „Pracujesz w systemie zarządzania produkcją, nie w systemie HR" — zmniejsza błędy wyboru narzędzia.
Stronniczość treningowa modelu. Niektóre modele mają dane treningowe, które faworyzują określone wzorce wywołań. Jeśli agent konsekwentnie wywołuje złe narzędzie mimo prawidłowego schematu, spróbujcie zmienić nazwę narzędzia — czasem nawet to pomaga.
Logika ponownych prób: pięć zasad, które uratują produkcję
Retry jest nieodłączną częścią niezawodnego tool callingu. Ale źle zaimplementowany retry jest gorszy niż brak retry — może zamienić chwilową awarię w kaskadowe niepowodzenie.
Zasada 1: Retry tylko dla błędów przejściowych. HTTP 503 (Service Unavailable) i timeout są przejściowe. HTTP 400 (Bad Request) i 403 (Forbidden) są trwałe — retry nie pomoże, tylko marnujecie zasoby.
Zasada 2: Wykładniczy backoff z jitter. Stała przerwa (retry co sekundę) przeciąży system podczas awarii. Wykładniczy backoff z losowym jitterem (±20 %) rozkłada obciążenie:
delay = min(base_delay * (2 ** attempt) + random.uniform(0, 0.3), max_delay)Zasada 3: Bezwzględny limit prób. Trzy próby zazwyczaj wystarczają. Pięć to maksimum. Więcej retries → większe ryzyko retry storm (wszyscy agenci jednocześnie ponawiają wywołania do niedostępnego systemu).
Zasada 4: Circuit breaker dla powtarzających się awarii. Jeśli dane narzędzie zawodzi wielokrotnie z rzędu, należy je tymczasowo „odłączyć" i zwracać wszystkie kolejne wywołania natychmiast jako CIRCUIT_OPEN. Po okresie cooldown puścić jedno testowe wywołanie. Chroni to również Państwa systemy backendowe.
Zasada 5: Akcja fallback. Gdy retry i circuit breaker zawiodą — co agent robi? Eskaluje do ludzkiego operatora, kontynuuje bez tej informacji czy zatrzymuje się? Każde krytyczne narzędzie musi mieć zdefiniowaną strategię fallback. Bez tego agent jest nieprzewidywalny przy awarii — co jest bezpośrednią motywacją dla HITL gates.
Testowanie tool callingu przed produkcją
Niezawodności tool callingu nie można zweryfikować wyłącznie ręcznym testowaniem happy path. Kilka wzorców testowych, które zalecamy:
Mock toolset testing — stwórzcie mockowe wersje wszystkich narzędzi, zwracające różne kombinacje odpowiedzi: sukces, błąd przejściowy, błąd trwały, nieprawidłowe dane wyjściowe. Uruchomcie agenta na tych mockach i obserwujcie zachowanie.
Testy graniczne argumentów — ręcznie testujcie, jakie argumenty model generuje dla granicznych danych wejściowych: pusty ciąg znaków, wartości null, skrajne liczby, daty z przeszłości/przyszłości. Schemat musi te przypadki wyraźnie wykluczać, inaczej agent będzie się na nich regularnie wywalał.
Chaos testing — z określonym prawdopodobieństwem (np. 20 %) wstrzykujcie błąd do narzędzia podczas testu. Sprawdza, czy logika retry i zachowanie fallback działają w praktyce, a nie tylko w testach jednostkowych.
Przegląd śladów — przy wdrożeniu produkcyjnym niezbędna jest obserwowalność i tracing. Każde wywołanie narzędzia musi być rejestrowane z argumentami, wynikiem, latencją i ewentualnym błędem. Bez tego przy incydencie produkcyjnym nie wiadomo nawet, gdzie szukać problemu.
Najczęstsze pytania
Ile narzędzi może mieć agent naraz?
Z praktyki: powyżej 15–20 narzędzi w jednym kontekście niezawodność wyboru wyraźnie spada. Rozwiązaniem jest toolset routing lub serwery MCP — agent najpierw wybiera właściwą grupę narzędzi, a następnie konkretne narzędzie z tej grupy. Jeśli macie 50+ narzędzi, hierarchiczna nawigacja to konieczność, nie optymalizacja.
Co zrobić, gdy agent generuje halucynowane argumenty — wartości, które nie istnieją?
Dwa kroki: po pierwsze, schemat musi wyraźnie pokazywać format prawidłowych wartości wraz z przykładami. Po drugie, walidacja musi sprawdzać istnienie wskazanych encji (np. sensor_id względem rejestru) i zwracać strukturalny błąd z podpowiedzią, jak znaleźć prawidłowe wartości. Dodatkowo: narzędzie list_available_X() (np. list_sensors()) pomaga agentowi samodzielnie wyszukać prawidłowe ID bez halucynacji.
Czy bezpieczne jest pozwolenie agentowi na automatyczne wykonywanie nieodwracalnych akcji?
Zależy od kontekstu. Dla akcji niskiego ryzyka (odczyt danych, generowanie raportów) pełna automatyzacja jest w porządku. Dla akcji nieodwracalnych (tworzenie zamówień, modyfikacja konfiguracji, wysyłanie wiadomości) zalecamy jawną bramkę HITL — agent proponuje akcję, ludzki operator zatwierdza. EU AI Act od sierpnia 2026 wymaga nadzoru ludzkiego dla systemów AI wysokiego ryzyka — co bezpośrednio implikuje HITL dla akcji z potencjalnie poważnymi konsekwencjami.
Jaki jest najszybszy sposób na zmniejszenie wskaźnika błędnych wywołań?
Z praktyki największy efekt daje naprawa schematów narzędzi — konkretnie rozróżniające opisy, przykłady prawidłowych wartości i wyraźne negatywne ograniczenie („tego nie używaj do…"). Drugi duży efekt daje zmniejszenie liczby narzędzi w jednym kontekście. Obie zmiany nie wymagają modyfikacji modelu ani architektury.
Jak monitorować jakość tool callingu w produkcji?
Rejestrujcie każde wywołanie z: timestamp, tool_name, argumenty (bez PII), status (ok/error), typ błędu, latencja, numer próby. Śledźcie wskaźnik błędów per narzędzie — jeśli jedno narzędzie ma trzykrotnie wyższy wskaźnik błędów od średniej, to sygnał do audytu schematu. Narzędzia takie jak Langfuse (self-hostable) lub LangSmith umożliwiają to bez własnej infrastruktury obserwowalności.
Podsumowanie
*Tool calling to miejsce, w którym zaczyna się większość problemów z niezawodnością agentów — a zarazem miejsce, w którym można je rozwiązać czysto na poziomie inżynieryjnym, bez zmiany modelu. Dobrze zaprojektowane schematy, walidacja argumentów, strukturalna obsługa błędów i ograniczona logika ponownych prób zamieniają agenta z nieprzewidywalnego systemu w kontrolowane narzędzie produkcyjne. Jeśli planują Państwo wdrożenie agenta do produkcji lub właśnie rozwiązują problemy z niezawodnością istniejącego systemu, chętnie ocenimy Państwa konkretny przypadek — zaprojektujemy narzędzia, walidacje i obsługę błędów dostosowaną do Państwa środowiska.*
