"LLMка вызвала инструмент". Нет, не вызывала 🤩🪚
Мы ежедневно употребляем фразы про вызов тулов ллмками, но по факту это лишь инженерная абстракция, давайте разбираться почему 🤔
Шаг 1: инъекция тулов в промпт
Модель ничего не вызывает, у LLM нет рантайма, она не ходит в сеть и даже не исполняет код. Единственное, что она умеет (и этого хватило для AI-революции) - предсказывать следующий токен. Тулы - это просто текст в контексте: раннер движка инференса собирает описания инструментов и кладет их в промпт. Для самой модели "системное описание тулов" не является какой-то сущностью и ничем не отличается от "привет, как дела?" - все это тот же самый тензор input_ids.
Шаг 2: попадание в "нужный режим"
А вот способность "вызвать тул" - результат файнтюна, а не архитектуры. Чтобы модель умела такое делать - ее специально обучают на примерах таких диалогов. То есть, собирают обучающий пример "контекст->задача->вызов тула->результат". Скормив модели тысячи таких диалогов, мы добиваемся, что она статистически выучивает закономерность: при наличии <tools> в контексте и подходящем запросе высоковероятны токены вызова, а не какой-либо текст.
И здесь есть важный момент про гарантию правильности всей схемы. Есть вариант доверия - мы просто ждем от модели, что она вернет правильную схему вызова тула, но здесь начинаются галлюцинации, битый json и прочие нерадости. Вариант так себе.
Второй вариант - насильное ограничение, именуемое так же "constrained decoding". В этом подходе на каждом шаге сэмплинга выбираются только подходящие под грамматику токены, а логиты, ведущие в невалидное состояние, обнуляются до -inf еще до софтмакса и json schema инструмента компилируется в автомат, который на каждом шаге допускает только валидные продолжения. Условно, если мы ожидаем закрытие кавычки, то модель физически не может выдать что-то другое.
Шаг 3: момент остановки
Модель генерирует вызовы и в какой-то момент ей надо остановиться. И это так же делается через stop-токен (условный "<|tool_call_end|>"), после которого декодирование завершается. Инференс-раннер должен это распознать и прекратить генерацию, то есть мы просто сгенерировали моделью блочок с намерением что-то вызвать, а дальше все передается на слой выше.
Шаг 4: оркестратор выполняет тул
Раннер выполняет намерение, получает результат, а затем просто точно так же весь вывод подает в модель. С точки зрения модели, здесь нет никакого "вызова" функции, она видит текст намерения, потом текст результата, а затем продолжает только то, что умеет хорошо - предсказывает следующий токен.
Здесь есть важный нюанс. Каждый тул-кол - это новый префилл - результаты добавляются в конец контекста и его надо прогонять через все слои. KV-cache предыдущих токенов переиспользуется, но новые токены результат требуют полноценного префилла для дельты - а значит чем короче будет ответ тула (и сам диалог перед ним!), тем лучше.
Ну вот и все!
Поэтому фраза "LLM вызвала инструмент" на самом расшифровывается так: модель, дообученная на соответствующих траекториях, сгенерировала и, возможно, под жестким контролем грамматики — последовательность токенов, похожую на структурированный вызов; затем выдала стоп-токен; затем внешний оркестратор распарсил эти токены, нашел реальную функцию, исполнил ее и закинул результат обратно в контекст новой пачкой токенов.
Но мы, конечно, будем продолжать говорить как говорили 🤩🙂
Post #84
1.62K
- 👍 18
- 🐳 3
- ✍ 2
- ❤ 1
- 👏 1
- 🤯 1
- 🙏 1