Есть отечественный файрвол (NGFW). У этого файрвола есть документация для пользователей powered by GitBook, на русском языке. В этой документации работает простой поиск — только по словам и словосочетаниям. И это плохо, потому что нет ответов на вопросы:

  • Какие алгоритмы шифрования ipsec поддерживаются у вас?

  • Как заблокировать ютуб?

  • Как настроить DMZ?

Хочется, чтобы поиск был "умным" и чтобы пользователи могли обращаться с подобными вопросами именно к поиску, а не к инженерам тех. поддержки. AI или ML внутри — не важно, как это называть. Но на простые вопросы из списка выше поиск должен отвечать.

Я решил эту задачу (Retrieval Question Answering), используя OpenAI API. Казалось бы, уже опубликованы сотни похожих инструкций, как это сделать. Но под катом будет не инструкция, а рассказ про сложности, которые пришлось решить на пути от идеи до запуска поиска: как прикинуть бюджет, быстро собрать прототип с сохранением всех ответов и оценок обратной связи, организовать альфа- и бета-тестирование, позвать асессоров и разметить собранный датасет, подвести итоги проекта и наметить следующие шаги.

Постановка задачи

Итак, уточняем задачу.

  1. Есть документация по продукту, внутри — база документов в формате markdown.

  2. Нужно по этой документации сделать "умный" поиск (далее — GPT поиск или бот).

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

Те, кто знакомы с большими языковыми моделями (LLM), знают, что эта задача называется Retrieval QA.

При решении этой задачи главным ограничением выступает максимальная длина запроса к модели: нужно как-то передать в модель всю базу документов, по которой должен работать поиск, а длина запроса ограничена. И самый простой способ обойти это ограничение — сначала во всей базе документов оставить только те, которые могут иметь отношение к ответу на вопрос, а затем передать эти документы (вместе с исходным вопросом) в языковую модель, и перейти к шагу генерации ответа.

Предварительные изыскания

Перед тем, как браться за задачу, проверим, а можно ли её вообще не делать?

Что было сделано на первых шагах (пишу во множественном числе, потому что мне помогали коллеги):

  1. Для начала мы собрали статистику поиска по документации продукта и ещё раз убедились, что поиск нужно делать. Вопросы, которые задавали пользователи продукта, в большинстве случаев оставались без ответов (см. три вопроса во вступительной части).

  2. Разобрались с GitBook, нельзя ли подключить плагин поиска Algolia или подобный? Нет, в июне 2023 года это сделать было нельзя. Были лишь планы разрешить сторонним разработчикам создавать свои собственные плагины, но без указания сроков.

  3. На тот момент у GitBook был доступен "умный" AI поиск — Lens (сегодня он по умолчанию включен). Мы включили его для теста, но оказалось, что с русским языком он не работает. Выглядело это так себе: задаёшь вопрос на русском языке, а получаешь ответ на английском. Да ещё и качество в сравнении с привычными чат-ботами chat.openai.com и perplexity.ai неудовлетворительное.

  4. Сначала попробовали сделать "умный" поиск локальным — скачать к себе языковую модель, локально задавать ей вопросы и получать ответы. Наиболее подходящей моделью тогда оказалась Dolly. Запустили прототип, на русском языке сравнили качество ответов модели с привычными чат-ботами, не понравилось.

Jupyter блокнот для знакомства с LLM Dolly
Jupyter блокнот для знакомства с LLM Dolly
  1. Всё, что мы смогли до сих пор сделать, по качеству ответов сильно уступало чат-боту ChatGPT. Похоже, пришло время протестировать OpenAI API.

Разработка прототипа "умного" поиска

Шаг 1. Быстро решить задачу Retrieval QA.

В этом проекте я решал задачу Retrieval QA впервые, за основу для первых набросков взял эту инструкцию.

Пример построен с применением фреймворка для работы с языковыми моделями — LangChain, в качестве языковой модели используется модель GPT 3.5 Turbo от компании OpenAI. Код не буду повторять, расскажу лишь про неочевидные детали:

  1. Для работы с OpenAI API понадобится API-ключ, который можно получить после регистрации на https://openai.com/ (доступ из России блокируется, помогает VPN).

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

  3. Начать можно с пробной версии, а дальше уже привязать карту (не российскую, помогает поисковая выдача по запросу: "Оплата иностранных сервисов, подписок и товаров").

Итак, цепочка 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. Доработать поиск по результатам альфа-тестирования.

Список доработок:

  1. Замечаем, что иногда модель отвечает на английском языке. Исправляю это уточнением промпта.

  2. Обнаруживаем недокументированные коды ошибок модели OpenAI, корректно обрабатываю их.

  3. Набираем примеры разметки источников. Иногда они дописываются в текст ответа, иногда — в структуру данных “источники”. Иногда используется HTML разметка, иногда нет.

  4. Делаю источники кликабельными, удобно же.

  5. Добавляю возможность для каждого ответа выставить оценку: "Плохо" (-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 — научный руководитель

  • дорогие асессоры Анастасия и Владислав