Команда бекенду в SaaS-компанії має 180 000 рядків коду, 40 runbook-ів у Confluence та автоматично згенеровану з Swagger документацію API. Новий програміст перший тиждень проводить переважно на питаннях на кшталт: «де обробляється ця помилка?», «як працює авторизація для ендпоінту X?» та «що робить цей прапорець?». Старші колеги знають. Проблема в тому, що ці знання ніде не записані у формі, з якої можна здійснити пошук.
Ми в Cashcrown досліджуємо, як RAG над репозиторіями та технічною документацією працює в практичних впровадженнях. Нижче стаття описує відмінності від RAG над прозою, підхід до символічного чанкінгу, роль гібридного пошуку та межі того, що вимагає рішення програміста.
Чому код і технічна документація — це інший виклик, ніж проза#
Документи HR, процедури чи FAQ мають лінійну структуру. Сплітер на основі символів або токенів дає осмислені фрагменти, бо речення, вирване з контексту, все ще залишається реченням.
Код має символічну структуру. Функція calculate_discount(order_id, user_tier) є семантичною одиницею. Перервеш її визначення посередині — отримаєш фрагмент без сигнатури або фрагмент без тіла, кожен з яких окремо некорисний. Те саме стосується класів, методів, блоків try/except і декораторів.
Технічна документація є гібридом: runbook містить список кроків, що посилаються на конкретні команди та змінні середовища, вікі API описує ендпоінти з точними типами параметрів. Пошук за ключовими словами працює тут значно краще, ніж зі вільною прозою. До того ж додається мінливість: RAG, що індексує раз на місяць, цитує сигнатури, які перестали існувати два тижні тому.
Чанкінг за символами, а не за абзацами#
Хороша стратегія чанкінгу коду базується на межах символів, а не токенів. Парсер AST (Abstract Syntax Tree) виокремлює функції, класи та методи як замкнені одиниці. Бібліотеки на кшталт tree-sitter підтримують понад 40 мов і повертають межі кожного символу як номери рядків.
Практичний шаблон: chunk = один символ з docstring, сигнатурою та тілом. Обов’язкові метадані: file_path, symbol_name, start_line, end_line, last_commit_sha. Для технічної документації правило простіше: ділити за заголовками секцій (H2/H3), а не за кількістю токенів. Runbook, поділений за кроками, дає фрагменти, самодостатні процедурно.
Нижче таблиця порівнює підхід до чанкінгу для різних типів ресурсів:
| Тип ресурсу | Стратегія чанкінгу | Обов’язкові метадані | Орієнтовний розмір |
|---|---|---|---|
| Вихідний код (Python, TS, Go) | AST за межами символу | file_path, symbol_name, start_line, end_line, last_commit_sha | 50–300 рядків на символ |
| Довідка API (OpenAPI/Swagger) | Один ендпоінт = один chunk | method, path, operationId, version | 200–600 токенів |
| Runbook / операційна процедура | Recursive за заголовками H2/H3 | назва секції, service_name, last_modified | 300–800 токенів |
| Внутрішня вікі (технічна проза) | Recursive з overlap 10–15% | page_title, page_url, author, last_modified | 512–1024 токени |
| Changelog / повідомлення комітів | Fixed-size або весь запис | commit_sha, author, date, branch | 128–256 токенів |
Більше про стратегії чанкінгу загалом описує стаття чанкінг документів для RAG.
Чому гібридний пошук тут необхідний#
Для документів у прозовій формі векторний пошук справляється добре, бо запити є семантичними: «як обробити скаргу?» потрапляє до відповідного абзацу навіть без буквального збігу слів.
У коді запити часто містять точні ідентифікатори: UserRepository.findByEmail, POST /v1/orders/{id}/cancel, CASHCROWN_API_JWT_SECRET. Семантичний пошук (embedding-базований) не справляється з рідкісними токенами. Модель бачить UserRepository як послідовність символів без широкого семантичного значення і потрапляє на загальні фрагменти замість визначення класу.
BM25 — це інвертований індекс на основі частоти слів. Для ідентифікаторів у вихідному коді працює як повнотекстовий пошук: знайде UserRepository.findByEmail саме там, де цей метод визначений або викликаний. Поєднання BM25 з векторним пошуком через гібридний пошук (RRF або лінійне зважування результату) підвищує recall для запитів з точними назвами без погіршення результатів для семантичних запитів.
У внутрішніх тестах Cashcrown на наборі з 12 000 символів з комерційного проекту recall@5 при чистому векторному пошуку становив 61%. Після додавання BM25 у гібридному режимі зріс до 79%. Різниця була помітною переважно при запитах, що містили сигнатури методів і шляхи файлів. Це орієнтовні цифри з одного проекту, не гарантія повторюваності на іншому коді.
Після retrieval варто додати шар reranking, який повторно сортує top-k фрагментів за релевантністю до конкретного запиту. Для коду це важливо, бо BM25 може просунути фрагменти з назвою, ідентичною лексично, але з іншого модуля або простору імен. Reranker оцінює повний контекст, а не лише наявність токена. Деталі порівняння векторних баз, що підтримують hybrid search нативно, описує стаття як обрати векторну базу.
Свіжість індексу: проблема, яку не можна ігнорувати#
Код має інший життєвий цикл, ніж процедура HR. Процедура відпустки змінюється раз на рік. Сигнатура методу в активному проекті змінюється кожен спринт.
RAG над кодом, який не оновлює індекс, генерує відповіді з deprecated API. Програміст, який отримає приклад використання функції, видаленої два комити тому, витрачає час на дебаг помилки, згенерованої асистентом.
Три рівні оновлення, які ми застосовуємо залежно від швидкості змін проекту:
Інкрементальна реіндексація після комиту. Webhook з GitLab/GitHub (або хук post-receive) запускає реіндексацію лише файлів, змінених у коміті. git diff --name-only HEAD~1 HEAD повертає список файлів. Кожен проходить через парсер AST заново. Символи, межі яких не змінилися, залишаються в індексі; змінені замінюють попередні версії.
Версіонування чанків за commit SHA. Кожен chunk зберігає last_commit_sha у метаданих. При запиті можна фільтрувати за гілкою (branch: main), що дозволяє відповідати як про актуальний код, так і про конкретну історичну версію.
TTL для документації. Runbook-и та вікі не мають webhook-а. Встановлюємо TTL, після якого документ автоматично реіндексується: 24-48 годин для активно змінюваної документації, 7-14 днів для стабільних специфікацій.
Асистент має показувати дату останньої індексації документа в цитаті. «Джерело: src/auth/user_service.py, рядок 142, індексовано 2 години тому» дає програмісту сигнал, чи можна довіряти відповіді. Пропуск цієї інформації — прихований ризик.
Цитування джерела як вимога, а не опція#
У випадку документів у прозовій формі цитування назви файлу та номера сторінки — це хороша практика. Для коду це вимога безпеки.
Програміст, який отримує відповідь «функція calculate_discount знаходиться в модулі pricing», не може її застосувати без верифікації. Може існувати кілька модулів pricing у monorepo. Метод у останньому коміті міг змінити сигнатуру. Асистент міг переплутати схожу за назвою функцію з іншого модуля.
Корисна технічно відповідь виглядає так: «calculate_discount(order_id: int, user_tier: str) -> Decimal (файл: src/billing/pricing.py, рядок 87, коміт a3f9c12, індексовано 40 хвилин тому)». Програміст переходить за посиланням на файл, бачить актуальний код, перевіряє сигнатуру і лише тоді застосовує.
Guardrails для RAG над кодом мають блокувати два класи відповідей:
Відсутність покриття в індексі. Якщо retrieval не повернув жодного фрагмента з результатом подібності вище порогу (орієнтовно 0,65–0,75 для моделі семантичного пошуку), асистент відповідає: «не знайшов цієї сигнатури в індексованій версії коду». Це краще, ніж спроба реконструкції з пам’яті моделі, яка створить неіснуюче API.
Старий індекс. Якщо метадані чанка вказують, що файл не реіндексувався більше ніж X годин (поріг залежить від темпу змін проекту), відповідь містить попередження про потенційну неактуальність.
Більше про те, як RAG над базою знань обробляє цитування та guardrails, у статті корпоративний GPT на базі знань.
Випадки використання: де асистент допомагає найбільше#
Три випадки, які ми зустрічаємо найчастіше в проектах, і яка роль людини в кожному з них:
Онбординг програміста. Новий член команди питає: «де обробляється авторизація OAuth?». Асистент повертає фрагмент src/auth/oauth_handler.py з початковим рядком, docstring та ім’ям автора комиту. Програміст читає код. Асистент скорочує час до першого осмисленого питання до старшого колеги, але не замінює код-рев’ю.
Питання «де реалізовано X?». На етапі code review або дебагу: «де реалізовано механізм retry для помилок 503?». Асистент повертає список місць у коді, які підходять семантично та лексично. Програміст вирішує, яке з них є правильним місцем для даного контексту. Асистент не знає нічого про архітектурний намір, який є в голові архітектора.
Відповіді на питання саппорту з документації. Клієнт саппорту питає про параметр API. Асистент шукає в індексованій довідці OpenAPI і повертає опис з номером версії специфікації. Саппорт перевіряє в Swagger UI перед відповіддю. Асистент скорочує час пошуку, але не усуває верифікацію.
У всіх трьох випадках роль людини є незамінною: архітектурні рішення, застосування коду у продакшені та відповіді, де помилка має реальні наслідки, завжди вимагають людського судження. Асистент RAG над кодом — це навігаційний інструмент, а не авторитет.
Ширший контекст гібридного пошуку для технічних баз знань описує стаття гібридний пошук BM25 і вектори.
FAQ#
Чи може RAG над кодом пропонувати готові фрагменти для вставки?#
Може повернути існуючий фрагмент коду з репозиторію як цитату. Це відрізняється від генерації нового коду: асистент показує, як даний патерн вже реалізований у проекті, з зазначенням файлу та рядка. Програміст оцінює, чи підходить фрагмент для повторного використання в новому контексті. Асистент не повинен генерувати код, якого немає в базі, бо ризик вигадування неіснуючої сигнатури високий.
Як обробити питання про код, який ще не індексовано?#
Коли retrieval повертає відсутність результатів або результати нижче порогу подібності, guardrail має відповісти прямо: «не знайшов покриття для цього запиту в індексованій версії репозиторію». Асистент може запропонувати альтернативний запит або вказати, що даний файл міг ще не бути індексований. Вгадування сигнатури функції на основі загальних знань моделі — найчастіша причина помилкових відповідей у таких системах.
Як часто слід реіндексувати технічну документацію в Confluence?#
Залежить від темпу змін. Runbook-и для активно розроблюваних сервісів варто реіндексувати кожні 24 години. Стабільна архітектурна специфікація може мати TTL 7-14 днів. Ключово — інформувати асистента про дату останньої індексації в кожній відповіді, щоб користувач знав, чи актуальні цитовані матеріали. Старий індекс без попередження гірший за відсутність індексу.
Чому чисто векторний пошук недостатній для коду?#
Модель ембедінгу представляє семантичне значення, але назви функцій, класів і змінних — це рідкісні токени без широкого контексту значення в загальному корпусі. Чисто векторний пошук буде просувати загальні фрагменти на тему аутентифікації замість конкретного класу AuthService. BM25 трактує ідентифікатори як ключові слова і знаходить їх точно. Поєднання обох через гібридний пошук обробляє як семантичні, так і лексичні запити.
Що робити, коли асистент цитує сигнатуру, яка вже не існує?#
Це сигнал, що індекс неактуальний. Реакція має бути двоетапною: з боку користувача — верифікація вихідного файлу перед застосуванням, бо це завжди обов’язково. З боку системи — скорочення TTL реіндексації для змінюваних файлів та додавання попередження у відповідь, якщо метадані вказують на старий commit SHA. Ніколи не застосовуй код, повернутий асистентом, без перевірки актуальної версії в репозиторії.
