· tutorial · Przeczytasz w 5 min
Jak uzyskać JSONa z GPT-4?
TL;DR
Function Calling to niedawno wprowadzone rozszerzenie do GPT-4 oraz GPT 3.5 dzięki któremu twój model odpowie ci w oczekiwany sposób, a ty unikniesz niespodzianek.
ChatGPT obniżył próg wejścia do świata ML/AI poprzez komunikowanie się z użytkownikiem z wykorzystaniem języka naturalnego. Ta początkowa przewaga okazała się jednak ciężarem dla programistów, którzy chcieli to narzędzie zintegrować ze swoimi aplikacjami, ale nie byli w stanie kontrolować formatu odpowiedzi z modelu. Nowość o nazwie Function Calling
wreszcie rozwiązuje ten problem.
Skuteczna komunikacja
W świecie systemów rozproszonych kluczem do skutecznej komunikacji jest przewidywalny kontrakt pomiędzy poszczególnymi aktorami. Chcemy, aby serwis A produkujący daną wiadomość (porcję danych) mógł być zrozumiany przez serwis B, który tę wiadomość skonsumuje.
Do skutecznej komunikacji potrzebujemy nie tylko danych, ale również formatu w jakim są one przygotowane. Na dzisiaj, formatem z jakim poradzi sobie większość środowisk programistycznych jest JSON, którego wsparcie oferują wszystkie najważniejsze języki programowania oraz ich ekosystemy.
JSON kojarzy się nam ze stosunkowo prostymi regułami formatowania, jednak aby otrzymać dokładnie tę składnię z LLMa trzeba było stosować wiele technik prompt engineeringu, które i tak nie dawały pełnej skuteczności:
Zobacz ten wątek z grudnia 2022
Mówiąc o pełnej skuteczności mam tutaj na myśli wdrożenie systemu opartego o prompty, zintegrowanego z API OpenAI, który będzie oferował stabilne procesowanie JSONa na przestrzeni tygodni lub miesięcy po wdrożeniu. Na przeszkodzie takiego rozwiązania stoi chociażby sama architektura LLMów, gdzie model językowy z definicji odpowiada tokenami wg pewnego stopnia prawdopodobieństwa.
Nowe narzędzia
Przedstawiciele firmy OpenAI deklarują na lewo i prawo, że dostępne dzisiaj modele będą regularnie rozbudowywane, a ich dzisiejsza forma jest jednym z wielu etapów rozwoju tej technologii. Trzeba przyznać, że kolejne wydania GPT-4 i rozszerzenia możliwości samego API zdają się potwierdzać te deklaracje.
W czerwcu tego roku ukazało się jedno z takich rozszerzeń do API o nazwie Function Calling
, które powinno raz na zawsze rozwiązać problem formatowania odpowiedzi bez stosowania kung-fu prompt engineeringu. Nieco myląca jest sama nazwa tego rozszerzenia - nie uzyskujemy bowiem możliwości wywoływania funkcji (R U SERIOUS?), ale możliwość dopasowania odpowiedzi z GPT tak, żeby pasowała do reszty naszego systemu.
Wygląda skomplikowanie? Zaraz wszystko się wyjaśni. Dla ułatwienia dodam, że nazwanie tej nowości Response Formatting
zdecydowanie ułatwiłoby jej zrozumienie wśród społeczności programistów.
Jak korzystać z Function Calling
Nie korzystałeś do tej pory z API firmy OpenAI? Zacznij od obejrzenia tego filmu.
Aby skorzystać z Function Calling, twoje zapytania do Chat Completion API powinny być rozszerzone o conajmniej dwa nowe pola - functions
oraz function_call
. Zgodnie z wyobrażeniem autorów, to pierwsze określa “pulę narzędzi” z jakich ma korzystać model, a to drugie definiuje które narzędzia w tej konkretnej konwersacji wykorzystać.
Podstawowe zapytanie do samego API mogło do tej pory wyglądać tak:
import "dotenv/config";
import { Configuration, OpenAIApi } from "openai";
import { SYSTEM_MESSAGE, USER_MESSAGE } from "./prompts.js";
const configuration = new Configuration({
apiKey: process.env.OPENAI_API_KEY,
});
const openai = new OpenAIApi(configuration);
const completion = await openai.createChatCompletion({
model: "gpt-4",
messages: [
{ role: "system", content: SYSTEM_MESSAGE },
{ role: "user", content: USER_MESSAGE },
],
});
console.log(completion.data.choices[0].message.content);
Poza promptami wydzielonymi do osobnych modułów korzystaliśmy tutaj z biblioteki openai
, która wystawiała klienta do API, oraz biblioteki dotenv
dzięki której wystawialiśmy zmienne środowiskowe z poziomu pliku .env
umieszczonego w samym projekcie.
Od teraz, wraz z dodaniem nowych modeli gpt-4-0613
oraz gpt-3.5-turbo-0613
, do zapytania możesz dodać wspomniane wcześniej pola określające formatowanie odpowiedzi:
{
...,
functions: [MESSAGE_CATEGORY_EXTRACTION],
function_call: { name: MESSAGE_CATEGORY_EXTRACTION.name }
}
Co znajduje się w zmiennej nazwanej przeze mnie MESSAGE_CATEGORY_EXTRACTION
? Jest to opis formatu odpowiedzi, który instruuje GPT-4 do zbudowania konkretnego JSONa zamiast odpowiadania językiem naturalnym.
Sama zmienna wygląda następująco:
// Definicja parametrów wirtualnej funkcji, do których GPT-4 spróbuje dopasować swoją odpowiedź
export const MESSAGE_CATEGORY_EXTRACTION = {
name: 'getMessageCategory',
description: 'Procesowanie wiadomości należącej do jednej z trzech kategorii',
parameters: {(
type: 'object',
properties: {
category: {
type: 'string',
enum: ['Improvement', 'Incident', 'Contribution']
},
keywords: {
type: 'array',
items: {
type: 'string'
}
}
}
}
};
Mamy tutaj definicję obiektu JSON przygotowaną wg JSON Schema z dwoma polami - category
oraz keywords
. Gdybyśmy pracowali w języku TypeScript, powyższy kontrakt pozwoliłby nam uzyskać obiekt pasujący do następującego interfejsu:
interface Response = {
category: 'Improvement' | 'Incident' | 'Contribution',
keywords: string[]
}
Warto dodać, że nazwa getMessageCategory
nie musi wcale odnosić się do żadnej istniejącej funkcji w naszym kodzie! To jedynie znacznik do którego możemy się odwołać budując zapytanie do modelu. Niestety, przez niefortunne nazewnictwo autorów tego rozszerzenia, całość może się kojarzyć z faktycznym wywoływaniem funkcji (co przecież nie następuje w sposób automatyczny).
Na koniec, procesując odpowiedź, musisz jeszcze zaktualizować sposób wyłuskiwania danych.
Zamiast:
completion.data.choices[0].message.content;
…oczekiwany format dostaniesz teraz w polu:
// Gotowe do wrzucenia do funkcji JSON.parse!
completion.data.choices[0].message.function_call.arguments
Samo rozszerzenie ogranicza tak naprawdę kreatywność korzystającego z niego progarmisty. Po zaktualizowaniu modeli i dodaniu dwóch nowych pól o których wspominałem powyżej możecie testować dowolne formaty odpowiedzi wpływające na integrację z waszymi systemami. Mogą to być (ekhm) “funkcje” służące do wysyłania maili, zapisywania komentarzy w bazie albo powiadamiania o wydarzeniach w systemie. Do każdej z tych (ekhm) “funkcji” GPT-4 postara się zbudować odpowiedniego JSONa, którego od teraz nie musicie parsować ręcznie licząc na spójne rezultaty.
Ile to kosztuje?
Poszukując informacji na temat wpływu function calling
na całościowy koszt konwersacji z API miałem początkowo wrażenie, że opis funkcji będzie traktowany niezależnie od samego promptu i okna kontekstowego. Nie miałem racji.
Wg dokumentacji na stronie platform.openai.com definicja funkcji wpływa na całościową ilość wykorzystywanych w konwersacji tokenów, a więc podnosi koszt konwersacji. W praktyce, tworzone funkcje wraz z ich opisami są po prostu wstrzykiwane do wiadomości systemowej, a następnie odpowiednio interpretowane przez sam model.
Jak wskazują autorzy, aby uniknąć dodatkowych kosztów i przekraczania context window
powinniśmy odpowiednio zarządzać opisem parametrów jak i całych funkcji, które dodajemy do naszych zapytań.
Korzyści dla programistów
Function calling
zdecydowanie ułatwia integrowanie modeli firmy OpenAI z aplikacjami i serwisami, które rozwijamy na codzień.
Poza niefortunną nazwą tego dodatku powinniśmy docenić jego opublikowanie - nasze prompty, których używamy w konwersacji z modelem, mogą być teraz znacząco uproszczone, a ich detale związane z formatowaniem zbudowane bardziej przewidywalnie. To wszystko dzięki możliwości określenia kształtu oczekiwanej odpowiedzi, co jak pokazałem wyżej - wcześniej nie było wcale trywialnym zadaniem.
Jeśli chcesz przetestować to rozwiązanie u siebie, to skorzystaj z naszego repozytorium gdzie znajdziesz cały przykład z kodem z tego wpisu.
Zobacz jak wykorzystać Function Calling
obserwując kanał Przeprogramowani:
Źródło: OpenAI