Зіткнулися з ситуацією: в monorepo на 500 000 рядків потрібно знайти функцію обробки платежів, але grep видає сотні збігів. RAG по кодовій базі вирішує цю проблему, але тільки якщо чанкінг зберігає структуру коду. Ми в таких проєктах використовуємо комбінацію Tree-sitter і AST для синтаксичного розбору та розбивки на логічні одиниці: функції, класи, модулі. Кожен чанк збагачується метаданими — ім'ям, сигнатурою, docstring, імпортами та повним шляхом у модульній нотації. Це дозволяє семантичному пошуку знаходити саме ту одиницю коду, яка потрібна, а не випадковий шматок тексту.
Навіщо зберігати структуру коду при чанкінгу?
Звичайний документний RAG ріже текст на абзаци. Для коду це не працює: розрив між сигнатурою та тілом функції вбиває контекст. Код має ієрархію — функція всередині класу, клас всередині модуля. Ми зберігаємо цю ієрархію в метаданих: модульний шлях, рядки початку та кінця, список методів для класу, декоратори для функції. Це дозволяє при пошуку за запитом «як реалізовано X» отримати саме ту одиницю коду, де X визначено.
Як ми реалізуємо code-aware парсинг?
Ми побудували індексатор на основі Tree-sitter. Він парсить код на 50+ мовах і дає синтаксичне дерево. Для кожного вузла (функція, клас, метод) витягуємо:
- ім'я та сигнатуру,
- docstring (якщо є),
- тіло функції/класу,
- декоратори та анотації,
- список імпортів (до 10).
Наприклад, для Python використовуємо ast для точного виділення:
import ast from tree_sitter import Language, Parser class CodebaseIndexer: def __init__(self): # Tree-sitter для syntax-aware парсингу PY_LANGUAGE = Language('build/languages.so', 'python') self.parser = Parser() self.parser.set_language(PY_LANGUAGE) def extract_python_units(self, file_path: str) -> list[dict]: """Витягування функцій та класів як окремих одиниць індексації""" with open(file_path, 'r', encoding='utf-8') as f: source = f.read() try: tree = ast.parse(source) except SyntaxError: return [{'text': source, 'type': 'file', 'file': file_path}] units = [] for node in ast.walk(tree): if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): # Отримання вихідного коду функції func_source = ast.get_source_segment(source, node) docstring = ast.get_docstring(node) units.append({ 'type': 'function', 'name': node.name, 'file': file_path, 'line_start': node.lineno, 'line_end': node.end_lineno, 'text': func_source, 'docstring': docstring or '', 'decorators': [ast.unparse(d) for d in node.decorator_list], 'signature': self._get_signature(node) }) elif isinstance(node, ast.ClassDef): class_source = ast.get_source_segment(source, node) docstring = ast.get_docstring(node) units.append({ 'type': 'class', 'name': node.name, 'file': file_path, 'line_start': node.lineno, 'line_end': node.end_lineno, 'text': class_source, 'docstring': docstring or '', 'methods': [m.name for m in ast.walk(node) if isinstance(m, ast.FunctionDef)] }) return units def _get_signature(self, func_node: ast.FunctionDef) -> str: args = [] for arg in func_node.args.args: annotation = f": {ast.unparse(arg.annotation)}" \ if arg.annotation else "" args.append(f"{arg.arg}{annotation}") return_type = f" -> {ast.unparse(func_node.returns)}" \ if func_node.returns else "" return f"def {func_node.name}({', '.join(args)}){return_type}" Збагачення метаданими: чому це важливо?
Просто розбити код на чанки недостатньо. Для якісного пошуку кожен чанк потрібно збагатити: додати ім'я, сигнатуру, docstring, імпорти та повний шлях у модульній нотації. Це перетворює плоский текст на структурований об'єкт, який при векторизації дає точніші ембединги. Ми формуємо rich_text — комбінацію всіх метаданих, яка подається на вхід моделі ембедингів.
class CodeMetadataEnricher: def enrich(self, unit: dict) -> dict: unit = unit.copy() # Створення rich text для ембедингу # Комбінування імені, сигнатури, docstring та коду rich_text_parts = [] if unit.get('name'): rich_text_parts.append(f"# {unit['name']}") if unit.get('signature'): rich_text_parts.append(f"Signature: {unit['signature']}") if unit.get('docstring'): rich_text_parts.append(f"Description: {unit['docstring']}") rich_text_parts.append(unit['text']) unit['rich_text'] = '\n\n'.join(rich_text_parts) # Витягування імпортів для контексту imports = re.findall(r'^(?:import|from)\s+\S+', unit['text'], re.MULTILINE) unit['imports'] = imports[:10] # Шлях у вигляді breadcrumb parts = unit['file'].replace('\\', '/').split('/') unit['module_path'] = '.'.join( p.replace('.py', '') for p in parts if not p.startswith('.') ) return unit Індексація Git історії: що змінилося?
RAG по коду може відповідати не тільки на питання про структуру, але й про історію змін. Ми індексуємо останні 100 комітів з diff та метаданими: автор, дата, повідомлення, файли. Це дозволяє знайти, коли і ким була змінена конкретна функція. Наприклад, запит «Хто правив calculate_total минулого місяця?» поверне коміти з цією функцією в diff.
import subprocess class GitHistoryIndexer: def get_recent_changes(self, repo_path: str, n: int = 100) -> list[dict]: """Індексація останніх комітів з diff""" result = subprocess.run( ['git', 'log', f'-{n}', '--format=%H|%an|%ae|%ad|%s'], cwd=repo_path, capture_output=True, text=True ) commits = [] for line in result.stdout.strip().split('\n'): if not line: continue hash_, author, email, date, subject = line.split('|', 4) # Отримання diff для цього коміту diff_result = subprocess.run( ['git', 'diff', f'{hash_}^', hash_, '--stat'], cwd=repo_path, capture_output=True, text=True ) commits.append({ 'hash': hash_, 'author': author, 'date': date, 'message': subject, 'changes_summary': diff_result.stdout[:500], 'text': f"Commit: {subject}\nAuthor: {author}\nDate: {date}\n\nChanges: {diff_result.stdout[:500]}" }) return commits Як оцінити якість code RAG?
Хороша метрика: при питанні «Як реалізовано X?» система має повернути функцію або клас, який реалізує X, а не просто файл зі схожою назвою. Для оцінки ми використовуємо golden set з 50–100 питань з відомими відповідями (конкретними функціями). Precision@3 > 0.8 — хороший результат. Нижче — порівняння стратегій чанкінгу:
| Стратегія чанкінгу | Точність (precision@3) | Витрати токенів | Підтримка ієрархії |
|---|---|---|---|
| Файловий (весь файл) | 0.45 | Низькі | Ні |
| Функціональний (AST) | 0.85 | Середні | Так |
| Змішаний (функції+класи) | 0.91 | Високі | Так |
Змішаний чанкінг дає виграш у точності в 2 рази порівняно з файловим. Ми використовуємо саме цей підхід: кожен чанк — функція або клас, а файл стає метаданими.
Яка модель ембедингів підходить для коду?
Для коду краще використовувати моделі, навчені на програмному коді, а не на загальних текстах. Нижче — порівняння популярних варіантів:
| Модель ембедингів | Розмірність | Пропускна здатність | Середня precision@3 |
|---|---|---|---|
text-embedding-3-small |
1536 | 1000 запитів/хв | 0.83 |
code-bert |
768 | 500 запитів/хв | 0.79 |
ada-002 (застаріла) |
1536 | 1000 запитів/хв | 0.74 |
Типові помилки при індексації коду
- Ігнорування docstring — без них модель не розуміє призначення функції, recall падає на 30%.
- Чанкінг по рядках — розриває логічні блоки, precision знижується вдвічі.
- Відсутність метаданих — тільки код без імені та сигнатури дає ембединг, схожий на випадковий шматок тексту.
- Пропуск Git-історії — втрачається інформація про авторство та контекст змін.
- Вибір не тієї моделі ембедингів — модель для документів погано працює на коді.
Що входить в роботу?
- Аудит кодової бази: оцінка розміру, мов, структури репозиторію.
- Проєктування пайплайну: вибір інструментів (Tree-sitter, векторна БД, модель ембедингів), налаштування метаданих.
- Реалізація індексації: написання парсера, збагачення, векторизація, завантаження у векторну БД.
- Тестування: перевірка на golden set, ітеративне покращення чанкінгу та метаданих.
- Інтеграція: налаштування API для пошуку, інтеграція з IDE, чат-ботами або внутрішніми інструментами.
- Деплой та моніторинг: розгортання, логування, метрики якості (precision, recall, latency p99).
Терміни та результати
Орієнтовні терміни — від 2 до 4 тижнів залежно від розміру кодової бази та складності інтеграції. Результати: повністю індексована кодова база з code-aware чанкінгом, API для семантичного пошуку, документація та навчання команди (1–2 години), підтримка протягом місяця після здачі.
Наш досвід — 5 років на ринку, понад 20 реалізованих RAG-проєктів для fintech, edtech та e-commerce. Гарантуємо якість: precision@3 не нижче 0.8 на вашому golden set. Зв'яжіться з нами — оцінимо проєкт за 1 день і запропонуємо архітектуру вашого code RAG. Отримайте консультацію з оптимізації вже на першому дзвінку.







