Slot Filling

Накопление аргументов инструмента по репликам диалога вместо попытки собрать их за один ход. Валидация откладывается до момента, когда набор полон, а недостающее агент спрашивает сам — вопросом, сформулированным по смыслу пустого поля.

Суть

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

Слот-машина разводит два разных момента: сбор и проверку. Аргументы накапливаются в состоянии сессии как отдельные слоты, и только когда набор становится полным, он уходит в схему целиком.

Зачем это нужно

Без этого разделения остаются два плохих выхода. Либо валидация срабатывает рано и диалог превращается в череду ошибок, либо схему делают целиком необязательной — и тогда она перестаёт что-либо гарантировать, а инструмент получает наполовину пустой вызов.

Слоты дают третий: строгость схемы сохраняется, но применяется в правильный момент.

Как работает

Изоляция. Инициализация модели данных отделена от сохранения параметров в сессии. Слот кладётся в состояние сам по себе, без попытки собрать из него валидный объект.

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

Динамический вопрос. Когда набор неполон, оркестратор не падает и не выдумывает значение, а приостанавливает вызов и формулирует уточняющий вопрос — исходя из семантики недостающего поля, а не из его имени. Пустое поле «дата доставки» превращается в вопрос про срок, а не в просьбу «укажите поле date».

Проверка на полноте. Как только слоты собраны, объект уходит в валидатор один раз и целиком — дальше работают обычные правила Structured Output и Validation Loops.

Пример

# слот кладётся в сессию как есть, без сборки объекта
session.slots.update(extract_slots(user_message))     # неразрушающее слияние

missing = REQUIRED - session.slots.keys()
if missing:
    return ask_about(missing)                          # вопрос по смыслу поля

args = ToolArgs(**session.slots)                       # валидация один раз, на полном наборе

Время жизни слотов и противоречия

Сколько держать частично заполненное. Три политики решают разные проблемы, и обычно нужны две сразу. Привязка к сессии проста, но на длинной сессии пользователь возвращается к теме через полчаса и удивляется, что система «помнит» отменённое намерение. TTL решает это, но не отличает паузу от смены темы. Смена темы — самый точный сигнал, но требует детекции интента, которая сама ошибается. Рабочая комбинация: TTL как страховка плюс сброс по явной смене интента, а неявные признаки смены темы использовать не для сброса, а для уточняющего вопроса.

Когда новая реплика противоречит заполненному слоту. Молчаливая перезапись опасна ровно в тех случаях, где цена ошибки высока: пользователь мог оговориться, а система тихо поменяла сумму перевода. Переспрашивать всегда — раздражает и делает диалог длиннее в разы. Граница проходит по обратимости действия, к которому ведёт слот: для необратимого переспрашивать обязательно, показывая старое и новое значение; для остального перезаписывать, но сообщать об этом в ответе («понял, меняю город на …»), чтобы у пользователя был шанс поймать ошибку. Молча — не делать никогда: это тот же класс тихих сбоев, что и в Agent Failure Modes.

Связано с

  • Tool Calling — базовая механика вызова, для которой собираются аргументы
  • Validation Loops — что происходит после того, как полный набор ушёл в схему
  • Structured Output — сама схема, определяющая список слотов
  • Human in the Loop — уточняющий вопрос как лёгкая форма участия человека
  • Agent Memory — состояние сессии, в котором живут накопленные слоты