Сегодня я хочу поделиться опытом реализации функциональности векторного поиска статей по базе знаний (далее БЗ). Результаты векторного поиска статей из БЗ мы показываем в чат-боте в виде статей-инструкций, которые пользователь читает и выполняет. Казалось бы функциональность проще некуда:
Человек пишет в чат-бот вопрос
Векторный поиск ищет в базе знаний подходящую статью и статья показывается пользователю
Если подходящую статью поиск не нашел, то происходит переключение на оператора
Просто? Вот и мне так показалось. Пообещав функциональному заказчику выдать готовое решение для поиска (примерно по 10 тысячам статей в БЗ) через два дня, я сел и действительно за пару дней написал простейший векторный поиск:
Все статьи (они же ответы пользователю) из БЗ “оцифровываются” в эмбеддинги и складываются в векторный индекс (FAISS)
Приходящий от пользователя текст точно также превращается в эмбеддинг вектор
Далее ищем алгоритмом косинусной близости N ближайших статей в БД и их возвращаем в качестве ответа
Если значение косинусной близости менее 0.5 (я интуитивно посчитал такой порог разумным), то переключаем на оператора
Протестировав что все работает “как надо” (на мой дилетантский на тот момент взгляд), вечером второго дня Кадавр полностью удовлетворенный я отправился спать, не подозревая даже, что не сделал даже 1% от того, что в реальности должно быть реализовано. В итоге на продакшен задача попала только через шесть месяцев долгой и упорной разработки командой из нескольких человек.
Ниже приведены самые трудоемкие проблемы с которыми мы столкнулись при попытке внедрения описанной выше функциональности в продакшен. После краткого перечисления этих “подводных камней”, я разберу каким образом каждая из этих проблем была решена.
Оценка качества поиска. Как понять, что модель ищет хорошо? А как понять что после “улучшения” поиск стал работать лучше? Какую модель эмбеддингов для поиска использовать, какая будет давать более точный результат? Понятно, что модель должна быть BERT, но их тьма тьмущая, просто ткнуть наугад или проводить какие-то тесты, чтобы сравнить их между собой. И сразу же встает в полный рост задача по быстрому (понятно дело автоматическому) тестированию качества результата векторного поиска.
Разный размер статьи и запроса пользователя. Статья в базе знаний большая (по сравнению с запросом пользователя), эмбеддинг вектор, получающийся по ней, сдвинут черт знает куда целой кучей слов входящих в эту статью. А пользователь в своем вопросе пишет, как правило, сильно короче и по сути того, что ему нужно. В результате, даже если статья и содержит практически 1:1 запрос пользователя, то остальные слова из этой статьи смещают эмбеддинг вектор так, что не оставляют модели шансов найти соответствие запроса пользователя и статьи в БЗ.
Разный язык/стиль статьи в базе знаний и запроса пользователя. Статьи в базе знаний, как правило, написаны совершенно не тем языком, на котором говорят пользователи. Для примера возьмите любой сайт крупной компании и посмотрите как там описана какая-нибудь услуга, которую они продают - качественно, красиво с использованием деепричастных оборотов и малопонятных обычному человеку прилагательных. Пользователь же это среднестатистический человек, он “высоким штилем” не владеет и в чат пишет обычным русским разговорным, а порой еще и матерным.
Нет подходящей статьи. Как понять, что “подходящей” статьи нет в базе знаний? Бездушная модель ВСЕГДА находит “подходящие” статьи и абсолютное значение косинусной близости тут не поможет никак. Нельзя сказать, что если значение меньше 0.5, то статья не подходит, а если больше 0.5 то подходит. Тестирование показало, что статьи с близостью 0.7 могут быть вообще не подходящими с точки зрения обычного человека, а 0.3 вполне себе отвечают на вопрос пользователя. А как понять это мы неправильно поняли вопрос пользователя или статьи реально нет в БЗ?
Переключение на оператора. Заказчик хотел вызывать оператора не только когда подходящая статья отсутствует в базе знаний, но также когда пользователь взбешен, начинает писать “негатив” или явно зовет оператора. Как понять, что человек пишет негатив и зовет оператора? ”Мат” - ок, еще как-то можно проследить по словарю, но как отследить фразы “позови кожаный мешок, бездушная железка”? А как, например, понять, что фраза от пользователя “вы там все офонарели в край, что ли” это негатив, что это не вопрос про фонари?
Обратная связь. Как получать от человека обратную связь о работе системы поиска? Просто попросить человека поставить оценку (например палец вверх/вниз) не совсем релевантно, может быть человеку просто содержимое статьи не понравилось и он “минусанул” потому что у него проблема не решена, а статью-то в БЗ мы нашли самую что ни есть подходящую
Дубли статей. Как бороться с дублями статей в БЗ. БЗ ведут люди и они очень часто ошибаются и создают дублирующие друг друга статьи. Особенно в нашем случае, когда статей в БЗ около 10 тыс, а адмирнистраторов, вносящих статьи в БЗ, десятки, если не сотни. Конечный пользователь как правило впадает в ступор, если ему чат-бот покажет две практически одинаковые статьи/ответы. Пользователь будет искать в чем тут подвох, не подозревая, что это просто кривые руки редакторов добавили две практически одинаковые статьи.
Дальше в статье будет разбор каждой проблемы и способ которым мы на данный момент эту проблему решили. Сразу скажу, что это далеко не все проблемы, с которыми мы столкнулись на этапе разработки и внедрения решения, но на мой ��згляд это наиболее важные и типовые, надеюсь они помогут читателям заранее озаботиться их решением. Буду рад, если в комментариях поделитесь своим опытом и расскажете какие проблемы были у вас при решении аналогичной задачи.
1. Проблема оценки качества поиска.
Итак, утром третьего дня, после “успешного выполнения” задачи я с большим удивлением обнаружил, что функциональный заказчик прям совсем-совсем недоволен получившимся решением. Его фидбек на мое решение был следующим: “что за поделку ты сделал, оно ищет вообще не то, что нужно”.
Первое, что пришло мне на ум, чтобы проверить мое решение (“а вдруг реально где-нибудь бага” - подумал я), это взять какую-нибудь стандартную метрику и посмотреть на сколько поиск качественный. Я решил попробовать метрику rouge - такая наивная попытка алгоритмически (т.е. не тратя много усилий) понять на сколько мой поиск качественный. И примерно сразу же стало понятно, что любая алгоритмическая проверка результата невозможна, так как если один алгоритм ищет лучше другого алгоритма, то можно просто заменить один на другой :). Надо размечать данные (т.е. готовить тестовый датасет) вручную. Т.е. берем реальный запрос пользователя, находим вручную для него статью с БЗ и делаем проверочный датасет из таких вот “ручных” примеров.
Я попросил заказчика прислать мне конкретные примеры того, где по его мнению мой поиск ошибается и выдает неверный результат. Получив три каких-то случайных примера, я понял, что понятия не имею как эти три несчастных примера мне помогут улучшить поиск. Стало ясно, что надо “трясти” заказчика на си��ьно большее количество примеров, ибо без них я пытаюсь найти черную кошку в темной комнате.
Потратив некоторое количество дней на препирательства с заказчиком, мне все же удалось его убедить, что мне нужна хотя бы сотня примеров, на которых я смогу проверять качество поиска. Еще через некоторое количество впустую потраченных дней и нервов у меня наконец появились на руках заветная сотня вот таких пар примеров:
реальный вопрос пользователя -> соответствующая ему статья в БЗ
Теперь я мог сделать некое подобие тестового датасета, чтобы попробовать разные модели и подходы к поиску и оценить качество получаемого результата.
Забегая вперед сразу скажу, что с методикой “проверки” тоже пришлось немного “повозиться”. Например вариант, когда нужная статья стоит не на первом, а на втором месте вовсе не так уж и плох, на третьем месте конечно похуже, но все равно терпимо. В итоге встал вопрос, а как измерять качество поиска даже имея примеры на руках? Самая простая и понятная функциональному заказчику метрика оказалась % ответов попавших в ТОП-4. Т.е. правильная статья есть в первых четырех ответах от алгоритма поиска. Почему четыре? Потому что по умолчанию в интерфейсе нашего чат-бота пользователю в качестве результата поиска предлагается четыре наиболее подходящих статьи.
Но, тем не менее, для разработчиков такая метрика как % попавших ТОП-4 не совсем подходит, потому что если правильный ответ находится не на 4-м месте, а на первом, то это явно лучше, но в нашей метрике мы этого не видим (метрика никак не меняется). Поигравшись довольно продолжительное время с разными метриками, мы опытным путем пришли к выводу что для разработчиков идеально подходит NDCG (Normalized Discounted Cumulative Gain) метрика. Суть ее в том, что чем выше правильный ответ стоит в результатах поиска, тем больше метрика. В итоге получилось так:
В самом начале, когда мы с каждой новой итерацией улучшали качество поиска на проценты мы использовали ТОП-4
Далее, когда потребовалась более “тонкая” шкала, мы используем NDCG чтобы измерять качество новой версии поиска
Также поделюсь одним лайфхаком, который нужно обязательно иметь в виду при тестировании и принятии решения выводить на продакшен новую версию или нет. В тестовом датасете надо всегда иметь “особо важные” вопросы, на которых вы демонстрируете свое решение и которые ваши vip-пользователи задают. Эти “особо важные” пары вопрос-статья, должны находиться поиском в 100% случаев, без вариантов. Вам не простят ситуации, в которой вы выкатили новую “улучшенную” версию поиска на продакшен, и на какой-нибудь важной презентации он сфейлил на вопросе, на котором раньше не ошибался. Или когда “самый большой начальник” задает свой “любимый” вопрос, а система (после очередного обновления) не может найти ему правильную статью в БЗ.
И последний вопрос с которым мы столкнулись, а сколько нужно иметь тестовых вопросов, чтобы более менее нормально оценивать качество? Когда нужно остановиться с тестовым датасетом и добавление новых пар “вопрос-статья” особо ни на что не влияет? Конечно это сильно зависит от содержимого БЗ, но исходя из опыта на 10 тыс статей в БЗ у нас около 400 тестовых примеров. Т.е. 4% от количества статей в БЗ. Наверное, если у вас статей тысяча, то нужно чуть больше %, если 100 тысяч, то чуть меньше %. Но в целом порядок примерно такой.
Краткий вывод: поиск по косинусной близости (как и любой другой проект ИИ) нужно начинать с подготовки тестового датасета.
2. Проблема разного размера сравниваемых статей.
Итак, вооружившись тестовым датасетом, я в полной уверенности в успехе начал тестировать одну BERT модель за другой в надежде найти ту самую, которая мне сейчас выдаст 99% результат. Но, к моему большому удивлению, практически все модели давали примерно одно и тоже (сильно меньше 50%) и я понял, что проблема тут не в моделях, а в том, что я делаю что-то принципиально не так.
Поскольку большого опыта работы с LLM моделями на тот момент у меня не было, то я допустил самую типичную ошибку, которую допускает, думаю, 99% начинающих ИИ программистов. Я взял статью из базы знаний целиком, добавил к ней заголовок и построил эмбеддинг-вектор сразу из всех слов статьи. В таком виде положил все вектора (для каждой статьи свой) в БД и решил, что как-нибудь сматчится (а точнее найдется алгоритмом поиска по косинусной близости) с запросом пользователя, это же ИИ, он сам разберется. Наивно да? Но я думаю, что все так или иначе с этого начинают. А потом приходит осознание, что делать надо по-другому, а именно:
разбивать статьи на предложения и хранить несколько эмбеддинг-векторов для одной статьи и искать по ним всем по отдельности
убирать из статей “мусор”, который сдвигает эмбеддинги в неправильном направлении, например мусором могут являться: числа, спецсимволы, а иногда даже сокращения и названия
у заголовка и текста разный “вес”, кроме того, вес разный у разных параграфов, как правило ответ на вопрос пользователя содержится в первых предложениях первого параграфа, а далее уже идут детали, которые сдвигают эмбеддинги в другое место, поэтому экспериментальным путем подбирайте веса для заголовка, параграфов статьи и других атрибутов, если они у вас есть
Таким образом, если вы будете реализовывать поиск по косинусной близости, нужно вначале “нормализовать” и почистить тексты, по которым предстоит искать и только потом по ним искать. Иначе получится так, что все статьи из Базы Знаний сливаются в единый “комок” в векторном пространстве (эдакая средняя температура по больнице), а запросы пользователей случайным образом вытаскивают из этого “комка” практически всегда случайную статью.
Краткий вывод: сравнивать между собой нужно тексты одинакового размера без мусора.
3. Проблема разного языка статьи и пользователя.
Почистив данные и нормализовав размер статей мне удалось существенно улучшить результаты поиска, но все равно % правильных ответов был сильно далек от идеала. Поиск возвращал примерно 50% правильных ответов, а в остальных 50% примеров фейлил. Пришло время погружаться детально в вопросы и ответы, т.е. начать вникать в предметную область заказчика. Присмотревшись внимательно к вопросам и статьям на которых алгоритм поиска фейлил, я нашел вот такие “яркие и выпуклые” примеры:
Что пишет пользователь | Соответствующая статья в БЗ |
Почему мой компьютер тормозит? | Устранение проблем с производительностью системы |
Не работает звук на ноутбуке | Диагностика и решение проблем с аудиовыходом на портативных компьютерах |
Как мне сохранить мои фотки? | Процедура бэкапирования и восстановления данных |
Первое, что пришло мне на ум, это сделать некий “словарь синонимов” что А это Б. Но как показал ряд проведенных экспериментов ничего хорошего из этого не получилось. Составление словаря синонимов это большая и неблагодарная работа, которая к тому же плохо автоматизируется. От идеи “словаря” было решено отказаться.
Вторая идея была - а давайте дообучим (отфайнтьюним) модель, чтобы она понимала что A это Б. Идея хорошая, но мы с трудом “выдавили” из заказчика 100 примеров, а для дообучения нужны десятки тысяч примеров.
Не буду утомлять читателя ненужными подробностями что было дальше, но в итоге все пришли к выводу, что никаким другим способом, кроме как “административным” эффективно решить задачу поиска не получиться. Административный способ означает, что в Базу Знаний добавляются типовые вопросы пользователей (именно так, как их пользователь формулирует на своем пользовательском языке), и поиск подходящей статьи осуществляется в первую очередь по ним. Работа тяжелая, неблагодарная, но она конечная. Для каждой статьи требуется от двух до пяти типовых вопросов (на языке пользователя) и далее алгоритм поиска косинусной близости уже сам понимает к чему ближе тот или иной вопрос пользователя.
Если у кого-то есть более элегантное решение этого вопроса (как именно сматчить между собой язык бизнеса и язык пользователя), пожалуйста, напишите в комментариях, с удовольствием обсужу и научусь чему-то новому.
Добавление типовых вопросов разумеется улучшило результаты поиска и мы постепенно поднялись к значениям в районе 75-80%. Но все равно этого было недостаточно, заказчик хотел видеть >90% правильных результатов в поиске.
Пришлось заняться файнтьюнингом модели. Кстати, модель, на которой мы в итоге остановились это multilingual-e5-large. Она лучше всех подошла по всем параметрам для нашей задачи.
Лайфхак - для того, чтобы увеличить количество примеров для обучения можно воспользоваться любой серьезной моделью типа DeepSeek, OpenAI, Gemini и т.д. (не могу знать какая модель на момент когда вы это читаете самая лучшая). Самая продвинутая модель вполне способна сгенерить разные варианты “вопросов пользователя” основываясь на тех примерах, которые вы ей предоставите.
Также большая и серьезная модель вполне можем самостоятельно разметить большую часть датасета для обучения вместо людей. Мы перепробовали несколько вариантов разметки датасетов (человеками и бездушной машиной), в итоге остановились на следующем подходе при разметке:
человеки размечают примерно 20% от общего числа необходимых тестовых и контрольных датасетов используя реальные запросы пользователей
далее самая лучшая модель генерирует оставшиеся 80% тестовых и контрольных данных
Краткий вывод: сопоставлять язык пользователя и язык бизнеса придется руками, при этом большие LLM вам в помощь.
4. Проблема отсутствия подходящей статьи.
Первым, по-настоящему серьезным вызовом оказалась задача определения когда пользователь запрашивает что-то, что есть в БЗ и когда пользователь задает какой-то заведомо левый вопрос, который отсутствует в БЗ.
Первое, что приходит на ум, чтобы понять есть статья в БЗ или нет, это взять и использовать для этого абсолютное значение косинусной близости, например:
все что больше 0.5 считать как “ответ найден и существует в БЗ”
все, что меньше 0.5 считать как “ответа нет в БЗ пользователь спрашивает что-то левое”
Но функциональный заказчик выпукло и ярко показал нам примеры, когда значение косинусной близости 0.3 и статья идеально подходит под вопрос, и когда статья со значением 0.7 вообще не соответствует тому, что спросил пользователь.
Более того, оказалось, что у разных BERT моделей значение косинусной близости разное, и забегая вперед, скажу что даже у одной и той же модели после файнтьюнинга значения близости “гуляет” в очень широких пределах.
Второй нашей гипотезой было следующее интуитивное предположение. Если существует статья или группа статей, которая находится значительно “ближе” к запросу пользователя, чем другие статьи в БЗ, значит ответ на вопрос пользователя есть в БЗ. Если же все статьи из БЗ более менее “равноудалены” от запроса пользователя, то ответ на вопрос пользователя не находится в БЗ.
Для проверки этой гипотезы мы сделали датасет из нескольких сотен вопросов на которые есть ответы в БЗ и вопросы, которые в БЗ отсутствуют (например на вопрос “Кто убил Кеннеди?” в нашей БЗ ответа явно нет). Далее мы получили результаты поиска по каждому из запросов (ТОП100 статей) и попытались обучить разные ML модели (в результате остановились на простой логистической регрессии) понимать когда возвращаемые числа (результаты косинусной близости ТОП100 статей из БЗ) свидетельствуют о том, то ответ есть в БЗ, а когда нет. Результат был частично успешным. Т.е. мы более менее точно определяли ситуацию, когда вопрос точно есть в БЗ (accuracy был более 90%). Но ситуации, когда ответа на вопрос в БЗ не было, а алгоритм показывал, что есть были примерно в 50% случаев (т.е. recall был 50%).
Для улучшения recall мы взяли исторические данные того, что пользователи реально спрашивали, с помощью большой не провославной GPT модели нагенерили еще искуственных примеров и сделали датасет для дообучения модели на предмет того, что “вопрос не к БЗ”.
В итоге мы остановились на достаточно спорном решении (в будущем мы его однозначно будем улучшать), у нас в системе появился флаг “clarify”, который свидетельствовал о том, что вопрос от пользователя “не понятен” и пользователя следует переспросить “Ваш вопрос не понятен, пожалуйста, сформулируйте по-другому”. Этот флаг “поднимается” когда модель показывает, что вопрос непонятен и пользователя надо переспросить, что он имеет ввиду.
Опытная эксплуатация показала, что первый раз пользователи более менее охотно пере формулируют свой запрос и мы получаем более менее адекватный запрос. Но! Если ситуация повторяется и мы второй, третий и т.д. пользователю пишем “Ваш вопрос не понятен…” пользователь начинает свирепеть и ничего адекватного добиться уже не получается. Проанализировав реальные диалоги, мы приняли решение задавать уточняющий вопрос пользователю ровно один раз, а на второй уже отвечать найденной статьей из БЗ.
Тем, кто заметил здесь логическую проблему, что на самом деле это две разные задачи:
Статьи нет в базе знаний
Статья есть но вопрос пользователе не понятен
пятерка за внимательность. Да мы сознательно решили эту проблему таким вот способом, потому то разделение этих двух сценариев требовало бы сильно больше инженерных затрах, чем мы могли себе позволить. Возможно в будущем мы придумаем способ как разделить эти два сценария и не тратить человеко месяцы на исследования. Если у вас есть мысли или статьи как это сделать быстро и просто, буду благодарен за обратную связь.
Краткий вывод: ситуацию, когда ответ на вопрос пользователя отсутствует в БЗ надо решать через уточняющий вопрос и дообучение модели на примерах когда надо доуточнять вопрос.
5. Проблема переключения на оператора
Вариант когда в БЗ нет статьи отвечающей на вопрос пользователя мы обрабатываем так, что однократно просим пользователя переформулировать вопрос и потом переключаем на оператора если все равно модель не может понять вопрос. Но это не единственный сценарий переключения на оператора, наш функциональный заказчик захотел также следующие сценарии переключения на оператора:
Когда пользователь явно говорит “позови оператора”. Проблема заключалась с том, что пользователь может делать это разными изощренными способами, например фразой “дай мне поговорить с кожаным мешком бездушная железяка”.
Когда пользователь пишет негатив. В русском языке способов выражения негатива также тьма тьмущая.
Когда пользователь начинает разговоры на очевидно левые (например политические) темы.
Не буду утомлять читателя всем путем, который мы прошли совершив при этом множество ошибок и потратив кучу времени впустую, сразу приведу здесь наше текущее решение.
Проверка “позвать оператора” состоит из нескольких частей:
Для определения мата и ругательств мы используем классический NLU по списку матерных слов великого и могучего
Для определения фраз типа “позови кожаный мешок” используется та же самая отфайнтьюненная e5-multilingual модель, которая определяет “тему” точно также, по типовым вопросам пользователя как и поиск по БЗ. Т.е. по сути мы завели еще одну виртуальную статью в БЗ с типовыми вопросами пользователя на тему “позвать оператора”
Для определения левых тем используем метод К ближайших соседей, т.е. мы загружаем в векторное пространство всю нашу БЗ, все левые темы, которые в теории может спрашивать пользователь (какая-нибудь большая GPT Вам в помощь, чтобы нагенерить варианты) и дальше смотрим к чему ближе запрос пользователя.
Если пользователь входит в “бесконечный цикл” и спрашивает одно и тоже, то на третий раз мы его переключаем на оператора.
Получилось не идеальное решение, но достаточное для того, чтобы быть задеплоенным на продакшен. Разметка реальных диалогов пользователей показала, что balanced accuracy функциональности переключения на оператора при таком подходе составляет 96%, что очень даже хорошо. Сразу отвечу на вопрос как мерили balanced accuracy. Было два класса “требуется переключение на оператора” и “не требуется”. Дисбалланс между ними 1 к 9. В выборку отправили все диалоги пользователей за месяц (несколько тысяч). Для бизнеса большой разницы в том, что мы подключили лишнего оператора или наоборот не переключили пока нет, так что классы равномощны.
Но, хочу обратить внимание, что политика Вашей компании по переключению пользователей на оператора может вносить значительные коррективы в то, какую функциональность Вы будете в итоге программировать, Как правило любая компания стремиться минимизировать число операторов, просто потому, что это дорого.
Краткий вывод: с самого начала обсудите с бизнесом правила переключения на оператора и смотрите реальные диалоги пользователей, чтобы понять когда нужен оператор.
6. Проблема получения обратной связи
После того, как мы сделали валидационные датасеты, мы получили возможность измерять качество поиска и казалось, что проблема с качеством решена. Но, мы же делаем продукт в первую очередь для конечного потребителя, поэтому имеет смысл также измерять качество поиска с точки зрения потребителя. Для этого давным-давно придумали кучу метрик, типа DAU, WAU, MAU, Retention rate и т.д. здесь не буду на них останавливаться, цель этой статьи именно специфика поиска в базе знаний.
Итак, что касается специфики поиска. Самым простым и логичным показателем на первоначальном этапе нам показалось сбор обратной связи от пользователя по результатам поиска. Например “пять звездочек”, или “палец вверх, палец вниз”. Но! Как оказалось, таким образом пользователь оценивает в первую очередь свое собственное общее впечатление от продукта, а не качество поиска. Т.е. грубо говоря, пользователь ставит одну звезду за то, что:
- в базе знаний отсутствует статья, которая отвечает на вопрос пользователя
- статья в базе знаний написана черт пойми как, хотя поиск нашел все правильно
- статья правильная, но в интерфейсе пользователя она отобразилась криво
и еще миллион случаев, где поиск отработал правильно, а пользователь все равно поставил “палец вниз”.
Я не говорю, что такая оценка пользователем в интерфейсе абсолютно бесполезна, но полагаться на нее не стоит. Мы долго искали каким образом можно было бы измерить качество именно поиска со стороны пользователя не особо напрягая при этом пользователя, но в итоге никакой серебряной пули мы не нашли и в данный момент измеряем несколько специфических именно для поиска показателей:
Использование ненормативной лексики - удивительно, но если человек не находит того, что искал, то очень часто он начинает ругаться прям в чате и это хороший показатель того, что поиск работает неправильно.
Доля пользователей вызвавших оператора - также наблюдается прямая корреляция между количеством взыванию к оператору и отсутствием ответа на вопрос пользователя.
Переходы по ссылкам в статье - если человек нашел правильную статью, то он ее читает и далее уже переходит по ссылкам, совершая какие-либо действия.
На данный момент мы не нашли еще каких-то прямых или косвенных метрик с помощью которых можно измерить качество поиска по базе знаний в автоматическом режиме. Если у Вас, есть идеи или опыт реализации подобного, делитесь, пожалуйста в комментариях - обсудим.
В итоге самым надежным способом оценки качества поиска стала старая добрая ручная разметка данных на основе реальных диалогов пользователей. Со случайными выборками, доверительными интервалами, белковыми разметчиками, экспертами для валидации случаев, когда разметка обычных пользователей не совпадает и т.д. Не буду останавливаться подробнее, так как это тема для отдельной статьи, но если кто-то хочет узнать подробности, пишите в комментарии, я напишу отдельную статью, про то, как правильно делать ручную разметку реальных диалогов пользователей для оценки качества работы ИИ.
Краткий вывод: реально оценить качество поиска можно только вручную, остальные автоматические методы могут только косвенно свидетельствовать о том, на сколько функциональность поиска по БЗ отрабатывает корректно.
7. Проблема дублей статей
Еще на этапе тестирования нашего решения фокус группой мы (как всегда ВНЕЗАПНО) выяснили, что статьи в базе знаний очень часто дублируют друг друга. И это в целом логично, когда база знаний большая и заполняется многими администраторами/редакторами, то неизменно возникает ситуация, когда появляются дублирующие статьи. Причем дубли статей могут быть нескольких типов:
похожая по смыслу статья, которая призвана заменить старую, но старую (по какой-то причине не нашли) и в результате в БЗ появилось две, почти одинаковые статьи
более детальная статья, например у вас в БЗ уже есть статья “как отформатировать жесткий диск”, а редактор добавил статью “как отформатировать диск на Mac”
и обратная ситуация, когда есть несколько специфических статей и добавляется новая обобщающая статья
Причем если Вы вот прям здесь и сейчас занимаетесь функциональностью поиска по базе знаний, это означает, что у редакторов не было инструмента который бы им помог обнаружить (с помощью еще не существующего качественного поиска) похожую статью и вместо того, чтобы просматривать сотни, а то и тысячи статей, редактор решает, что гораздо проще и быстрее написать новую. Редактор думает: “ну и фиг с ним, больше не меньше, будет две”. А о том, что у конечного пользователя взрывается мозг, когда пользователь в результатах поиска видит две практически одинаковые статьи, редактор думает в последнюю очередь.
Единственным действенным способом избежать дублей является создание отдельного инструмента, который на регулярной основе (еженочно, по факту добавления/редактирования статьи и т.д.) проверяет всю БЗ на тему похожих статей и выдает редактору предупреждение (или прямо запрещает редактирование) о наличии похожей статьи.
К счастью, это оказалось относительно простой задачей с учетом того, что поиск по БЗ уже был готов. Мы взяли наш текущий алгоритм поиска статей если расстояние между двумя верними статьями было меньше определенного порога (мы его постоянно меняли в пределах от 0.3 до 0.1) то мы показывали статьи как дубли.
От команды потребовалось только две вещи:
Осуществить первоначальную очистку “авгиевых конюшен” базы знаний от дублей. Это можно и нужно сделать в полу ручном режиме, с подбором коэффициентов.
Создать отдельный инструмент, который будет регулярно следить за появлением новых дублей и информировать о них.
В целом это больше административная работа, но ее требуется провести ДО запуска в продакшен, в противном случае вам придется заниматься вычисткой дублей в авральном режиме, получая от ваших пользователей кучу негатива на пустом месте.
Краткий вывод: как минимум 50% вашего успеха зависит от качества статей в БЗ, не поленитесь и сделайте отдельный инструмент для редакторов и вместе с ними заранее очистите базу знаний от дублей.
