Skip to content
← Wszystkie wpisy
6 min czytania Michał Smykowski

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:

  1. 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.
  2. Wysyłasz pytanie użytkownika razem z tymi opisami.
  3. 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ś.
  4. 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.
  5. 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: 1234 z 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_status dla 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.