Путь кода VS путь языковых моделей

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

Давайте сравним эти подходы на примере простой задачи — автоматизации проверки оформления задачи на канбан-доске.

Начнем с того, что подразумевается под этими подходами:

  • Путь кода — это ручное написание проверок, куча if-ов. Если вы разработчик, то скорее всего вы так умеете.

  • Путь языковой модели — использование большой языковой модели (ИИ).

Задачка

Наша цель: недостойно оформленные задачи не должны попадать в работу.

На канбан-доске этого можно добиться, отслеживая перемещение карточек из стадии «Оформление» в стадию «Готово к работе». Конечно, есть и организационные методы, но сегодня не о них.

Пути решения

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

Путь кода

af96098d5962c93f2b9953008f66d8b9.png

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

public bool checkIsVald(string document) 
{
    return !string.isNullOrEmpty(document);
}

Если у карточки на канбан-доске есть формализованная модель, состоящая из полей, то тут все тоже относительно просто.

public bool checkIsValid(Document document)  
{
    return !string.isNullOrEmpty(document.Description) 
         && !string.isNullOrEmpty(document.Title);
}

Если любое содержимое поля нас устраивает, то можем тут и остановиться и дальше не заходить. Но если захотим пойти дальше, то всё резко становится сложнее. 

Это путь кода, он не должен быть простым.

Что ещё можно сделать? Можно вместе собраться, пообщаться и формализовать набор правил, по которым мы хотим определять правильные, хорошие карточки. Например:

1. В карточках, содержащих исправления в UI, нужно не забыть добавить указания для тестировщика

2. …

Дальше по пути кода нам придётся в документную модель карточки добавить флаг «Правки по UI», добавить поле «Вводные для тестировщика» и так далее. Получится что-то такое:

public bool checkIsValid(Document document)  
{
   return !string.isNullOrEmpty(document.UIFixes) 
        && !string.isNullOrEmpty(document.NotesForTester);
}

То есть придумываем набор правил, формализуем их в полях, которые пользователь должен будет заполнить. Пока ему не надоест. А если на входе просто текст, а не набор полей, то придется написать парсер, который извлекает эти поля. Парсер далеко не всегда штука простая, обычно наоборот. Кстати таким образом устроен известный многим Петрович — библиотека, которая позволяет склонять ФИО по правилам русского языка. Примерно так были устроены самые первые системы языкового перевода.

У пути кода есть проблема: очень быстро растущая комбинаторная сложность. Особенно если вы захотите таким набором правил формализовать какие-то не очень строгие вещи. Например, насколько въедливо написаны требования. Количество If-ов в такой программе довольно быстро начинает превышать предел, который может вместить голова в попытках уследить, чтобы if-ы не противоречили друг другу. В какой-то момент голова от количества правил просто лопнет.

Но у такого подхода, конечно, есть плюсы. В этом случае набор правил — это белая коробка. Он быстро работает, и к нему не нужна видеокарта. Но нужен человек с большой головой, чтобы эти правила писать. Тем более если набор правил сложный, объемный и местами противоречивый.

Путь большой языковой модели

21c0e11fac167e8a3eeb433de5cf1735.png

Если у нас есть API, позволяющее использовать языковую модель, то мы можем отдать ей на вход свой набор правил и просто задать вопрос. Этот способ назовем путем языковых моделей.

public async Task checkIsValid(Document document)  {
return await llm.Generate(Rules.DocumentQuality + "\n----\n" 
                          + "Name:" + document.Name
                          + "\nTitle:" + document.Title + "\n----\n"
                          + "Проверь соответствие полей на выполнение "
                          + "правил. Отвечай только да или нет, больше ничего не пиши"
                         ) == "да";
}

Обращение по такому API эквивалентно к обращению к ChatGPT. Проверить, как работает модель, подобрать запрос (промпт) можно в самом интерфейсе.

Грабли и костыли на пути большой языковой модели

  • Не все модели любят отвечать коротко, а ещё нет гарантии, что модель напишет подробный рассказ, почему она ответила «Да» или «Нет».

Костыль:  API, предоставляющие языковые модели (не все), позволяют вытаскивать вероятности продолжения в токенах. Нам нужна вероятность «Да» по отношению к «Нет».

Другой костыль:  Вызывать модель до тех пор, пока не выпадет «Да» или «Нет». И учесть, что модель может написать дальше много не очень важного текста, а также не проверять регистр.

Костыль:  Повторить несколько раз. Усреднить.

Костыль: Может полечиться подбором промпта, но не всегда помогает.

Ещё:  Дообучить модель. Потратив вычислительные ресурсы, собрав кучу примеров, мы сможем научить модель значительно лучше решать нашу задачу. Сейчас в API провайдерах появляются методы дообучения, но набор примеров все равно нужно собрать. Кстати примеры можно подать на вход вместе с данными, иногда помогает.

А ещё может помочь смена модели.

Все эти грабли и костыли идут от того, что языковые модели по сути продолжатели вероятностного пути, в котором считают частоты слов и различные условные вероятности.

До сих пор эти способы никуда не делись из продакшн систем и хорошо работают в ряде сценариев (например, фильтруют спам). Но как и у любой вероятностной модели, теперь появляется вероятность ошибки, с которой нужно жить. 

Outro

По сути мы обменяли негибкость принятия решения на допустимость наличия ошибки. И это основное отличие пути кода и пути языковых моделей.

В контексте бизнес-сценария канбан-доска хорошо стыкуется с языковой моделью, так как у нее низкая цена за ошибку. Даже если модель ошиблась, пользователь может еще раз перетащить карточку после отказа или пойти править правила. В том же документообороте никто не запрещает нам, например, применить подобный подход для того, чтобы предварительно проверить документ за человека, сэкономив время. Главное учесть больные места языковых моделей. И дать возможность пропустить проверку.

Тут стоит сразу оговориться, по поводу выбора пути. Почему же, иногда получается так, что применение языковой модели оправдано, ведь всегда можно открыть среду разработки, написать заветные несколько If-ов? Если вы разработчик, часто так и есть. Но попробуйте формализовать требования к стилю, тону, вежливости.

Также не стоит забывать, сколько времени и сил стоит добавить в код поддержку всех полей, учесть их вариативность. А что если требования меняются? Это всё работа. А с использованием языковой модели набор правил в такой системе может править не разработчик, а любой сотрудник. И не нужно делать сложные формы с галочками и полями.

Главное, это не забыть предоставить инструменты для оценки того, насколько часто или редко конкретный набор правил заставляет модель ошибаться. Иногда удается так описать правила, что языковая модель ошибается реже.

Сценарий валидации не является единственным, также можно использовать языковую модель для парсинга. Например, чтобы извлечь из документа нужные поля в JSON.

Главное помнить, что правила по сути те же.

© Habrahabr.ru