中文翻译暂不可用,显示俄语原文。
Как вы делаете long context для code generation (модель должна видеть весь репозиторий)?
Краткий тезис
Подача всего репозитория целиком в LLM|контекст LLM — неэффективный и дорогой подход. Вместо этого строится граф зависимостей кода (через AST или динамический анализ), и по запросу пользователя (например, «напиши функцию для парсинга CSV») извлекается релевантный подграф: связанные функции, классы, импорты. Этот подграф, а не весь репозиторий, загружается в контекст. Используются retrieval-методы (BM25, эмбеддинги кода) и инструменты вроде RepoCoder, CodeGraph, Cursor.
1. Проблема: «Весь репозиторий» — это неразумно
Long context (длинный контекст) — способность LLM обрабатывать большой объём текста на входе. Для code generation это означает, что модель должна «видеть» код, от которого зависит новая функция: импорты, типы, API других модулей, конфигурацию.
Однако загружать весь репозиторий (сотни файлов, миллионы токенов) в контекст — плохая идея:
- Стоимость: токены контекста дороги (пропорционально длине).
- Шум: модель отвлекается на нерелевантный код, падает качество генерации (эффект lost in the middle).
- Латентность: время первого токена растёт линейно с длиной контекста.
- Ограничения: даже модели с 128k–200k контекста не вмещают большие репозитории.
Решение: не весь репозиторий, а релевантный подграф зависимостей.
2. Репозиторий как граф: AST и граф вызовов
Код — это не плоский текст, а структура. Чтобы понять, какие части кода нужны для генерации, строят:
- AST (Abstract Syntax Tree) — дерево разбора исходного кода. Из него извлекают определения функций, классов, переменных, импорты.
- Граф вызовов (call graph) — направленный граф, где узлы — функции/методы, а рёбра — вызовы. Например, функция
parse_csv()вызывает split(),validate_row(). - Граф зависимостей (dependency graph) — расширение графа вызовов, включающее импорты, наследование, использование типов.
Пример построения графа вызовов (упрощённо на Python с ast):
import ast
class CallGraphVisitor(ast.NodeVisitor):
def __init__(self):
self.graph = {} # функция -> список вызываемых функций
self.current_func = None
def visit_FunctionDef(self, node):
self.current_func = node.name
self.graph[self.current_func] = []
self.generic_visit(node)
def visit_Call(self, node):
if isinstance(node.func, ast.Name):
self.graph[self.current_func].append(node.func.id)
self.generic_visit(node)
# Пример использования
code = """
def parse_csv(line):
parts = split(line)
return validate(parts)
def split(s):
return s.split(',')
def validate(parts):
return [p.strip() for p in parts]
"""
tree = ast.parse(code)
visitor = CallGraphVisitor()
visitor.visit(tree)
print(visitor.graph)
# {'parse_csv': ['split', 'validate'], 'split': [], 'validate': []}
Такой граф позволяет понять: для генерации parse_csv нужны split и validate.
3. Retrieval для кода: как найти релевантные узлы графа
Когда приходит запрос (например, «добавь функцию для чтения CSV из файла»), нужно найти в графе релевантные узлы. Используются два подхода:
3.1 Лексический поиск (BM25)
- Индексируются все функции/классы по их сигнатурам и документации.
- Запрос сопоставляется с индексами через BM25 (TF-IDF с насыщением частоты).
- Быстро, не требует эмбеддингов, хорошо работает для точных совпадений имён.
3.2 Семантический поиск (эмбеддинги кода)
- Каждый узел графа (функция, класс) превращается в вектор через CodeBERT, GraphCodeBERT или UniXcoder.
- Запрос тоже эмбеддится, ищется top-k ближайших соседей в векторной БД (FAISS, Annoy).
- Лучше понимает семантику: «прочитать CSV» найдёт
read_csv,load_data,parse_csv.
Гибридный подход: BM25 + эмбеддинги с реранжировкой (например, Cohere Rerank).
4. Стратегии загрузки контекста: от узла к подграфу
После того как найден релевантный узел (например, функция parse_csv), нужно загрузить в контекст не только её, но и всё, от чего она зависит. Стратегии:
4.1 Полный файл
- Загружается весь файл, где определена релевантная функция.
- Плюс: простота, контекст включает соседние функции.
- Минус: файл может быть большим (1000+ строк), много шума.
4.2 Зависимые функции (графовый подход)
- Из графа вызовов берётся транзитивное замыкание зависимостей: функция A → B → C.
- В контекст попадают только нужные функции, даже если они в разных файлах.
- Пример: для
parse_csvзагружаются split иvalidate(из других файлов), но неsave_to_db.
4.3 Иерархический контекст
- Сначала загружается сигнатура и документация зависимых функций.
- Если модель запрашивает больше (через tool calling), подгружается полное тело.
- Экономит токены, но требует нескольких раундов.
Сравнение стратегий
| Стратегия | Токенов на запрос | Риск упустить зависимость | Сложность реализации |
|---|---|---|---|
| Полный файл | Высокий | Низкий | Низкая |
| Зависимые функции | Средний | Средний (если граф неполон) | Средняя |
| Иерархический | Низкий | Высокий (если модель не запросит) | Высокая |
5. Инструменты и фреймворки
- RepoCoder (Microsoft, 2023): строит граф репозитория, при генерации нового кода извлекает релевантные фрагменты через BM25 + эмбеддинги, добавляет их в промпт. Показал улучшение CodeBLEU на 10–15%.
- CodeGraph (Sourcegraph): анализирует код, строит граф зависимостей, интегрируется с IDE и LLM.
- Cursor: IDE с AI-ассистентом, использует @Codebase — автоматически находит релевантные файлы через эмбеддинги и граф.
- GitHub Copilot: использует контекст открытого файла + соседние табы, но не весь репозиторий. Для полного контекста — Copilot Chat с ручным указанием файлов.
- Aider: инструмент для code generation с поддержкой map of repo — сжатое представление репозитория (сигнатуры функций, классы) для LLM.
6. Обработка длинного контекста: техники для LLM
Даже после отбора релевантного подграфа контекст может быть большим (10–30 файлов, 50k–100k токенов). Как с этим работают LLM:
- RoPE (Rotary Position Embedding) — позволяет моделям (Llama 3, Qwen 2.5) обрабатывать до 128k токенов без потери качества.
- Sliding window attention — Mistral, Gemma используют окно внимания (4k–32k токенов), но с rolling cache для длинных контекстов.
- Context distillation — сжатие контекста: извлечение ключевых строк (сигнатуры, комментарии) вместо полного кода.
Пример сжатия контекста для функции:
# Исходная функция (50 строк)
def parse_csv(filepath, delimiter=','):
with open(filepath, 'r') as f:
lines = f.readlines()
header = lines[0].strip().split(delimiter)
data = []
for line in lines[1:]:
row = line.strip().split(delimiter)
data.append(dict(zip(header, row)))
return data
# Сжатый контекст (2 строки)
# parse_csv(filepath, delimiter=',') -> List[Dict]
# Зависимости: open, readlines, split, strip
7. Оценка качества long context для code generation
Метрики для оценки, насколько хорошо модель использует контекст репозитория:
- CodeBLEU — BLEU для кода с учётом синтаксиса и семантики.
- Compilation success rate — доля сгенерированного кода, который компилируется без ошибок.
- Functional correctness — прохождение юнит-тестов (HumanEval, MBPP).
- Context utilization — метрика, показывающая, сколько релевантных зависимостей из графа было использовано в генерации.
Пример оценки
| Подход | CodeBLEU | Compilation rate | Functional correctness |
|---|---|---|---|
| Без контекста | 45.2 | 62% | 38% |
| Полный файл | 52.1 | 71% | 49% |
| Граф зависимостей | 56.8 | 78% | 58% |
| Граф + иерархия | 58.3 | 80% | 61% |
8. Компромиссы и практические советы
- Скорость vs полнота: графовый retrieval быстрее (меньше токенов), но может пропустить неявные зависимости (глобальные переменные, monkey-patching).
- Стоимость токенов: для больших репозиториев используйте иерархический контекст или context caching (API Gemini, Claude поддерживают кэширование контекста).
- Динамические языки: Python, JavaScript — граф вызовов строится неточно из-за динамической типизации. Дополняйте статический анализ runtime tracing (запуск тестов).
- Монорепозитории: если репозиторий огромен, используйте multi-stage retrieval: сначала найти модуль, потом функцию внутри модуля.
Пет-проект для закрепления
Задача: Создать инструмент, который по текстовому описанию генерирует функцию на Python, автоматически подгружая зависимости из репозитория.
Инструменты:
- Python +
astдля построения графа вызовов. sentence-transformers(all-MiniLM-L6-v2) для эмбеддингов кода.FAISSдля векторного поиска.ollamaили API OpenAI для генерации.
Шаги:
- Напишите скрипт, который обходит все
.pyфайлы в репозитории, строит AST и извлекает функции/классы с их сигнатурами и документацией. - Постройте граф вызовов: для каждой функции запишите, какие функции она вызывает.
- Для каждой функции создайте эмбеддинг её сигнатуры + docstring.
- По запросу пользователя (например, «напиши функцию для чтения CSV и подсчёта строк») найдите top-3 релевантные функции через FAISS.
- Из графа вызовов возьмите транзитивное замыкание зависимостей для этих функций.
- Сформируйте промпт: «Вот релевантный код из репозитория: [зависимые функции]. Напиши новую функцию, которая [запрос]».
- Отправьте в LLM, получите код, проверьте, что он использует зависимости.
Ожидаемый результат: Рабочий CLI-инструмент, который для запроса «функция для валидации email» находит в репозитории validate_email, regex_match и генерирует новую функцию, вызывающую их.
Связь с другими вопросами
| Вопрос | Тема |
|---|---|
| 5 | Как вы оцениваете качество retrieval'а в RAG-системе? |
| 7 | Как вы уменьшаете latency RAG-системы? |
| 9 | Как вы обновляете документы в существующей RAG-системе? |
| 10 | Что такое Self-RAG и когда его использовать? |
| 15 | Как вы обрабатываете запросы, на которые нет ответа в документах? |
| 20 | Как бы вы спроектировали RAG-систему для 10 000 документов с разной структурой? |
Навигация
- Предыдущий: 646
- Следующий: 648
- Индекс: 00. Индекс разборов