Этот мини-проект, над которым мы собираемся работать, по сути представляет собой чат-бот на основе GPT, который может отвечать на вопросы, которых нет в знаниях GPT, например, о событиях, вышедших за рамки 2021 года, личных документах, которые не доступны публично, или продуктах. -конкретная информация.

Например, вы можете передать ему внутренние правила или кодекс поведения вашей компании, чтобы ваши сотрудники могли легко спрашивать о них, не прочитывая весь документ только для того, чтобы получить ответ.

Инструменты и фреймворки

Вот инструменты и фреймворки, которые мы собираемся использовать:

  • Язык: Python
  • ChromaDB — векторная база данных с открытым исходным кодом, которую вы можете разместить на своем компьютере.
  • Вы также можете использовать другие векторные базы данных, такие как Pinecone, Milvus и т. д. Но для простоты я буду использовать временное хранилище Chroma для хранения данных.
  • LangChain — Фреймворк, значительно упрощающий работу с LLM и векторными базами данных. По сути, он действует как мост между языковыми моделями и векторной базой данных. Он также предоставляет множество функций, которые помогают генерировать подсказки и обрабатывать запросы.
  • OpenAI — это будет наш LLM-бот, который преобразует данные, запрошенные из базы данных, в правильные предложения.

Пакеты Python

Вот пакеты Python, которые мы собираемся использовать:

  • python-dotenv
  • langchain
  • tiktoken
  • wikipedia
  • chromadb

ВАЖНОЕ ПРИМЕЧАНИЕ

LangChain все еще находится на ранней стадии разработки и постоянно обновляется новыми функциями. Код, который вы видите в этом проекте, скорее всего, сломается в будущем. Если вы застряли, ознакомьтесь с официальной документацией.

# Install required packages
%pip install -q openai langchain python-dotenv tiktoken chromadb wikipedia
Note: you may need to restart the kernel to use updated packages.

Давайте начнем

Ниже представлены все пакеты, которые нам понадобятся. Вы можете быть сбиты с толку относительно того, что что делает. Не волнуйся. Я также буду импортировать их в нужные ячейки, чтобы вы знали, когда их использовать.

import tiktoken
import os

from dotenv import load_dotenv, find_dotenv
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.chat_models import ChatOpenAI
from langchain.chains import ConversationalRetrievalChain, RetrievalQA
from langchain.document_loaders import WikipediaLoader

Подготовьте окружающую среду

Мы собираемся использовать переменную среды с именем OPENAI_API_KEY. Это совершенно необязательно, и вы можете передать ключ вручную функциям, которым он нужен. Я воспользуюсь этим, чтобы сэкономить время.

Пример файла .env:

OPENAI_API_KEY=your_openai_api_key
import os
from dotenv import load_dotenv, find_dotenv

# Load the environment variables from .env file
# This will set the API key for OpenAI so we don't have to manually enter it later
load_dotenv(find_dotenv(), override=True)
True

load_dotenv() загружает переменные среды из указанного файла. В данном случае он загружается из файла .env, найденного в корневой папке с find_dotenv().

Подготовьте и сохраните текстовые данные в виде векторов

Прежде чем мы сможем попросить бота ответить на наши вопросы, нам необходимо иметь информацию, которую бот будет использовать в первую очередь.

Загрузить текстовые данные

Давайте получим текстовый контент. LangChain поддерживает множество способов загрузки текста от .docx или .pdf до очистки веб-сайтов, таких как Wikipedia или Hacker News.

Ознакомьтесь с загрузчиками документов LangChain, чтобы получить полный список поддерживаемых загрузчиков.

В этом проекте мы будем загружать страницу Apple Inc. в Википедии. Это будет контент, который мы будем хранить в векторной базе данных.

from langchain.document_loaders import WikipediaLoader

# Let's load a Wikipedia page about YouTuber
loader = WikipediaLoader(query='Apple Inc.', lang='en', load_max_docs=30)
data = loader.load()

WikipediaLoader() создает экземпляр загрузчика. Экземпляр загрузчика имеет функцию load(), которая загружает документы Википедии, очищает их и возвращает текстовые данные.

print(len(data))
10

Разделить текст на куски

Чтобы сохранить текстовые данные в виде векторов, нам нужно разделить их на фрагменты.

# Split data into chunks
from langchain.text_splitter import RecursiveCharacterTextSplitter
    
text_splitter = RecursiveCharacterTextSplitter(chunk_size=512, chunk_overlap=0)
chunks = text_splitter.split_documents(data)

RecuriveCharacterTextSplitter разбивает текст на более мелкие фрагменты. Параметр chunk_size — это длина каждого фрагмента. По умолчанию длина каждого фрагмента определяется количеством символов (с использованием len()), но вы можете изменить ее на другие функции, передав функцию в параметр length_function. В этом проекте я собираюсь использовать функцию len по умолчанию. chunk_overlap — насколько текст одного фрагмента должен перекрываться с другим.

Векторное внедрение и хранение данных

Встраивание векторов — это способ превратить данные в векторы, числовое представление фрагментов информации, которые можно хранить в векторных базах данных. Векторы используются для сравнения фрагментов данных. Чем ближе их направления друг к другу, тем больше похожи детали.

Чтобы встроить векторы, нам придется полагаться на модели внедрения. В этом проекте мы будем использовать модель внедрения «Ada v2» от OpenAI.

Встраивание векторов стоит денег, поэтому давайте посчитаем стоимость. Посетите страницу Цены OpenAI для получения дополнительной информации.

# Calculate the number of tokens used for the documents
import tiktoken

# Currently, there are only one embedding model: 'text-embedding-ada-002'
enc = tiktoken.encoding_for_model('text-embedding-ada-002')
token_count = sum([len(enc.encode(page.page_content)) for page in chunks])

# Embedding cost per 1000 tokens as of the time of this writing
embedding_cost = 0.0001
total_cost = (token_count / 1000) * embedding_cost

print(f'Token count: {token_count}')
print(f'Total cost (USD): {total_cost:.6f}')
Token count: 8472
Total cost (USD): 0.000847

Продолжим встраивание текста. Как упоминалось ранее, мы будем использовать модель встраивания текста OpenAI.

LangChain поддерживает множество векторных баз данных, таких как Chroma, Pinecone, Milvus и Weaviate. Для простоты мы будем хранить текстовые данные вместе с их векторными представлениями в базе данных временных векторов Chroma.

from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.vectorstores import Chroma

# Create an OpenAI Embedding model instance
# using the API key from the environment variable OPENAI_API_KEY
embeddings = OpenAIEmbeddings()
# or do this if you want to manually enter your API key
# embeddings = OpenAIEmbeddings(openai_api_key="my-api-key")

# Store the chunks and their vector embeddings with Chroma
# and return a vectorstore object
# This is a transient database, meaning it's not persistent
# and will be gone with the session is closed.
# To make the data persistent, pass in `persist_directory="./chroma_db"`
vectorstore = Chroma.from_documents(chunks, embeddings)

OpenAIEmbeddings создает экземпляр модели внедрения, а Chroma.from_documents использует эту модель для «встраивания» текстовых данных в векторы и сохранения их в базе данных, а затем возвращает вам объект vectorstore, который можно использовать для запроса данных.

Настройка бота вопросов и ответов

Далее давайте настроим бота GPT, которого мы будем использовать, чтобы задавать вопросы о только что сохраненных данных. Мы собираемся использовать GPT-3.5 Turbo в качестве помощника искусственного интеллекта, который будет отвечать на наши вопросы. Вы также можете использовать GPT-4, если ваша учетная запись OpenAI имеет на это право.

from langchain.chat_models import ChatOpenAI
from langchain.chains import ConversationalRetrievalChain, RetrievalQA

# Create a OpenAI LLM instance
# You can also try the GPT-4 model if your account is eligible
llm = ChatOpenAI(model='gpt-3.5-turbo', temperature=0.7)

# Turn the Chroma vectorstore into a retriever to be used in the chain
# You can increase the 'k' value to make it return more results for better context
retriever = vectorstore.as_retriever(search_type='similarity', search_kwargs={'k': 10})

# Create a Q&A chain instance
# Use RetrievalQA instead if you don't want the ability to have a chat history
qa_chain = ConversationalRetrievalChain.from_llm(llm=llm, retriever=retriever)

ChatOpenAI создает экземпляр OpenAI LLM. Это модель чата, которая в основном будет просматривать запрошенные текстовые данные и отвечать на наши вопросы.

ConversationalRetrievalChain.from_llm — это функция LangChain, которая создает экземпляр диалоговой цепочки, который обрабатывает выборку данных из векторного хранилища, создает запрос для LLM и затем возвращает ответ. Он также может сохранять «память» и использовать предыдущие вопросы в качестве контекста для ответа на другие вопросы.

Задавать вопросы

Теперь мы готовы задавать вопросы нашему боту.

# Create an empty list to store chat history
chat_history = []

# Ask the chat bot something
query = "When was Apple founded?"

# Get the result
result = qa_chain({"question": query,
                   "chat_history": chat_history
                  })

# Append the chat history
# The format is (question, answer)
chat_history.append((query, result['answer']))

result['answer']
'Apple was founded on April 1, 1976.'

В этом проекте мы будем хранить историю чата в простом списке. Этот формат для каждого объекта в истории чата — (question, answer).

Мы задаем вопрос, вызывая цепочку (в данном случае qa_chain) и передавая вопрос вместе со списком истории чата. Бот вернет объект результата. Здесь мы записываем ответ в список chat_history.

Как видите, бот способен ответить на наш вопрос. А что, если мы попросим его умножить номер года на 2?

# Ask the chat bot something again
query = "Multiply the year by 2"

# Get the result
result = qa_chain({"question": query,
                   "chat_history": chat_history
                  })

# Append the chat history
chat_history.append((query, result['answer']))

result['answer']
'The year Apple was founded is 1976. When you multiply 1976 by 2, the result is 3952.'

Как видите, даже если мы оставили вопрос расплывчатым, не указав, какой год мы имеем в виду, бот успешно пришел к выводу, что мы имеем в виду год основания Apple, благодаря предыдущему вопросу.

Вот история чата:

chat_history
[('When was Apple founded?', 'Apple was founded on April 1, 1976.'),
 ('Multiply the year by 2',
  'The year Apple was founded is 1976. When you multiply 1976 by 2, the result is 3952.')]

Вот и все! LangChain и векторная база данных очень мощные. Мы можем использовать это со многими вещами, такими как чат-бот для ответов клиентам о ваших продуктах или услугах или бот для ответов на вопросы, касающиеся правил и юридических документов. Возможности безграничны.