Всё началось не с мечты про «поисковик нового поколения». Мне понадобился быстрый self-hosted Tavily-compatible API для собственных рабочих и личных AI-решений: без оплаты за каждый запрос, без внешнего сервиса в обязательной цепочке и с индексом, содержимое которого контролирую я сам.
Тут я вспомнил про YaCy. Когда-то я уже поднимал его ноду. Идея мне нравилась, а реализация - заметно меньше: Java, тяжёлая машина и примерно шесть секунд ожидания ответа на моей тогдашней установке. Для человека, который один раз нажал Enter, это ещё можно пережить. Для агента, делающего несколько поисков, уточнений и
extractподряд, это превращает один шаг в минутный перекур.Поэтому вместо ещё одной обёртки над чужим поиском я оставил от YaCy сетевой протокол и начал собирать поисковую ноду заново: на Go, с отдельным краулером, embedded storage, нормальным API и ranking pipeline из современных работ по information retrieval.
Под катом - немного сетевой археологии, Bleve, bbolt, gRPC, BM25, LambdaMART и рассказ о том, как задача «дайте локальный endpoint для AI-агентов» постепенно превратилась в реинкарнацию YaCy.
Всё началось с API
Мне нужен был не поисковый портал для человека, а машинный интерфейс для агентов. Обычный сценарий выглядит примерно так:
агент отправляет поисковый запрос;
получает несколько результатов;
уточняет формулировку;
вытаскивает содержимое выбранных страниц;
иногда запускает ограниченный crawl или строит карту сайта;
повторяет всё это ещё несколько раз, пока не соберёт достаточно фактов.
В таком цикле задержка складывается. Шесть секунд на один поиск - это не «ну, страница чуть медленно открылась». Десять обращений к поиску и extract - уже минута чистого ожидания, ещё до работы модели.
Tavily оказался удобной точкой совместимости: его контракт уже понимают разные agent frameworks, а /search, /extract, /crawl и /map закрывают большую часть обычных исследовательских задач. Но мне хотелось endpoint у себя - без тарифа за каждый чих и без обязательной отправки рабочих запросов во внешний сервис. «Бесплатный» здесь, конечно, означает «без платы провайдеру за запрос»: электричество, диск и железо всё ещё принадлежат суровой физической реальности.
Тут из дальнего ящика памяти выпал YaCy. Я когда-то поднимал с ним ноду и хорошо запомнил двойственное впечатление. Сам замысел был отличный: свой crawler, свой индекс, свой search server и возможность разговаривать с другими нодами. Но конкретная Java-машина на моём стенде была тяжёлой и тормозной. Возможно, на другом железе, другом индексе и после долгого тюнинга цифра была бы иной; это не универсальный benchmark YaCy. Но для моего агентского контура вердикт был простой: не подходит.
Можно было написать тонкий метапоиск поверх нескольких провайдеров. Но тогда я снова зависел бы от чужой выдачи, чужих лимитов и чужой доступности. Можно было собрать crawler + Elasticsearch и самостоятельно изобрести весь lifecycle документов. А можно было взять у YaCy уже существующий peer-протокол и совместимость, но не тащить за собой JVM, Solr и историческую архитектуру.
На Хабре о YaCy много писали и ещё больше спорили в комментариях:
«Распределённый поисковик YaCy версия 1.0», 2011 год;
«YaCy: три года спустя», 2014 год;
Комментарии там полезнее многих обзоров. Народ не обсуждал сферическую распределённость в вакууме, а ставил ноду в виртуалку и довольно быстро находил настоящие углы:
Java-процесс охотно ел память;
глобальная выдача была медленной и не всегда убедительной;
HTTP- и HTTPS-версии одной страницы могли приходить как два разных результата;
русскоязычный индекс рос медленно;
нужная настройка краулера обнаруживалась не там, где её ожидали;
было непонятно, как ограничить размер индекса;
вокруг
robots.txt, remote crawl и сетевого поведения хватало сюрпризов;устройство DHT проще было вычитать из исходников, чем из документации.
Эти старые вопросы хорошо легли поверх моей практической задачи. Первый критерий был совсем не романтическим: локальный API должен быстро и предсказуемо отвечать агенту. Уже вокруг него пришлось заново решить хранение, crawl, релевантность, совместимость, безопасность и эксплуатацию.
Не форк с новым CSS
Сразу проведу жирную черту. YaGo - не перевод интерфейса, не набор плагинов для Java YaCy и не попытка портировать класс за классом.

От YaCy берётся то, что действительно ценно и уже стало протоколом:
peer identity и seed lists;
/yacy/hello.html,/yacy/query.html;обмен RWI через
transferRWIи метаданными черезtransferURL;удалённый поиск по RWI;
выбор узлов по DHT-кольцу;
yacysearch.*, OpenSearch и совместимые клиентские поверхности;возможность войти в существующий swarm и разговаривать с Java-нодами.
Всё, что находится за этой границей, можно было проектировать заново: локальное хранилище, полнотекстовый индекс, краулер, ранжирование, админку, API, безопасность, метрики и процедуру обновления.
Вся архитектура сводится к одной фразе:
YaCy RWI/DHT - протокол обмена и совместимости. Bleve + document vault - локальный поисковик.
Это важный трюк. RWI хорош как общий сетевой формат: хеши слов, postings, URL metadata, передача между узлами. Но заставлять его изображать современный локальный полнотекстовый движок только ради исторической верности незачем.
Поэтому YaGo хранит YaCy-совместимый слой для peer-протокола, а локальную выдачу строит в отдельном индексе Bleve. Java-клиент видит знакомые endpoint-ы. Пользователь локального поиска получает BM25, поля, анализаторы, нормальные сниппеты и отдельный ranking pipeline.
Оригинальный YaCy при этом никуда не исчез. Слово «воскрешение» здесь про архитектурную идею: оставить совместимый скелет и собрать вокруг него новые внутренности.
Что находится под капотом

Минимальная установка состоит из двух процессов:
yago-node - это собственно поисковая машина. В ней живут документы, индекс, peer identity, DHT, поиск, API, портал и админская часть.
yago-crawler - расходный материал. Его задача - сходить наружу, скачать страницу, пережить кривую кодировку, при необходимости запустить Firefox, разобрать PDF или DOCX и отдать результат ноде. Его можно убить, перезапустить, посадить в cgroup или размножить в несколько экземпляров.
Между ними не стоит отдельный Kafka/NATS-зоопарк. Очередью владеет сама нода. Задание выдаётся краулеру по lease, продлевается heartbeat-ом и исчезает только после ACK. NAK, таймаут или рестарт возвращают его обратно. Ingest идёт с backpressure: если нода захлебнулась, worker не делает вид, что всё хорошо, а притормаживает и повторяет передачу.
Ранняя версия проекта как раз смотрела в сторону NATS JetStream. Потом стало ясно, что на небольшой машине это третий постоянно работающий процесс, который дублирует долговечность уже имеющегося embedded storage. NATS был выкинут, очередь переехала в ноду, а транспорт остался на gRPC.
Получился не «микросервисный enterprise», а две разные зоны риска. Поиск должен спокойно отвечать. Краулер обязан ходить по минному полю чужого веба. Лучше не смешивать их судьбу в одном процессе.
Собственный индекс — это не режим, а основа
В старых обсуждениях YaCy часто всплывала проблема курицы и яйца: в общем индексе мало нужных сайтов, поэтому их приходится добавлять самому; если все будут ждать готового индекса, он не вырастет никогда.
В YaGo эта дилемма решена довольно приземлённо. Главный сценарий - сначала свой корпус.
Можно скормить ноде:
один сайт;
несколько документаций;
sitemap;
список тематических ресурсов;
внутренний портал;
архив старых страниц;
коллекцию PDF, Office-документов, EPUB и обычного текста.
Оператор задаёт глубину, лимит страниц на хост, скорость, расписание повторного обхода, разрешённые форматы и размер хранилища. После этого получается индекс, границы которого хотя бы известны.
Поддерживаются HTML, PDF, DOCX/XLSX/PPTX, старые DOC/XLS/PPT, ODT/ODS/ODP, RTF, EPUB, Markdown, CSV и plain text. Сначала краулер пробует обычный HTTP. Headless Firefox включается только как ограниченный запасной вариант для страниц, которые без браузера не отдают полезный текст.
Поверх локального индекса можно добавить другие источники:
поиск по YaCy-пирам;
необязательный web fallback;
автоматический crawl найденных страниц - только когда это разрешено политикой оператора.
Но локальный запрос остаётся локальным. resource=local не ходит ни к пирам, ни к внешнему источнику.

Практический смысл здесь простой. Индекс из ста тысяч нужных страниц обычно полезнее, чем теоретическая возможность искать по миллиардам страниц, о составе которых никто ничего не знает.
Теперь самое больное: релевантность
Поисковик легко написать до первого запроса. Разобрали HTML, положили слова в индекс, нашли совпадения - готово. Потом вводишь название проекта и получаешь на первом месте страницу, где оно один раз встретилось в футере.
Поэтому в YaGo нет одного «главного коэффициента релевантности». Есть цепочка небольших, ограниченных этапов, каждый из которых можно объяснить и проверить.
Сначала набираем кандидатов
Локальный поиск начинается со строгого fielded BM25. Слова в заголовке, headings, anchor text, URL и body имеют разные веса. В строгой ветке должны присутствовать все термы.
Для длинных запросов есть relaxed-ветка: она допускает неполное покрытие, но не может перепрыгнуть точные совпадения просто потому, что набрала красивый score. Смешанные идентификаторы вроде CVE-2026-1234, systemd-resolved или Go1.26 остаются обязательными точными свидетельствами.
Документы направляются в языковые анализаторы. Для русского учитываются словоформы; точное совпадение всё равно ценится выше морфологического. Для арабского есть нормализация и light stemming, для CJK - ограниченные биграммы. Важная мелочь: морфология расширяет recall, но не должна притворяться точной фразой.
Затем аккуратно расширяем запрос
Верхние локальные документы могут дать несколько дополнительных термов через ограниченный RM3 feedback. Именно несколько, а не сотню слов из удачно заспамленной страницы. Расширение не отменяет исходные правила покрытия и имеет жёсткие пределы по числу документов и токенов.
Склеиваем списки без чёрной магии
Локальный индекс, YaCy-пиры и внешний fallback возвращают score в разных шкалах. Сравнивать их напрямую - всё равно что складывать температуру с оборотами вентилятора.
Поэтому списки объединяются через Reciprocal Rank Fusion. RRF смотрит в первую очередь на позиции результатов в независимых списках, а не пытается угадать общий смысл чужого score.
После слияния работают MMR и ограничение crowding: выдача не должна состоять из десяти почти одинаковых страниц одного домена.
Смотрим на сам документ
Для каждого кандидата собирается набор признаков:
совпадения в title, headings, anchors, URL и body;
порядок слов и расстояния между ними;
соответствие исходным промежуткам в запросе;
входящие ссылки;
авторитет домена;
качество текста и признаки мусора;
уверенность в дате публикации;
свежесть;
длина и форма URL;
canonical и near-duplicate cluster;
поддержка со стороны нескольких источников;
репутация пира.
Дата скачивания не выдаётся за дату публикации. Неизменившийся документ не становится «свежим» после каждого recrawl. nofollow, ugc и sponsored не дают authority и anchor boost.
Для near-duplicates используются shingles, SimHash-кандидаты и Jaccard-проверка. URL при этом не выкидываются из хранилища: кластер нужен, чтобы выбрать нормального представителя в выдаче, а не потерять историю документа.
И только потом — learning to rank
YagoRank умеет обучать две модели внутри Go-процесса:
signed linear LambdaRank;
небольшой histogram LambdaMART.
Никакого Python-сервера, GPU, ONNX Runtime или отдельного inference sidecar. Модель и её данные лежат рядом с нодой.
Но кнопка «обучить» не означает «немедленно испортить production».
Запросы делятся по кластерам, чтобы почти одинаковые формулировки не разъехались между train и test. Новый snapshot проверяется на замороженном наборе кандидатов и должен обойти одновременно чистый lexical baseline и текущую модель. Смотрятся NDCG, Recall, MRR, разнообразие, дубли, spam/safety exposure и p95 времени rerank. Promotion проходит только при достаточном числе независимых запросов и доверительном интервале, который не пересекает ноль.
Патент, статья или громкое название алгоритма дают повод открыть редактор. Права поднять новую модель в production они не дают.
Научные статьи вместо шаманского README
Современный стек - это не только Go, Docker и симпатичная админка. В поиске гораздо интереснее, откуда взялась логика ранжирования.
В YaGo использованы или разобраны следующие работы и семейства методов:
Откуда идея |
Что из неё взято |
|---|---|
BM25 и fielded retrieval |
базовый локальный поиск |
Metzler–Croft |
ordered/unordered term dependence |
RM3 / relevance models |
ограниченное расширение запроса |
Reciprocal Rank Fusion |
объединение независимых выдач |
PageRank/TrustRank |
ограниченный authority/trust signal |
SimHash, shingles, Jaccard |
поиск похожих документов |
LambdaRank/LambdaMART |
обучаемый rerank |
Team Draft и FairPairs |
online-сравнение порядков |
ранние работы Яндекса по морфологии и позиционным признакам |
словоформы и расстояния между термами |
Здесь важно не перепутать научную публикацию с украденным рецептом поисковой формулы. Публичные материалы Google и Яндекса показывают классы сигналов и исторические подходы. Они не раскрывают сегодняшние веса и не доказывают, что конкретный приём полезен на моём корпусе.
Поэтому для нового сигнала действуют скучные правила:
запрос должен укладываться в ограниченный CPU- и memory-budget;
у сигнала должна быть понятная abuse model;
он не должен требовать отправки корпуса во внешний сервис;
выигрыш проверяется на собственном holdout;
latency и recall не должны просесть;
оператор должен иметь возможность понять, почему результат поднялся.
По этой причине я не стал добавлять generative doc2query, transformer-reranker и хранение «успешных» запросов только ради галочки AI inside. Такие вещи могут быть полезны. Сначала пусть покажут измеримый выигрыш и объяснят, сколько памяти, времени и новых способов отравить индекс они приносят.
Диск не резиновый
Ещё один вопрос из старых комментариев: как сделать так, чтобы индекс не съел весь раздел.
У ноды есть явная storage quota. Документы и служебные записи лежат в шардированном vault на bbolt, значения сжимаются zstd. Полнотекстовый Bleve-index тоже разбит на shards.
Шардинг здесь не про красивый слайд. Один файл на 200 Гбайт — плохая единица резервного копирования и прекрасная единая точка отказа. В YaGo vault-файлы и индексные файлы ограничены по размеру и разложены по каталогам. Потеря одного shard означает потерю части keyspace, а не всего хранилища. Повреждённый индексный shard можно пересобрать из документов в vault.
За это приходится платить: общей транзакции на все shards нет. Поэтому сложные операции записывают долговечное состояние поэтапно и используют markers/replay для восстановления. Это не бесплатная надёжность, а выбранный компромисс.
Bleve тоже выбран не потому, что его название лучше смотрится на схеме. Рассматривался Tantivy sidecar, но для целевого self-hosted-узла это ещё один runtime, сериализация через границу процессов и отдельная эксплуатационная история. Bleve работает в том же Go-процессе, использует mmap/page cache и на целевом масштабе в один-два миллиона страниц даёт более простой appliance. Если реальные измерения покажут, что движок упёрся в стену, интерфейс SearchIndex оставляет место для замены.
И да: Go не создаёт оперативную память из воздуха. Индекс, page cache, PDF parser и Firefox всё равно потребляют ресурсы. Поэтому честный разговор о производительности начинается с таблицы: железо, корпус, RSS, размер vault, размер index, ingest pages/min, p50/p95 запросов. Фраза «у меня быстро» метрикой не является.
Краулер: туда лучше ходить в каске
Веб редко похож на набор аккуратных HTML-файлов. Там лежат бесконечные календари, кривые redirect loops, robots.txt размером с повесть, кодировки из прошлой жизни, PDF с мусором вместо текста и JavaScript-приложения, которые на обычный GET отвечают пустой оболочкой.
Поэтому yago-crawler умеет не только GET -> links -> repeat:
ограничивает размер ответов и извлечённого текста;
соблюдает
robots.txt, но парсит его под фиксированным лимитом;держит отдельный pacing на каждый host;
ограничивает глубину, страницы на host и весь run;
нормализует URL и ловит crawl traps;
заранее отбрасывает явно запрещённые форматы;
извлекает boilerplate и метаданные;
распознаёт дубли;
повторно обходит документы по расписанию;
удаляет из индекса страницы, которые окончательно ушли с
404/410;проверяет адрес назначения на этапе dial и по умолчанию не ходит в loopback, private, link-local и cloud metadata ranges.
Последний пункт особенно полезен, когда crawler управляется через API. Нельзя позволять случайному URL превратить поисковый worker в SSRF-прокладку до внутренней сети.
Remote crawl из YaCy-протокола принимается в совместимой форме, но выполнение по умолчанию выключено. Чужой peer не должен выбирать, куда полезет моя машина, пока я сам явно этого не разрешил.
Headless Firefox работает небольшим ленивым пулом и включается только после обычного HTTP. Запускать браузер на каждый URL — отличный способ превратить индексатор в обогреватель.
DHT без гадания по логам

YaCy DHT - штука не мистическая, но условий там хватает.
Чтобы peer начал нормально участвовать в обмене, мало открыть порт и увидеть себя в seed list. Нужна стабильная 12-символьная identity, reachable address, правильные flags, успешный callback, достаточный возраст peer, нужное число подключённых узлов, локальные RWI и открытые sender gates.
Передача идёт в два хода:
отправитель посылает RWI postings на
transferRWI;получатель отвечает списком URL hashes, для которых не хватает metadata;
отправитель догружает соответствующие записи через
transferURL.
Цели выбираются по позиции word hash в YaCy-кольце с учётом возможностей и возраста peer. Неудачная передача не должна испарять chunk: он возвращается в очередь, получает retry с jitter и остаётся восстанавливаемым после рестарта.
В старом стиле это часто диагностировалось так: включаем подробный лог, ждём, смотрим, не помогло — читаем Switchboard.java.
В YaGo есть DHT gate report. Он показывает:
открыт ли отправляющий контур вообще;
первую причину блокировки;
значения входных параметров;
результат каждого gate;
состояние peer roster и transfer queue.
То есть вместо лампочки «P2P: ON» можно увидеть конкретное «локальных RWI меньше порога», «peer ещё слишком молодой» или «внешний endpoint не проходит self-test».
Peer listener, публичный search и admin слушают разные порты. :8090 можно выставить наружу, :8080 завернуть через reverse proxy, а :9090 оставить на loopback. Смешивать peer-трафик и админскую сессию на одном входе причин нет.
Админка, которую не надо искать на форуме
Сразу маленькая просьба: за UI пока не бейте. Проект находится в активной разработке, и первым продуктом для меня был именно API. Админка появилась как приборная панель, без которой невозможно нормально запускать crawler, смотреть индекс, разбирать ranking и понимать, почему DHT опять закрыл gate. Полировать каждый отступ раньше, чем стабилизирован машинный контракт, я сознательно не стал. Поэтому страницы уже рабочие, но внешний вид и компоновка ещё будут меняться.

У старых self-hosted-систем есть особый жанр настройки: нужный флажок существует, но находится в третьей вкладке страницы с названием, которое никто не догадается открыть.
В YaGo admin/ops - отдельная часть продукта. Там находятся:
first-run setup;
Argon2id login, sessions, CSRF и CSP;
scoped API keys;
crawler fleet, задания и live progress;
просмотр и удаление документов;
storage quota;
backup/restore;
peer roster и DHT gates;
параметры ranking;
Search Explain;
Prometheus metrics, health и readiness;
журнал конфигурационных событий.
Интерфейс рендерится на сервере и дополняется htmx. Без JavaScript основные функции не исчезают. Внешнего CDN тоже нет: статические файлы приезжают вместе с нодой.
Search Explain особенно полезен, когда выдача выглядит странно. Вместо ответа «так решил алгоритм» можно посмотреть retrieval branch, field evidence, RRF, learned contributions, tree path и итоговую позицию.
Для машин есть несколько поверхностей: YaCy-compatible JSON/RSS/HTML, OpenSearch и Tavily-compatible /search, /extract, /crawl, /map. Именно этот контур был исходной причиной проекта: обычный поисковый contract для агентов и приложений, без обязательного внешнего провайдера, встроенного LLM-прокси и генерации красивых ответов из воздуха.
Быстрый старт
Проект активно развивается. На своих личных и рабочих задачах я уже считаю поисковое ядро и API production-ready и использую их именно так. При этом в репозитории сохраняется осторожная пометка alpha: мои стенды не равны всему реальному интернету, а чужие корпуса, прокси, NAT, кодировки и способы эксплуатации обязательно найдут новые углы.
CI требует 100% statement coverage по Go-модулям, гоняет unit-, integration- и containerized E2E-тесты, race detector и архитектурные проверки. Но backup всё равно нужен: сто процентов покрытия кода не превращают один SSD в RAID и не означают сто процентов покрытых жизненных сценариев.
Для пробы достаточно Docker или Podman:
git clone https://github.com/D4rk4/yago.git cd yago export YAGO_SEARCH_API_KEY="$(openssl rand -hex 32)" cp docker-compose.yml.example docker-compose.yml make compose-images docker compose up -d
После старта получаем три входа:
Порт |
Что там живёт |
|---|---|
|
публичный портал, YaCy search surfaces, OpenSearch и API |
|
YaCy peer protocol и RWI/DHT |
|
admin, health, readiness и metrics |
Открываем:
http://127.0.0.1:9090/admin/
Проходим первичную настройку, добавляем crawl seed и ждём первые документы.
Проверяем локальную выдачу через YaCy-compatible JSON:
curl -fsS \ 'http://127.0.0.1:8080/yacysearch.json?query=поисковые+технологии&resource=local'
Для постоянной установки есть Compose, systemd units и пакеты. На одну роль приходится один статический бинарник. JVM, отдельный Solr и внешний SQL-сервер не требуются.
Что это не такое
Перед тем как открыть комментарии, лучше сразу снять несколько популярных вопросов.
Это не SearXNG. SearXNG собирает ответы чужих поисковиков. YaGo сам обходит страницы, хранит документы и строит собственный индекс. Внешняя выдача здесь только необязательный дополнительный источник.
Это не Elasticsearch с пауком сбоку. Полнотекстовый индекс — лишь одна деталь. Здесь ещё есть crawler appliance, lifecycle документов, YaCy peer-протокол, RWI/DHT и совместимые search surfaces.
Это не полная копия Java YaCy. Старые admin servlet-ы и embedded Solr API не переносятся. Совместимость проверяется endpoint за endpoint-ом и описывается в отдельной матрице.
Это не готовый индекс всего веба. Полнота зависит от того, что вы обошли сами и что доступно у пиров. Маленькая установка не становится Google после docker compose up.
Это не «высечено в граните». Я считаю ядро production-ready для уже проверенных сценариев, но проект продолжает активно меняться. Особенно это заметно по UI: он вторичен относительно API и пока развивается быстрее, чем успевает покрываться дизайнерским лаком.
100% test coverage — не 100% реальности. Тесты доказывают, что известные ветки кода исполняются и проверяются. Они не доказывают, что я заранее придумал каждый битый PDF, корпоративный proxy, странный sitemap и запрос живого пользователя.
Наличие LambdaMART не гарантирует хорошую выдачу. Без qrels, baseline, holdout и воспроизводимых измерений название алгоритма ничего не доказывает.
Теперь нужен посетитель, который спросит, где туалет
Есть старый анекдот про тестировщика и бар. Тестировщик заказывает кружку пива, ноль кружек, минус одну, миллиард кружек, NULL и ящерицу. Бар выдерживает всё. Потом приходит первый настоящий посетитель, спрашивает, где туалет, и заведение сгорает.
С поисковиком всё ещё веселее. Можно стопроцентно покрыть код тестами, прогнать race detector, поднять две ноды в контейнерах, сломать lease, рестартовать crawler посреди ingest и проверить восстановление индекса. А потом реальный сайт пришлёт PDF, в котором шрифт хранится как семейная тайна, proxy подменит Content-Type, sitemap зациклится через календарь, а агент сформулирует запрос так, как автор ни разу не догадался.
Поэтому приглашаю всех желающих потестировать YaGo на реальных задачах. Проект уже готов к production-нагрузке в тех сценариях, которые я проверил, и покрыт тестами на 100%, но ему всё ещё не хватает чужого железа, чужих корпусов и чужих способов всё подключить.
Особенно интересны такие проверки:
подключить Tavily-compatible
/searchи/extractк своему агенту и посмотреть на p50/p95, ошибки и качество результатов;проиндексировать техническую документацию, внутренний портал или тематическую коллекцию сайтов;
накормить crawler странными PDF, Office-файлами, старыми кодировками и JavaScript-стенами;
поднять ноду за NAT или reverse proxy и проверить YaCy interop;
запустить всё на скромном VPS, домашнем сервере, ARM-плате или, наоборот, на большом корпусе;
принести запросы, на которых ranking очевидно ошибается.
Самый полезный bug report - не просто «тормозит», а tag или commit, железо, размер корпуса, конфигурация, запрос, ожидаемый результат, фактический результат и кусок логов или метрик. Даже один воспроизводимый случай полезнее десятка общих похвал.
Замечания по UI тоже принимаются, но приоритет сейчас остаётся прежним: API, правильность поиска, latency, crawler и эксплуатация. Панель можно перекрасить позже. Шесть секунд внутри агентского цикла — уже архитектурная проблема.
Что получилось в сухом остатке
YaGo - это попытка вернуть в строй лучшую часть YaCy: поисковый узел, которым владеет оператор, собственный индекс и открытый протокол обмена.
Но вернуть не музейным экспонатом, а нормальным self-hosted-сервисом:
без JVM и встроенного Solr;
с отдельным ограничиваемым crawler;
с локальным Bleve-index;
с шардированным и сжатым storage;
с многоязычным lexical search;
с объяснимым ranking pipeline;
с learning to rank, который проходит holdout перед promotion;
с наблюдаемым DHT;
с backup, metrics и админкой;
с API, к которому можно подключить обычное приложение или агента.
Технологический каркас уже собран. Теперь ценнее всего реальные измерения: сто тысяч и миллион страниц, русский технический корпус с qrels, p50/p95 выдачи, RSS во время crawl, размер vault и Bleve, interop между Go- и Java-нодами и качество до и после YagoRank.
Потому что поисковик начинается не с красивой главной страницы. Он начинается с первого кривого документа, первого переполненного диска и первого запроса, на который система обязана ответить быстрее и лучше, чем grep -R.
kezess
Я таким же образом решил свою либу писать, g4d. мне дискорго не то, чтобы сильно зашел, он тяжелый, много легаси по ощущениям, да и я не знаю всей его структуры(я тогда только го начинал учить, было много тем для изучения, на дискордго времени не было), ну я и начал писать свой проект, а-ля учебный.. уже третий месяц не могу отвязать, каждый день возвращаюсь и рефачу код, хахах