Я поломался, поломался — и поломался на осколки. Признаю́: железные помощники Т9 действительно могут приносить пользу в разработке. Единственное, что мне не нраЯ поломался, поломался — и поломался на осколки. Признаю́: железные помощники Т9 действительно могут приносить пользу в разработке. Единственное, что мне не нра

Ragex: Гибридный RAG для анализа кода

Я поломался, поломался — и поломался на осколки. Признаю́: железные помощники Т9 действительно могут приносить пользу в разработке. Единственное, что мне не нравилось — то, что весь проект большой и хорошо натренированной модели не скормишь, а значит — неизбежны потери контекста, размывание смыслов и джойсовские галлюцинации.

Я уже давно понял: если мне нужно, чтобы что-то было сделано хорошо, — делегирование отпадает, придётся брать в руки молоток самому. Это касается любых жизненных аспектов: варки борща, замены сантехники, перевода Эдгара Аллана По или Антонио Мачадо на русский, или, там, программирования.

Когда БЯМ научились подключать сторонние MCP-сервера, произошел качественный скачок. Теперь не нужно файнтьюнить модель, можно файнтьюнить буковку «R» из акронима «RAG». Я-то лучше знаю, как правильно извлекать смыслы из моего личного контента. Если речь про код — лучше всего искать правду в AST.

Так и был зачат Ragex — MCP-сервер для семантического анализа кодовых баз с элементами чёрной магии. Проект, понятно, написан на Elixir, потому что ну а на чем еще?

Ragex — это (вроде, довольно успешная) попытка объединить статический анализ кода с векторными представлениями и графами знаний. В результате получается система, которая может ответить на вопросы типа «где у меня функция, которая парсит JSON?» не хуже, чем ваш коллега, который неделю назад это писал, но уже всё забыл.

Занахрена́?

Я задумывал всё это как retrieval engine для передачи в какой-нибудь клод, но, поскольку сам я ассистентами не пользуюсь, в результате получилось решение, подходящее и для подключения к БЯМ, и для помощи (если не замены) обычному LSP.

Три кита, на которых держится Ragex:

  1. Local-first: Никаких внешних API. Всё работает локально. Код не отправляется в облако на растерзание корпоративным серверам. Параноики оценят. Кроме того, использовать БЯМ для извлечения контекста из кода — глупо, когда у нас есть AST. Но простенький локальный семантический поиск я тоже, конечно, прикрутил.

  2. Гибридный поиск: Символьный анализ (AST) + семантический поиск (эмбеддинги) + графы знаний. Это как смотреть в тринокль: один окуляр видит близко, другой далеко, третий вообще смотрит в прошлое.

  3. Производительность: Запросы выполняются за разумное время, иногда — в ущерб качеству. Потому что жизнь коротка, а ждать результатов анализа кода — это издевательство и вообще прерогатива создателей IDE корпоративного масштаба.

Архитектура

Ragex состоит из нескольких слоёв, каждый из которых старается не испортить работу остальных:

┌─────────────────────────────────────┐ │ MCP Server (JSON-RPC 2.0) │ │ stdio + Unix Socket │ └──────────────┬──────────────────────┘ │ ┌───────┴────────┐ ▼ ▼ ┌─────────────┐ ┌──────────────┐ │ Анализаторы │ │ Graph Store │ │ (AST) │ │ (ETS) │ └──────┬──────┘ └──────┬───────┘ │ │ │ ┌──────┴───────┐ │ ▼ ▼ │ ┌──────────┐ ┌─────────────┐ └──►│ Vector │ │ Bumblebee │ │ Store │ │ (ML Model) │ └──────────┘ └─────────────┘

Компоненты

1. MCP Server
Реализация Model Context Protocol — протокола, который позволяет AI-ассистентам общаться с внешними инструментами. JSON-RPC 2.0 через stdio (для интеграции) и Unix-сокет (для интерактивного использования). Люди, использующие ассистентов могут подключить этот MCP туда, я просто добавил его в свой LunarVim (см. ниже).

2. Анализаторы
Парсеры готовы для Elixir, Erlang, Python (частично), JavaScript/TypeScript (наброски), скоро добавлю раст и го, наверное. Каждый парсер извлекает AST, модули, функции, вызовы. Elixir и Erlang используют нативные парсеры, Python вызывает через порт в питоновский модуль ast, JavaScript использует регулярные выражения (потому что жизнь — боль).

3. Graph Store
Граф знаний на основе ETS (Erlang Term Storage). Узлы: модули, функции, вызовы. Рёбра: :calls, :imports, :defines. Поверх графа работают алгоритмы: PageRank, поиск путей, метрики центральности, детекция сообществ.

4. Embeddings & Vector Store
Локальная ML-модель (по умолчанию — sentence-transformers/all-MiniLM-L6-v2, настраивается в конфигах) через Bumblebee. Генерирует 384-мерные векторные представления для каждой функции и модуля. Косинусная близость для семантического поиска. Всё работает без интернета.

5. Hybrid Retrieval
Reciprocal Rank Fusion (RRF) — алгоритм объединения результатов символьного и семантического поиска. Три стратегии: fusion (RRF), semantic-first, graph-first.

6. Editor System
Безопасное редактирование кода с атомарными операциями, бэкапами, валидацией синтаксиса, форматированием. Поддержка multi-file транзакций и семантического рефакторинга через AST.

Зачем это вообще нужно?

1. Семантический поиск по кодовой базе

Проблема: Вы помните, что где-то была функция для работы с HTTP-запросами, но где именно — забыли. Название тоже не помните. Grep не поможет.

Решение: Семантический поиск, да.

# Подключаемся к Ragex alias Ragex.VectorStore # Ищем функцию {:ok, results} = VectorStore.search("HTTP request handler", limit: 5, threshold: 0.7, node_type: :function ) # Результаты отсортированы по релевантности Enum.each(results, fn {node, similarity} -> IO.puts("#{node.name} (#{similarity})") IO.puts(" File: #{node.metadata.file}") IO.puts(" Line: #{node.metadata.line}") end)

2. Анализ зависимостей и вызовов

Проблема: Нужно понять, откуда вызывается функция process_data/2, и что она сама вызывает. Рефакторинг без такой информации — русская рулетка (которая в запутанных случаях с макросами — до сих пор лучше удается^W удавалась эвристикам из навороченных IDE, чем БЯМ).

Решение: Граф вызовов.

alias Ragex.Graph.Store alias Ragex.Graph.Algorithms # Найти все функции, которые вызывают process_data/2 callers = Store.get_callers({:function, "MyModule.process_data/2"}) IO.puts("Callers:") Enum.each(callers, fn caller -> IO.puts(" - #{caller.id}") end) # Найти все функции, которые вызывает process_data/2 callees = Store.get_callees({:function, "MyModule.process_data/2"}) IO.puts("\nCallees:") Enum.each(callees, fn callee -> IO.puts(" - #{callee.id}") end) # Найти все пути между двумя функциями (с лимитом) paths = Algorithms.find_all_paths( {:function, "MyModule.start/0"}, {:function, "MyModule.process_data/2"}, max_depth: 10, max_paths: 100 ) IO.puts("\nFound #{length(paths)} paths")

Защита от плотных графов: Если у узла >10 рёбер, Ragex предупредит о потенциальных проблемах с производительностью. Параметр max_paths предотвращает зависание на экспоненциальных взрывах. Не вижу проблемы дать этот параметр на откуп разработчику.

3. Поиск бутылочных горлышек в архитектуре

Проблема: Какие функции являются критичными для всей системы? Если они упадут — всё рухнет.

Решение: Betweenness Centrality (метрика центральности по посредничеству — если кто знает, как перевести менее коряво, свистните, пожалуйста).

alias Ragex.Graph.Algorithms # Вычислить betweenness centrality для всех функций scores = Algorithms.betweenness_centrality( max_nodes: 1000, normalize: true ) # Отсортировать по убыванию top_bottlenecks = scores |> Enum.sort_by(fn {_node, score} -> score end, :desc) |> Enum.take(10) IO.puts("Top 10 bottleneck functions:") Enum.each(top_bottlenecks, fn {node_id, score} -> IO.puts(" #{node_id}: #{Float.round(score, 4)}") end)

4. Определение (извлечение) архитектурных модулей

Проблема: Код разросся, структура неочевидна. Хотелось бы понять, какие модули логически связаны и образуют кластеры.

Решение: Community Detection (алгоритм Louvain).

alias Ragex.Graph.Algorithms # Найти сообщества (кластеры модулей) communities = Algorithms.detect_communities( algorithm: :louvain, hierarchical: true, resolution: 1.0 ) IO.puts("Found #{map_size(communities)} communities") # Группировка по сообществам grouped = Enum.group_by(communities, fn {_node, community} -> community end) Enum.each(grouped, fn {community_id, members} -> IO.puts("\nCommunity #{community_id} (#{length(members)} nodes):") Enum.each(members, fn {node_id, _} -> IO.puts(" - #{node_id}") end) end)

5. Безопасный рефакторинг

Проблема: Нужно переименовать функцию old_function/2 в new_function/2 во всём проекте. Ручной рефакторинг — это ошибки и страдания.

Решение: Семантический рефакторинг через AST. Да, даже для питона.

alias Ragex.Editor.Refactor # Переименовать функцию во всём проекте result = Refactor.rename_function( :MyModule, :old_function, :new_function, 2, # arity scope: :project, validate: true, format: true ) case result do {:ok, details} -> IO.puts("Success! Updated files:") Enum.each(details.edited_files, &IO.puts(" - #{&1}")) {:error, reason} -> IO.puts("Rollback performed. Error: #{inspect(reason)}") end

Через граф знаний нашли все места, откуда вызывается функция, заменили AST по месту (то есть, работает даже для сгенерированного мюнхгаузен-кода), форматирует результат, валидирует синтаксис, и (!) атомарно применяет изменения (или откатывает всё при ошибке).

6. Multi-File транзакции

Проблема: Нужно одновременно изменить несколько файлов. Если хоть одно изменение невалидно — откатить всё.

Решение: Атомарные транзакции.

alias Ragex.Editor.{Transaction, Types} # Создать транзакцию txn = Transaction.new(validate: true, format: true) |> Transaction.add("lib/module_a.ex", [ Types.replace(10, 15, "def new_version do\n :ok\nend") ]) |> Transaction.add("lib/module_b.ex", [ Types.insert(20, "@doc \"Updated documentation\"") ]) |> Transaction.add("test/module_test.exs", [ Types.replace(5, 5, "# Updated test") ]) # Применить все изменения атомарно case Transaction.commit(txn) do {:ok, result} -> IO.puts("Edited #{result.files_edited} files successfully") {:error, result} -> IO.puts("Transaction rolled back!") IO.puts("Errors: #{inspect(result.errors)}") end

Тут важна атомарность: при любой ошибке — автоматический откат всех изменений, все бэкапы хранятся (в ~/.ragex/backups/<project_hash>/) и т. д.

7. Бонус-трек — экспорт в DOT и D3.js

Интеграция с LunarVim

LunarVim — это мой редактор кода, Neovim на стероидах. Ragex интегрируется через MCP и предоставляет команды для семантического поиска прямо из редактора.

Установка

Скопируйте конфигурационные файлы:

cp ragex/lvim.cfg/lua/user/*.lua ~/.config/lvim/lua/user/

Добавьте в ~/.config/lvim/config.lua:

-- Ragex integration local ragex = require("user.ragex") local ragex_telescope = require("user.ragex_telescope") -- Setup ragex.setup({ ragex_path = vim.fn.expand("~/Proyectos/Ammotion/ragex"), enabled = true, debug = false, }) -- Keybindings (using "r" prefix) lvim.builtin.which_key.mappings["r"] = { name = "Ragex", s = { function() ragex_telescope.ragex_search() end, "Semantic Search" }, w = { function() ragex_telescope.ragex_search_word() end, "Search Word" }, f = { function() ragex_telescope.ragex_functions() end, "Find Functions" }, m = { function() ragex_telescope.ragex_modules() end, "Find Modules" }, a = { function() ragex.analyze_current_file() end, "Analyze File" }, d = { function() ragex.analyze_directory(vim.fn.getcwd()) end, "Analyze Directory" }, c = { function() ragex.show_callers() end, "Find Callers" }, r = { function() vim.ui.input({ prompt = "New name: " }, function(name) if name then ragex.rename_function(name) end end) end, "Rename Function" }, g = { function() ragex.graph_stats() end, "Graph Stats" }, b = { function() ragex.show_betweenness_centrality() end, "Betweenness" }, n = { function() ragex.show_communities("louvain") end, "Communities" }, e = { function() ragex.export_graph("graphviz") end, "Export Graph" }, }

Использование

<leader>r
<leader>r

Пример Workflow

Типичный сценарий работы с Ragex в LunarVim:

1. Открыли проект: <leader>rd (анализировать директорию, по умолчанию — автоматом) 2. Нужно найти функцию: <leader>rs → "database connection" 3. Нашли функцию, открыли файл <Enter> в телескопе 4. Хотим узнать, кто вызывает: <leader>rc 5. Решили переименовать: <leader>rr → "connect_to_db" 6. Проверили статистику: <leader>rg 7. Экспортировали граф для визуализации: <leader>re

Настройка Auto-Analyze

Ragex может автоматически анализировать код при сохранении файлов:

ragex.setup({ auto_analyze = true, auto_analyze_on_start = true, auto_analyze_dirs = { "/path/to/project" }, })

Инкрементальные обновления: Ragex отслеживает изменения через SHA256-хеширование и перегенерирует эмбеддинги только для изменённых файлов.

Память

На совсем жиденьких лэптопах я бы не стал пользоваться Ragex.

  • ML-модель: ~400 MB RAM

  • ETS-таблицы: линейный рост, ~400 bytes на узел

  • Эмбеддинги: ~400 bytes на вектор (384 float32)

  • Кеш-файлы: ~15 MB на 1000 сущностей

Настройка

Поддержка пользовательских Embedding-Моделей

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

# config/config.exs config :ragex, :embedding_model, "sentence-transformers/all-MiniLM-L6-v2" # Альтернативы: # - "sentence-transformers/all-MiniLM-L12-v2" (больше точность) # - "sentence-transformers/paraphrase-MiniLM-L3-v2" (быстрее) # - "sentence-transformers/multi-qa-MiniLM-L6-cos-v1" (для Q&A)

Миграция моделей:

mix ragex.embeddings.migrate --from old_model --to new_model

File Watching

Автоматическое переиндексирование при изменении файлов:

# Через MCP {"method": "tools/call", "params": { "name": "watch_directory", "arguments": {"path": "/project/lib"} }} # В коде Ragex.FileWatcher.watch("/project/lib")

Экспорт Графа для Визуализации

alias Ragex.Graph.Algorithms # Graphviz DOT format {:ok, dot} = Algorithms.export_graphviz( color_by: :betweenness, include_communities: true ) File.write!("graph.dot", dot) # Рендерим через Graphviz System.cmd("dot", ["-Tpng", "graph.dot", "-o", "graph.png"]) # D3.js JSON format (для веб-визуализации) {:ok, json} = Algorithms.export_d3_json(include_communities: true) File.write!("graph.json", json)

Ух, обожаю картинки и D3.js-диаграммки, которые можно пошевелить мышкой.

Ограничения и подводные камни

Потому что честность — моё третье «я»:

  1. JavaScript/TypeScript анализатор: Использует регулярные выражения. Работает для «простых» случаев. Когда-нибудь, может быть, руки дойдут.

  2. Семантический рефакторинг: Пока только для Elixir. Erlang/Python/JS в планах, но не сегодня.

  3. Плотные графы: Если у функции >100 вызовов, поиск путей может занять вечность. Используйте max_paths и max_depth.

  4. Память: 400 MB для модели — это цена локального ML. Если RAM критична, можно отключить эмбеддинги (но зачем тогда вообще Ragex?).

  5. Cold start: Первая генерация эмбеддингов занимает время. После этого — кеширование спасает.

В общем

Ragex — это попытка сделать анализ кода менее болезненным и более семантическим. Граф знаний + векторные эмбеддинги + алгоритмы на графах = инструмент, который может ответить на вопросы типа «где это используется?», «что это делает?», «почему всё сломалось?». А еще это MCP-сервер, который можно подключить к вашему ассистенту кода, чтобы тому тоже было проще ответить на эти вопросы.

MCP-протокол, потому что AI-ассистенты — будущее (или настоящее, в зависимости от того, насколько вы параноик).

Интеграция с LunarVim превращает рефакторинг в нечто почти приятное. Семантический поиск работает, граф не врёт, рефакторинг никогда не ломает код. В теории.


P. S. Если возникло желание попробовать, скачайте проект, скачайте зависимости, запустите ./start_mcp.sh и наслаждайтесь. Если что-то сломается — issue в GitHub. Если всё заработало — это, конечно, тоже можно написать в issue, но кто так делает?

P. P. S. Проект open-source, лицензия MIT. Делайте что хотите, на свой страх и риск. Автор не несёт ответственности за потерянное время, сломанный код и экзистенциальные кризисы, вызванные чтением чужих графов вызовов.

Репозиторий: https://github.com/am-kantox/ragex

Источник

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

Вам также может быть интересно

Цена Bitcoin растёт, поскольку Питер Шифф предупреждает, что MSTR может понести большие убытки

Цена Bitcoin растёт, поскольку Питер Шифф предупреждает, что MSTR может понести большие убытки

Цена Ethereum выросла на 1,7% за последние 24 часа и торгуется на уровне 3 025 $ по состоянию на 04:02 EST, в то время как объем торгов снизился на 23% до [...]
Поделиться
Insidebitcoins2026/01/02 10:16
Главная биржа Южной Кореи заявляет о готовности к Bitcoin ETF, но регуляторы всё ещё тормозят процесс

Главная биржа Южной Кореи заявляет о готовности к Bitcoin ETF, но регуляторы всё ещё тормозят процесс

Председатель Корейской биржи Чон Ын Бо объявил о планах запуска крипто-ETF и продлении торговых часов до круглосуточного режима работы во время первой торговой сессии биржи
Поделиться
CryptoNews2026/01/02 22:09
Новогоднее поздравление от Loyal Miner: "Спасибо за ваше доверие, давайте вместе отправимся в новое путешествие"

Новогоднее поздравление от Loyal Miner: "Спасибо за ваше доверие, давайте вместе отправимся в новое путешествие"

С началом Нового года Loyal Miner благодарит своих пользователей за доверие и запускает ограниченные по времени предложения с увеличенными ежедневными доходами для более успешного начала года
Поделиться
Crypto.news2026/01/02 22:15