Rozmowa z modelem to fajne i przydatne narzędzie. W kilku pierwszych wpisach zwracałem uwagę na to, że przy produkcyjnych aplikacjach wystawienie pola dla użytkownika może być ryzykowne, ponieważ nie wiemy jak model zareaguje i trudno jest skutecznie bronić się przed np przejęciem modelu.
Function calling nie sprawia, że problem znika. Natomiast może niezwykle ułatwić łączenie modelu z różnymi zewnętrznymi usługami, czy po prostu w uzbrojeniu asystenta w narzędzia, które przewidzieliśmy lub których po prostu potrzebujemy.
Jak to działa? Model dostaje zapytanie od użytkownika, w formie prośby, czy polecenia, oraz listę dostępnych funkcji wraz z opisem parametrów wejściowych. W takim przypadku zamiast przygotowywać odpowiedź dla użytkownika, model zwraca informację jaką metodę należy wywołać i z jakimi parametrami. Pozostaje wywołać funkcję, a do użytkownika zwrócić wiadomość o wykonaniu zadania. Spróbujmy wykorzystac ten mechanizm robiąc coś praktycznego. Niedawno do slacka podłączyłem wirtualnego aystenta Iris. Rozwiązanie było bardzo prymitywne i podstawowe, bo jedyne co robiło to wysyłało wiadomość z minimalnym opisem roli asystenta wprost do GPT-4, a odpowiedź była wysyłana jako wiadomość na slacku. Model nie otrzymywał nawet historii wiadomości, aby zachować charakter rozmowy. Function calling nie rozwiąże tego problemu, zapewne zajmę się nim w przyszłości. Dajmy jednak Iris jakieś inne przydatne możliwości / narzędzia.
Krok 0. Założenia
Powiedzmy, że chciałbym, aby mój asystent potrafił mi podać aktualną pogodę w dowolnym mieście. Z oczywistych względów obecnie GPT nie jest w stanie odpowiedzieć na to pytanie. Ale czy na pewno? Sprawdźmy jak poradzi sobie chatGPT:
Otrzymałem odpowiedź. Trwało to bardzo długo, bo najpierw wywoływany był Bing, a dopiero potem serwis pogodowy. Jest to nowość w chatGPT, który bez konieczności używania pluginów potrafi sięgać do internetu. No to sprawdźmy, czy możemy zrobić to samodzielnie i uzbroić Iris w podobną funkcję.
Krok 1. Rozpoznanie akcji – function calling
Tym razem od razu zaczynamy od głównego tematu. Dotychczas, gdy Iris otrzymywała wiadomość od razu wywoływała gpt-4 i zwracała odpowiedź. Tym razem będzie inaczej. Pierwsze zapytanie pójdzie do gpt-3.5-turbo, w celu rozpoznania akcji do wykonania.
$response = $this->AIChat->chat( [ 'model' => 'gpt-3.5-turbo-1106', 'messages' => [ [ 'role' => 'system', 'content' => 'Determine function to run based on question. Don\'t answer the question.' ], [ 'role' => 'user', 'content' => $question ], ], 'tools' => $this->chatTools->getFunctionsDescriptions() ] ); return $response->choices[0]->message->toolCalls;
Zapytanie do modelu wygląda dosyć standardowo. Największą różnicą jest węzeł tools
, którego nie było wcześniej. Zawiera on następujący opis dostępnych funkcji:
public function getFunctionsDescriptions(): array { return [ [ 'type' => 'function', 'function' => [ 'name' => 'getCurrentWeather', 'description' => 'Get current weather based on city name', 'parameters' => [ 'type' => 'object', 'properties' => [ 'city' => [ 'type' => 'string', 'description' => 'City name eg. Warsaw, Paris, Opole' ] ], 'required' => [ 'city' ] ] ] ], [ 'type' => 'function', 'function' => [ 'name' => 'answerFromGeneralKnowledge', 'description' => 'Answer from general knowledge', 'parameters' => [ 'type' => 'object', 'properties' => [ 'question' => [ 'type' => 'string', 'description' => 'question' ] ], 'required' => [ 'question' ] ] ] ], ]; }
Znajduje się opis dwóch funkcji dostępnych dla modelu. Jego zadaniem jest zwrócić informację o tym jakie funkcje może wywołać i jakie parametry te funkcje przyjmują. W odpowiedzi modelu znajdziemy specjalny węzeł toolCalls
, który będzie zawierał te informacje.
Następnie wystarczy wywołać tę metodę w swoim kodzie. Oprócz generalnej funkcji do znalezienia odpowiedzi dodałem metodę do pobierania pogody.
Krok 2. Pobranie pogody
W celu pobierania pogody wykorzystam WeatherAPI. Przygotowuję prostą metodę:
$cityName = json_decode($weatherJsonCity)->city; $url = 'https://api.weatherapi.com/v1/current.json?key=' . $this->weatherApiKey . '&q=' . $cityName . '&aqi=no'; $client = HttpClient::create(); $response = $client->request('GET', $url); $weatherJson = $response->getContent(); $weather = json_decode($weatherJson); $dataForModel = [ 'last_updated' => $weather->current->last_updated, 'temp_c' => $weather->current->temp_c, 'condition_text' => $weather->current->condition->text, 'pressure_mb' => $weather->current->pressure_mb, 'humidity' => $weather->current->humidity, ]; $response = $this->AIChat->chat( [ 'model' => 'gpt-3.5-turbo-1106', 'messages' => [ ['role' => 'system', 'content' => 'Prepare short information about current weather based on provided data. Message will be directly send to slack. ### Weather Data ###' . json_encode($dataForModel)], ['role' => 'user', 'content' => $originalQuestion], ], ] ); return $response->choices[0]->message->content ?? "Sorry, I was unable to get the weather for $cityName.";
Metoda od razu po pobraniu danych wysyła je do modelu GPT w celu przygotowania na jej podstawie odpowiedzi dla użytkownika.
Krok 3. Testy
Zapytajmy więc Iris o pogodę.
Zadałem jej również ogólne pytanie. Jak widać model prawidłowo rozpoznaje funkcję do wykonania i reaguje na input. A co jeśli spróbujemy połączyć i jednocześnie zapytać o pogodę i wiedzę ogólną?
Co prawda model nie do końca zrozumiał moje intencje, ale jakoś z tego wybrnął (myślę, że można mu wybaczyć, bo to tylko gpt-3.5) i połączył informacje o pogodzie z wiedzą ogólną. Ale jak to się w ogóle stało? Okazuje się, że model potrafi łączyć narzędzia, które posiada i wykorzystywać je równolegle, w celu spójnego przygotowania odpowiedzi:
"toolCalls":[ { "id":"call_uX1gBpVKU0bmnhP7t5ecRYkb", "type":"function", "function":{ "name":"getCurrentWeather", "arguments":"{\"city\": \"Opole\"}" } }, { "id":"call_01dbaIxn4EVCIVR833zFpAwC", "type":"function", "function":{ "name":"answerFromGeneralKnowledge", "arguments":"{\"question\": \"What is a famous AC/DC song?\"}" } } ]
Gdy poznał aktualną pogodę i sławną piosenke AC/DC był już w stanie połączyć te dwie informacje i wygenerować spójną odpowiedź.