Дисклеймер. Я занимаюсь кодинговыми AI-ассистентами: начинал с автодополнения, сейчас работаю с агентами и внедрением AI в SDLC. Последние три года я регулярно выступаю с докладами и образовательными лекциями. За это время у меня накопилось много заметок о том, как устроены AI-ассистенты и агенты, написанных простым языком. Эта одна из них.

Клоуз-тест (cloze-test) - это упражнение, в котором некоторая часть текста скрывается (маскируется), и участнику необходимо заполнить эти пробелы учитывая лексику и контекст. Слово cloze произошло от closure из гештальдпсихологии, а сам тест предложил Вилсон Тэйлор в далеком 1953 году [1]. В 2018 году похожий тест применили для обучения языковой модели BERT [2]. Если при классическом обучении LLM предсказывает следующий токен некоторой входной последовательности

token1, token2, token3, ... -> predict_next_token

то в cloze-подходе авторы статьи заставили BERT-модель предсказывать замаскированные токены, получая фрагменты контекста слева и справа, которые обычно называют PREFIX и SUFFIX

Предсказание замаскированного текста
Предсказание замаскированного текста

Подобный метод обучения моделей называют Fill-in-the-Middle (FIM). Эту идею можно применить и к тексту, содержащему код. Обычно при FIM-подходе во время обучения добавляют специальные токены, которые обозначают PREFIX, SUFFIX, MIDDLE (замаскированный код). На картинках эти токены представлены как |PREFIX| , |SUFFIX| и |MIDDLE| соответственно

FIM промпт
FIM промпт

В этом примере |END_OF_TEXT| также является специальным токеном, который обозначает конец генерации. Его можно воспринимать как один из стоп-сигналов: если модель сгенерировала его, клиент прекращает генерацию и возвращает ответ (без этого токена). На практике часто добавляют и другие стоп-условия.

В итоге, так как LLM просто продолжает входящую последовательность, отправляя на вход
|PREFIX|{prefix}|SUFFIX|{suffix}|MIDDLE|, мы ожидаем получить
{masked_code}|END_OF_TEXT|

FIM пример
FIM пример

Рассмотрим небольшой пример: пусть в некотором файле card.py мы замаскировали код Card("Q", "Hearts") и хотим, чтобы LLM его предсказала. Во время обучения модель учится предсказывать продолжение PREFIX так, чтобы сгенерированный код логично соединял PREFIX и SUFFIX. Она заметит в префиксе созданный датакласс Card, а из суффикса видно, что ниже ожидается объект card с полем rank, т.к. идет обращение к его полю card.rank. Составим промпт для LLM по принципу, описанному выше

Пример запроса для card.py
Пример запроса для card.py

В нашем примере мы можем получить примерно такой ответ

Обычно модели содержат и другие специальные токены, которые позволяют предоставлять модели дополнительный контекст: содержание других файлов, содержание pull request, репорты из GitHub/GitLab issues, и т.д.

Пример запроса для нескольких файлов
Пример запроса для нескольких файлов

Единого соглашения об именовании таких специальных токенов нет, поэтому мы используем обобщенный вид |{TOKEN_NAME}|, не привязываясь к конкретной модели. Вот небольшая таблица с примерами таких токенов для популярных кодинговых моделей

Примеры спец. токенов для разных FIM-моделей
Примеры спец. токенов для разных FIM-моделей

Токен |FILE_SEP|, как легко догадаться, служит для разграничения кода из разных файлов. Как видно из таблицы - не все модели во время обучения явно имеют специальный токен для этого. Например, модель Codestral в режиме multifile собирает несколько секций вида

+++++ path/to/file.ts  
{content}  
  
+++++ path/to/current.ts  
{prefix}

и после объединяет их в финальный шаблон вида

[SUFFIX]{suffix}[PREFIX]{all_files_plus_current_prefix}

Другими словами, все кодовые сниппеты добавляются к префиксу основного файла. Подробнее о том, как FIM используется при обучении кодовых моделей, можно прочитать в работах [3]-[7].

Тут важно отметить, что предсказывать замаскированный код можно не только с помощью FIM-обученной модели, но и с помощью обычной instruct LLM по типу GPT. Но обычно так не делают. FIM-модель обучена ровно на такую задачу, когда instruct-модель использует другой интерфейс: ей дают человеческую инструкцию, а она отвечает как ассистент. Примером может служить следующий промпт

Пример промпта для instruct-модели
Пример промпта для instruct-модели

Ожидается, что instruct-модель допишет только содержимое completion. Аналог
|END_OF_TEXT| в этом запросе играют теги <COMPLETION>, </COMPLETION>

Пример ответа от instruct-модели
Пример ответа от instruct-модели

При этом сама FIM-модель - только одна из частей реальной системы автодополнения. Хорошая подсказка зависит не только от того, что модель умеет предсказывать код между PREFIX и SUFFIX, но и от того, какой контекст мы ей передали, как выбрали этот контекст и что сделали с ответом модели. На практике именно из этих дополнительных слоев и складывается полноценная система автодополнения.

Полезные ссылки

  1. Cloze Procedure: A New Tool for Measuring Readability - Wilson L. Taylor, 1953. Работа, в которой был предложен cloze-тест.

  2. BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding - Jacob Devlin et al., 2018. Описание masked language modeling - задачи восстановления скрытых токенов, использованной при обучении BERT.

  3. Efficient Training of Language Models to Fill in the Middle - OpenAI, 2022. Исследование FIM-обучения и способов преобразования исходных последовательностей в пары из префикса и суффикса.

  4. Qwen2.5-Coder Technical Report - Qwen Team, 2024. Технический отчет о семействе Qwen2.5-Coder, включая обучение моделей решению задач дополнения кода.

  5. StarCoder 2 and The Stack v2: The Next Generation - BigCode, 2024. Описание моделей StarCoder2, обучающего корпуса The Stack v2 и используемого подхода к FIM-обучению.

  6. Seed-Coder: Let the Code Model Curate Data for Itself - ByteDance Seed, 2025. Технический отчет о подготовке данных и обучении семейства кодовых моделей Seed-Coder.

  7. DeepSeek-Coder-V2: Breaking the Barrier of Closed-Source Models in Code Intelligence - DeepSeek AI, 2024. Описание архитектуры, обучения и возможностей DeepSeek-Coder-V2.