Всем привет! На связи София из команды применения больших языковых моделей ecom.tech. Сегодня хочу поделиться одной малоизвестной библиотекой, которую мы волей Всем привет! На связи София из команды применения больших языковых моделей ecom.tech. Сегодня хочу поделиться одной малоизвестной библиотекой, которую мы волей

Grep-AST или Как мы заменили векторный поиск всего одной библиотекой

2026/03/03 17:20
10м. чтение
Для обратной связи или замечаний по поводу данного контента, свяжитесь с нами по адресу [email protected]
96e23d6d02bd38e9e631aaf49eba6e58.png

Всем привет! На связи София из команды применения больших языковых моделей ecom.tech. Сегодня хочу поделиться одной малоизвестной библиотекой, которую мы волей судьбы откопали на просторах github, попробовали использовать для поиска по нашей кодовой базе, и, о чудо! Это ощутимо помогло нам. Казалось бы, такой маленький шаг для человечества, но такой полезный для нашего проекта.

Что решаем?

Здесь нужно понимать специфику и детали нашей задачи. Мы автоматизируем процесс reverse engineering - восстановление документации REST API и Kafka по коду. Простыми словами нам надо искать релевантные куски кода в одном или нескольких репозиториях и обрабатывать их, после чего генерить документацию с помощью LLM. Про часть, связанную с LLM, мы тоже обязательно расскажем в наших следующих статьях, сегодня поговорим только про поиск.

Говоря про код, немаловажным фактом является то, что нам нужно покрывать репозитории на различных языках - Golang, Kotlin, Ruby, Elixir и PHP. В нашей компании сотни микросервисов, написанных на разных языках программирования, с использованием разных фреймворков. Несмотря на это обилие технологий, архитектурные стандарты предписывают размещать описание REST‑эндпоинтов в строго определённых местах.

Например, в Golang‑проектах это хендлеры, привязанные к маршрутам через http.HandleFunc или вызовы методов GET/POST фреймворка Gin/Echo. В Kotlin — контроллеры Spring с аннотациями @RestController, @RequestMapping, либо декларативные маршруты в Ktor (routing { get("/") { ... } }). В Ruby — контроллеры Rails и файл routes.rb, где resources или get явно объявляют эндпоинты. В Elixir — модули с использованием Phoenix.Router, макросы get "/", PageController, :index. В PHP — маршруты в routes/api.php Laravel (callbacks или массивы [Controller::class, 'method']) либо аннотации в контроллерах Symfony.

Аналогичная картина с Kafka: продюсеры в Golang вызывают producer.SendMessage (библиотека sarama), в Kotlin — инжект KafkaTemplate и метод send, в Ruby — вызов producer.produce у ruby-kafka, в Elixir — KafkaEx.produce, в PHP — методы KafkaProducer::send поверх php-rdkafka. Консьюмеры аннотируются @KafkaListener (Kotlin/Spring), подписываются на топики через consumer.Subscribe (Go, Sarama), используют consumer.subscribe в Ruby, объявляются как GenConsumer в Elixir, или через консьюмер‑группы в PHP.

Иными словами, разработчики следуют единым корпоративным стандартам, поэтому паттерны поиска точек входа известны заранее и одинаковы для всех проектов внутри каждого языка. Казалось бы, бери любой инструмент, ищи эти аннотации/вызовы — и документация готова.

С чего мы начинали?

Сначала мы пробовали применить библиотеку Tree-sitter напрямую, но она не показала хорошего качества на нашей кодовой базе. В нашей задаче важен не столько синтаксис, сколько семантика фреймворков. В коде активно используются обёртки над HTTP/Kafka-библиотеками, базовые контроллеры, DSL и метапрограммирование (особенно в Ruby и Elixir), поэтому вызовы фреймворка в дереве выглядят как обычные пользовательские функции. В результате tree-sitter либо пропускал реальные эндпоинты, либо давал много ложных совпадений. Кроме того, для каждого языка и фреймворка приходилось поддерживать отдельный набор правил, чувствительный к стилю кода и версиям библиотек, поэтому точность сильно плавала от проекта к проекту, а поддержка стала занимать уйму времени.

Потом мы построили классический RAG пайплайн с эмбеддером gte-multilingual-base, затем преобразовали его в гибридный RAG с последующим ре-ранжированием через BM-25. Но поиск занимал довольно много времени и всё равно находились не все нужные нам файлы. Средняя точность по всем ЯП была 85%. Тогда мы стали смотреть в сторону обычного grep из linux, дешево и сердито.

Особенности болезненной реальности?

При использовании grep нам приходилось буквально «выкручивать» регулярные выражения, чтобы отсечь ложные срабатывания: комментарии на русском и английском, строковые литералы, в которых встречаются похожие сигнатуры, импорты и объявления типов. Например, в Java аннотация @GetMapping может быть частью Javadoc, а в Golang комментарий // GetUser не имеет ничего общего с хендлером. Без понимания синтаксической структуры кода невозможно надёжно отличить объявление эндпоинта от простого упоминания тех же слов в другом контексте. Мы пытались уточнять паттерны, добавлять просмотр назад/вперёд, ограничивать расстояние от начала строки — это помогало, но хрупкость таких правил давала о себе знать при малейшем изменении стиля форматирования (например, аннотация перенесена на новую строку, или перед ней стоит другой декоратор). Проектов много, код обновляется ежедневно, и поддерживать этот набор костыльных регулярных выражений становилось всё больнее. И это я ещё молчу про легаси код, не поддающийся актуальным правилам архитектуры.

Учитывая всё это, решение было неплохим, но со своими нюансами, связанными с трудностью подбора точных паттернов и непониманием структуры кода и проекта. Тогда мы стали искать его модернизированные версии. Так мы и наткнулись на grep-ast. Искали медь, а нашли золото.

8609612887976e6baeeeeec835c14290.png

Что же такое этот ваш grep-ast

grep-ast — это Python-библиотека для поиска и анализа исходного кода на основе его абстрактного синтаксического дерева (AST). В отличие от обычного grep она выполняет не только простой текстовый поиск, но и может искать структурные паттерны в коде, понимает его синтаксическую структуру (что нам критически важно!). Grep-ast показывает совпадения не как строки, а как элементы AST с контекстом: функции, классы, циклы и вложенные блоки.

С grep-ast можно смотреть соответствующие строки с полезным контекстом, показывающим, как они вписываются в код. Можно увидеть циклы, функции, методы, классы и т.д., которые содержат все необходимые строки, узнать о том, что находится внутри соответствующего определения класса или функции. Мы видим нужный код на каждом уровне абстрактного синтаксического дерева, над и под совпадениями. Grep-AST базируется на основе tree-sitter и tree-sitter-languages, поэтому поддерживает много языков (русский, английский, китайский, ладно, шутка :) По сути это убойное комбо grep и tree-sitter.

По дефолту grep-AST выполняет рекурсивный поиск по текущему каталогу во всех файлах. А также он поддерживает работу с .gitignore. Интерфейс похож на grep: базовый вызов выглядит как grep-ast [pattern] [filenames...], есть флаг игнорирования регистра, управление цветом вывода, явная настройка кодировки, а также режим печати таблицы парсеров (--languages).

3e4c5b4221c412da5a8ae4f51fb6efbc.png

Чем AST деревья отличаются от тополя

AST (Abstract Syntax Tree) – это абстрактное синтаксическое дерево, структура данных, которая представляет исходный код программы в виде дерева, отражая его логическую и синтаксическую структуру, а не конкретный текст. AST – это способ, которым компилятор или интерпретатор «понимает» код.

Он разбирает текст программы и превращает его в дерево из узлов: операций, переменных, выражений и т.д. В отличие от Parse Tree, который содержит все синтаксические элементы (скобки, запятые, точки с запятой), точно следуя грамматике, AST -- упрощенная, семантическая версия, которая содержит только структурные элементы кода, необходимые для компиляции или интерпретации, исключая лишние символы.

ae3d2d2b8fd75fc90ba3093b93501f24.png

Сравним grep и grep-ast на примерах. Чтобы разница была наглядной, возьмём небольшой синтетический проект из двух файлов.

1.Поиск метода

Найдём метод check_requirement внутри helpers.py

Обычный grep
``grep -n "def check_requirement" mini_project/helpers.py``

07634d443cf56646c33d65890bad79c7.png

Мы получили только номер строки. Теперь нужно открыть файл, прокрутить, понять где начинается метод и где он заканчивается. Попробуем получить контекст:
``grep -n -B 3 -A 10 "def check_requirement" mini_project/helpers.py``

6079a2191a098b22ff5998da1e0ed2fe.png

Кому-то может показаться, что grep -B/-A решает проблему контекста. Формально — да, мы можем видеть несколько строк до и после совпадения. Но по факту этот контекст случаен, он не знает, где начинается и заканчивается метод, он легко «захватывает» код из соседних функций, он полностью теряет информацию о том, в каком классе или логическом блоке мы находимся.

grep-ast, в отличие от этого, всегда работает в терминах AST. Метод показан ровно в своих границах, внутри нужного класса, без ручного подбора количества строк и без риска сломать структуру

grep-ast
``grep-ast "def check_requirement" mini_project/helpers.py``

25109ff30c6c73092d1fe026056b33be.png

Мы получаем метод целиком, docstring, его тело и главное, что он принадлежит классу JudgeAgent. По сути, вместо «точки входа» мы сразу получаем осмысленный фрагмент кода, с которым уже можно работать дальше – как человеку, так и AI-агенту.

2. Поиск по всем файлам проекта
Теперь найдём JudgeAgent во всём проекте.

Обычный grep
```grep -r "JudgeAgent" mini_project/ --include="*.py"
```

c63d050751537ffc250ff202475f915c.png

В результате перемешаны объявления, импорты и использования, нет различия между JudgeAgent и NotJudgeAgent, нет понимания связей, нужно открывать каждый файл вручную. Это просто список совпадений.

grep-ast
```grep-ast "JudgeAgent" mini_project/
```

e1f20f5c1862fefc97e6d89604cc5151.png

При поиске по всему проекту, кажется, разница видна невооруженным взглядом. grep возвращает набор разрозненных строк из разных файлов — импорты, объявления классов, вызовы без малейшего понимания связей между ними. grep-ast группирует результаты по файлам и показывает их в структурном контексте: где объявлен класс, где он используется, в каком именно месте и в рамках какого логического блока. Для человека это экономит десятки переходов по файлам, а для AI-агента - превращает поиск из «поиска строк» в поиск смысловых сущностей кода.

В итоге ключевая разница между grep и grep-ast не в том, находит ли он строку, а в том, что именно он возвращает в качестве результата. Grep отвечает на вопрос «где встречается этот текст», а grep-ast – «какая часть программы за этим стоит».

Для задач анализа кода, навигации по проекту и особенно для AI-инструментов второе оказывается значительно важнее первого.

Почему все вокруг путают нас с тобой?

По запросу grep-ast в гугле вы также найдете библиотеку с перевернутым названием - AST-GREP. Она похожа в большей мере только названием и общим концептом, оба инструмента в своей основе используют AST, построены на Tree-sitter и полиглоты.

3747449bb5398fd26e530bfaa75bbd8e.jpeg

Различаются они тем, как используют полученное AST. Grep-ast: показывает контекст вокруг найденных строк, ast-grep: выполняет структурный поиск и переписывание. Есть еще парочка нюансов: AST-GREP быстрее, так как написан на Rust, сложнее, потому что нужны сложные структурные запросы по AST, а не по строкам, и многофункциональнее или, попросту говоря, перегруженнее, есть возможности переписывания кода, линтинга с кастомными правилами, композиции правил (операторы, сложные условия). Мы пришли к выводу, что для нашего AI-агента это слишком сложный инструмент с избыточным функционалом, и в итоге остались верны grep-ast.

Критерий

grep-ast

ast-grep

Язык реализации

Python

Rust

Производительность

Умеренная

Очень высокая

Основная задача

Поиск с AST-контекстом

Поиск+замена+линтинг

Паттерны

Regex по тексту

Структурные паттерны AST

Рефакторинг

Нет

Да (интерактивный codemod)

Кастомные правила

Нет

Да (YAML конфиги)

Интерфейсы

CLI

CLI+Node.js API + LSP

Как grep-ast стал компасом для LLM

В какой-то момент мы посмотрели на всё это чуть под другим углом. Если grep-ast уже умеет находить в коде структурные сущности - классы, методы, точки входа, значит это не просто CLI-утилита для разработчика. Это готовая операция навигации по коду, которую можно дать агенту как инструмент.

Фактически можно превратить grep-ast в инструмент для tool calling. После этого LLM перестаёт работать вслепую. Вместо того, чтобы угадывать по кускам контекста или полагаться на эмбеддинги, она начинает действовать как разработчик: находит релевантный класс, смотрит методы, понимает зависимости, уточняет следующий запрос. В таком случае, как только мы перестаем давать модели готовые куски кода и позволяем ей самой обходить репозиторий, поиск перестает быть разовым retrieval-запросом. Он превращается в итеративное исследование.

Кстати, именно благодаря обёртке в tool calling мы можем получить ещё один неожиданный, но крайне ценный бонус – полную прослеживаемость. Каждый вызов grep-ast от лица LLM автоматически логируется: с каким паттерном пошла, в каком файле что нашла, куда решила нырнуть дальше. Теперь не нужно гадать, почему агент принял то или иное решение. Мы буквально можем видеть его цепочку рассуждений: «сначала ищет контроллеры, потом проваливается в DTO, затем уточняет Kafka-продюсеров». Чёрный ящик перестает быть чёрным.

Не каждую задачу поиска по коду нужно решать через RAG

В нашем случае большая часть ценности была не в семантическом поиске по всему проекту, а в точном и воспроизводимом нахождении структурных элементов кода – классов, методов, точек входа. И здесь AST-подход оказался куда эффективнее вездесущих эмбеддингов.

grep-ast стал для нас недостающим звеном между «тупым» grep и более сложными инструментами. Он не пытается быть всем сразу, но отлично решает свою узкую задачу и именно поэтому хорошо ложится в архитектуру наших AI-агентов. Возможно, grep-ast не станет вашим основным инструментом, но как минимум заслуживает места в тулбоксе.

Источник

Возможности рынка
Логотип Astroon
Astroon Курс (AST)
$0.001623
$0.001623$0.001623
0.00%
USD
График цены Astroon (AST) в реальном времени
Отказ от ответственности: Статьи, размещенные на этом веб-сайте, взяты из общедоступных источников и предоставляются исключительно в информационных целях. Они не обязательно отражают точку зрения MEXC. Все права принадлежат первоисточникам. Если вы считаете, что какой-либо контент нарушает права третьих лиц, пожалуйста, обратитесь по адресу [email protected] для его удаления. MEXC не дает никаких гарантий в отношении точности, полноты или своевременности контента и не несет ответственности за любые действия, предпринятые на основе предоставленной информации. Контент не является финансовой, юридической или иной профессиональной консультацией и не должен рассматриваться как рекомендация или одобрение со стороны MEXC.