Rozmowa z OpenAI GPT przy użyciu LangChain na temat j‑labs

Marcin Tarka

ChatGPT to superużyteczne narzędzie opierające się o potężne modele GPT-4 i GPT-3.5. Może pomóc w pisaniu, wspierać Cię ogromną bazą wiedzy i oczywiście pomagać w kodowaniu. Ciągle jednak ma znaczącą wadę – ma dostęp do informacji z internetu publikowanych tylko do września 2021 roku i nie był szkolony na pełnej ludzkiej wiedzy. Chcesz stworzyć chatbota opartego na modelu GPT z dostępem do: rzeczywistego źródła wiedzy, niszowych informacji, których nie użyto podczas szkolenia GPT, albo wewnętrznej bazy wiedzy Twojej firmy? Z pomocą przychodzi LangChain. Pokażę Ci, jak zbudować prosty skrypt do dyskusji na temat j‑labs.

Podstawy LangChain

Co to jest LangChain? Jak mówi oficjalna strona internetowa:

LangChain to framework do tworzenia aplikacji wspieranych przez modele językowe. Wierzymy, że największe i zróżnicowane aplikacje nie tylko będą korzystać z modelu językowego, ale również będą:

1. Świadome danych – łączyć model językowy z innymi źródłami danych

2. Agencyjne – pozwalać modelowi językowemu oddziaływać na swoje środowisko

W tym artykule nie będę używał agenta LangChain. Uczynienie dużych modeli językowych (LLM) świadomymi kontekstu danych to jendak dokładnie to, czego potrzebujemy.

  • Proces dostarczania wiedzy do LLM w LangChain jest zarówno interesujący, jak i prosty. Proces ten przebiega następująco:
  • LangChain łączy się ze źródłem zewnętrznej wiedzy. Może to być np. lista plików tekstowych.
  • Następnie dzieli zewnętrzną wiedzę na fragmenty i tworzy z nich osadzenia (jeśli nie wiesz, co to jest osadzenie, polecam artykuł z przewodnika TensorFlow). Na tym etapie użytkownik może wybrać model osadzający.
  • Po obliczeniach LangChain przechowuje wszystkie osadzenia w wektorowej bazie danych. Można używać niektórych popularnych silników open source, takich jak Chroma lub Faiss. Ten punkt kończy fazę przygotowania danych.
  • Po otrzymaniu pytania od użytkownika LangChain szuka odpowiadającej wiedzy w bazie wektorowej i konstruuje ostateczne zapytanie do LLM za pomocą szablonu.

Poniżej znajdziesz jeden z szablonów zaimplementowanych w LangChain. Będzie używany w naszym kodzie.

Użyj poniższych fragmentów kontekstu, aby odpowiedzieć na pytanie na końcu. Jeśli nie znasz odpowiedzi, po prostu powiedz, że nie wiesz, nie próbuj wymyślać odpowiedzi. {kontekst} Pytanie: {pytanie} Pomocna Odpowiedź:

Wygląda bardzo obiecująco, czyż nie? Jak szybko zauważysz, naprawdę dobrze się sprawdza, ale pamiętaj, że ten sposób ma pewne ograniczenia. Głównym problemem jest to, że przekazuje wszystkie dane w jednym zapytaniu. Rozmiar zapytania jest ograniczony – w momencie pisania tego artykułu wynosi 4096 tokenów. LangChain ma pewne funkcje, które starają się zminimalizować tę wadę (np. map_reduce, map_rerank i refine chain types), ale nie są one omówione w tym artykule.

Wymagania wstępne

Istnieje kilka wymagań, które musisz spełnić, aby przejść przez ten samouczek.

  • Najważniejsze to klucz API OpenAI. Możesz go uzyskać, rejestrując się na stronie OpenAI. Po zalogowaniu przejdź do podstrony https://platform.openai.com/ i kliknij ikonę użytkownika w górnym prawym rogu. Następnie wybierz „View API keys”. Potem nastąpi przekierowanie na stronę, gdzie możesz przeglądać i generować klucze API.
  • Kolejnym wymaganiem jest Poetry. To bardzo proste w użyciu narzędzie do pakowania i zarządzania zależnościami dla Pythona. Oczywiście możesz skompilować prezentowany kod, używając zwykłej instalacji Pythona, ale gorąco polecam korzystanie z Poetry.

Tworzenie środowiska pracy

Stwórz nowy folder zawierający konfigurację Poetry, wpisując w terminalu: poetry new jlabs-chat. Teraz nadszedł czas, aby zdefiniować zależności projektu. Otwórz plik pyproject.toml i dodaj poniższe:

[tool.poetry.dependencies]
python = "^3.10"
openai = "^0.27.7"
langchain = "^0.0.189"
transformers = "^4.29.2"
chromadb = "^0.3.26"
tiktoken = "^0.4.0"

Jak wcześniej wspomniałem, jeśli nie planujesz korzystać z Poetry, możesz po prostu zainstalować te biblioteki przy pomocy pip. Jeśli korzystasz z Poetry, wystarczy teraz wywołać poniższe polecenie w folderze projektu:

poetry install

Gotowe? Możesz już uruchomić dowolny skrypt Pythona za pomocą komendy:

poetry run python your_script_name.py

Baza wiedzy

Teraz nadszedł czas, aby zgromadzić wiedzę na dla naszego chata. Dla uproszczenia użyję jednego pliku txt. Ja skopiowałem całą zawartość tekstową z https://www.j-labs.pl i wkleiłem ją do mojego pliku tekstowego, który nazwałem jlabs_pl.txt i przechowuję w folderze projektu. Przykładowy fragment pliku wygląda tak:

Kim jesteśmy?
Jesteśmy niezawodni. Poświęcamy się temu, co robimy, i utrzymujemy wysoką jakość.

Kierujemy się podejściem opartym na zasługach. Budujemy zespół ekspertów w konkretnych dziedzinach, którzy ciągle się rozwijają.

Skupiamy się na technologii. W "laboratorium" Java (stąd nazwa j‑labs!), sprzęt i wyniki idą w parze z rozwojem technologii.

Wiedza jest warta dzielenia się. J-labs zgromadził zaangażowaną społeczność ludzi, których łączy pasja do technologii. Talk4Devs jest festiwalem wiedzy od inżynierów dla inżynierów.
Spotykamy się od 2014 roku.

Podstawowy skrypt

Pierwszą rzeczą, jaką musisz zrobić, to utworzyć skrypt Pythona. To on będzie korzystał z LangChain do prowadzenia rozmowy. Nazwij go conversation_script.py. Kod możesz znaleźć poniżej:

from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.text_splitter import CharacterTextSplitter
from langchain.llms import OpenAI
from langchain.chains import ConversationalRetrievalChain
from langchain.memory import ConversationBufferMemory
from langchain.document_loaders import TextLoader

openai_api_key = '<tu wklej swój klucz api>'

loader = TextLoader("jlabs_pl.txt")
documents = loader.load()

text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=100)
documents = text_splitter.split_documents(documents)

llm = OpenAI(temperature=0, openai_api_key = openai_api_key)
embeddings = OpenAIEmbeddings(openai_api_key = openai_api_key)
vectorstore = Chroma.from_documents(documents, embeddings)
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)

qa = ConversationalRetrievalChain.from_llm(
    llm,
    vectorstore.as_retriever(),
    memory=memory,
    )

try:
    while True:
        query = input("> ")
        result = qa({"question": query})
        print('J-chat:', result['answer'])
except KeyboardInterrupt:
    print('\nBye!')

Przejdźmy przez niego krok po kroku.

  • Po pierwsze, zadeklarowałem zmienną z kluczem API (w jednej z poprzednich sekcji opisałem, jak go uzyskać).
  • Następnie, dzięki TextLoaderowi – prostej klasie do ładowania informacji z plików tekstowych, wczytywany jest nasz dokument z wiedzą.
  • Klasa CharacterTextSplitter służy do przekształcania tekstu o dowolnej długości na serię dokumentów. Dzieli nasz plik tekstowy na kawałki zawierające 1000 znaków. Każdy z nich nakłada się na poprzedni o 100 znaków. Ten krok jest konieczny z powodu ograniczeń wielkości komunikatów, o których wspomniałem wcześniej. Nasz skrypt obejdzie ten problem, dzieląc tekst na kawałki, a następnie przekazując do komunikatu tylko najlepiej dopasowane części.
  • Pora na deklarowanie kluczowych obiektów skryptu, takich jak llm klasy OpenAI reprezentujący model OpenAI GPT LLM, a także osadzenia klasy OpenAIEmbeddings reprezentujące model osadzeń od OpenAI.
  • W kolejnym kroku osadzenia i dokumenty wyprodukowane przez splitter są użyte do utworzenia obiektu vectorstore, który stanowi naszą bazę wektorową. Upraszczając, używana jest wbudowana baza danych Chroma, jednakże LangChain obsługuje również ogromną listę innych baz danych.
  • Następnie deklarowany jest bufor pamięci do przechowywania kontekstu rozmowy. Należy zauważyć, że ten kod nie jest gotowy do użycia w kontekście wielowątkowym, ponieważ zawsze używa „zahardcodowanego” klucza pamięci.
  • Teraz nadszedł czas na zadeklarowanie najważniejszego obiektu w LangChain, a więc łańcucha.

Co to jest łańcuch LangChain?

Definicja z oficjalnej strony internetowej LangChain mówi, jest to strukturalne zestawienie komponentów umożliwiające realizację określonych zadań wysokopoziomowych.

Naszym łańcuchem jest ConversationalRetrievalChain, który jest opracowany w celu rozmów z ludźmi. Ma on zdolność do pobierania wiedzy z bazy wektorowej i zachowuje kontekst. Istnieje jednak wiele innych łańcuchów w LangChain. Są to np.

  • lang math chain – który wykonuje zadania matematyczne wyrażone w języku naturalnym,
  • text moderation chain – który pobiera dane z bazy danych SQL,
  • tagging chain – który może oznaczać dokumenty.

Na dole skryptu, dla ułatwienia, zadeklarowałem prostą pętlę, która oczekuje na wejście i drukuje odpowiedź w terminalu. Można ją zakończyć za pomocą przerwania klawiatury (ctrl+c).

Testowanie i dostosowywanie odpowiedzi

Wszystko gotowe? Pora, aby przeprowadzić krótką rozmowę z naszym j‑chatem.

> Opisz kulturę organizacyjną j‑labs.

j-chat: Kultura organizacyjna j‑labs opiera się na wartościach takich jak rzetelność, podejście oparte na zasługach i technologia. To środowisko, w którym praca przynosi satysfakcję i rozwój osobisty, a współpraca jest inicjowana z ludźmi, którzy podzielają te same przekonania. j‑labs gromadzi również zaangażowaną społeczność ludzi, którzy są zjednoczeni przez swoją pasję do technologii.

> Czy j‑labs to dobre miejsce do pracy?

j-chat: Praca w j‑labs to satysfakcjonujące doświadczenie. Firma została stworzona przez inżynierów dla inżynierów i ceni rzetelność, podejście oparte na zasługach i technologię. Inwestują w ludzi i tworzą środowisko, w którym praca przynosi satysfakcję i rozwój osobisty. Gromadzą zaangażowaną społeczność ludzi, którzy są zjednoczeni przez swoją pasję do technologii. Oferują outsourcing specjalistów, powiększanie personelu IT i powiększanie zespołów IT.

> Fajnie, więc poleciłbyś mi złożyć podanie?

j-chat: Tak, j‑labs to świetna firma dla inżynierów do pracy. Mają zaangażowaną społeczność ludzi, którzy pasjonują się technologią. Skupiają się na inżynierach IT z wąskimi, specjalistycznymi umiejętnościami i kilkuletnim międzynarodowym doświadczeniem. Mają także podejście oparte na zasługach i skupiają się na technologii. Oferują powiększanie zespołów IT, gdzie można współpracować z ich specjalistami, aby dostarczyć kompleksowe rozwiązania.

> Jak mogę więc złożyć podanie?

j-chat: Możesz skontaktować się z j‑labs pod adresem j‑labs@j-labs.pl, aby złożyć podanie.

> Ile pracowników ma j‑labs?

j-chat: Nie wiem

Z jednej strony jest to dość imponujące. Czat generuje zgrabną rozmowę na podstawie tekstów ze strony j‑labs. Z drugiej strony, komunikacja jest dość chłodna. Gdybyśmy sobie wyobrazili firmę IT prowadzącą czat na swojej stronie internetowej, prawdopodobnie ta firma wolałaby, aby formułował on wiadomości tak, jakby był rzeczywistym pracownikiem. Dodatkowo forma, w jakiej nasz czat przyznaje, że nie zna odpowiedzi, jest dość problematyczna, ponieważ nie jest zbyt uprzejma ani informatywna. Co powoduje to zachowanie? Cóż, spójrzmy na szablon rozmowy, który przedstawiłem wcześniej.

Jak dostosować odpowiedzi?

Szablon nie precyzuje, że czat powinien podawać się za pracownika. Ponadto narzuca mu on udzielanie bardzo krótkich odpowiedzi na pytania, gdy nie posiada dokładnej wiedzy. Co zrobić? Warto dostosować szablon do nowych wytycznych:

from langchain import PromptTemplate
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.text_splitter import CharacterTextSplitter
from langchain.llms import OpenAI
from langchain.chains import ConversationalRetrievalChain
from langchain.memory import ConversationBufferMemory
from langchain.document_loaders import TextLoader

openai_api_key = '<tu wklej swój klucz api>'

loader = TextLoader("jlabs_pl.txt")
documents = loader.load()

text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=100)
documents = text_splitter.split_documents(documents)

llm = OpenAI(temperature=0, openai_api_key = openai_api_key)
embeddings = OpenAIEmbeddings(openai_api_key = openai_api_key)
vectorstore = Chroma.from_documents(documents, embeddings)
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)

template = """
Zachowaj się, jak gdybyś był pracownikiem j‑labs, więc pisząc o j‑labs, używaj formy "my" zamiast "oni".
Podawaj ciepłe i entuzjastyczne odpowiedzi.
Użyj poniższych fragmentów kontekstu, aby odpowiedzieć na pytanie na końcu.
Jeśli nie znasz odpowiedzi, po prostu powiedz, że nie wiesz, nie próbuj wymyślać odpowiedzi i zasugeruj skontaktowanie się z j‑labs.

{context}

Pytanie: {question}
Ciepła i entuzjastyczna odpowiedź napisana przez pracownika j‑labs:
"""

qa = ConversationalRetrievalChain.from_llm(
    llm,
    vectorstore.as_retriever(),
    memory=memory,
    combine_docs_chain_kwargs={
        "prompt": PromptTemplate(
            template=template,
            input_variables=["question", "context"],
        ),
    }
    )

try:
    while True:
        query = input("> ")
        result = qa({"question": query})
        print('J-chat:', result['answer'])
except KeyboardInterrupt:
    print('\nBye!')

W przedstawionym wyżej fragmencie przekazałem dostosowany szablon do łańcucha. Określa on, że czat musi prowadzić rozmowę, udając pracownika j‑labs. Trochę też rezygnuje z oczekiwań dotyczących sytuacji, w których czat nie zna odpowiedzi.

Po ponownym uruchomieniu cała rozmowa wygląda teraz tak:

> Opisz kulturę organizacyjną j‑labs.

j-chat: W j‑labs stworzyliśmy środowisko, w którym praca daje nam satysfakcję i rozwój osobisty. Nasza kultura organizacyjna opiera się na wartościach takich jak rzetelność, podejście oparte na zasługach i technologii. Dążymy do zbliżenia partnerów do ich celów biznesowych i ciągłego doskonalenia wiedzy naszych j‑specjalistów. Inwestujemy w ludzi, bo to oni są sercem j‑labs. Tworzymy także zaangażowaną społeczność ludzi, którzy są zjednoczeni przez swoją pasję do technologii. Talks4Devs to festiwal wiedzy od inżynierów dla inżynierów, który organizujemy od 2014 roku.

> Czy j‑labs to dobre miejsce do pracy?

j-chat: W j‑labs tworzymy środowisko, w którym praca daje nam satysfakcję i rozwój osobisty. Jesteśmy rzetelni i oddani temu, co robimy, utrzymując wysoką jakość. Skupiamy się na technologii i inwestujemy w ludzi, bo to oni są sercem j‑labs. Postępujemy zgodnie z podejściem opartym na zasługach i budujemy zespół ekspertów w konkretnych dziedzinach, którzy ciągle się rozwijają. Gromadzimy także zaangażowaną społeczność ludzi, którzy są zjednoczeni przez swoją pasję do technologii. Spotykamy się od 2014 roku i jesteśmy dumni, że dołączyliśmy do największych firm na świecie, którym pomogliśmy w transformacji cyfrowej. Wszystko to sprawia, że j‑labs to wspaniałe miejsce do pracy!

> Fajnie, więc poleciłbyś mi złożyć podanie?

j-chat: Zdecydowanie! Zawsze szukamy utalentowanych inżynierów z pasją do naszego zespołu. Oferujemy świetne środowisko pracy, gdzie możesz rozwijać swoje umiejętności i pracować nad interesującymi projektami. Oferujemy także konkurencyjne wynagrodzenie i pakiet świadczeń. Jeśli więc szukasz świetnej okazji do pracy z zaangażowaną społecznością ludzi, to j‑labs jest miejscem dla Ciebie!

> Jak mogę złożyć podanie?

j-chat: W j‑labs zawsze szukamy utalentowanych inżynierów z pasją do naszego zespołu. Jeśli jesteś zainteresowany/a aplikacją, prześlij nam swoje CV i list motywacyjny na adres j‑labs@j-labs.pl. Czekamy na Twoją wiadomość i chętnie dowiemy się więcej o Twoim doświadczeniu i umiejętnościach.

> Ilu pracowników ma j‑labs?

j-chat: W j‑labs jesteśmy dumni z posiadania zespołu doświadczonych inżynierów i specjalistów, którzy pasjonują się technologią i zobowiązani są do dostarczania najlepszego możliwego produktu naszym partnerom. Obecnie mamy ponad 100 pracowników i zawsze szukamy nowych talentów do naszego zespołu. Jeśli chciałbyś dowiedzieć się więcej o naszym zespole i naszych usługach, nie wahaj się skontaktować z nami pod adresem j‑labs@j-labs.pl.

Jak możesz teraz zauważyć, wygląd rozmowy lepiej wpisuje się w nasz przypadek. Teraz czat udaje, że jest pracownikiem j‑labs. Odpowiedzi są dłuższe i bardziej entuzjastyczne.

Jest także pewien efekt uboczny, który w niektórych przypadkach może być uznany za negatywny. Odpowiadając na ostatnie pytanie, czat zmyślił odpowiedź, która nie wynika bezpośrednio z zapewnionej bazy wiedzy. Ten problem można rozwiązać, dostarczając wiedzy, która bezpośrednio odnosi się do liczby pracowników j‑labs.

Pamiętaj, że pełny kod tej aplikacji możesz znaleźć na moim koncie na GitHubie.

Co uwzględnić, budując rozwiązanie do użytku rzeczywistego?

Pamiętaj, że powyższe przykładowe rozwiązanie jest bardzo uproszczone i działa na bardzo ograniczonej bazie wiedzy. Z tego powodu niektóre wyrażenia powtarzają się często w różnych odpowiedziach.

Niedoskonały jest również używany szablon. Przy eksperymentowaniu na własną rękę możesz na przykład próbować przekonać czat, aby rozmawiał na tematy, o których nie powinien (np. w gotowym do użycia rozwiązaniu czat j‑labs prawdopodobnie nie powinien chętnie rozmawiać o polityce).

Istnieje także wiele aspektów technicznych, które zostały uproszczone. Zwróć uwagę, że zapis rozmowy zawsze używa tego samego klucza pamięci. Kod nie jest więc gotowy do działania w środowiskach wielowątkowych, gdzie potencjalnie musi konwersować z wieloma ludźmi jednocześnie.

Inny aspekt, który można zoptymalizować, to tworzenie wektorowej bazy danych. Przy każdym uruchomieniu skryptu tworzona jest baza danych w pamięci. Wymaga to obliczeń osadzeń, co jest płatną usługą świadczoną przez OpenAI.

LangChain nie ogranicza programistów do implementowania rozwiązań tylko za pomocą API OpenAI, więc koszty można także zredukować, eksperymentując z różnymi osadzeniami i modelami.

Podsumowanie

Ten artykuł jest przewodnikiem, który pokazuje, jak stworzyć chatbota wykorzystującego potęgę modelu GPT od OpenAI z dostępem do zaktualizowanej wiedzy przy użyciu biblioteki LangChain. Wskazuje na ograniczenia obecnych modeli GPT, w szczególności na ich niezdolność do uzyskiwania dostępu do informacji czasu rzeczywistego. LangChain oferuje rozwiązanie tego problemu, pozwalając na połączenie Large Language Models (LLM) z zewnętrznymi źródłami danych.

Tekst przedstawia też podstawy LangChaina. Wyjaśnia, jak połączyć go z zewnętrznymi źródłami wiedzy, stworzyć osadzenia z tej wiedzy, przechowywać je w wektorowej bazie danych i używać inputu użytkownika do pobierania potrzebnej wiedzy z bazy danych.

Samouczek skupia się na stworzeniu conversational retrieval chain, który nadaje się do rozmowy z ludźmi i potrafi pozyskać wiedzę z bazy wektorowej.

Referencje

Poznaj mageek of j‑labs i daj się zadziwić, jak może wyglądać praca z j‑People!

Skontaktuj się z nami