Function calling: pozwól modelowi używać twoich narzędzi
Function calling pozwala LLM-owi poprosić twój kod o uruchomienie funkcji i skorzystanie z wyniku — model, który umiał tylko mówić, zaczyna wyszukiwać dane i działać. Szczegółowo o tym, jak to naprawdę działa, dlaczego to po prostu ustrukturyzowany output z pętlą dookoła i dlaczego to ty w pełni kontrolujesz, co się faktycznie uruchamia.
Zwykły LLM ma twardy limit: potrafi jedynie generować tekst na podstawie tego, co już wie. Nie sprawdzi
dzisiejszego statusu zamówienia, nie odpyta twojej bazy danych, nie zajrzy do bieżących stanów
magazynowych — potrafi tylko mówić. Function calling (OpenAI udostępniło tę funkcję na początku roku
i od tego czasu ją dopracowuje) znosi ten limit w konkretny, kontrolowany sposób: opisujesz modelowi
swoje funkcje, a gdy odpowiedź na pytanie wymaga którejś z nich, model zamiast prozy zwraca prośbę —
„wywołaj get_order_status z order_id 1234”. Twój kod uruchamia funkcję, oddaje wynik, a model na
jego podstawie formułuje odpowiedź. Model, który umiał tylko mówić, zaczyna wyszukiwać informacje i
działać. Rozłóżmy to na części, bo pod modnym hasłem kryje się mechanizm prostszy i bardziej znajomy,
niż się wydaje — a najważniejsza jest w nim właśnie kwestia kontroli.
Problem, który rozwiązuje
Warto ten limit nazwać wprost, bo to on jest całą motywacją. Wiedza LLM-a jest zamrożona w momencie treningu i ograniczona do danych treningowych. Model nie zna danych twojej aplikacji, nie wie nic o tym, co wydarzyło się po treningu, i nie potrafi wykonać żadnej akcji w świecie. Na pytania „jaki jest status mojego zamówienia?” albo „ile sztuk mamy na stanie?” sam model nie odpowie — odpowiedź znajduje się w twoich systemach, do których nie ma dostępu. Function calling jest mostem: ustrukturyzowanym sposobem, w jaki model może powiedzieć „żeby na to odpowiedzieć, potrzebuję, żebyś uruchomił tę funkcję i podał mi wynik”.
Jak to naprawdę działa
Mechanizm to pętla — i jest w nim mniej magii, niż sugeruje otoczka. Krok po kroku:
- Opisujesz modelowi swoje funkcje — nazwę każdej z nich, co robi i jakie przyjmuje parametry, w formie ustrukturyzowanego schematu. W ten sposób mówisz modelowi, jakimi narzędziami dysponuje.
- Wysyłasz pytanie użytkownika razem z tymi opisami.
- Model decyduje. Jeśli może odpowiedzieć od razu, zwraca tekst jak zwykle. Jeśli odpowiedź wymaga którejś z twoich funkcji, zwraca zamiast tego ustrukturyzowaną prośbę: nazwę funkcji i argumenty, których potrzebuje — wydobyte z pytania użytkownika i ujęte w kształt parametrów, który opisałeś.
- Twój kod uruchamia funkcję. To zwykły kod, który sam napisałeś — zapytanie do bazy, wywołanie API, cokolwiek. Model niczego nie uruchamia; on tylko prosi.
- Odsyłasz wynik do modelu, który ma już brakujące dane i formułuje z nich odpowiedź w języku naturalnym.
functions = [{
name: "get_order_status",
description: "Sprawdź aktualny status zamówienia po jego ID",
parameters: {
type: "object",
properties: { order_id: { type: "integer" } },
required: ["order_id"]
}
}]
# Model, zapytany "gdzie jest zamówienie 1234?", odpowiada:
# { name: "get_order_status", arguments: { order_id: 1234 } }
# Uruchamiasz SWÓJ kod:
status = Order.find(args[:order_id]).status
# ...i odsyłasz `status` do modelu, żeby sformułował odpowiedź.
I to cała konstrukcja. Zadanie modelu to (a) rozpoznać, że potrzebna jest funkcja, i (b) wydobyć właściwe argumenty z nieuporządkowanego pytania w języku naturalnym i ująć je w czystą, ustrukturyzowaną formę. Twoje zadanie to faktycznie ją wykonać i zwrócić wynik. Dwóch współpracowników i jasny podział pracy.
Ustrukturyzowany output z pętlą dookoła
Oto obserwacja, która odziera to z magii — i łączy się wprost z tym, co już omawialiśmy: function calling to ustrukturyzowany output plus pętla wykonania. Model nie robi niczego fundamentalnie nowego — zamiast prozy produkuje ustrukturyzowany JSON (nazwę funkcji i otypowane argumenty), czyli korzysta dokładnie z tej samej zdolności „każ modelowi zwrócić konkretny kształt”, na której opieraliśmy się przy klasyfikacji i ekstrakcji danych. Nowa jest wyłącznie pętla dookoła: bierzesz tę ustrukturyzowaną prośbę, uruchamiasz prawdziwy kod i odsyłasz wynik na kolejną rundę. Z tej perspektywy function calling nie jest egzotyczną zdolnością doszytą do modelu; to znany ci już mechanizm ustrukturyzowanego outputu, wpięty w cykl wywołania i zwrotu z twoim własnym kodem. Takie ujęcie sprawia, że budowanie na tym przestaje onieśmielać.
To ty decydujesz, co się uruchamia
Najważniejsza właściwość — ta, która powinna kształtować twoje myślenie o całym mechanizmie — brzmi:
model niczego nie wykonuje. On tylko prosi. Kiedy model „wywołuje” get_order_status, po jego
stronie nic się nie uruchamia; model zgłasza prośbę, a twój kod decyduje, czy i jak ją spełnić. Ten
punkt kontroli to miejsce na całą twoją inżynierską ostrożność:
- Walidujesz argumenty, zanim na ich podstawie cokolwiek zrobisz. Model wydobył
order_id: 1234z tekstu użytkownika — potraktuj to dokładnie jak każde dane wejściowe od użytkownika: zwaliduj, sprawdź typy, nie ufaj w ciemno. Argument wydobyty przez model to niezaufane wejście, kropka. - Egzekwujesz autoryzację. To, że model poprosił o
get_order_statusdla zamówienia 1234, nie znaczy, że ten użytkownik może to zamówienie zobaczyć. Twój kod stosuje te same kontrole dostępu co zawsze — prośba modelu nie omija autoryzacji, bo prośba modelu niczego nie robi, dopóki twój kod jej nie obsłuży. - Decydujesz, co jest w menu. Model może prosić tylko o funkcje, które mu opisałeś. Sam wybierasz, co udostępnić — i odczyty udostępnisz znacznie chętniej niż cokolwiek destrukcyjnego. Funkcja, która zmienia albo usuwa dane, uruchamiana na prośbę modelu, zasługuje na szczególną ostrożność — najlepiej na krok potwierdzenia, nigdy na ślepe wykonanie.
To jest część, którą szum medialny pomija, a która naprawdę decyduje o odpowiedzialnym budowaniu. Function calling nie wręcza modelowi kluczy do twoich systemów. Daje modelowi sposób, żeby poprosić, a każdą realną decyzję — walidację, autoryzację, wykonanie albo odmowę — zostawia w twoim kodzie, tam gdzie jej miejsce. Traktuj prośbę modelu o wywołanie funkcji dokładnie tak, jak żądanie HTTP od niezaufanego klienta: jako dane do sprawdzenia, a nie rozkaz do wykonania.
Do czego się nadaje i na co uważać
Function calling błyszczy przy wyszukiwaniu: odpowiadaniu na pytania na podstawie żywych danych, do których model sam by nie dotarł — status zamówienia, szczegóły konta, bieżące stany magazynowe, wszystko, co mieszka w twoich systemach. To także fundament ambitniejszych wzorców „agentowych”, w których model wykonuje kolejne wywołania narzędzi, dążąc do celu — ale to znacznie szerszy temat z własnymi pułapkami. Kilka praktycznych przestróg:
- Każda runda to osobne wywołanie modelu — wolne i płatne, dokładnie w tym modelu kosztów, o którym mówiliśmy przez cały cykl. Jedna interakcja z function calling może oznaczać kilka wywołań modelu (prośba, potem odpowiedź), więc opóźnienia i koszty się sumują. Uwzględnij to w budżecie.
- Model może wybrać złą funkcję albo złe argumenty. To probabilistyczny ekstraktor, a nie mechanizm z gwarancją poprawności. Waliduj i projektuj tak, żeby błędny strzał kończył się bezpiecznie, a nie szkodą.
- Utrzymuj mały i dobrze opisany zestaw funkcji. Przy zbyt wielu mgliście opisanych funkcjach model zaczyna wybierać źle. Jasne nazwy i opisy to — po raz kolejny — rzecz, która daje najwięcej przy najmniejszym wysiłku; to prompt engineering zastosowany do definicji narzędzi.
Werdykt
Function calling znosi twardy limit LLM-a — to, że umie tylko mówić — dając mu ustrukturyzowany sposób, żeby poprosić twój kod o uruchomienie funkcji i zwrócenie wyniku. Model zamrożony w momencie treningu zaczyna sięgać po żywe dane i działać poprzez twoje systemy. Mechanizm to prosta pętla: opisz swoje funkcje, pozwól modelowi zwrócić ustrukturyzowaną prośbę, gdy jakaś jest potrzebna, uruchom swój kod i odeślij wynik. Po odarciu z magii to nic więcej niż znany ci już ustrukturyzowany output owinięty w cykl wywołania i zwrotu — żadna egzotyczna supermoc. A właściwość, która liczy się najbardziej, to że kontrola zostaje po twojej stronie: model niczego nie wykonuje, tylko prosi, więc każdy argument od modelu to niezaufane wejście do walidacji, każda prośba podlega twojej autoryzacji, a ty decydujesz, jakie funkcje w ogóle są w menu — odczyty udostępniasz swobodnie, a wszystko destrukcyjne chowasz za prawdziwymi zabezpieczeniami. Używaj go tam, gdzie błyszczy, czyli przy sięganiu po dane, uwzględnij w budżecie dodatkowe rundy i traktuj prośby modelu dokładnie tak, jak dane wejściowe od dowolnego niezaufanego klienta. Stosowany w ten sposób jest naprawdę potężnym mostem od modelu, który umie tylko mówić, do takiego, który potrafi realnie pomóc — i to bez oddawania mu kluczy.