
AI-агенты умеют искать по Obsidian-хранилищу, но делают это слишком топорно через glob и grep. Да, для кода эти инструменты работают потрясающе, но хранилище в Obsidian не имеет такой же высокой структурированности. Искать по нему чисто лексически – значит терять инсайты, которые связаны по смыслу.
Чтобы решить эту проблему, я разработал Obsidian Hybrid Search – MCP-сервер и CLI, которые дают агенту мощный поисковый движок поверх заметок.
Оглавление
Проблема glob и grep
Искать вручную по огромному хранилищу слегка утомительно. Хотя за долгое время я придумал довольно много стратегий для обзора заметок, но ни одна из них не может с достаточной надежностью дать ответ на вопрос, который я ещё сам не до конца осмыслил.
При этом чрезвычайно сложно отлавливать инсайты, которые могут рождаться из связи заметок, лежащих на разных полюсах графа. А ведь это чертовски ценно и приятно – ловить междисциплинарные инсайты.
Казалось бы, умные ИИ-агенты могут прошерстить хранилище и добыть для нас новую связь. Однако ИИ модели glob-ают и grep-ают контент. Это весьма хорошо работает в случае кода, так как он высоко структурирован, но плохо работает для базы знаний, где структуры, концепции, терминология и связи размыты.
Пример проблемы glob и grep
Если я захочу выжать из базы знаний максимум того, что я знаю про управление вниманием, то даже умный Claude Code будет искать в стиле Glob("notes/**/внимание*.md") или Grep("внимание|attention"). Если агент в заметках найдёт новые лексические зацепки, например, где написано «фокус» или «deep work» – он расширит запрос и найдёт больше релевантных заметок. Но это всё равно принципиально не решает проблему, что glob/grep не могут найти семантически близкое, которое отличается лексически.
Подобная проблема неплохо решается векторизованным поиском. Заметки превращаются в векторы, а далее мы задаём какой-то вопрос, преобразуем его в такой же вектор и смотрим, насколько он математически похож на другие векторы. Работает это круто. Помогает находить неявные связи между заметками, которые объединены какой-то одной темой.
Уже только использование этой техники может сильно помочь для нахождения связей в развесистом хранилище. Мне лично нравится в этом смысле плагин Similar Notes, который чётко вписывается в дефолтный интерфейс.
Однако такой поиск имеет всё же недостаток.
Нередко мы ищем какую-то заметку по упоминаемой в ней детали, чтобы за неё заякориться.
Пример проблемы векторизованного поиска
Если я месяц назад документировал порядок работы и отладку с определенными докер-контейнерами, но забыл в каком проекте это оставил, то полнотекстовый поиск по упоминанию docker qdrant выдаст точную выборку. Векторизованный же поиск легко может размазать результаты.
В итоге, с одной стороны хочется, чтобы находились самые точные заметки, а с другой – чтобы были семантически связанные заметки, которые могли бы послужить дополнительными инсайтами.
Это противоречивое желание как раз-таки решает гибридный поиск.
Гибридный поиск
Гибридный поиск объединяет два вида поиска и выдаёт один ранжированный список.
Разберём кратко схему работы:
-
Формирование поискового запроса
-
Поисковый запрос идёт по двум путям
-
Лексический поиск
-
Обычно построен вокруг FTS-индекса
-
-
Семантический поиск
-
Сначала запрос векторизуется
-
Затем математически сравнивается с векторами в базе векторов
-
-
-
Объединение результатов двух поисков с помощью RRF и аналогичных алгоритмов
-
Опционально реранг
-
Отдельная ИИ-модель, опираясь на изначальный запрос и чанки из полученных результатов, сравнивает и выталкивает наиболее релевантные результаты наверх
-
Это работает после гибридного поиска
-
Есть материал, который утверждает, что его эффективно применять именно после гибридного поиска
-
Мои тесты это тоже подтверждают
-
-
-
Результат – ранжированный список заметок
Ключевые особенности OHS
Я пошёл немного дальше и адаптировал инструмент (далее OHS — Obsidian Hybrid Search) для Obsidian. При этом гибридный поиск в моём решении в себе объединяет сразу три режима:
-
BM25
*-
Точный полнотекстовый, который очень хорошо работает на ключевых словах
-
-
Fuzzy title/alias
*-
trigram на названиях и aliases
-
Толерантен к опечаткам
-
Я пробовал другие реализации
-
Например, character-level fuzzy matching с редакционным расстоянием как это сделано в плагине Another Quick Switcher
-
Но на Golden Set триграм показал себя лучше всех
-
-
-
Semantic
-
Векторный по эмбеддингам, который находит близкие по смыслу
-
* BM25 и триграм-поиск являются частью лексического пути.
Результаты всех поисков объединяются через RRF (важная пометка 1, важная пометка 2).
RRF
Reciprocal Rank Fusion – это метод ансамблирования ранжированных списков.
Суть его в следующем. Каждый из трёх поисков возвращает свой отсортированный список. RRF объединяет их по принципу: чем выше заметка сразу в нескольких списках, тем выше её итоговая позиция.
Пример
Заметка, которая занимает первое место в одном списке и последнее в двух других, проиграет заметке со стабильно высокими позициями везде.
Это нивелирует слабые стороны каждого метода в отдельности.
Поверх вишенкой на торте – опциональный cross-encoder reranking через bge-reranker-v2-m3 для более точного ранжирования. Улучшает ранжирование весьма круто, но это требовательно к железу. Поэтому реранкинг сделан как опция.
Из специфичного для самого Obsidian. Локальный граф прямо в результатах поисков. Вдобавок можно отдельно получить граф любой глубины относительно конкретной заметки. Это довольно круто, так как мы или агент можем быстро понять, что выходит из заметки (что она порождает и как далеко) и кто на эту заметку ссылается (что является источником вдохновения).
Aliases и теги индексируются и доступны для поиска. Можно фильтровать по тегам и папкам.
По умолчанию используется локальная модель Xenova/multilingual-e5-small (небольшая ~117 MB, поддерживает русский и 100+ языков). Работает она сносно, но я бы рекомендовал использовать модели посильнее через Ollama c GPU ускорением или через облачных провайдеров.
Хранится все как один SQLite файл в корне хранилища. Никаких внешних серверов, никаких бинарников. Даже на большом хранилище оно будет занимать не убийственно много места. Можно открыть любым SQLite viewer.
Инструмент также не требует ручной переиндексации при каждом изменении. Chokidar следит за файлами и инкрементально обновляет индекс в фоне. Но это пока работает только для MCP-режима.
Как настроить
OHS работает как MCP-сервер и как CLI.
CLI
Я рекомендую такой способ установки.
Установить пакет глобально, чтобы можно было обращаться из любого места:
npm install -g obsidian-hybrid-search
Задать переменные окружения. Покажу полный конфиг для OpenRouter.
.zshrc
Переменные окружения для работы терминальной утилиты:
# Абсолютный путь до хранилищаexport OBSIDIAN_VAULT_PATH="/path/to/your/vault"# Игнорируемые папкиexport OBSIDIAN_IGNORE_PATTERNS=".obsidian/**,templates/**"# API-ключ для OpenRouterexport OPENAI_API_KEY="sk-..."# End-point до OpenRouterexport OPENAI_BASE_URL="https://openrouter.ai/api/v1"# Модель для эмбеддинговexport OPENAI_EMBEDDING_MODEL="baai/bge-m3"
Удобные сокращения:
alias ohs='obsidian-hybrid-search'alias ohss='obsidian-hybrid-search --mode semantic'alias ohst='obsidian-hybrid-search --mode title'alias ohsf='obsidian-hybrid-search --mode fulltext'alias ohsi='obsidian-hybrid-search reindex'alias ohsst='obsidian-hybrid-search status'
Перезагружаем конфиг zsh:
source ~/.zshrc
Запускаем индексацию хранилища:
ohs reindex --force
Проверяем гибридный поиск:
ohs "как достичь величия и вершин мастерства"
MCP
В корне хранилища нужно создать такой конфиг. На примере Claude Code:
.mcp.json
{ "mcpServers": { "obsidian-hybrid-search": { "command": "npx", "args": ["-y", "-p", "obsidian-hybrid-search@latest", "obsidian-hybrid-search-mcp"], "env": { "OBSIDIAN_VAULT_PATH": "/path/to/your/vault", "OBSIDIAN_IGNORE_PATTERNS": ".obsidian/**,templates/**", "OPENAI_API_KEY": "sk-or-v1-...", "OPENAI_BASE_URL": "https://openrouter.ai/api/v1", "OPENAI_EMBEDDING_MODEL": "baai/bge-m3" } } }}
После запуска агента хранилище само начнет индексироваться.
Как использовать
Паттерн – поиск того, что не помню точно
ohs "управление вниманием" – гибридный режим найдёт и точные совпадения, и семантически близкие заметки.
• • •
Это дефолтный паттерн использования в том числе для агента.
Паттерн – найти конкретную заметку, когда знаешь примерное название или содержание
ohs "zettleksten" --mode title – fuzzy title через триграм терпит опечатки
ohs "zettelkasten" --mode fulltext – полнотекстовый поиск
• • •
Быстрый режим, без семантики, нужен когда примерно знаешь, что ищешь.
Паттерн — агентный сценарий
Можно попросить: «найди всё, что я писал про spaced repetition, и выдели противоречия» – агент сам сформирует запрос, прочитает нужные заметки через дополнительный метод read, и выдаст синтез.
Это главный сценарий, ради которого всё и делалось. Claude Code с подключённым MCP-сервером не будет нуждаться в том, чтобы вставлять заметки в контекст – он сам делает search-запрос, получает ранжированные результаты со сниппетами (короткие куски релевантного текста для экономии контекста), тегами и ссылками.
Обратите внимание на схему в начале главы. Это то, как агент может отработать поиск. Это фактически тонкий, но очень мощный щуп. Этот сценарий классно отрабатывается на фреймворке Discourse Graph (значительно его усиливает).
Важно отметить, что я достаточно точно описал все инструменты, как рекомендует Anthropic. Я даже написал отдельный скилл для тестирования через OpenCode с разными моделями. Это всё к тому, что агент должен приоритетно использовать гибридный поиск без явного указания.
Паттерн — граф от конкретной заметки
-
ohs --path notes/pkm/zettelkasten.md--related– показывает связанные заметки.-
-1/-2 = backlinks (кто ссылается на эту заметку)
-
+1/+2 = outgoing links (что эта заметка порождает)
-
• • •
Это быстрый способ понять откуда заметка идет и куда ведёт.
Отдельно отмечу, что в CLI есть опция --json, чтобы было проще использовать в скриптах. Вывод будет примерно такой:
json
ohs "типы внимания" --limit 1 --rerank --json
[ { "path": "base/notes/основные типы внимания.md", "title": "основные типы внимания", "tags": [ "note/basic/primary", "mark/aggregator" ], "aliases": [ "внимание" ], "snippet": "Можно выделить условно два вида внимания:\n\n1. Сфокусированное внимание\n2Постоянное внимание", "score": 0.999852717451378, "scores": { "semantic": 0.6205639243125916, "bm25": 0.9321392483716592, "fuzzy_title": 0.9812279888249745, "hybrid": 1 }, "matchedBy": [ "semantic", "bm25", "title" ], "links": [ "base/notes/внимание.md", ], "backlinks": [ "base/notes/детализация этапов написания текста.md", ], "rank": 1 }]
И важно ещё отметить опцию --open. Она откроет результаты поиска через Obsidian CLI. Убедитесь, что он у вас включен.
С остальными фичами можете ознакомиться на GitHub.
А что другие инструменты?
Самое близкое по функционалу – это QMD. Это мощный инструмент, он также работает из терминала и его можно подключить как MCP-сервер.
Однако этот инструмент архитектурно переусложнён и при этом не адаптирован под специфику Obsidian. Чтобы получить от него простой ответ, нужно запустить сразу три немаленькие модели. И хотя автор довольно удачно выбрал библиотеку node-llama-cpp, которая поддерживает нормальное GPU-ускорение, реальный прирост качества результатов от предварительной генерации гипотетического ответа (HyDE), множественных запросов (Query Expansion), их мёржа и прочего нагромождения остаётся неизвестным в контексте PKM-хранилища.
Короче говоря, решение будет сильно нагружать ваш процессор или GPU, а качество результатов будет неизвестным.
Хотя не могу не отметить, что некоторые вещи я у него всё-таки подцепил. Я даже проанализировал pull-requests, чтобы понять, что люди пытаются внедрить дополнительно. Кто бы мог подумать, что чтение чужих pull-requests в чужом репозитории – это отличный способ расширить кругозор 🙂
Вдобавок это решение не позволяет фильтровать по определённым тегам/папкам и не поддерживает обход графа. QMD подходит для поиска, но оно не является в полном смысле щупом, который позволит скользить по персональному хранилищу.
Система, а не поиск
Изначально я хотел улучшить работу агента, чтобы он искал релевантные заметки не лексически, как в коде, а по смыслу, что эффективнее в PKM-системе. Однако всё же не могу не отметить обратную сторону такого поиска для человека.
Полнотекстовый поиск, который реализован в том же Omnisearch, приучает юзеров меньше полагаться на системность и порядок. Даже опытные юзеры порой несут ахинею, что поиск лучше системы.
Зачем проявлять аккуратность, щепетильность и продумывать архитектуру, если можно швырнуть заметку в хранилище как попало, ведь современные инструменты всё равно её смогут найти?
Векторизованный или гибридный поиск ещё сильнее провоцирует пренебрегать системой и тем самым усугубляет проблему.
На мой скромный взгляд, сила системы заметок заключается в том, что она обеспечивает преемственность между идеями, мыслями, концепциями и терминами. Заметки пишутся не просто как факт, как запечатление какой-то реальности, но и вкладываются в определённую коллекцию рядом с другими заметками, которые дополнительно обогащают и контекстуализируют их.
Механика иерархичности в заметках, когда нам известно то, как проистекает одно из другого, формирует основу для развития последовательного и глубокого понимания. В этом смысле заработать понимание, его добыть, стои́т на несколько порядков выше, чем получение быстрого ответа, пусть даже правильного.
Короче говоря, беспорядок, в котором хорошо ориентируешься сам или с помощью продвинутых инструментов – это всё ещё беспорядок.
На этом у меня всё.
Если вам не хватает хардкорных статей по PKM, загляните в мой Digital Garden. Там я, например, разбираю, как создать эпистемическую систему для упорядочивания и фильтрации источников в Zotero, и почему простой bottom-up подход в заметках плохо работает на длинной дистанции.
Новые материалы раньше всего появляются в моём Telegram-канале.
А на Boosty я публикую более глубокие разборы сложных кейсов по управлению знаниями и проектами. Там есть и теория, например, гибридная база знаний, и практика про то, как развивать конкретный проект как экосистему из 4 уровней. Ещё там выходят эссе.
ссылка на оригинал статьи https://habr.com/ru/articles/1040948/