В предыдущем посте я объяснил, как легко создать чат-бота, который может отвечать на вопросы по конкретной теме, используя ваши собственные данные и встраивания (чтобы прочитать, нажмите здесь).
Несмотря на то, что такой подход отлично подходит для демонстрации целей, если вы попытаетесь использовать его для чего-то более серьезного, вы очень быстро столкнетесь с проблемой.
query = f"""Use the below article to answer questions about benefits in our company, if you don't know the answer write "Sorry I don't know the answer please contact HR" Article: \"\"\" {benefits} \"\"\" Question: {question}?"""
Используя пример из предыдущего сообщения в блоге, предположим, что ваша компания начинает предоставлять больше преимуществ, а ваша переменная преимущества теперь содержит набор текста. Если вы попытаетесь выполнить вызов API OpenAI, вы получите такая ошибка:
InvalidRequestError: максимальная длина контекста этой модели составляет 4097 токенов, однако вы запросили 5125 токенов (4125 в приглашении; 1000 для завершения). Пожалуйста, уменьшите подсказку; или длина завершения.
Как видите, существует ограничение на количество токенов, которые вы можете отправить в одном запросе.
Чтобы решить эту проблему, вы должны проанализировать вопрос и отправить только те вложения, которые могут помочь ИИ ответить на этот вопрос.
Простой поиск в куче текста, где запрос - это ваш вопрос, не сработает, нужен семантический поиск и это реализовано с помощью векторного поиска.
Но что такое векторы?
Когда мы говорим о векторах в контексте машинного обучения, мы имеем в виду следующее: Векторы — это группы чисел, представляющие что-то.
Это может быть изображение, слово или почти что угодно.
Поскольку создание вектора из текста или изображения сложно и за этим стоит целая наука, OpenAI предлагает модель через API, которая создает векторы для вас.
Вы можете преобразовать свой текст и вопрос пользователя в векторы и выполнить векторный поиск. на том тексте, где запрос является вектором вопроса пользователя.
Как работает векторный поиск?
Векторный поиск использует концепцию измерения близости между группами чисел. В этом случае группы представлены в виде векторов, которые на графике представляют собой линии. Механизм поиска векторов сравнивает эти векторы и спрашивает: «Какие пары векторов будут ближе всего друг к другу?»
Чтобы упростить понимание, давайте рассмотрим группы только с двумя числами. Например, сравним группу [1,2] с двумя другими группами: [2,2] и [2,500]. В этом случае группа [1,2] будет ближе к [2,2], чем к [2,500].
Важно отметить, что векторы обычно содержат несколько чисел, представляющих разные измерения. Хотя визуализация этих векторов в нескольких измерениях может быть сложной задачей, принцип остается прежним.
Этот подход к определению сходства невероятно эффективен, потому что векторы, представляющие такие слова, как «доктор» и «медицина», будут демонстрировать гораздо большую близость при графическом отображении, чем такие слова, как «доктор» и «камень».
Теперь, когда мы знаем, как используется векторный поиск, мы знаем, что можем преобразовать текст в вектор, сохранить его где-нибудь и написать какую-нибудь функцию, которая математически выполнит поиск. Но это неэффективно, есть инструменты, которые можно использовать для хранения и поиск векторов, этими инструментами являются: базы данных векторов.
Для нашего примера мы будем использовать векторную базу данных с открытым исходным кодом под названием Milvus (https://milvus.io/).
Вы можете следовать этому руководству, чтобы настроить его локально: https://milvus.io/docs/install_standalone-docker.md
Мы все объяснили, у нас настроена наша база данных векторов, давайте погрузимся в код.
Прежде всего, мы должны подготовить данные. Допустим, мы хотим создать чат, который может отвечать на вопросы о льготах, общей информации о процедурах компании, процедурах PTO, больничных и т. д.
Мы создадим файл .txt для каждой категории и поместим информацию, которую можно использовать для извлечь ответы на вопросы из этой категории.
Как только у нас будут готовы данные, мы можем подключиться к базе данных и создать схему:
from pymilvus import ( connections, FieldSchema, CollectionSchema, DataType, Collection, ) connections.connect("default", host="localhost", port="19530") fields = [ FieldSchema(name="pk", dtype=DataType.INT64, is_primary=True, auto_id=False), FieldSchema(name="title", dtype=DataType.VARCHAR, max_length=300), FieldSchema(name="file_name", dtype=DataType.VARCHAR, max_length=300), FieldSchema(name="embeddings", dtype=DataType.FLOAT_VECTOR, dim=1536) ] schema = CollectionSchema(fields, "articles with titles") article_collection = Collection("articles2", schema)
Мы создаем простую схему, которая содержит первичный ключ, заголовок (первая строка в файле), имя файла и вложения.
Последнее поле — это вектор, который будет сгенерирован с помощью OpenAI text-embedding-ada-002 модель.
Последнее, что нужно сделать при настройке базы данных, — это настроить индексацию:
index_params = { "metric_type":"L2", "index_type":"IVF_FLAT", "params":{"nlist":32} } article_collection.create_index( field_name="embeddings", index_params=index_params )
Приведенный выше код создает индекс для поля встраивания с использованием метрики L2, которая представляет евклидово расстояние.
Когда база данных готова, мы можем ее заполнить. Что нам делать:
- Пройтись по всем файлам
- Вызвать OpenAI API для генерации вектора из текста из файла
- Сохранить все векторы для всех файлов в базе данных
def get_embedding(text, model="text-embedding-ada-002"): text = text.replace("\n", " ") return openai.Embedding.create(input = [text], model=model)['data'][0]['embedding'] i = 0 primary_keys = [] titles = [] embeddings = [] file_names = [] for file in data: with open(file, 'r') as data_file: lines = data_file.readlines() title = lines[0] text = ' '.join([str(elem) for elem in lines]) vector_embedding = get_embedding(text) primary_keys.append(i) titles.append(title) file_names.append(data_file.name) embeddings.append(vector_embedding) i = i+1 article_collection.insert([primary_keys, titles, file_names, embeddings])
Теперь у нас все готово для ответов на вопросы.
Ответ на вопрос будет состоять из нескольких шагов.
Шаг 1. Нам нужно найти, какая статья в базе данных лучше всего соответствует заданному запросу
Вопрос, указанный в качестве параметра метода, является вектором, подробнее об этом позже
def find_articles(question): query_embeddings = get_embedding(question) search_params = {"metric_type": "L2", "params": {"nprobe": 10}, "offset": 0} results = article_collection.search( data=[query_embeddings], anns_field="embeddings", param=search_params, limit=1, output_fields=['title', 'file_name'], expr=None, consistency_level="Strong" ) for result in results[0]: return result.entity.get('file_name')
Мы используем встроенную функцию поиска от Milvus и поиск L2 (евклидово расстояние).
Мы также говорим базе данных, чтобы она возвращала только 1 результат, в производственной среде вы должны возвращать, возможно, первые 3 или 5 результатов, и мы говорим это вернуть заголовок и имя файла.
Шаг 2. Прочитайте необработанный текст для возвращенного файла, чтобы его можно было использовать для встраивания
def get_raw_text(file): with open(file, 'r') as data_file: lines = data_file.readlines() return ' '.join([str(elem) for elem in lines])
Простой метод, просто прочитайте файл с заданным именем файла и верните все его строки в одну строку.
Шаг 3. Позвоните в OpenAI, чтобы получить ответ на вопрос
def get_final_answer(question, summaries): prompt = f"""Use the below article to answer questions about benefits in our company, if you don't know the answer write "Sorry I don't know the answer please contact HR: Article: \"\"\" {summaries} \"\"\" Question: {question}? """ response = openai.Completion.create(model="text-davinci-003", prompt=prompt, temperature=0, max_tokens=1000) return response['choices'][0]['text']
Тот же метод, что и в предыдущем посте, ничего дополнительно объяснять, просто задайте вопрос и встраивание (резюме) и получите ответ.
Шаг 4. Объедините все вышеперечисленное, чтобы получить ответ
Последний шаг — создать единую функцию, которая будет принимать вопрос и давать ответ.
def answer(question): article_collection.load() article_candidates = find_articles(question) context = get_raw_text(article_candidates) final_answer = get_final_answer(question, context) return final_answer
Функция сначала загружает всю коллекцию из базы данных в память, после чего вызывает созданную ранее функцию, которая выполняет векторный поиск в базе данных и предоставляет кандидата статьи для этого вопроса.
После этого мы читаем необработанный текст и вызываем функцию, которая вызывает OpenAI, вот и все.
Сделав это, теперь у нас есть система, которую мы можем расширить настолько, насколько захотим, мы можем предоставить дополнительные знания, просто вставив данные в базу данных векторов, и / или чат-бот сможет ответить на вопрос об этих новых данных.
Небольшое примечание:
article_collection.load()
Эта строка означает, что вся коллекция загружается в память, поэтому, если у вас много данных, вам понадобится много оперативной памяти, просто есть над чем подумать.
Блокнот с кодом из этого поста доступен по адресу:
https://github.com/dsl94/gpt-vector-database/blob/main/gpt-vector-database.ipynb