Работа над ошибками
Большие языковые модели типа GPT ворвались в IT-мир, буквально заставляя всех вовлеченных, от дата-сайентистов до традиционных веб-разработчиков, пересматривать свои приоритеты. Помните времена, когда веб обходился без GPT? Теперь без элементов искусственного интеллекта не обходится не только ни один стартап, но и вполне традиционные проекты поневоле вовлекаются в использование нового инструментария. Этот взрыв интереса вызвал не только волну переобучения в индустрии, но и заставил многие команды переосмыслить традиционные подходы к машинному обучению и разработке продуктов в целом. Но особенность любой стремительно развивающейся прикладной области состоит в том, что она порой оставляет слишком много знаков вопроса.
Когда мы с командой брались за создание MVP нашего консьерж-сервиса для букинга отелей — части большого мира тревел-технологий, казалось, что это область, в которой давно не осталось нерешенных, и при этом значимых проблем, суть лишь в том, чтобы сделать сам процесс гибче и удобнее. Но на практике, разумеется, все оказалось несколько сложнее.
Использование стандартного набора инструментов (python для ML, собственный код на бэке и веб-интерфейсы) в сочетании с новыми коммерческими API открыло перед нами не только новые горизонты, но и обеспечило массу подводных камней, вполне традиционных для такого рода стартапов. О том, как мы с ними справлялись, мы и решили написать эту небольшую статью. Надеемся, что наши уроки помогут вам избежать наших ошибок и ускорить разработку вашего прототипа.
Шаг нулевой: Только базовый инструментарий
Для прототипа нашего чат-бота мы решили использовать минимальный набор инструментов: в качестве интерфейса — bot-master на socket.io, для серверной части — Node.js, а сердцем всего стал gpt-4-turbo-preview от OpenAI. При этом мы рассматривали и другие варианты, включая опенсорс-решения и конкурентные коммерческие API, хотя в итоге и остановились на самом очевидном. Важно отметить, что наши выводы не привязаны к конкретной архитектуре или продуктам отдельных разработчиков, они касаются общего подхода к использованию современных генеративных моделей.
Главная идея заключалась в том, чтобы не углубляться в сложности технической реализации. Нам нужно было создать систему, способную гибко передавать данные между пользователем и AI, обеспечивая прокидывание данных обратно. Более сложные функции, такие как продвинутое сохранение истории чата, анализ контекста и вариативные диалоговые механизмы, мы сознательно оставили за рамками MVP. Давайте сделаем просто, прежде чем начинать делать сложно. К нашему удивлению, именно такой подход позволил достичь наилучшего результата, что мы и покажем далее.
Шаг первый: Бесконечность – не предел!
Первая же примитивная сборка, больше похожая на то, что в современном коммерческом chatGPT несколько тавтологично называется gtp’s, а чуть раньше называлось просто «ассистент», сразу показала себя звездой. Наш виртуальный помощник путешественника не просто владел искусством разговора, он уверенно галлюцинировал на разные темы, щедро обещая всевозможные чудеса, скидки и кэшбэки. Разумеется, большинство из этого не соответствовало реальности, но в тот момент это показалось нам наименьшей из проблем, ведь эта штука работала!
Весь наш фокус на базовой модели LLM заключался в добавлении кратких инструкций в стартовое сообщение: «Кто мы? Где мы? К черту подробности!» И, конечно, мы не забывали кормить систему предыдущими репликами диалога для следующей итерации, для консистентности ответов. Этот чат-бот был почти как живой: рекомендовал отели (иногда устаревшие или вовсе выдуманные), планировал бюджет (чуть ли не с потерей сознания от перевода валют), одновременно вежливо игнорируя любые разговоры не по теме путешествий, и все это придавало нам уверенности в том, что мы на правильном пути.
Шаг второй: Ошибка осведомленности
Когда мы, чувствуя себя опытными мастерами ML, столкнулись с тем, что наша нейросеть не умеет чего-то нужного, решение было очевидным: обучить ее этому. Мы принялись за работу с энтузиазмом, бросившись формировать обучающие выборки, писать обвязочный код, пускай четвертая gpt от OpenAI еще не доступна для кастомного обучения широкому кругу разработчиков, значит, обойдемся уже довольно устаревшей gpt-.3.5-turbo, для демки сойдет. Тем более, что задача не выглядела такой уж неподъемной — взять в качестве базы для файн-тюнинга генеративный бред базовой модели, подправить ее слегка с учетом реальной действительности применительно к нашему конкретному сервису, добавить вариативности, загнать в машинку, подождать полчаса и пожалуйста — кастомизированная модель действительно стала значительно меньше ошибаться в фактологии.
Однако ключевая проблема такого подхода была замечена сразу – наш чат-бот стремительно глупел даже по отношению к базовой 3.5, в сравнении же со свежей четвертой версией он проигрывал во всем — и в первую очередь в количестве производимого им машинного бреда. Его стало гораздо больше, генерация начала местами буквально заикаться. Можно было двигаться дальше экстенсивным путем, уточняя обучающие диалоги, расширяя предметную область, но тут мы спохватились и, к счастью, не стали так делать.
По сути, мы тем самым попытались превратить LLM широкого профиля в LLM узкого, в котором «тонкий клиент» между моделью и пользователем стремительно толстел», быстро обрастая жонглированием разными моделями, тонной регулярок и уводя разработку в область, ну, да, традиционного олдскульного ML с упором на NLP, просто на ином технологическом уровне в лице новомодной трансформерной архитектуры. А это было не то, к чему мы изначально стремились. Опять же, как мы покажем далее, это также было правильным решением, в противном случае мы начали бы двигаться совсем не туда.
Шаг третий: Вторая жизнь обучающих диалогов
К счастью, одним из артефактов неудачного предыдущего захода нам достался набор готовых диалогов, которые мы бы хотели видеть в исполнении нашего электронного консьержа. Поскольку главная его функция — все-таки подсказывать и давать советы, а не заменять базовые интерфейсы, то логично было сразу пропустить целую фазу разработки и сразу приступить к сути.
А именно, поскольку тупое запихивание сырых проектных хелпов в «шапку» ассистента — не самый экономный способ (расход токенов растет вместе с размером промпта на каждый запрос), сами хелпы приходится или сокращать, или резать на тематические куски. Но именно в этом ранее и состояла суть генерации обучающих диалогов. Потому основной идеей на данном этапе стало дозированное подсовывание этих диалогов в качестве якобы уже данных ранее ассистентом ответов, и пожалуйста, уровень бреда стал минимальным, промпты же почти не изменились в объеме.
Элегантное решение пришло в виде интеграции с elasticsearch. Мы стали подсовывать чат-боту в качестве аугментации наиболее релевантные ответы из базы вопрос-ответ, добавляя немного случайности и фильтруя ключевые слова из нашего проектного словаря. Это позволило существенно снизить уровень машинного бреда в ответах, сохранив прежний объем промптов. По сути, мы сперва искали наиболее подходящую «шапку» помощника, а он уже отвечал с ее учетом. Такой подход не только улучшил качество ответов, но и создал понятный датафлоу, где любые новые инструкции механически превращались в четкие указания для бота.
Теперь настала пора научить его тому, что базовая модель не знала вовсе, или же ее знания были достаточно отрывочными. Обучить ее правильно подбирать отели, поскольку туристически важные города, страны и регионы свежая gpt-4-turbo-preview описывала и рекомендовала исчерпывающе прямо из коробки.
Шаг четвертый: Промпт не резиновый
И тут перед нами встала вся грандиозность проблемы – если хелпы были вполне ограничены в объеме, и на самом деле исключительно экономия токенов мешала просто поместить в генерацию все ключевые диалоги разом (не делайте так), то отелей в нашей базе было более 3 миллионов, а их описания в сыром тексте составляли десятки гигабайт. Наш проверенный метод с эластиком и ключевыми словами работал и тут, но только если путешественник точно знал, куда и зачем он едет. Мы вложили уйму времени в то, чтобы наш чат-бот мог вытаскивать из диалога всю необходимую информацию о поездке: цели, даты, бюджет, количество гостей и так далее, чтобы затем предложить направления и конкретизировать города и страны.
Однако когда доходило до выбора конкретного отеля, наш бот вновь показывал свою несовершенность, ограничиваясь предложением из заранее подготовленного списка в пару десятков одних и тех же заезженных вариантов, что, по сути, повторяло функционал тех самых надоевших всем веб-интерфейсов. Ведь главная задача любого консьержа — предложить не просто любой вариант, а именно тот, что идеально подойдет вам, учитывая все пожелания и требования. Какой смысл в чат-боте, если он не способен справиться с этой задачей лучше, чем стандартный поиск по сайту?
И тут мы столкнулись с ограничениями по токенам, которые не позволяли нашему роботу быть достаточно гибким и многофункциональным, особенно когда речь идет о таком многообразии, как, например, Париж с его огромным выбором отелей. Бот выдавал похожие, механические ответы, не способный учитывать сложные запросы вроде «хочу жить вдали от центра, но с удобным доступом».
Тут мы осознали, что нужен ключевой механизм, часть которого мы уже разработали, работая над базовым функционалом. Этот механизм должен был сделать нашего чат-бота настоящим электронным консьержем, способным удовлетворить любые запросы путешественника.
Шаг пятый: Кролик из шляпы фокусника
Одна из ключевых особенностей работы с естественными запросами состоит в их неопределенности. Недостаточно знать контекст, необходимо уметь анализировать намерения пользователя даже там, где он их не формулирует явно. Например, такой вполне естественный диалог:
— Я собрался в отпуск на неделю.
— О, круто, на майские?
— Какой там, фигли меня отпустят. В середине июня.
— Как обычно, с семьей в Анталью?
— Нет, на этот раз выберу что-то менее бюджетное, Грецию или Кипр.
Что, с формальной точки зрения даже самый умный робот эры до LLM понял бы из этого разговора? Точных дат не указано, точного места нет, что такое «с семьей», а «менее бюджетное» — это дешевле или дороже? Но современные большие языковые модели щелкают такие заходы, как орехи. Мы быстро научили нашего робота запихивать в контекст максимально формализованный и максимально машиночитаемый intent, на основе которого легко получались высокоуровневые выборки из базы, единственный вопрос, который не решался «в лоб» — это подмешивание более точных наборов предложений: не хватало персонализации.
И тут мы вновь применили старую наработку с базой знаний для сайта, а именно, подготовили при помощи того же gpt-4 по каждому отелю краткие брифы для основных классов поездок (по бизнесу, пляжный, романтический и т. п.) и ценовых когорт, загнали их в «эластик», и научили робота выбирать себе в аугментацию случайные 20 из наиболее релевантных 50 отелей для конкретного контекста, после чего просили его провести анализ и выбрать наиболее подходящие на основе уже совсем точечных его требований вроде наличия парковки, размещения не в туристическом центре и тп.
Результат получился очень выразительным — ответы были уникальными, отели подбирались в точности соответствующие запросам пользователя, а общее разнообразие предложений (робот бодро предлагал варианты чуть подороже с учетом скидок и кэшбеков), предложения проверялись на лету на доступность.
Шаг шестой: Тренируемся на кошках
К тому моменту в нашем чат-боте появилась новая фишка — он начал привлекать свежие отзывы о гостиницах в реал-тайме прямо с публичных платформ. Это преобразило нашего робота в настоящего критика: он научился высказывать сомнения по поводу звезд и рейтингов отелей, предлагая пользователям ознакомиться с живыми отзывами. С одной стороны, это дало нам уникальный инструмент для пересмотра рейтингов отелей, которые казались не такими уж идеальными на фоне реальных мнений. С другой — это позволило нам задуматься над более тщательным дообучением модели, чтобы она сама могла различать, какие отели действительно стоят внимания, даже без привлечения мнений пользователей.
Эта эволюция бота обнажила один недостаток: наш электронный помощник стал довольно прожорливым по части ресурсов. Хотя экономическая сторона вопроса нас устраивала, мы все же хотели сделать систему более экономичной. Решение? Тонкая настройка базовой модели под специфику поиска и рекомендаций отелей, чтобы сделать ее работу эффективнее без необходимости постоянно привлекать дополнительные данные.
Как будто по волшебству, у нас уже была вся необходимая база для начала такого дообучения. Это открыло перед нами возможность создать версию чат-бота, которая не только точно отвечала бы на запросы пользователей, но и делала это с меньшими затратами на аугментацию. В итоге, наши усилия привели нас к началу нашего MVP, но на более высоком уровне кодовой базы и понимания наших целей, возможностей и задач. Что само по себе очень правильно, поскольку в идеале любая разработка — это закольцованный бесконечный процесс, в котором каждое нововведение ведет к новым идеям и улучшениям.
Выводы
Итак, в процессе разработки нашего консьержа мы обнаружили, что самым многообещающим методом работы с gpt является комбинация «промпт-мастеринга» и метода «аугментации данных». Этот подход позволяет нам на полную мощь использовать все преимущества предобученных коммерческих LLM моделей, избегая при этом обилия костылей и держа уровень генеративного бреда под контролем.
Это, по сути, единственный путь, который позволяет нам аккуратно и с предсказуемым результатом переходить к более глубокому файн-тюнингу специализированных моделей без потери их когнитивных способностей. Наш фреймворк дает возможность гибко балансировать между моделями разной «прожорливости» — например, за сбор интента и контекста может отвечать GPT-3, сами же хранилища исходных данных даже при значительных их объемах не составляют для подобной обвязки никакой заметной проблемы.
Хотя мы пришли к этому решению не сразу и по дороге приняли несколько поздних ключевых решений, уровень повторного использования большинства наших наработок и кодовой базы оказался удивительно высок для R&D проекта, где сохраняется высокий уровень неопределенности. Надеемся, наш опыт окажется полезным для коллег, ищущих эффективные способы внедрения LLM в свои продукты.