Code RAG: індексуємо код за допомогою Tree-sitter і AST

У великому репозиторії розробники годинами шукають потрібний фрагмент, а звичайний пошук за текстом видає не те. Ми налаштовуємо індексацію кодової бази під ключ: розбір синтаксису, правила доступу, регламенти оновлення. Команда знаходить потрібний код у рази швидше, а новачки виходять на результат за дні, а не тижні.
Часті запитання

Індексація кодової бази: чому пошук за кодом не працює

Наївний пошук за кодом спирається на розбиття файлів на частини фіксованого розміру — за кількістю рядків або символів, як це роблять універсальні системи семантичного пошуку.

У монорепозиторії така схема ламається одразу: функція на сорок рядків розпадається на три шматки, контекст губиться, а у видачу потрапляє середина класу без сигнатури й без місця виклику.

Релевантність видачі падає і на рівні векторних представлень. Вектор фрагмента не знає, що перед ним метод сервісу, а не конфіг чи тест, що ім'я змінної тут — тип домену, а не рядок. Пошук упевнено повертає схожі за набором слів шматки, але не ті, що пов'язані зі змістом задачі. Розробник отримує десяток файлів і вручну з'ясовує, який узагалі стосується справи.

Далі підключається мовна модель і добиває картину: на неповному контексті вона впевнено добудовує неіснуючі методи, вигадує параметри й посилається на класи, яких у проєкті немає.

Перевірка такої підказки займає більше часу, ніж написати код самому, — інструмент перетворюється на джерело хибних сигналів, а довіра до нього падає до нуля.

Корінь проблеми не в моделі й не в розмірі індексу, а в тому, що код — структурований артефакт, а його індексують як звичайний текст. Розбір синтаксичного дерева дає межі, що збігаються з реальними сутностями: функція, клас, модуль, область видимості. Нижче — як це змінює якість пошуку.

Що дає розбір синтаксису: вимірюваний ефект

При індексації без розбору синтаксису файл ріжеться на вікна фіксованого розміру, і у видачу потрапляють обривки: половина функції, чужа сигнатура, шматок умови.

Модель за таким фрагментом відповідає неточно або добудовує відсутнє — звідси хибні збіги та посилання на код, якого в проєкті немає. Tree-sitter будує дерево розбору, і одиницею індексу стає ціла функція, клас або метод.

Точність видачі зростає тому, що збіг прив'язаний до осмисленого вузла, а не до рядка. Пошук за іменем методу в різних компонентах дає різні вузли з різним оточенням, і контекст виклику відсікає хибні збіги ще до того, як вони дійдуть до моделі. У верхній частині видачі майже не залишається сміття з розірваних блоків.

Швидкість пошуку потрібного фрагмента — це навігація деревом: одразу переходимо до визначення та його викликів замість ручного гортання файлів. Кількість звернень до першоджерела падає, бо інженеру рідше потрібно відкривати репозиторій і перевіряти, що мала на увазі модель.

Окремий клас помилок — «модель послалася на неіснуючий код» — закривається тим, що кожен вузол зберігає координати у файлі. Посилання можна перевірити, а фрагмент, якого немає, у видачу не потрапляє. Розбір синтаксису переводить пошук за кодом із «схоже на те» в «цей метод у цьому файлі».

Способи розбору коду: порівняння підходів

Вибір способу розбору визначає, які питання взагалі зможе закрити пошук за репозиторієм.

Індексація кодової бази — це завжди компроміс між точністю структури та стійкістю до різнорідного коду: строгий розбір дає зв'язки між сутностями, але ламається на діалектах і згенерованих файлах, а розмитий працює всюди, але повертає шум.

Окремо вирішується гранулярність розбиття: частина файлу, функція, клас або модуль цілком. Чим більша одиниця, тим більше контексту потрапляє у відповідь і тим вищий шанс, що потрібна деталь у ньому потоне.

Варіант Коли підходить Обмеження
Плоский пошук за текстом файлів Точні імена, шляхи, рідкісні рядки конфігурацій Не розрізняє сенс, ловить збіги в коментарях, синоніми не знаходить
Векторний пошук за частинами файлу Смислові питання, різнорідний код, багато мов програмування Гранулярність розбиття задана формально: функції рвуться, у видачі багато близьких за темою, але нерелевантних фрагментів
Розбір у синтаксичне дерево Однорідна кодова база, важливі зв'язки «визначення — виклик — залежність» Потрібна підтримка розбору для кожної мови; на діалектах і шаблонному коді розбір частково губиться
Гібридна схема Великі репозиторії, питання «де це використовується і що зламається» Складніше у впровадженні, потребує перерахунку індексу при змінах

Гібридна схема зазвичай і дає робочий результат: кандидати набираються лексичним і векторним пошуком, а межі блоків уточнюються за синтаксичним деревом.

Якість перевіряється часткою відповідей з коректним посиланням на файл і рядок — це і є критерій, за яким підходи варто порівнювати на вашому коді.

Етапи впровадження: від аудиту репозиторіїв до передачі

Індексація кодової бази — не разовий скрипт, а регламент, який живе разом із репозиторіями. Якість відповідей асистента визначається тим, як код нарізаний на фрагменти і наскільки свіжа видача, тому роботу ведемо етапами і на кожному фіксуємо перевірюваний результат.

Послідовність робіт

  1. Аудит репозиторіїв. Рахуємо кількість репозиторіїв, мов і обсяг коду, відокремлюємо генеровані та вендорні каталоги, позначаємо занедбані гілки: сміття в індексі дає хибні спрацювання.
  2. Проєктування схеми розбору. Визначаємо одиницю сенсу — функція, клас, модуль. Нарізка йде за синтаксичним деревом зі збереженням зв'язків між викликами, а не за фіксованою кількістю рядків.
  3. Складання індексу. Первинне складання по всій кодовій базі з прив'язкою до коміту: будь-яку видачу можна зіставити з конкретною версією коду.
  4. Налаштування оновлення. Індекс перебудовується за подією в системі контролю версій — точково, за зміненими файлами, а не цілком.
  5. Заміри якості. Готуємо еталонний набір питань до кодової бази, рахуємо частку влучань у потрібний модуль і повноту контексту у відповіді.
  6. Розмежування доступу. Права успадковуються від репозиторіїв і гілок: пошук не віддає код, який співробітнику не належить бачити.
  7. Передача документації. Віддаємо регламенти оновлення, схему розбору та метрики, за якими команда сама контролює якість індексу.

Після передачі індекс залишається керованим: зрозуміло, що лагодити при падінні якості й як підключати нові репозиторії без перезбирання з нуля.

Артефакти впровадження: схеми, правила доступу, регламенти

Впровадження закінчується не демонстрацією пошуку на підготовлених питаннях, а моментом, коли команда замовника сама підключає новий репозиторій, змінює правила доступу й оновлює індекс без нас.

Тому ми передаємо не «працюючий сервіс», а комплект артефактів, де кожен закриває свою ділянку експлуатації.

  • Схема індексу. Які сутності виокремлюються з коду (модуль, клас, функція), як зв'язуються за викликами та успадкуванням, де лежать метадані та векторні представлення фрагментів. За нею видно, чому пошук знаходить визначення функції за непрямим описом.
  • Карта розбору. Перелік мов і граматик, які розбираються в синтаксичне дерево, і що потрапляє в індекс як звичайний текст. Окремо відзначені ділянки неповного розбору — щоб від пошуку не чекали неможливого.
  • Правила доступу. Які репозиторії, гілки й каталоги видно кожній команді, на якому шарі перевіряється право і як обмеження відображається у видачі. Права успадковуються з наявних груп, а не заводяться другим списком.
  • Регламент оновлення. Що запускає переіндексацію (злиття в основну гілку, розклад, ручний запуск), скільки вона триває, що відбувається з індексом при видаленні коду й перейменуванні файлів. Плюс порядок повного перезбирання, якщо схема розбору змінилася.
  • Журнал замірів якості. Еталонний набір питань до кодової бази з очікуваними відповідями та звіт за часткою влучань до і після правок. Зміни ваг і розбору перевіряються на ньому, а не на відчуттях.
  • Інструкції для розробників. Як формулювати запит, щоб пошук повернув потрібний фрагмент, як додати новий репозиторій, куди дивитися в логах при невірному результаті.
  • Експлуатаційні матеріали. Конфігурації, схема розгортання, точки моніторингу та порядок відкату на попередню версію індексу — цього достатньо, щоб чергова зміна підняла систему після збою без нас.

Кейс: пошук за монорепозиторієм до і після

Монорепозиторій на кілька тисяч пакетів і десятки мільйонів рядків: відповідь на питання «де це визначено і хто це викликає» займала в інженера до семи хвилин — з перемиканням між каталогами, читанням схожих за іменем сутностей і ручною перевіркою.

Повнотекстовий пошук за рядками чіпляв збіги в коментарях, фікстурах і згенерованому коді, тому в топ-5 релевантних влучань виявлялося менше третини.

Що змінили

Додали розбір структури через Tree-sitter: обхід файлів, побудову дерева розбору та індексу сутностей — модулів, класів, методів, викликів та імпортів із прив'язкою до діапазонів рядків.

Запит іде не за текстом, а за графом зв'язків: «хто викликає цей метод», «де реалізовано інтерфейс», «що зачепить зміна сигнатури». Переіндексація інкрементальна: після коміту оновлюється лише зачеплена гілка дерева, повний прохід не потрібен.

Метрики до і після

Метрика До Після
Час знайти потрібну ділянку близько 7 хвилин близько 40 секунд
Релевантних збігів у топ-5 31% 82%
Помилок «посилання на неіснуючий метод» близько 35 на тиждень поодинокі

Розмежування доступу успадковується із системи контролю версій: індекс зберігає ті самі права на файл, що й сам репозиторій, тому закриті модулі не спливають у видачі в тих, кому доступ до них не видано.

Ролі та групи не дублюються в окремому списку — при перенесенні співробітника між командами права на пошук змінюються разом із правами на код.

Чим інкрементальна індексація відрізняється від повного перезбирання?

Повне перезбирання обходить усю кодову базу цілком: кожен файл заново розбирається, ріжеться на фрагменти й векторизується.

Час такої операції залежить від розміру репозиторію, а не від обсягу правки — у монорепозиторії це десятки хвилин, і весь цей час пошук віддає вчорашній зріз коду.

Інкрементальне оновлення стартує від змін у коміті або злитті гілки. Розбір через Tree-sitter дає дерево розбору, і за ним видно, які функції, класи й модулі справді зачеплені: перераховуються лише змінені вузли, а решта фрагментів та їхні вектори перевикористовуються як є.

В інкремента є межі застосовності, і вони важливіші за саму оптимізацію.

Оновлювати за змінами не можна при зміні моделі векторизації, схеми нарізки на фрагменти або додаванні нової мови: старі й нові вектори опиняться в неузгоджених просторах, і близькість перестане щось значити.

Видалені файли відстежуються записами-надгробками — інакше видача повертає код, якого в репозиторії вже немає.

За навантаженням різниця теж відчутна: інкремент дописує нові сегменти замість перебудови сховища цілком, тому піковий запис іде фоном і не конкурує з пошуком. Періодичне стиснення за розкладом прибирає сміття від видалених і переписаних файлів.

На практиці ми тримаємо гібрид: інкремент після кожного злиття в основну гілку, повне перезбирання — за розкладом і обов'язково при зміні моделі чи схеми розбору. Свіжість видачі тоді вимірюється хвилинами, а не годинами.

Технічні сумніви перед стартом проєкту

Перед стартом сумніви зазвичай одні й ті самі: чи не перетвориться спільний індекс на діру в правах, що буде після переписування історії, чи потягнемо обсяг і мови, чи доведеться ламати вже працююче. Нижче — як ці питання закриваються на практиці.

Індекс спільний — чи не вийде, що код витече між командами?

Індекс збирається окремим шаром, а права беруться із системи контролю версій і успадковуються за шляхами. Фільтрація за доступом виконується до видачі фрагментів, а не після: у відповідь моделі не потрапляють шматки з репозиторіїв, закритих для користувача. Склад груп перераховується при кожній зміні, тому видача завжди відповідає поточним правам.

Що відбувається при переписуванні історії та примусовому перезаписі гілки?

Індексування прив'язане до комітів, а не до «поточного стану файлів». При переписуванні історії перезбираються лише зачеплені шляхи, решту не чіпаємо.

Застарілі версії фрагментів позначаються неактуальними й видаляються за правилом зберігання — пошук не видає код, якого в основній гілці вже немає.

У нас кілька мов і великий монорепозиторій — чи потягне?

Розбір іде за синтаксичним деревом для кожної мови окремо, тому зв'язки між функціями й класами не розмиваються на змішаному коді. Файли без розбірника обробляються резервним способом — за розділами та відступами. Монорепозиторій індексується частинами: пріоритет в активних гілок, решта доганяє в черзі.

У нас уже розгорнуто свій пошук за кодом — усе доведеться переробляти?

Ні. Ми звіряємо покриття наявного рішення, підключаємо його як джерело й доповнюємо там, де бракує структурного розбору. Повна заміна потрібна лише якщо поточний пошук не віддає фрагменти з прив'язкою до файлу й рядка.

Обговоримо задачу: повернемося з оцінкою та планом

Постановка задачі в нас починається з ввідних, а не з комерційної пропозиції: чим точніші вихідні дані, тим менше припущень в оцінці обсягу робіт і тим реалістичніший порядок етапів. Для первинного розбору достатньо такого набору:

  • доступ до репозиторію або вивантаження та список мов, які потрібно покрити індексацією кодової бази;
  • структуру каталогів і правила збирання — за ними видно, як код ділиться на модулі та сервіси;
  • приклади питань, на які рішення має відповідати: пошук за функцією, трасування викликів між модулями, пояснення незнайомої ділянки;
  • обмеження за доступами: які каталоги, гілки й файли не повинні потрапляти в індекс;
  • чим не влаштовує поточний пошук за кодом — швидкість, повнота видачі, відсутність контексту;
  • бажані точки, де потрібне ваше погодження етапів.

На дзвінку розберемо межі індексації, частоту переіндексації при змінах у гілках, вимоги до якості видачі та спосіб підключення до робочих інструментів команди.

За підсумками замовник отримує схему індексації з розбором коду на синтаксичному дереві, порядок робіт за етапами та план впровадження — з точками погодження й вимірюваним результатом кожного етапу.

Опишіть задачу у двох-трьох абзацах — повернемося з оцінкою обсягу робіт і планом впровадження.

Напрямки AI-розробки

Останні роботи

  • Розробка сайту компанії B2B ADVANCE
    Розробка сайту компанії B2B ADVANCE
    1542
  • Розробка веб-додатків для компанії FEEDME
    Розробка веб-додатків для компанії FEEDME
    1393
  • Розробка веб-сайту для компанії БЕЛФІНГРУП
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    1081
  • Розробка інтернет магазину для компанії FURNORO
    Розробка інтернет магазину для компанії FURNORO
    1368
  • Розробка логотипу компанії B2B Advance
    Розробка логотипу компанії B2B Advance
    815
  • Розробка веб-додатків для компанії Enviok
    Розробка веб-додатків для компанії Enviok
    1139