Podstawy
2 godz. 43 min · OpenAI · Full-stack i Programowanie
Adam GospodarczykChociaż dokładne działanie dużych modeli językowych nie jest zrozumiałe nawet dla twórców tych narzędzi, to i tak istnieje szereg dobrze znanych faktów. Zapoznanie się z nimi pozwoli Ci lepiej wyobrazić sobie sposób działania LLM bez konieczności wchodzenia w głębokie detale techniczne. To z kolei w sposób bezpośredni przełoży się na skuteczność projektowanych promptów. W pierwszej części kursu skupiamy na wysokopoziomowym spojrzeniu na modele firmy OpenAI, uwzględniając ich aktualne możliwości oraz aktualne ograniczenia.
Instrukcje kierowane do modeli takich jak GPT-3.5-Turbo czy GPT-4, mogą realizować najróżniejsze zadania. Pomimo tego ich ogólna struktura uwzględnia pewne schematy z których możemy korzystać. W zależności od celu jaki chcemy osiągnąć, możemy uwzględniać lub pomijać poszczególne elementy promptu. I chociaż wspomniane modele są w stanie świetnie radzić sobie z nieustrukturyzowanymi danymi, to w przypadku projektowania instrukcji, bardzo istotne jest zachowanie precyzji oraz wyraźne oddzielenie jej fragmentów. Na praktycznych przykładach zobaczysz, jak łatwo niewłaściwe formatowanie może negatywnie wpływać na generowaną odpowiedź. Poznasz także ogólne zasady, które pomogą Ci kształtować instrukcje w sposób, który zwiększy prawdopodobieństwo wygenerowania odpowiedzi, której oczekujesz.
Zero-shot, Few-shot, Chain of Thought czy Tree of Thoughts — to techniki projektowania promptów, dzięki którym znacząco zwiększysz ich skuteczność. I choć każda z nich zwykle opiera się o bardzo proste koncepcje, tak ich praktyczne wykorzystanie nie zawsze jest oczywiste. W tym kursie nie tylko poznasz przykłady ich zastosowania, ale także będziesz w stanie połączyć je z ogólnymi zasadami tworzenia promptów, co jeszcze bardziej ulepszy tworzone przez Ciebie instrukcje. Ciekawostka: Technika Tree of Thoughts w niektórych przypadkach zwiększa skuteczność GPT-4 z 4% do nawet 74%.
Ostatnia część kursu uwzględnia kilka przykładów, których celem jest pokazanie Ci szerokiego kontekstu w którym umiejętność projektowania promptów, umożliwia wyjście daleko poza zwykłe odpowiadanie na pytania i generowanie tekstu. Zobaczysz, jak tworzone przez Ciebie instrukcje mogą zostać wykorzystane w aplikacjach webowych oraz systemach uwzględniających realizujących złożone zadania z pomocą dużych modeli językowych. Mowa tutaj o pamięci długoterminowej, embeddingu danych oraz nawet autonomicznych agentach, zdolnych do posługiwania się narzędziami. Pamiętaj jednak, że wszystkie z wymienionych tematów zostały omówione tylko z szerokiej perspektywy, ponieważ ich faktyczna implementacja, wykracza poza zakres tego kursu.
Ten kurs został zaprojektowany z myślą o osobach chcących rozpocząć naukę projektowania promptów dla dużych modeli językowych, takich jak GPT-3.5-Turbo czy GPT-4. Wcześniejsze doświadczenie oraz zaawansowane umiejętności techniczne czy programistyczne nie są wymagane. Wiedza z tego kursu da Ci podstawy do dalszej nauki projektowania promptów oraz pozwoli Ci uzyskać szerszą perspektywę na temat roli promptów przy codziennej pracy z narzędziami takimi jak ChatGPT czy integracjami dużych modeli językowych z aplikacjami.
W tej lekcji chciałbym, abyśmy skupili się na drugiej części naszego promptu, którą
w tym przypadku stanowi kontekst oraz podsumowanie.
Niezależnie od tego, jakie sekcje będą występować w Twoim prompcie, ale jeżeli
projektujesz prompt na potrzeby chatbota bądź np.
jakiegoś narzędzia, które posługuje się
zewnętrzną wiedzą, no to takie sekcje będą odgrywały istotne znaczenie.
Rzecz w tym, że aktualnie ich treść została wpisana przeze mnie ręcznie, a to
oznacza, że w trakcie konwersacji nie ulega zmianie.
Naturalnie mógłbym tutaj przywitać się, a
następnie wykonać to zapytanie i w kolejnym kroku zaktualizować nasz kontekst
oraz podsumowanie, aby kontynuować rozmowę.
Nie o to w tym jednak chodzi, ponieważ
oczywiście w ramach testów moglibyśmy aktualizować te informacje ręcznie.
Natomiast w przypadku aplikacji
praktycznie zawsze taki kontekst wczytywane jest dynamicznie.
Wówczas dość istotnym problemem jest
zachowanie balansu pomiędzy tym, jak długa jest rozmowa, a tym ile informacji
znajduje się w kontekście, bądź też w podsumowaniu.
Jak już wiemy, jesteśmy ograniczeni około
ośmioma tysiącami tokenów w przypadku modelu GPT-4 i w związku z tym nawet
pomimo tego, że mój prompt jest w miarę zoptymalizowany jeżeli chodzi o liczbę
tokenów, to i tak bardzo szybko może się okazać, że np.
do podsumowania rozmowy będzie trafiało
stopniowo coraz więcej informacji, aż końcu dojdziemy do limitu.
Wówczas kolejne zapytania będą odrzucone, a rozmowa nie będzie możliwa.
No i teraz to wszystko możemy adresować na dwa sposoby.
Pierwsze dotyczy kodu i tego akurat nie będziemy poruszać, aczkolwiek tylko
wspomnę, że do liczenia tokenów wykorzystujemy narzędzia takie jak tiktoken
bądź GPT-3 encoder dla środowiska JavaScript.
My jednak będziemy się teraz skupiać na tym, aby zadbać o długość naszego
kontekstu z punktu widzenia projektowania promptu.
I teraz przykładowo jeżeli chodzi o nasz
kontekst, to tak jak już wspominałem w poprzednich lekcjach, sam kontekst składa
się z pojedynczych, tak zwanych dokumentów, które stanowią skrawki
informacji, na których może pracować nasz asystent.
Takie fragmenty informacji powinny być
oczywiście wcześniej przygotowane na podstawie np.
zestawu informacji, na których chcemy, aby ten asystent pracował.
Przykładem takiego zestawu może być np.
długi dokument PDF.
Jeżeli sami programujemy, to cały proces będziemy realizować samodzielnie.
W przypadku gdy pracujemy z zespołem
programistycznym, to do nas w dużym stopniu będzie należało to, w jaki sposób
opracować te poszczególne dokumenty pod kątem ich długości oraz zawartości.
Obecnie istnieje już dość dużo technik, którymi możemy się posługiwać.
Najprostszą z nich jest pocięcie dużego dokumentu na małe fragmenty względem np.
liczby tokenów.
Rzecz w tym, że jeżeli wyobrazisz sobie jakiś duży dokument, np.
książkę, to w momencie, gdy potniemy ją na małe fragmenty, może się okazać, że
nawet jeżeli wczytamy je do kontekstu, no to asystent nie znajdzie w nich sensownych
informacji do tego, aby udzielić odpowiedzi.
Z tego powodu zwykle poza samą treścią dokumentu dołączamy tzw.
metadane.
W przypadku wspomnianej książki metadanymi może być np.
nazwa rozdziału bądź jakiś rodzaj szerszego kontekstu, o czym jest dana
książka, bądź też nawet krótkie podsumowanie danego rozdziału.
Oczywiście przygotowanie takich danych
wymaga czasu oraz dodatkowo, aby skutecznie wczytywać je do kontekstu,
należy zaprojektować sensowne system wyszukiwania oparty np.
o tzw. bazy wektorowe i wyszukiwanie podobieństw.
To jednak w dużym stopniu już wykracza poza zakres naszego kursu
w związku z tym zapamiętaj tylko tyle, że
w momencie, gdy pracujesz nad tekstem, w pierwszej kolejności musisz zastanowić
się, w jaki sposób podzielić duży zestaw danych na małe dokumenty, a następnie
jakie dodatkowe informacje każdy z tych dokumentów powinien posiadać, aby ułatwić
zarówno ich późniejsze odnalezienie, jak i także aby stanowiły wartość dla asystenta,
który będzie odpowiadał na kolejne pytania.
Naturalnie najlepiej byłoby opracować
takie dokumenty ręcznie lub każdy z nich przynajmniej fizycznie przejrzeć.
W praktyce jednak narzędzia wykorzystujące sztuczną inteligencję pracują na ogromnych
zestawach danych, których nawet przejrzenie ręcznie jest dość wymagające.
Nic jednak nie stoi na przeszkodzie, aby
do przeglądania takich dokumentów również wykorzystywać modele np.
GPT-3,5-Turbo.
W ten sposób możemy na różne sposoby procesować dane, z którymi pracujemy,
sprawiając, by stawały się zdecydowanie bardziej użyteczne oraz abyśmy byli w
stanie automatycznie sklasyfikować bądź natychmiast odrzucić te, które nie będą
nadawać się do wykorzystania w naszej aplikacji.
Na podstawie moich doświadczeń mogę
powiedzieć tutaj tylko tyle, że od jakości kontekstu, który tutaj przygotujemy,
będzie w bardzo dużym stopniu zależała skuteczność odpowiedzi naszego asystenta.
Z tego powodu nie bez powodu mówi się, że
tworzenie aplikacji wykorzystujących AI to w 99 procentach wysiłek związany z
przygotowaniem danych, a cała reszta jest już w zasadzie formalnością.
Tymczasem zanim przejdziemy dalej, chciałbym tylko powiedzieć, że jeżeli
programujesz bądź pracujesz z zespołem programistów, to praktycznie
najważniejszym wyborem można podjąć jest wybór biblioteki bądź też frameworka o
nazwie LangChain, który oferuje zestawy gotowych narzędzi np.
do wczytywania dokumentów, a także do
tego, aby dzielić duże fragmenty tekstu na mniejsze.
Tutaj warto jeszcze dodać, że kilka tygodni temu to narzędzie było na tyle niegotowe,
że nie zdecydowałbym się na jego produkcyjne zastosowanie.
Jednak tempo rozwoju repozytorium dostępnego na GitHubie
jest tak duże, że praktycznie każdego
dnia, gdy odwiedzam tą stronę pojawia się tutaj coś nowego.
W związku z tym praktycznie nie widzę
powodu, aby pisać ręcznie narzędzia takie jak np.
Tekst Splittery.
Oczywiście czasem mogą zdarzyć się
sytuacje, w których opracowane tutaj narzędzia nie będą dla nas wystarczające i
będziemy musieli stworzyć coś całkowicie dostosowanego do swoich potrzeb,
natomiast ja jeszcze nie spotkałem się z takim przypadkiem.
Wracając teraz do naszego promptu, drugą
sekcję w moim przypadku stanowi podsumowanie bieżącej konwersacji.
Chodzi tutaj o to, że w momencie gdy ta dyskusja będzie się rozwijać,no to
naturalnie stopniowo będziemy dochodzić do limitów danego modelu.
Wówczas może się okazać, że po wymianie kilku, kilkunastu bądź kilkudziesięciu
wiadomości dojdziemy do momentu, w którym po prostu wypełnimy Token Window.
Wówczas musimy albo zatrzymać konwersację,
albo zastosować techniki kompresji, które pozwolą na jej kontynuację.
Jedną z takich technik kompresji jest
właśnie podsumowanie, które polega na tym, że po każdym zapytaniu tworzone jest
podsumowanie aktualnej dyskusji i zamiast przechowywać np.
całą historię konwersacji, przechowujemy tylko główną instrukcja systemową oraz
podsumowanie i np dwie lub trzy ostatnie wiadomości.
W ten sposób jesteśmy w stanie kontrolować
przepełnienie się Token Window i tym samym umożliwić znacznie dłuższe konwersacje.
Drugim sposobem, który nie wykorzystuje
podsumowanie, jest tak zwane pływające okno konwersacji.
Chodzi w nim głównie o to, aby utrzymywać
całą historię konwersacji w oryginale, ale w momencie zbliżania się do przekroczenia
Token Window po prostu ucinamy pierwsze wiadomości.
Wówczas doprowadzić to do sytuacji, w której asystent po prostu zapomni początek
naszej rozmowy, natomiast konwersacja będzie mogła być kontynuowana.
Wszystko to, o czym mówię, to pewnego
rodzaju ograniczenia, które musimy brać aktualnie pod uwagę.
Pamiętaj jednak, że tak jak wspominałem już w jednej z poprzednich lekcji,
możliwości modeli nieustannie się rozwijają.
Do tego czasu, jeżeli przyjdzie Ci
implementować takie rozwiązania ponownie, dobrym wyborem będzie tutaj LangChain.
Znajdziemy tutaj gotowe narzędzia, które wykorzystują tzw.
Buffer Window, czyli dokładnie ucinanie
rozmowy po przekroczeniu określonej liczby wiadomości, bądź też ewentualnie
podsumowanie konwersacji, które dzieje się praktycznie automatycznie, a jedyne o co
musimy zadbać, to po prostu o to, aby w szablonie naszego promptu umieścić
odpowiedniej placeholder, w którego miejsce będzie wpisywane podsumowanie.
Jak więc widać, z pomocą narzędzi takich jak LangChain, implementowanie takich
mechanizmów jest stosunkowo proste, a przynajmniej proste się wydaje.
W praktyce jednak może dojść do sytuacji, w której będzie konieczne przeprowadzanie
nieco innej sumaryzacji niż domyślnie będzie to realizował LangChain.
Wówczas to my bierzemy na siebie zadanie
polegające na przygotowaniu odpowiedniego promptu, który będzie skutecznie
podsumowywał rozmowę w kontekście szerszego ujęcia naszej aplikacji.
Zatem podsumowując teraz tą lekcję, jeżeli
chodzi o projektowanie promptu, musisz brać pod uwagę różnego rodzaju dynamiczne
dane oraz samą konwersację w kontekście ograniczeń modelu.
Do tego chciałbym jeszcze dodać, że samo ograniczenie liczby tokenów nie jest
jedyną rzeczą, którą musimy brać pod uwagę.
Przepełnienie naszego promptu nieznaczącymi
informacjami bądź takimi, które mogą wprost wprowadzić asystenta w błąd
jest czymś, co również zdecydowanie powinniśmy brać pod uwagę.
Oczywiście w tym wszystkim istnieje jeszcze szereg detali, takich jak np.
długość pojedynczego dokumentu bądź
znaczniki separatorów, które będziemy tutaj wykorzystywać.
Tak jak jednak już powiedziałem
odpowiedzi na te pytania są bardzo
indywidualne dla zestawu informacji, na których pracujemy.
Czasem będziemy chcieli dołączać tutaj tylko jeden, ale obszerny dokument.
Innym razem będziemy chcieli dołączyć ich więcej, ale zdecydowanie krótszych.
Poza tym pracując np.
z tak zwanym surowym tekstem, czyli po
prostu tekstem, który jest podzielony na mniejsze fragmenty, warto także rozważyć
technikę o nazwie Overlap, polegającą na tym, aby dzielić fragmenty naszego
większego dokumentu w taki sposób, aby się na siebie zazębiały.
Inaczej mówiąc, jeżeli te dwa dokumenty
byłyby ze sobą powiązane, to ten drugi dokument zawierałby np.
końcówkę poprzedniego.
Tutaj również musimy odpowiedzieć sobie na
pytanie, jak bardzo chcemy zazębiać poszczególne fragmenty,
natomiast jest to jedna z dość skutecznych
technik, które umożliwiają łączenie dokumentów.
Do tego warto zaplanować sobie także metadane, które niekoniecznie muszą
pojawiać się w prompcie, ale mogą zostać wykorzystane od strony programistycznej po
to, aby połączyć ze sobą powiązane dokumenty.
Oczywiście jeżeli chodzi o Overlap, tutaj również pomocny okazuje się LangChain,
w związku z tym ponownie nie ma sensu
implementować takiego rozwiązania samodzielnie.
Pomimo jednak tej pomocy, na którą możemy
liczyć ze strony LangChain w praktyce, zaprojektowanie całego
mechanizmu rozmowy i tak pozostaje naszą odpowiedzialnością.
Jeżeli chodzi o tą lekcję, to by było na
tyle, więc dziękuję za uwagę i zapraszam do kolejnej.