Подключаем умный поиск (GPT) к своей базе документов
Есть отечественный файрвол (NGFW). У этого файрвола есть документация для пользователей powered by GitBook, на русском языке. В этой документации работает простой поиск — только по словам и словосочетаниям. И это плохо, потому что нет ответов на вопросы:
Какие алгоритмы шифрования ipsec поддерживаются у вас?
Как заблокировать ютуб?
Как настроить DMZ?
Хочется, чтобы поиск был «умным» и чтобы пользователи могли обращаться с подобными вопросами именно к поиску, а не к инженерам тех. поддержки. AI или ML внутри — не важно, как это называть. Но на простые вопросы из списка выше поиск должен отвечать.
Я решил эту задачу (Retrieval Question Answering), используя OpenAI API. Казалось бы, уже опубликованы сотни похожих инструкций, как это сделать. Но под катом будет не инструкция, а рассказ про сложности, которые пришлось решить на пути от идеи до запуска поиска: как прикинуть бюджет, быстро собрать прототип с сохранением всех ответов и оценок обратной связи, организовать альфа- и бета-тестирование, позвать асессоров и разметить собранный датасет, подвести итоги проекта и наметить следующие шаги.
Постановка задачи
Итак, уточняем задачу.
Есть документация по продукту, внутри — база документов в формате markdown.
Нужно по этой документации сделать «умный» поиск (далее — GPT поиск или бот).
В ответах мы хотим увидеть и ссылки на источники. Как окажется позже, наличие правильно подобранных ссылок на источники даже при ответе плохого качества — нравится пользователям поиска.
Те, кто знакомы с большими языковыми моделями (LLM), знают, что эта задача называется Retrieval QA.
При решении этой задачи главным ограничением выступает максимальная длина запроса к модели: нужно как-то передать в модель всю базу документов, по которой должен работать поиск, а длина запроса ограничена. И самый простой способ обойти это ограничение — сначала во всей базе документов оставить только те, которые могут иметь отношение к ответу на вопрос, а затем передать эти документы (вместе с исходным вопросом) в языковую модель, и перейти к шагу генерации ответа.
Предварительные изыскания
Перед тем, как браться за задачу, проверим, а можно ли её вообще не делать?
Что было сделано на первых шагах (пишу во множественном числе, потому что мне помогали коллеги):
Для начала мы собрали статистику поиска по документации продукта и ещё раз убедились, что поиск нужно делать. Вопросы, которые задавали пользователи продукта, в большинстве случаев оставались без ответов (см. три вопроса во вступительной части).
Разобрались с GitBook, нельзя ли подключить плагин поиска Algolia или подобный? Нет, в июне 2023 года это сделать было нельзя. Были лишь планы разрешить сторонним разработчикам создавать свои собственные плагины, но без указания сроков.
На тот момент у GitBook был доступен «умный» AI поиск — Lens (сегодня он по умолчанию включен). Мы включили его для теста, но оказалось, что с русским языком он не работает. Выглядело это так себе: задаёшь вопрос на русском языке, а получаешь ответ на английском. Да ещё и качество в сравнении с привычными чат-ботами chat.openai.com и perplexity.ai неудовлетворительное.
Сначала попробовали сделать «умный» поиск локальным — скачать к себе языковую модель, локально задавать ей вопросы и получать ответы. Наиболее подходящей моделью тогда оказалась Dolly. Запустили прототип, на русском языке сравнили качество ответов модели с привычными чат-ботами, не понравилось.
Jupyter блокнот для знакомства с LLM Dolly
Всё, что мы смогли до сих пор сделать, по качеству ответов сильно уступало чат-боту ChatGPT. Похоже, пришло время протестировать OpenAI API.
Разработка прототипа «умного» поиска
Шаг 1. Быстро решить задачу Retrieval QA.
В этом проекте я решал задачу Retrieval QA впервые, за основу для первых набросков взял эту инструкцию.
Пример построен с применением фреймворка для работы с языковыми моделями — LangChain, в качестве языковой модели используется модель GPT 3.5 Turbo от компании OpenAI. Код не буду повторять, расскажу лишь про неочевидные детали:
Для работы с OpenAI API понадобится API-ключ, который можно получить после регистрации на https://openai.com/ (доступ из России блокируется, помогает VPN).
Услуга платная, для новых пользователей дают несколько долларов, чтобы протестировать возможности.
Начать можно с пробной версии, а дальше уже привязать карту (не российскую, помогает поисковая выдача по запросу: «Оплата иностранных сервисов, подписок и товаров»).
Итак, цепочка RetrievalQAWithSourcesChain работает, я получаю ответы на вопросы по базе документов. При выставленном параметре «температура» в ноль (так я прошу модель в качестве источников использовать только предъявленную базу документов) качество ответов вполне устраивает, ответы заметно лучше по сравнению с Dolly и GitBook Lens.
Теперь всё это нужно упаковать в сервис для пользователей.
Шаг 2. Прикинуть бюджет.
Но сначала прикидываю бюджет. Точнее, выбираю между GPT-3.5 Turbo и GPT-4. Тариф на входящие токены для модели GPT-3.5 Turbo — $0.001 за тысячу токенов, в то время как у GPT-4 — $0.03. Тариф на исходящие токены для GPT-3.5 Turbo — $0.002, а у GPT-4 — $0.06.
Выходит, что вопросы и ответы у модели GPT-4 дороже в ~30 раз. Пока выбираю модель GPT-3.5 Turbo.
Первые эксперименты показали, что с учётом всех комиссий один вопрос к модели стоит ~1 российский рубль. Сразу же для защиты от непорядочных бета-тестеров выставляю порог расходов в личном кабинете OpenAI. Если начнут ддосить — сервис просто перестанет давать ответы.
Пока это всего лишь прототип для проверки гипотезы, поэтому на следующих шагах быстро собираю финальное решение.
Шаг 3. Быстро сделать фронтенд.
Фронтенд будет обращаться к бекенду с вопросами пользователей. Для быстрого старта подойдёт Twitter Bootstrap 5.
Шаг 4. Быстро сделать бекенд.
Выбираю FastAPI. Не зря он fast, отлично подходит для решаемой задачи.
Шаг 5. Подключить к модели полную документацию продукта.
Шаг 6. Сохранить все вопросы и ответы.
Для разбора ответов и улучшения сервиса будем сохранять все вопросы пользователей, полученные моделью ответы, источники, ошибки. В самом продукте активно используется ClickHouse, поэтому эти данные складываю туда же.
Шаг 7. Арендовать сервер, где будет работать поиск.
Поскольку к OpenAI API нельзя обращаться напрямую из России, выбираю дата-центр за пределами.
Шаг 8. Выбрать доменное имя.
Шаг 9. Подключить сертификаты LE.
И проверить, что всё работает и нет предупреждений безопасности, которые могут отпугнуть будущих пользователей поиска.
Шаг 10. Запустить альфа-тестирование.
Устраиваем альфа-тестирование нового поиска среди сотрудников компании. Обрабатываем шквал скриншотов с замечаниями и предложениями, даже находим ошибки в исходной документации, исправляем. Стало понятно, что перед бета-тестированием нужно добавить обратную связь.
Вспоминаем Алексея Лысенкова — бессменного ведущего передачи «Сам себе режиссер». Он рассказывал, что когда команда редакторов просматривает присланные видеоролики, то первые комментарии — они самые живые и смешные, но в эфир попасть не могут, потому что не пройдут цензуру. Чувствуем себя примерно так же, особенно разбирая вопрос про коз.
Лог первых вопросов и ответов модели
Шаг 11. Доработать поиск по результатам альфа-тестирования.
Список доработок:
Замечаем, что иногда модель отвечает на английском языке. Исправляю это уточнением промпта.
Обнаруживаем недокументированные коды ошибок модели OpenAI, корректно обрабатываю их.
Набираем примеры разметки источников. Иногда они дописываются в текст ответа, иногда — в структуру данных «источники». Иногда используется HTML разметка, иногда нет.
Делаю источники кликабельными, удобно же.
Добавляю возможность для каждого ответа выставить оценку: «Плохо» (-1), «Нормально» (0), «Хорошо» (+1). Учу бекенд принимать оценки и сохранять в ClickHouse.
Шаг 12. Запустить бета-тестирование.
Публикуем новость о запуске поиска в официальных каналах компании-разработчика файрвола, запускаем бета-тестирование.
Шаг 13. Оценить результаты тестирования.
Набираем за несколько дней более 300 запросов и уходим обрабатывать ответы. По каждому запросу просим наших дорогих асессоров (сотрудников компании-разработчика файрвола, которые в совершенстве владеют документацией) разметить выборку. Проверить каждый ответ и выставить свою оценку: -1, 0, 1. Также просим прикрепить комментарий, чтобы знать, что можно улучшить.
Разметка асессорами собранного датасета с вопросами и ответами
Подведение итогов
Для оценки качества поиска я выбрал простой подход — Human Evaluation. По размеченной пользователями и асессорами выборке составил таблицу:
Статистика | От пользователей | От асессоров |
Всего запросов (с 22.09 по 02.10) | 336 | 336 |
Количество отрицательных оценок | 18 | 122 |
Количество нейтральных оценок | 7 | 63 |
Количество положительных оценок | 18 | 139 |
Без оценок | 293 | 12 |
Асессорам мы доверяем больше, чем пользователям. Поэтому смотрим на самый правый столбец.
Положительных оценок больше отрицательных. Но это ещё не значит, что GPT поиск работает хорошо. Среди ответов с положительной оценкой асессоров много «плохих» вопросов, не относящихся к теме, вроде: «Как настроить велосипед». Поэтому вместе с асессорами обсуждаем детали.
Что работает хорошо:
Если вопрос подробно сформулирован и про это есть конкретно в документации, то GPT поиск генерирует хорошие ответы. Иногда даже добавляет то, чего может не хватать в документации для понимания.
Бот приемлемо хорошо умеет собирать ответ из нескольких блоков с разных страниц документации. Опять же, если вопрос подробно сформулирован и части ответов есть в документации.
В подавляющем большинстве случаев бот находит корректные источники в документации. Уже в этом есть польза, даже если сам ответ плохого качества.
Всё, что мы пробовали до GPT-3.5 Turbo (Dolly, GitBook Lens), по качеству на голову ниже.
Однозначно, такой поиск работает лучше поиска по умолчанию в GitBook.
Что работает плохо:
Бот плохо отвечает на вопросы, которые не освещены в документации. За исключением общих и/или энциклопедических вопросов («Что такое DNS?»).
Модель может фантазировать.
Редко, но модель может ошибаться в источниках.
Модель может давать неполные ответы.
Модель может давать по большей части верные ответы, но добавлять и неподходящие блоки.
Иногда ответ генерируется на данных из файлов changelog старых версий.
Финальный вывод: GPT поиск по документации файрвола работает лучше других поисков, почти всегда даёт правильные источники, генерирует ответ хорошего качества при условии конкретного и подробного вопроса, который описан в документации.
От идеи до запуска GPT поиска прошло около 3 месяцев, ещё месяц ушёл на подведение итогов. Опыт внедрения современных технологий искусственного интеллекта считаем успешным, пора задуматься о стратегии развития AI/ML в компании.
Что делать дальше:
Ответить на вопрос: «Если модель сгенерирует неправдоподобный/ложный ответ, будет ли это безопасно для пользователя?»
Научить бота не отвечать матом.
Научить бота распознавать версию файрвола в вопросе и генерировать ответ по документации нужной версии.
Разобраться, как максимально ускорить сервис. Сейчас отвечает достаточно медленно (и Сэм Альтман признаёт это).
Перенести поиск в общую инфраструктуру компании.
Добавить фильтрацию чувствительных данных, которые могут появиться в ответе.
Попробовать GPT-4, GigaChat API, Yi-34B и что там ещё.
Короткие итоги от меня лично. Теперь я согласен с Chip Huyen: «It«s easy to make something cool with LLMs, but very hard to make something production-ready with them.» (Building LLM applications for production)
GPT поиск доступен для всех желающих по ссылке: https://gpt-docs.ideco.ru/
Скриншот умного поиска
Вместе со мной над проектом работали:
@socketpair — идейный вдохновитель
@fisher85 — научный руководитель
дорогие асессоры Анастасия и Владислав