Chat Towards Data Science: создание чат-бота с Zilliz Cloud
Эта статья — первая часть серии блога Chat Towards Data Science.
В эпоху генеративного ИИ разработчики используют большие языковые модели (LLM) для создания более интеллектуальных приложений. Однако LLM склонны к галлюцинациям из-за ограниченности своих знаний. Retrieval Augmented Generation (RAG) эффективно решает эту проблему, дополняя LLM внешними знаниями. В моей серии блога Chat Towards Data Science я покажу вам, как создать чат-бота на основе RAG, используя ваш набор данных в качестве основы знаний.
В первой части моей серии блога мы проведем вас через создание чат-бота для веб-сайта Towards Data Science с использованием веб-скрейпинга для создания базы знаний, хранящейся в Zilliz Cloud, полностью управляемом сервисе векторной базы данных, построенном на Milvus.
Сбор веб-данных с помощью BeautifulSoup4
Первый шаг в любом проекте машинного обучения (ML) — собрать необходимые данные. Для этого проекта мы применяем методы веб-скрейпинга, чтобы собрать данные для нашей базы знаний. Мы используем библиотеку requests для получения веб-страниц, а затем применяем BeautifulSoup4 (библиотеку, которая извлекает информацию с веб-страниц) для разбора HTML-информации и извлечения абзацев.
Подготовка BeautifulSoup4 и Requests для веб-скрейпинга
Чтобы начать, установите BeautifulSoup, выполнив pip install beautifulsoup4 sentence-transformers. Для этого раздела нам нужны только два импорта: requests и BeautifulSoup. Затем мы создаем словарь URL-адресов, которые хотим просканировать. В этом примере мы собираем контент только с Towards Data Science, но вы также можете скрейпить другие веб-сайты. Мы получаем данные с каждой страницы архива, используя формат, показанный в коде ниже.
import requests
from bs4 import BeautifulSoup
urls = {
'Towards Data Science': 'https://towardsdatascience.com/archive/{0}/{1:02d}/{2:02d}',
}
Нам также нужны две вспомогательные функции для нашего веб-скрейпинга. Первая функция преобразует номер дня в году в формат месяца и дня. Вторая получает количество хлопков (хлопки показывают поддержку статьи на Medium) из статьи.
Функция преобразования дня относительно проста. Мы жестко задаем количество дней в каждом месяце и используем этот список для выполнения преобразования. Поскольку этот проект веб-скрейпинга явно нацелен на 2023 год, нам не нужно учитывать високосные годы. При желании вы можете изменить ее для других лет.
Функция подсчета хлопков представляет хлопки для статьи на Medium. Некоторые статьи могут получать тысячи хлопков, которые Medium обозначает буквой "K." Поэтому нам нужно учитывать это представление в нашей функции.
# takes the number day of the year and returns month, day
def convert_day(day):
month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
m = 0
d = 0
while day > 0:
d = day
day -= month_days[m]
m += 1
return (m, d)
# converts the claps string
def get_claps(claps_str):
if (claps_str is None) or (claps_str == '') or (claps_str.split is None):
return 0
split = claps_str.split('K')
claps = float(split[0])
claps = int(claps*1000) if len(split) == 2 else int(claps)
return claps
Разбор ответа веб-скрейпинга BeautifulSoup4
Теперь, когда мы настроили необходимые компоненты, мы можем приступить к веб-скрейпингу. В этом разделе мы импортируем дополнительную библиотеку под названием "time.” Я добавил эту библиотеку, потому что во время процесса столкнулся с ошибками 429 (слишком много запросов). Используя библиотеку time, мы можем ввести задержку между отправкой запросов. Кроме того, мы используем библиотеку sentence transformers, чтобы получить нашу модель эмбеддингов, а именно модель MiniLM, с Hugging Face.
Как упоминалось ранее, мы собираем данные только за 2023 год, поэтому устанавливаем год равным 2023. Кроме того, нам нужны данные только с дня 1 (1 января) по день 243 (30 августа). На момент сбора данных был начало сентября. Оглядываясь назад, было бы лучше собрать данные до дня 244, но пока мы продолжим с тем, что я уже сделал. В первой части нашей функции, вплоть до первого вызова time.sleep(), мы настраиваем необходимые компоненты. Мы преобразуем итератор в месяц и день, преобразуем это в строку даты, а затем получаем HTML-ответ с URL архива.
После получения HTML мы разбираем его с помощью BeautifulSoup и ищем элементы div с определенным именем класса (указанным в коде), что означает, что это статья. Затем мы извлекаем заголовок, подзаголовок, URL статьи, количество хлопков, время чтения и количество ответов. После этого мы снова используем requests, чтобы получить статью по URL статьи.
Мы вызываем time.sleep() во второй раз, чтобы завершить второй раздел этого цикла for. На этом этапе мы получили большую часть метаданных, необходимых для каждой статьи. Мы извлекаем каждый абзац и соответствующий ему embedding с помощью нашей модели Hugging Face. Затем мы создаем словарь, содержащий все метаданные для этого конкретного абзаца и статьи.
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("sentence-transformers/all-MiniLM-L12-v2")
import time
# data_batch = []
year = 2023
for i in range(1, 243):
month, day = convert_day(i)
date = '{0}-{1:02d}-{2:02d}'.format(year, month, day)
for publication, url in urls.items():
response = requests.get(url.format(year, month, day), allow_redirects=True)
if not response.url.startswith(url.format(year, month, day)):
continue
time.sleep(8)
page = response.content
soup = BeautifulSoup(page, 'html.parser')
articles = soup.find_all(
"div",
class_="postArticle postArticle--short js-postArticle js-trackPostPresentation js-trackPostScrolls")
for article in articles:
title = article.find("h3", class_="graf--title")
# print(title.contents)
if title is None:
continue
title = str(title.contents[0]).replace(u'\xA0', u' ').replace(u'\u200a', u' ')
# title = title.contents[0]
subtitle = article.find("h4", class_="graf--subtitle")
subtitle = str(subtitle.contents[0]).replace(u'\xA0', u' ').replace(u'\u200a', u' ') if subtitle is not None else ''
article_url = article.find_all("a")[3]['href'].split('?')[0]
try:
claps = get_claps(article.find_all("button")[1].contents[0])
except:
claps = 0
reading_time = article.find("span", class_="readingTime")
reading_time = 0 if reading_time is None else int(reading_time['title'].split(' ')[0])
responses = article.find_all("a")
if len(responses) == 7:
responses = responses[6].contents[0].split(' ')
if len(responses) == 0:
responses = 0
else:
responses = responses[0]
else:
responses = 0
article_res = requests.get(article_url)
time.sleep(8)
soup = bs4.BeautifulSoup(article_res.text)
paragraphs = soup.select('[class*="pw-post-body-paragraph"]')
for i, paragraph in enumerate(paragraphs):
embedding = model.encode(paragraph.text)
data_batch.append({
"_id": f"{article_url}+{i}",
"article_url": article_url,
"title": title,
"subtitle": subtitle,
"claps": claps,
"responses": responses,
"reading_time": reading_time,
"publication": publication,
"date": date,
"paragraph": paragraph.text,
"embedding": embedding
})
Последний шаг — сериализовать наш файл с помощью pickle.
import pickle
filename="TDS_8_30_2023"
with open(f'{filename}.pkl', 'wb') as f:
pickle.dump(data_batch, f)
Как выглядят наши данные?
Всегда полезно визуализировать данные. Ниже показано, как данные выглядят в Zilliz Cloud. Обратите внимание на embeddings, которые представляют вектор. Мы генерируем эти векторные embeddings из текста в следующем разделе.
Загрузка данных из TDS в вашу векторную базу данных
Когда у нас есть данные, следующий шаг — передать их в векторную базу данных. В этом проекте я использовал отдельный notebook для загрузки данных в Zilliz Cloud, полностью управляемый сервис векторной базы данных, построенный на Milvus, вместо того чтобы парсить их с Towards Data Science.
Чтобы вставить наши данные в Zilliz Cloud, мы выполняем следующие шаги:
Подключиться к Zilliz Cloud.
Определить параметры для нашей collection.
Вставить данные в Zilliz Cloud.
Настройка Jupyter Notebook
Выполните команду pip install pymilvus python-dotenv, чтобы настроить наш Jupyter Notebook и запустить процесс загрузки данных. Я, как обычно, использую библиотеку dotenv для управления переменными окружения. Для пакета pymilvus нам нужно импортировать следующие модули:
utility- для проверки статуса ваших collectionsconnections- для подключения к вашему экземпляру MilvusFieldSchema- для определения схемы поляCollectionSchema- для определения схемы collectionDataType- типы данных, хранящиеся в полеCollection- способ доступа к collection
Затем мы открываем данные, которые ранее сериализовали с помощью pickle, извлекаем наши переменные окружения и подключаемся к Zilliz Cloud.
import pickle
import os
from dotenv import load_dotenv
from pymilvus import utility, connections, FieldSchema, CollectionSchema, DataType, Collection
filename="TDS_8_30_2023"
with open(f'{filename}.pkl', 'rb') as f:
data_batch = pickle.load(f)
load_dotenv()
zilliz_uri = os.getenv("ZILLIZ_URI")
zilliz_token = os.getenv("ZILLIZ_TOKEN")
connections.connect(
uri= zilliz_uri,
token= zilliz_token
)
Настройка вашей векторной базы данных Zilliz и загрузка данных
Теперь нам нужно настроить Zilliz Cloud. Мы должны создать collection для хранения и организации данных, которые мы собираем с сайта TDS. Требуются две константы: dimension и collection name. Dimension относится к количеству измерений, которое имеет наш вектор. В этом случае мы используем модель MiniLM с 384 измерениями. Collection name говорит само за себя.
С новой функцией dynamic schema в Milvus мы можем просто определить поля id и embedding для collection, не беспокоясь о количестве других сохраняемых полей или их требуемом типе данных. Однако крайне важно помнить конкретные имена полей, которые мы сохранили, чтобы корректно их извлекать.
В этом примере проекта мы используем функцию dynamic schema, чтобы определить только поля ID и embedding. Затем мы назначаем эти поля схеме, а схему — collection. Далее мы указываем параметры для индекса. В этом случае мы используем инвертированный файловый индекс (IVF) без квантования (FLAT), евклидово расстояние (L2) и 128 центроидов для индекса.
IVF без квантования — самый интуитивно понятный метод индексирования. По сути, мы кластеризуем векторы на 128 секций для запросов. 128 в данном случае — произвольное число. Это гиперпараметр, который мы можем настраивать в зависимости от того, как выглядят наши результаты. Эвристически это хорошая отправная точка, потому что она может обеспечить хорошее разнообразие кластеров, а также снизить начальную сложность запроса. После того как мы определили эти параметры, мы создаем индекс по полю embedding и загружаем collection в память.
DIMENSION=384
COLLECTION_NAME="tds_articles"
fields = [
FieldSchema(name='id', dtype=DataType.VARCHAR, max_length=200, is_primary=True),
FieldSchema(name='embedding', dtype=DataType.FLOAT_VECTOR, dim=DIMENSION)
]
schema = CollectionSchema(fields=fields, enable_dynamic_field=True)
collection = Collection(name=COLLECTION_NAME, schema=schema)
index_params = {
"index_type": "IVF_FLAT",
"metric_type": "L2",
"params": {"nlist": 128},
}
collection.create_index(field_name="embedding", index_params=index_params)
collection.load()
После того как мы определили коллекцию и загрузили ее в память, у нас есть два варианта вставки данных:
Перебор пакета данных в цикле и вставка каждого элемента по отдельности
Вставка данных пакетами
После вставки всех данных важно выполнить flush коллекции, чтобы проиндексировать ее и обеспечить согласованность. Загрузка больших объемов данных может занять некоторое время.
for data in data_batch:
collection.insert([data])
collection.flush()
Запрос сегментов статей TDS
Теперь все настроено и готово к выполнению запросов. В этой статье мы сделаем эту часть простой: векторизуем наш запрос и будем искать ближайшее совпадение в Zilliz Cloud. В следующих статьях мы также будем использовать LlamaIndex и LangChain.
Получите свою модель HuggingFace и настройте запросы к Zilliz
Вам необходимо получить модель эмбеддингов и настроить векторную базу данных для запросов к нашей базе знаний Towards Data Science. Этот шаг требует запуска отдельного ноутбука. Я буду использовать библиотеку dotenv для управления переменными окружения. Кроме того, нам нужно использовать модель MiniLM из Sentence Transformers. Для этого шага вы можете повторно использовать код, приведенный в разделе Web Scraping.
import os
from dotenv import load_dotenv
from pymilvus import connections, Collection
load_dotenv()
zilliz_uri = os.getenv("ZILLIZ_URI")
zilliz_token = os.getenv("ZILLIZ_TOKEN")
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("sentence-transformers/all-MiniLM-L12-v2")
Выполнение запроса векторного поиска
Теперь вам нужно подключиться к вашей векторной базе данных и выполнить поиск. В этом проекте мы подключимся к экземпляру Zilliz Cloud и получим созданную ранее коллекцию tds_articles. Чтобы получить наш запрос, мы предлагаем пользователю ввести свой вопрос о TDS до сентября 2023 года включительно.
Затем мы используем модель эмбеддингов из Hugging Face для кодирования запроса. Этот процесс преобразует вопрос пользователя в 384-мерное векторное представление. Затем мы используем этот закодированный запрос для поиска в векторной базе данных. Во время поиска нам нужно указать поле приблизительных ближайших соседей (anns_field), параметры индекса, желаемый лимит количества результатов поиска и выходные поля, которые мы хотим получить.
Ранее мы использовали функцию динамической схемы Milvus, чтобы упростить определение схемы полей. Теперь, когда мы выполняем поиск в векторной базе данных, важно включить нужные динамические поля в результаты поиска. Этот конкретный сценарий предполагает запрос поля paragraph, которое содержит текст каждого абзаца в статьях.
connections.connect(uri=zilliz_uri, token=zilliz_token)
collection = Collection(name="tds_articles")
query = input("What would you like to ask Towards Data Science's 2023 publications up to September? ")
embedding = model.encode(query)
closest = collection.search([embedding],
anns_field='embedding',
param={"metric_type": "L2",
"params": {"nprobe": 16}},
limit=2,
output_fields=["paragraph"])
print(closest[0][0])
print(closest[0][1])
Я попросил приложение предоставить информацию о больших языковых моделях, и оно вернуло следующие два ответа. Хотя эти ответы упоминают "language models" и включают некоторую связанную информацию, они не дают подробного объяснения больших языковых моделей. Второй ответ семантически похож, но должен быть достаточно близок к тому, что мы запрашивали.
Добавление в базу знаний векторной базы данных
До сих пор мы создали базу знаний на основе статей TDS, используя Zilliz в качестве нашей векторной базы данных. Хотя нам удавалось легко получать семантически похожие результаты поиска, они лишь иногда оказываются тем, что нам нужно. Следующий шаг — улучшить наши результаты, включив новые фреймворки и технологии.
В следующем разделе мы рассмотрим добавление LlamaIndex для маршрутизации наших результатов, аналогично маршрутизации, которую мы выполняли в моем предыдущем блоге о запросах к нескольким документам с использованием LlamaIndex, LangChain и Milvus.
Резюме
В этом руководстве рассматривается создание чат-бота для публикации Towards Data Science. Мы иллюстрируем процесс веб-скрейпинга для создания и хранения базы знаний в векторной базе данных (а именно Zilliz Cloud). Затем мы демонстрируем, как запросить у пользователя запрос, векторизовать запрос и выполнить запрос к векторной базе данных.
Однако мы обнаружили, что, хотя результаты семантически похожи, они не совсем такие, какие нам нужны. В следующей части этой серии блогов мы рассмотрим использование LlamaIndex для маршрутизации запросов и посмотрим, сможем ли мы добиться лучших результатов. Помимо шагов, обсуждаемых здесь, вы также можете поэкспериментировать с Zilliz Cloud, заменив модель, объединив тексты или используя другой набор данных!
Чтобы снова прочитать всю серию Chat Towards Data Science, вот ссылки:
Читать далее

Zilliz Cloud Delivers Better Performance and Lower Costs with Arm Neoverse-based AWS Graviton
Zilliz Cloud adopts Arm-based AWS Graviton3 CPUs to cut costs, speed up AI vector search, and power billion-scale RAG and semantic search workloads.

Our Journey to 35K+ GitHub Stars: The Real Story of Building Milvus from Scratch
Join us in celebrating Milvus, the vector database that hit 35.5K stars on GitHub. Discover our story and how we’re making AI solutions easier for developers.

Legal Document Analysis: Harnessing Zilliz Cloud's Semantic Search and RAG for Legal Insights
Enhance legal document analysis with Zilliz Cloud’s Semantic Search and RAG. Improve accuracy, efficiency, and scalability for contracts, case law, and compliance.



