Как работает Code Completion?
Напомню, что это подсказки на основе LLM, которые ассистент предлагает в процессе того, как вы пишете код. Попробуем разобраться как это работает под капотом на простом примере. Допустим, у нас есть код
from models import User
from telemetry import telemetry_for_user_event
def create_user(data: dict) -> User:
user = User.model_validate(**data)
<CURSOR>
return userТам, где <CURSOR> находится каретка пользователя. Все что до — PREFIX, а все, что после — SUFFIX. Конечно, это самая важная инфа, ведь модель должна в своей подсказке продолжить PREFIX, причем так, чтобы после логично шел SUFFIX. В импорте есть функция telemetry_for_user_event, ее LLM и должна использовать в подсказке.
Отправим всю эту информацию в модель. Для примера рассмотрим LLM, которая специально обучена дописывать код. Например, starcoder2. Промпт для нашей задачи будет выглядеть вот так
<fim_prefix>
from models import User
from telemetry import telemetry_for_user_event
def create_user(data: dict) -> User:
user = User.model_validate(**data)
<fix_suffix>
return user
<file_sep>
<|endoftext|>где <fim_prefix>, <fix_suffix> и т.д. — это специальные токены, на которых обучалась модель (подробнее можно почитать в самом пэйпере про starcoder2). Ответ будет следующим
telemetry_for_user_event(user)
<|endoftext|>Выглядит хорошо, но есть одно но — посмотрим на определение функции telemetry_for_user_event
def telemetry_for_user_event(user: User, event: str):
...Она принимает на вход два параметра, но LLM ничего об этом не знает. Поэтому нам нужно добавить в запрос CODE SNIPPETS, которые содержат вспомогательную информацию из других файлов проекта. Их можно собирать, например, добавив определения всех функций или классов, которые импортируются в наш целевой файл. Теперь наша схема имеет следующий вид:
PREFIX+SUFFIX + CODE SNIPPETS -> Prompt builder -> LLM -> ResponseОтправим новый запрос в модель
<file_sep>telemetry.py
def telemetry_for_user_event(user: User, event: str):
...
<file_sep>main.py
<fim_prefix>
from models import User
from telemetry import telemetry_for_user_event
def create_user(data: dict) -> User:
user = User.model_validate(**data)
<fix_suffix>
return user
<file_sep>
<|endoftext|>И получим следующий ответ
telemetry_for_user_event(user, event="user_created")
<|endoftext|>Выглядит как то, что нам нужно. Но и в таком варианте есть «Но». Вот как должен выглядит тот код, который нужен пользователю
promo_available = check_user_promo(user)
if promo_available:
user.activate_promo()
telemetry_for_user_event(user, event="user_created")А почему? Потому что так принято в нашем игрушечном проекте. И мы не сможем просто по импортам нагрепать такую информацию. Но мы можем использовать USER BEHAVIOR — информацию о поведении нашего пользователя в IDE. Вот статья про использование USER BEHAVIOR в компании ByteDance (tiktok).
Например, мы можем использовать историю последних просмотренных файлов — наш пользователь перед тем, как начать реализацию create_user, посмотрел в проекте как реализуются другие похожие хэндлеры. Там может быть следующий код
from telemetry import telemetry_for_user_event
from models import User
from promotions import check_user_promo
def handle_auth(user: User):
promo_available = check_user_promo(user)
if promo_available:
user.activate_promo()
telemetry_for_user_event(user, event="auth_success")Теперь наша схема работы Code Completion имеет вид
PREFIX+SUFFIX + CODE SNIPPETS + USER BEHAVIOR -> Prompt builder -> LLM -> ResponseНовый запрос к модели
<file_sep>auth_service.py
…
def handle_auth(user: User):
…
<file_sep>telemetry.py
…
<file_sep>main.py
<fim_prefix>
...
<fix_suffix>
…
<file_sep>
<|endoftext|>Ответ будет следующим
promo_available = check_user_promo(user)
if promo_available:
user.activate_promo()
telemetry_for_user_event(user, event="user_created")
<|endoftext|>И это уже то, что нам нужно. В нашей схеме осталось еще одно «Но».
Мы смотрим на код, в котором не более 10 строк. В реальных проектах обычно не так — тысячи или десятки тысяч строк кода, иногда даже в одном файле. Так же пользователь может посещать десятки файлов и производит множество действий в проекте. Это порождает десятки или даже сотни CODE SNIPPETS и USER BEHAVIOR. Чтобы не забивать весь контекст LLM этой информацией — необходимо ранжировать все сниппеты и выбирать только наиболее релевантные (для текущих PREFIX и SUFFIX). Вот финальная схема
PREFIX+SUFFIX + Ranking(CODE SNIPPETS + USER BEHAVIOR) -> Prompt builder -> LLM -> ResponseПримерно так и работает Code Completion. Общая схема предельно понятная, но каждый отдельный пункт — содержательная инженерная задача.