
Привет, чемпионы! Сегодня я расскажу вам, как мы придумали решение, как используя небольшую надстройку над LLM сделать ее несколько самооценивающейся, что позволило нам при работе с локальными данными и внутренней документацией уменьшить число иницирований работы RAG пайплайна, когда в этом нет необходимости.
Суть проблемы
Мы разрабатывали Endpoint
, который должен был иметь следующие функции:
Ответы на вопросы по аналитике полученных данных сотрудниками
Взаимодействие с внутренними документами для ответов на вопросы
Ответы на вопросы сотрудников системы при всех остальных случаях, когда внутренние документации или данные не требуются

Как итог, мы получаем систему в виде модели, в которую идут запросы по разным темам.
В каждом из узлов нашей системы мы решали системы по оптимизации вычислений совместно с повышением качества ответов. И одним из таких узлов стало использование вычислений на RAG пайплайны.
Решение с тем, чтобы разделить 1 и 2, 3 направлением задач было весьма простое, хоть и потенциально странное для кого-то, но работало очень эффективно, мы добавлением простой кнопочки для разделения того, к какой инфраструктуре хранения обращаться, смогли снизить нагрузки на gpu на 11%, однако с тем, чтобы разделить 2 и 3 направление задач возникла сложность.
Добавление кнопочки "обратись к внутренним документам" возможно и было бы простым решением, однако ответьте себе честно, как часто вы бы сами нажимали эту переключалку?
Ведь даже добавление перехода режима работы с данными на диалоговый уже потребовал много усилий по тому, чтобы сотрудники не забывали переключиться. А как вы знаете, чем больше кнопок и разных настроек, тем менее дружелюбным становится интерфейс.
Наши гипотезы и их результаты
Тут мы разберем пул поставленных нами гипотез, опишем их оценку или попытку интеграции, а также то, к чему мы пришли сами.
Сначала появилась куриц...корпус из 100 диалогов, в которых мы оценивали ответы на вопросы по логам из обращений в RAG и производили оценку, разбивая их на сообщения с ответами модели и делая оценку насколько вообще внешние источники данных реально помогали модели, а где она справлялась сама. После составив тестовый корпус, что состоял из примерно 3500 взаимодействий юзер-модель мы пришли к выставлению набора решений
Дать модели оценить саму себя
Идея проста, отправить запрос модели, после чего с помощью промта сделать модель, что будет эмулировать проверяющего, который оценивает ответ своего брата-близнеца и выдает свои оценки от 1-5 по категориям: "Релевантность", "полнота", "степень уверенности в ответе"
Как прототип для старта в целом пойдет, однако результат снижения обращений к RAG, а как следствие понижение нагрузки на дашбордах был 2% и порядка 15 процентов recall. И действительно, чем это отличается от того, когда вы на самостоятельной меняетесь с другом листочками и проверяете друг друга?

Обращение к модели с более большим числом параметров
Данная идея вышла бы неплохо в случае api, заплатить сколько-то денег за токены, пооценивались и круто. Закончилось все правда на том, что инфраструктура локальна.
Не беда! Возьмем опенсорсную модель просто большего размера. Но тут мы сталкиваемся с тем, что это влияет напрямую на пиковую нагрузку на VRAM при работе с большой моделью.
Коллективная проверка
Мы задаем трижды запрос пользователя к модели, получая при этом разные ответы. Разные, но! Часто-ли вы отвечаете по-разному о том, как создать функцию на питоне? Или какова корреляция будет у ответов студентов о том, что такое интернет? В среднем должна быть высокая.
Данная гипотеза состоит из того, что мы получаем несколько ответов на вопросы
Скрытый текст
def self_check_consistency(question: str, n_variants: int = 3):
answers = []
for _ in range(n_variants):
a = generate_answer(question)
answers.append(a)
emb_score = embedding_similarity(answers)
return emb_score, answers[0], answers
После чего мы получаем фичи у ответов и выстроим их корреляцию
Скрытый текст
def embedding_similarity(answers: list[str]) -> float:
embeddings = embed_model.encode(answers, convert_to_tensor=True)
similarities = []
for i in range(len(answers)):
for j in range(i + 1, len(answers)):
sim = util.cos_sim(embeddings[i], embeddings[j]).item()
similarities.append(sim)
return sum(similarities) / len(similarities) if similarities else 0.0
Какие плюсы? Данное решение делала отсев ответов, которые в явном виде на запросы из документаций внутри системы позволяло получить низкий скор, что требовало обращение к RAG и повторному ответу. Это дало 5 процентов понижения нагрузки c 43 recall.
Минусы? Нет явного отсева чистого рода "коллективного бреда", то есть когда вы хотите спросить про метод из документации, а модели посовещались и решили, что знают одноименный, поэтому ответим примерно также.

Соединяем коллективизацию с оценкой.
Мы также считали скор между ответами, а также выдавали оценку от критика. Итогом мы получаем оценку скора схожести и оценку критика. Но остается проблема, вернее теперь их две, первая и вторая соответствует тем решениям, что описаны выше. Тогда мы придумали костыли для нашей модельки в виде небольшого взвешивания всех результатов и итогового принятия решения нужен ли RAG или нет. Тут же возник момент с тем, что мы можем до обучать на ответах модели нужен-ли ей RAG, но диапазон признакового пространства в этом случае становится сильно выше. Тогда мы сделали решение обучить на ответах от созданных наших аналитиков меток и целевой меткой нужен-ли наш пайплайн, ведь информация эта уже была.
После обучения нашей выдачи весов мы получили наш итог маленького модуля

Такое решение дало наше финальное снижение на 11% с recall 93 процента.
Итоги
На текущий момент мы прорабатываем более оптимальное и более качественное решение. Если вы хотите что-то предложить, пишите об этом в комментариях. Самое интересное, что несмотря на всю казалось бы костыльность решений, это действительно помогло нам.
? Ставьте лайк и пишите, какие темы разобрать дальше! Главное — пробуйте и экспериментируйте!
✔️ Присоединяйтесь к нашему Telegram-сообществу @datafeeling, где мы делимся новыми инструментами, кейсами, инсайтами и рассказываем, как всё это применимо к реальным задачам