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 zobrazować nieco
lepiej to, w jaki sposób mogą funkcjonować Twoje prompty np.
w aplikacji takie jak Chatbot.
Zasady działania takiej aplikacji są dość proste i jego rolą jest udzielenie
odpowiedzi na podstawie naszych własnych danych.
Aby przygotować taką aplikację, zanim
przejdziemy do pisania promptu, w pierwszej kolejności potrzebujemy zebrać
wszystkie dane, na których może pracować taki Chatbot.
Na pierwszym etapie jest to raczej tylko zebranie informacji, najlepiej w jedno
miejsce, tak aby móc na nich wygodnie pracować.
Następnie musimy te dane przejrzeć oraz
przefiltrować te, które nie są pomocne do udzielania odpowiedzi lub z jakiegoś
powodu nie chcemy ich uwzględnić w pamięci naszego Chatbota.
Taki proces może odbywać się w sposób ręczny, półautomatyczny lub nawet w
niektórych sytuacjach w pełni automatyczny.
Niezależnie od wybranej ścieżki, najważniejsze jest tutaj to, aby do
naszego zestawu danych trafiły tylko te, na których rzeczywiście chcemy pracować.
Później mając te dane musimy zadbać o to,
aby podzielić je na mniejsze fragmenty oraz odpowiednio opisać.
Otóż dokładnie tak jak pokazywałem w poprzednich lekcjach, w momencie, gdy te
dane będą wykorzystywane jako kontekst naszego promptu, to po pierwsze jesteśmy
ograniczeni limitem tokenów dla pojedynczego zapytania, a w związku z tym
nie możemy wczytać zbyt dużej liczby takich fragmentów lub też ewentualnie
możemy wczytać ich więcej, ale wówczas każdy z nich musi być po prostu krótszy.
Niestety nie ma jednoznacznej odpowiedzi na to, w jaki sposób powinniśmy dzielić
nasze Chunki ze względu na to, że zależy to mocno od danych, na których pracujemy.
Wyobraź sobie jednak prosty scenariusz, w
którym masz do czynienia z długim wpisem na blogu na jakiś temat.
Jeżeli podzielisz go na wybrane fragmenty, to jeżeli użytkownik zada pytanie, a
następnie do udzielenia odpowiedzi zostanie wykorzystany tylko jeden Chunk,
to może się okazać, że odpowiedź nie jest kompletna.
Jeżeli jednak aplikacja poprawnie zidentyfikuje, że np.
odpowiedź na pytanie użytkownika znajduje się w tych dwóch Chunkach, to może
wykorzystać oba te Chunki do wygenerowania odpowiedzi.
Czasem może zdarzyć się jednak tak, że
użytkownik poprosi o podsumowanie artykułu.
W związku z tym będziemy musieli
uwzględnić wszystkie Chunki, ale już teraz wiemy, że nie jest to możliwe,
ponieważ nie zmieścimy ich w jednym zapytaniu.
W takim scenariuszu do gry wchodzi programowanie oraz np.
zastosowanie LangChain.
Otóż nic nie stoi na przeszkodzie, aby wygenerować odpowiedź pojedynczo dla
poszczególnych Chunków, a następnie na podstawie wygenerowanych odpowiedzi ułożyć
tą docelową, która będzie skierowana do użytkownika.
Innym sposobem może być także
przechodzenie przez Chunki i stopniowe ulepszanie końcowej odpowiedzi.
To, który z tych sposobów sprawdzi się lepiej, zależy już od wybranego przypadku.
Istotne w tym mechanizmie jest jednak to,
żeby po pierwsze poszczególne Chunki były podzielone w taki sposób, aby np.
nie ucinały jakiejś informacji oraz aby metadane, które będą je opisywać zawierały
informacje, które pozwolą je w jakiś sposób odnaleźć np.
taką metadaną może być chociażby adres
URL wskazujący, że te wszystkie Chunki pochodzą z jednego wpisu.
W tym momencie chciałbym tylko zaznaczyć,
że właśnie poruszamy się po bardzo szerokiej perspektywie tego tematu.
W praktyce istnieje tutaj mnóstwo detali,
na które trzeba zwracać uwagę oraz także wiele technik, które mamy do dyspozycji.
W każdym razie w momencie, gdy już przygotujemy nasze Chunki oraz upewnimy się, że
są odpowiednio opisane, to kolejnym krokiem będzie tzw.
Embedding.
Embedding jest to pojęcie, które padało już kilka
razy w naszym kursie, ale tak jak mówiłem wykracza ono poza nasz zakres.
Jeżeli jednak miałbym w skrócie opisać o
co tutaj chodzi, to po prostu mówimy tutaj o procesie, który zamieni dane tekstowe na
ich liczbową reprezentację, która będzie opisywała ich znaczenie.
Inaczej mówiąc, tekst zostanie zamieniony
liczby po to, aby móc zapisać go w tak zwanej bazie wektorowej.
Coś takiego jest wymagane do późniejszego
wyszukiwania poszczególnych Chunków, o czym będziemy sobie mówić za chwilę.
Zatem, aby było to jasne, w momencie, gdy przygotujesz dane i odpowiednio je
opiszesz, konieczne jest wykorzystanie wyspecjalizowanego modelu, który zamieni
tekst na liczby, a następnie wygenerowany Embedding zostanie zapisany w specjalnej bazie,
która później umożliwi nam wyszukiwanie tych informacji.
I teraz w związku z tym, że Embedding to
zestaw liczb, które w żaden sposób nie są czytelne dla użytkownika, konieczne jest
także zapisanie naszych fragmentów w klasycznej bazie danych.
Oczywiście w przypadku niektórych
projektów sama treść Chunku może także zostać uwzględniona jako metadana.
Wówczas ta oryginalna treść również będzie
mogła trafić do bazy wektorowej i tam będziemy mogli na niej pracować.
Ja jednak osobiście przekonałem się o tym,
że często przyjdzie nam wykorzystywać opracowane dane również w inny sposób niż
bezpośrednio w działaniu Chatbota, a w związku z tym posiadanie oddzielnej bazy
danych, która może być, a w zasadzie powinna być połączona z bazą wektorową np.
za pomocą odpowiednich identyfikatorów jest jak najbardziej wskazane.
Zatem jeżeli teraz popatrzymy sobie na cały ten proces,
to wygląda on mniej więcej tak, że w pierwszej kolejności gromadzimy dane,
następnie filtrujemy te, które są nam faktycznie potrzebne,
dzielimy je na małe fragmenty oraz odpowiednio opisujemy, tak aby można je
było później odnaleźć oraz nawet momentami łączyć, a następnie te fragmenty
zamieniamy na Embedding, czyli zestawy liczb opisujących znaczenie tekstu, które mamy
tutaj zapisane, a następnie Embedding ten trafia do bazy wektorowej,
no i w jaki sposób musimy też przechowywać oryginalne treści.
W ten sposób, oczywiście na bardzo uproszczonym schemacie wygląda gromadzenie
danych, które później mogą zostać wykorzystane na potrzeby naszego Chatbota.
No i jeżeli chodzi o samo wykorzystanie, to mamy tutaj następujący schemat.
Po pierwsze mamy pytanie pochodzące od użytkownika.
Takie pytanie również musi zostać zamienione na Embedding, aby móc wykorzystać
ten Embedding do porównania z tymi, które wcześniej zaindeksowaliśmy
i na tej podstawie wykonamy tzw.
Similarity Search, czyli po prostu
odnajdziemy Chunki, które znaczeniem są podobne do pytania użytkownika
i w ten sposób baza wektorowa zwróci nam ich listę.
Następnie taka lista również może zostać
odpowiednio przefiltrowana, po to, aby mogła zostać złożona jako kontekst.
Kontekst ten to nic innego jak kontekst naszego promptu.
Zatem fragment, który piszemy tutaj, to
tak naprawdę dokładnie nasza praca dotycząca projektowania promptu,
natomiast do tego Promptu trafia
dynamicznie wczytywany kontekst i na samym końcu doklejane jest także oryginalne
pytanie użytkownika. Już nie Embedding, ponieważ ten został wykorzystany tylko na
potrzeby wyszukiwania, tylko normalny tekst, który wpisał użytkownik.
Na podstawie tak zbudowanego promptu
możliwe jest wygenerowanie odpowiedzi, która zostaje przesłana do użytkownika.
Oczywiście znowu mamy tutaj do czynienia z
uproszczonym schematem, który nie uwzględnia np.
tego, że możemy mieć tutaj historię konwersacji z użytkownikiem, bądź też
jakieś inne dodatkowe informacje, takie jak np.
podsumowanie rozmowy bądź jakieś metadane opisujące np.
to, w którym miejscu naszego serwisu jest w danej chwili użytkownik.
W każdym razie patrząc teraz na te dwa
schematy widzimy, że przy faktycznym projektowaniu aplikacji faktyczna budowa
promptu jest tylko małym wycinkiem całości.
Coś takiego nie wynika wyłącznie z tego,
jak zbudowałem ten schemat, ale rzeczywiście oddaje stan faktyczny.
W praktyce większość pracy, którą należy wykonać dotyczy odpowiedniego
przygotowania danych, a następnie całego systemu, który będzie odpowiedzialny za
ich odzyskiwanie no i wstrzykiwanie do naszego promptu.
Oczywiście to nie umniejsza samej roli, jaką pełni prompt w tej całej układance
natomiast jak widzisz, mówimy tutaj o elemencie większej układanki.
No i teraz jeżeli chcemy zobaczyć jak coś takiego działa w praktyce, przejdziemy sobie do
Eduweb.pl i na przykład do jednej z lekcji naszego kursu.
Następnie w zakładce Asystent mogę tutaj zadać np.
pytanie o czym jest ta lekcja?
A Chatbot dokładnie przejdzie przez cały
schemat, który pokazałem przed chwilą, a następnie wygeneruje odpowiedź.
No i jak widać uzyskałem odpowiedź dotyczącą bieżącej lekcji, więc Chatbot
poprawnie zidentyfikował w którym miejscu serwisu aktualnie się znajduje, a
następnie dość zwięźle odpowiedział mi na moje pytanie.
Oczywiście mam tutaj możliwość zadawania
kolejnych pytań, natomiast w tym momencie nie będziemy już tego robić.
Zwrócił jednak uwagę, że rzeczywiście mamy tutaj do czynienia z pytaniem użytkownika,
które także zostało zamienione w tle na Embedding następnie zostały tutaj wykorzystane
informację, w której lekcji aktualnie się znajduje
i to wszystko posłużyło do tego, aby odnaleźć wszystkie fragmenty, w tym
przypadku transkryptu oraz opisu naszego kursu, aby wygenerować taką oto odpowiedź.
Z punktu widzenia użytkownika mamy tutaj
bardzo prostą interakcje a z punktu widzenia systemu, szereg różnych kroków, które
musiały zostać podjęte, aby ta odpowiedź została wygenerowana.
Oczywiście nawet nie uwzględniam tutaj ilości pracy, która już teraz została
włożona w przygotowanie danych, na których pracuje nasz asystent.
Jeżeli chodzi o tą lekcję to by było na tyle.
Zatem dziękuję za uwagę i do usłyszenia.