Skip to content

Repository files navigation

CodeLens — умный поиск по кодовой базе (RAG-система)

Чемпионат Ростелекома «Топ уровень» | Направление: ИИ / Data Engineering


Содержание

  1. Что это и зачем
  2. Архитектура — как всё устроено
  3. Структура проекта
  4. Быстрый старт
  5. Стратегия чункования
  6. 5 примеров запросов с ответами
  7. Метрики качества
  8. Частые проблемы
  9. FAQ — часто задаваемые вопросы

1. Что это и зачем

Проблема: в крупных IT-компаниях десятки репозиториев. Если новый сотрудник спросит «как здесь обрабатывается авторизация?» — обычный поиск (grep, Ctrl+F) не поможет, потому что слова вопроса не совпадают с именами функций в коде.

Решение: CodeLens понимает смысл вопроса, а не буквальные слова. Работает на русском и английском языке.

Тип задачи: RAG (Retrieval-Augmented Generation) — «найди похожее + покажи». LLM-ответы реализованы как дополнительная функция.


2. Архитектура — как всё устроено

Большая картина

┌─────────────────────────────────────────────────────────────────┐
│                        ЭТАП 1: ИНДЕКСАЦИЯ                        │
│                      (запускается один раз)                      │
│                                                                   │
│  codebase_python.zip                                             │
│         │                                                         │
│         ▼                                                         │
│  [index.py] читает .py-файлы                                     │
│         │                                                         │
│         ▼                                                         │
│  AST-парсер нарезает код на куски (чанки):                       │
│    • каждая функция → отдельный чанк                             │
│    • каждый метод класса → отдельный чанк                        │
│    • каждый класс целиком → отдельный чанк                       │
│         │                                                         │
│         ▼                                                         │
│  SentenceTransformer превращает текст чанка в вектор             │
│    "def create_access_token..." → [0.12, -0.45, 0.78, ...]       │
│    (список из 384 чисел — «координаты» в смысловом пространстве) │
│         │                                                         │
│         ▼                                                         │
│  ChromaDB сохраняет: id + текст кода + вектор + метаданные       │
│  Файл на диске: ./chroma_db/                                     │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                        ЭТАП 2: ПОИСК                             │
│                   (при каждом вопросе пользователя)              │
│                                                                   │
│  Вопрос: "как создаётся JWT-токен?"                              │
│         │                                                         │
│         ▼                                                         │
│  SentenceTransformer → вектор вопроса [0.09, -0.51, 0.82, ...]  │
│         │                                                         │
│         ▼                                                         │
│  ChromaDB сравнивает вектор вопроса со всеми векторами чанков    │
│  (косинусное расстояние = угол между векторами в 384D-пространстве)│
│         │                                                         │
│         ▼                                                         │
│  Возвращает топ-5 самых похожих чанков (≤ 0.17 сек.)            │
│         │                                                         │
│         ▼                                                         │
│  Streamlit показывает результаты с подсветкой кода               │
└─────────────────────────────────────────────────────────────────┘

Что такое эмбеддинг (вектор)?

Представьте, что каждый кусок кода — это точка в пространстве из 384 измерений. Тексты, похожие по смыслу (даже если написаны по-разному или на разных языках), оказываются рядом в этом пространстве.

"создать токен доступа"  →  рядом с  →  "def create_access_token()"
"проверить пользователя" →  рядом с  →  "def authenticate_user()"

Этим занимается модель paraphrase-multilingual-MiniLM-L12-v2 — она обучена понимать смысл текста на 50+ языках.

Зачем numpy?

numpy — это библиотека для работы с массивами чисел. Она нужна потому что:

  • sentence-transformers возвращает эмбеддинги как numpy.ndarray
  • Вызов .tolist() переводит numpy-массив в обычный список Python
  • Без numpy эти операции были бы в 10-50 раз медленнее
  • numpy — обязательная зависимость sentence-transformers, без неё он не установится

Где что хранится?

./chroma_db/               ← папка с векторной БД (создаётся index.py)
    chroma.sqlite3         ← SQLite-файл: все чанки, векторы, метаданные
    index_metadata.json    ← статистика индексации (сколько файлов, чанков)

./results.json             ← создаётся при нажатии "Запустить оценку"
                             содержит топ-5 chunk_id для каждого из 15 вопросов
                             нужен для внешнего scorer'а (score.py)

3. Структура проекта

project/
├── index.py              ← Компонент 1: индексирует код, создаёт chroma_db/
├── app.py                ← Компонент 3: веб-интерфейс Streamlit
├── score.py              ← скрипт оценки Precision@5 (из датасета, не трогать)
├── eval_questions.json   ← 15 тестовых вопросов с эталонными ответами
├── sample_queries.txt    ← 20 примеров запросов для демо
├── codebase_python.zip   ← архив с кодом GymHero (FastAPI-проект)
├── requirements.txt      ← зависимости
├── README.md             ← этот файл
└── chroma_db/            ← создаётся автоматически после python index.py

4. Быстрый старт

Шаг 1. Установить зависимости

pip install -r requirements.txt

Если возникают ошибки с версиями — попробуйте:

pip install chromadb sentence-transformers streamlit numpy

Шаг 2. Проиндексировать код (один раз)

python index.py

Что происходит внутри:

  1. Распаковывает codebase_python.zip → папка gymhero/
  2. Обходит все .py-файлы рекурсивно
  3. Для каждого файла строит AST-дерево и вырезает функции/классы
  4. Генерирует эмбеддинги батчами по 32 штуки
  5. Сохраняет всё в chroma_db/

Занимает 1–3 минуты. Модель (~120 МБ) скачивается автоматически при первом запуске.

Успешный финиш выглядит так:

============================================================
INDEXING COMPLETE
============================================================
Files processed:        82
Total chunks indexed:   650+
  - Functions:          250+
  - Methods:            300+
  - Classes:            80+
Vector DB stored at:    ./chroma_db
============================================================

Если хотите переиндексировать заново (например, после изменений):

python index.py  # автоматически пересоздаёт коллекцию

Шаг 3. Запустить веб-интерфейс

streamlit run app.py

Браузер откроется автоматически на http://localhost:8501


5. Стратегия чункования

Выбранная стратегия: один чанк = одна функция или один класс.

Почему именно так?

Стратегия Плюсы Минусы
Фиксированные N строк Просто Разрезает функции посередине, теряет смысл
Весь файл = один чанк Не теряет контекст Вектор "размывается", плохая точность
Функция/класс (выбрано) Смысловая единица кода, хорошая точность Очень длинные функции могут быть шумными

Как реализовано

Используется стандартный модуль Python ast (Abstract Syntax Tree — абстрактное синтаксическое дерево). AST — это структура, которую Python строит при парсинге кода, ещё до исполнения.

# ast.parse() читает код и строит дерево
tree = ast.parse(source)

# ast.walk() обходит дерево и находит все функции и классы
for node in ast.walk(tree):
    if isinstance(node, ast.FunctionDef):   # нашли функцию
        # node.lineno — начальная строка
        # node.end_lineno — конечная строка
        # ast.get_docstring(node) — докстринг, если есть

Текст чанка для эмбеддинга

Если у функции есть docstring — он добавляется перед кодом:

"Returns JWT access token for authenticated user\ndef create_access_token(data: dict):\n    ..."

Это улучшает поиск: docstring написан на человеческом языке и ближе к тому, что спрашивает пользователь.


6. Примеры запросов с ответами

Запрос 1 (русский): «как в проекте создаётся токен доступа?»

Ожидаемый топ результат: gymhero/security.py → функция create_access_token Содержит логику создания JWT с помощью библиотеки jose.

Запрос 2 (английский): «how does JWT verification work?»

Ожидаемый топ результат: gymhero/security.py → функция verify_token или decode_token Показывает, как сервис проверяет подпись токена и извлекает payload.

Запрос 3 (русский): «где хранятся настройки подключения к базе данных?»

Ожидаемый топ результат: gymhero/config.py → класс Settings Содержит DATABASE_URL, SECRET_KEY и другие параметры конфигурации.

Запрос 4 (русский): «как добавить упражнение в тренировочный блок?»

Ожидаемый топ результат: роутеры или сервисы в gymhero/api/ или gymhero/crud/ Функции для работы с тренировками (workout blocks, exercises).

Запрос 5 (английский): «what fields are required to register a new user?»

Ожидаемый топ результат: Pydantic-схемы в gymhero/schemas/ Классы UserCreate или UserRegister с обязательными полями.


7. Метрики качества

Precision@5

Что это: доля правильных чанков среди топ-5 результатов поиска.

Precision@5 = (число правильных чанков в топ-5) / min(5, число эталонных чанков)

Пример:

  • Вопрос: «как создаётся токен?»
  • Эталонные ответы: [security.py:create_access_token:12] (1 правильный)
  • Топ-5 нашли: [security.py:create_access_token:12, config.py:Settings:11, ...]
  • Precision@5 = 1/1 = 100%

Целевое значение: ≥ 60%

Как проверить

Способ 1 — через интерфейс Streamlit: Вкладка «Метрики (Precision@5)» → кнопка «▶ Запустить оценку»

Способ 2 — через командную строку:

# Сначала запустить оценку в Streamlit (создаст results.json)
# Затем:
python score.py --predictions results.json --questions eval_questions.json

Latency (время ответа)

Требование: ≤ 3 секунды на запрос. Текущий результат: ~0.1–0.2 секунды (видно на скриншоте: 0.17 сек)

Почему релевантность показывает 0.0%?

Это не ошибка поиска — система находит нужные файлы. Это особенность формулы.

ChromaDB возвращает косинусное расстояние от 0 до 2:

  • 0 = векторы идентичны (100% совпадение)
  • 1 = векторы перпендикулярны (0% связи)
  • 2 = векторы противоположны

Формула перевода в проценты:

score_pct = (1.0 - distance / 2.0) * 100

Если ChromaDB вернул расстояние ≈ 2.0, то score = (1 - 2/2) * 100 = 0%.

Почему расстояние ≈ 2? Это может быть из-за того что ChromaDB по умолчанию использует L2 (евклидово) расстояние вместо косинусного. При L2 значения могут выходить за диапазон [0, 2], и формула даёт неверный результат.

Как исправить — при создании коллекции указать метрику явно:

# В index.py, в методе CodeIndexer.__init__:
self.collection = self.client.create_collection(
    name=config.chroma_collection_name,
    metadata={
        "description": "Code chunks from Python project",
        "hnsw:space": "cosine"   # ← ДОБАВИТЬ ЭТУ СТРОКУ
    }
)

После этого пересоздать индекс (python index.py) — проценты станут корректными (30–90%).


8. Частые проблемы

«Не найдена векторная БД» при запуске app.py

→ Сначала запустите python index.py

Ошибка импорта chromadb или sentence-transformers

pip install chromadb sentence-transformers streamlit numpy

index.py завис на «Generating embeddings»

→ Нормально, это занимает 1–3 минуты. Модель обрабатывает 32 чанка за раз.

«No module named 'numpy'»

pip install numpy

Браузер не открылся автоматически

→ Откройте вручную: http://localhost:8501


9. FAQ

Q: Почему numpy в requirements.txt? A: numpy нужен sentence-transformers для хранения векторов в памяти. Вызов .tolist() в коде переводит numpy-массив в стандартный Python-список, который принимает ChromaDB. Без numpy sentence-transformers вообще не установится.

Q: Что такое AST? A: Abstract Syntax Tree (абстрактное синтаксическое дерево) — это структура данных, которую Python строит при разборе кода. Каждый узел дерева — это конструкция языка: функция, класс, вызов, присваивание. Модуль ast позволяет обходить это дерево и находить функции/классы без ручного разбора текста.

Q: Почему модель «multilingual»? A: Модель paraphrase-multilingual-MiniLM-L12-v2 обучена на 50+ языках одновременно. Вопрос на русском «создание токена» и код на английском create_access_token попадают в соседние точки в 384-мерном пространстве смыслов.

Q: Можно ли добавить LLM-ответы? A: Да, это дополнительный компонент. Нужно передать найденные чанки + вопрос в языковую модель (Ollama локально или API). Это даст более связный ответ на русском языке.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages