Полнотекстовый поиск, который уже лежит в SQLite: FTS5
Когда агент спрашивает у llm-wiki «какие у нас правила про логирование», нужен поиск. Можно поднять Elasticsearch, а можно открыть документацию SQLite и обнаружить, что всё уже есть. Разбираю расширение FTS5.
Создать индекс
FTS5 — это виртуальная таблица:
CREATE VIRTUAL TABLE pages USING fts5(title, body);Типы, ограничения и PRIMARY KEY здесь не указываются. Есть неявный rowid, а колонки с именами rowid и rank запрещены.
Искать
SELECT * FROM pages WHERE pages MATCH 'логирование';Язык запросов богаче, чем кажется:
"точная фраза"— фраза целиком;лог*— префиксный поиск;логи AND NOT debug— логические операторы;NEAR(секреты env, 10)— слова на расстоянии не больше 10 токенов;title : секреты— поиск только в заголовке.
Ранжировать
Встроенная функция bm25() даёт релевантность (меньше — лучше), и колонкам можно дать разный вес:
SELECT title FROM pages
WHERE pages MATCH 'секреты'
ORDER BY bm25(pages, 10.0, 1.0); -- совпадение в заголовке в 10 раз важнееЕщё быстрее сортировать по скрытой колонке rank.
Показать результат
highlight(pages, 1, '<b>', '</b>')— подсветить совпадения;snippet(pages, 1, '<b>', '</b>', '…', 20)— вырезать кусок текста вокруг совпадения.
Готовая выдача поисковика в одном SQL-запросе.
Токенизаторы
unicode61(по умолчанию) — понимает Unicode, убирает диакритику;porter— стемминг, но только для английского;trigram— поиск по любой подстроке, а не по целым словам.
Полезные приёмы
- Внешние таблицы: FTS5 может индексировать обычную таблицу, не дублируя данные (
content='...'), а синхронизация делается триггерами. - Префиксные индексы (
prefix='2 3') ускоряют поиск по началу слова. detail='none'уменьшает индекс, если не нужны фразы и NEAR.INSERT INTO pages(pages) VALUES('optimize')склеивает индекс в одно дерево.
Мой комментарий
В llm-wiki индекс — производный: его можно удалить и пересобрать из Git в любой момент, поэтому он лежит отдельно от состояния, которое нужно бэкапить. Страницы режутся на куски по заголовкам, и индексируются три колонки: заголовок страницы, заголовок раздела и текст. Для русского porter бесполезен, поэтому стоит unicode61 remove_diacritics 2, а формы слов ловит префиксный запрос вроде логир*. Не морфология, но для правил компании хватает.
Один файл, ноль серверов, BM25 из коробки. Иногда лучшая инфраструктура — та, которой нет.