SKU Search
Поиск и сопоставление номенклатуры для 1С и других учетных систем.
SKU Search — это self-hosted сервис, который умеет:
- искать товары по названию, коду, артикулу, штрихкоду и произвольным полям;
- понимать опечатки, транслитерацию и неправильную раскладку клавиатуры;
- учиться на обратной связи операторов и предлагать синонимы;
- (PRO) искать по смыслу через векторные эмбеддинги;
- (PRO) работать с иерархиями (BOM / Where-Used) и кросс-кодами.
Документация разбита на разделы — выбирайте нужный в оглавлении слева или используйте поиск.
С чего начать
- Что такое SKU Search
- Быстрый старт — скачайте архив, запустите
setup.sh(илиsetup.cmdна Windows) и поднимите сервис за пару минут - API
- CLI-утилита
- Конфигурация
Редакции
- Free — полнотекстовый поиск, бесплатен для любого использования.
- PRO — добавляет эмбеддинги, иерархию, кросс-коды, аналитику и требует лицензии.
Что это и зачем? (для 1С-специалистов)
Вы работаете в 1С. Приходит заказ на "Болт М16 оцинк.". Вы начинаете создавать новую позицию, а в базе уже есть "Болт М16х60 оцинкованный ГОСТ 7798-70". Если вы не заметите — появится дубль, остатки разъедутся, бухгалтерия будет ругаться.
sku-search — это сервис, который автоматически находит похожие товары за 2-10 миллисекунд и подсказывает: "Возможно, вы имеете в виду вот это?"
Что умеет сервис
- Ищет даже с опечатками — "блот" найдет "болт", "гака" найдет "гайка"
- Понимает неправильную раскладку — "ljvbr d lthtdyt" найдет "Домик в деревне"
- Понимает формы слов — "болты", "болтом", "болтов" → все найдет "болт"
- Работает с синонимами — "оцинк" = "оцинкованный", "цинк" = "оцинкованный"
- Учится на ваших действиях — анализирует, когда вы нажали "неверно", и предлагает синонимы
- Очень быстрый — ответ за 2-10 мс. 1С не тормозит, оператор не ждет
- Выдерживает большие объемы — бенчмарк проведен до 100 000 товаров
Когда это нужно (реальные сценарии)
- Перед созданием нового товара — оператор вводит название, сервис проверяет — нет ли уже такого в базе. Не создаем дубли!
- При загрузке прайса поставщика — автоматически сопоставить позиции прайса с вашей номенклатурой
- При очистке справочника — найти и объединить дубли (например, "Болт М6" и "Болт М6х30")
- При подборе аналогов — клиент хочет деталь, которой нет в наличии. Сервис показывает аналоги из той же группы.
- При сборке спецификаций (BOM) — показать из каких деталей состоит изделие (PRO only)
Цифры, которые вас заинтересуют
| Показатель | Значение |
|---|---|
| Скорость точного поиска по ID/штрихкоду | 1-2 мс — быстрее, чем вы моргаете |
| Скорость нечеткого поиска по названию | 2-5 мс (p50) — даже с опечатками |
| Объем индекса на 100 000 товаров | ~37 МБ (PRO-режим) |
| Потребление RAM | зависит от объема данных и модели; Free-режим занимает сотни МБ, PRO — см. раздел бенчмарков |
- Вы загружаете свои товары через
POST /add(один раз или при каждом изменении) - Сервис строит индекс — специальную структуру для быстрого поиска
- При поиске (
POST /find) сервис за миллисекунды находит самые похожие товары - Индекс сохраняется на диск — при перезапуске сервиса данные не теряются
Как работает поиск? (объясняем "на пальцах")
Пошагово: что происходит, когда вы отправляете запрос
- Вы отправляете запрос в 1С
Например, оператор вводит "блот м16 цынк" (с двумя опечатками). - 1С вызывает сервис
POST /find {"items":[{"name":"блот м16 цынк"}]} - Сервис исправляет опечатки (если включено)
"блот" → "болт" (расстояние Левенштейна)
"цынк" → "цинк" - Сервис приводит слова к нормальной форме
"болты", "болтом", "болтов" → "болт". Это называется стемминг. - Применяются синонимы
Если в конфиге есть синоним "цинк" → "оцинкованный", сервис ищет оба варианта. - Поиск в индексе
Сервис быстро находит все товары, которые содержат "болт", "м16", "оцинкованный". - Ранжирование (сортировка по релевантности)
Сервис считает для каждого товара score — число от 0 до 1, где 1 — идеальное совпадение. Товары, совпавшие с запросом по большему числу слов, получают более высокий score и поднимаются выше в списке. Это настраивается параметромsearch.weights.coverage_boost_per_token. - Фильтрация по порогам
Сервис находит максимальный score среди кандидатов и отбрасывает товары, у которых score нижеmax_score × auto_match_threshold(по умолчанию 0.85). Также учитывается абсолютный минимумabsolute_min_score. Порог работает одинаково для BM25, эмбеддинг и гибридных стратегий. - Ответ сервиса
Сервис возвращает JSON с найденными товарами, их score и способом совпадения. - 1С показывает результат оператору
"Возможно, вы имели в виду Болт М16х60 оцинкованный?"
Что такое score и как его понимать
Score — это число от 0 до 1, которое показывает, насколько найденный товар похож на ваш запрос.
| Значение score | Что значит | Что делать в 1С |
|---|---|---|
| 1.0 | Идеальное совпадение (тот же GUID или штрихкод) | Автоматически использовать — это точно тот же товар |
| 0.90 - 0.99 | Почти точно тот же товар (отличается только незначительно) | Показать оператору: "Скорее всего, это он" |
| 0.75 - 0.89 | Похоже, но не уверены | Показать список вариантов — пусть оператор выберет |
| < 0.75 | Слабое совпадение | Не показывать — создать новый товар |
- Если сервис часто предлагает неверные товары → увеличьте
auto_match_thresholdдо 0.90-0.92 - Если сервис часто ничего не находит (хотя товар есть) → уменьшите
auto_match_thresholdдо 0.75-0.80 - Начните с 0.85 — это баланс для большинства баз
Три уровня поиска (от простого к сложному)
- Точные совпадения (мгновенно, 1-2 мс)
Если вы ищете поid,barcode,codeилиart— сервис сначала проверяет их. Нашел → сразу возвращает результат. Дальше даже не ищет. - N-gram поиск (для коротких запросов, 2-5 мс)
Если вы ищете "перф", а в базе "Перфоратор". Обычный поиск не находит, потому что "перф" слишком короткий. N-gram разбивает "Перфоратор" на части: "Пер", "Перф", "Перфо", "Перфор" — и находит по "Перф". Настраивается вsearch.edge_ngram. - Нечеткий поиск (fuzzy, 3-10 мс)
Если точного совпадения нет, сервис ищет с учетом опечаток, разных форм слов и синонимов. Использует расстояние Дамерау-Левенштейна — алгоритм, который считает, сколько операций (замена, вставка, удаление или перестановка соседних символов) нужно, чтобы слово совпало. "блот" → "болт" (перестановка соседних букв) — найдет. "блооот" → "болт" (2 операции) — тоже найдет, если разрешено.
Как синонимы улучшают поиск
Синонимы — это слова, которые считаются одинаковыми при поиске. Например:
- "цинк" = "оцинкованный" = "zn" = "zinc"
- "нерж" = "нержавеющий" = "stainless" = "a2"
# Добавляем синонимы через API
curl -X POST http://localhost:8080/synonyms \
-H "Content-Type: application/json" \
-d '{"items":[
{"word":"цинк","expansions":["оцинкованный","zn","zinc"]},
{"word":"нерж","expansions":["нержавеющий","stainless","a2"]}
]}'
# Тот же запрос через CLI
sku-cli synonyms add --word "цинк" --expansions "оцинкованный,zn,zinc"
После добавления синонимов запрос "болт м16 цинк" найдет "Болт М16х60 оцинкованный" с высоким score.
- Сервис сам анализирует ваши действия и предлагает синонимы через
GET /admin/learn(PRO only) - Вы можете добавлять вручную через API или CLI
- Синонимы применяются мгновенно, перезапуск сервиса не нужен
Пример из реальной жизни
Запрос оператора: "блот м16 цынк" (с двумя опечатками)
Что делает сервис:
- Исправляет опечатки: "блот" → "болт", "цынк" → "цинк"
- Применяет синонимы: "цинк" → "оцинкованный"
- Ищет в индексе: товары, содержащие "болт", "м16", "оцинкованный"
- Находит "Болт М16х60 оцинкованный"
- Считает score: 0.94 (очень похоже)
- Возвращает результат
{
"matches": [{
"name_out": "Болт М16х60 оцинкованный ГОСТ 7798-70",
"score": 0.94,
"matched_by": "fuzzy_text"
}]
}
Оператор в 1С видит: "Возможно, вы имели в виду Болт М16х60 оцинкованный? Нажмите 'Да' чтобы использовать существующий товар."
Поля type и ref: фильтр по типу и прямая ссылка на объект 1С
Помимо id, сервис поддерживает два поля, которые упрощают интеграцию с 1С:
ref— сериализованная ссылка 1С (например, результатЗначениеВСтрокуВнутр()). Сервис хранит ее как есть и возвращает в ответе, а поиск по ней — точный.type— тип объекта (например,Справочник.Номенклатура). Позволяет ограничить поиск одним классом объектов или комбинировать сart,barcode,code.
Оба поля участвуют в exact-поиске и работают даже с пустым name.
# Поиск по ref
curl -X POST http://localhost:8080/find \
-H "Content-Type: application/json" \
-d '{"items":[{"name":"","ref":"e1cib/data/Справочник.Номенклатура?ref=a1b2c3d4e5f6..."}]}'
# Тот же запрос через CLI
sku-cli search --ref "e1cib/data/Справочник.Номенклатура?ref=a1b2c3d4e5f6..."
# Поиск с фильтром по type
curl -X POST http://localhost:8080/find \
-H "Content-Type: application/json" \
-d '{"items":[{"name":"болт","type":"Справочник.Номенклатура"}]}'
# Тот же запрос через CLI
sku-cli search "болт" --type "Справочник.Номенклатура"
Транслитерация и неправильная раскладка
Если в конфиге включено search.transliteration_enabled = true, сервис автоматически понимает запросы, набранные не в той раскладке или латиницей.
# «Домик в деревне», набрано на русской раскладке как латиница
curl -X POST http://localhost:8080/find \
-H "Content-Type: application/json" \
-d '{"items":[{"name":"ljvbr d lthtdyt"}]}'
# Та же фраза латиницей
curl -X POST http://localhost:8080/find \
-H "Content-Type: application/json" \
-d '{"items":[{"name":"domik v derevne"}]}'
# Тот же запрос через CLI
sku-cli search "ljvbr d lthtdyt"
sku-cli search "domik v derevne"
Сервис размножает каждый токен запроса на возможные варианты (исходный, исправленная раскладка, транслитерация) и ищет по всем сразу. Работает для кириллицы и латиницы.
search.transliteration_threshold (по умолчанию 0.35). Уменьшите порог, если сервис слишком редко срабатывает на транслит; увеличьте, если появляются ложные срабатывания.
Если включены эмбеддинги (PRO only), сервис может искать товары по смыслу, а не только по словам.
Например: "шуруповерт" найдет "дрель-шуруповерт" и "винтоверт", даже если этих слов нет в запросе. Векторы "понимают", что эти слова означают похожие инструменты.
Быстрый старт за 5 минут
Хотите попробовать прямо сейчас? Вот пошаговая инструкция:
Скачайте и запустите сервис
# Скачайте архив для вашей ОС (ссылка в релизах) и распакуйте его
# Выполните скрипт первого запуска:
./setup.sh # Linux / macOS
# setup.cmd # Windows
# Или вручную скопируйте пример конфигурации:
# cp config.toml.default config.toml
# PRO-версия: CPU-рантайм ONNX Runtime уже включен в архив.
# Для GPU скачайте и положите рантайм в папку runtime/ (см. ONNX Runtime в PRO).
# Запустите сервис.
# После setup.sh в корне появится ссылка/батник sku-service, которую удобно запускать:
./sku-service
# Можно также запускать исполняемый файл напрямую из папки bin/ — сервис найдет config.toml:
# ./bin/linux/sku-service
# ./bin/windows/sku-service.exe
Добавьте первый товар
curl -X POST http://localhost:8080/add \
-H "Content-Type: application/json" \
-d '{"items":[{"name":"Болт М16х60 оцинкованный","id":"TEST-001"}]}'
# Или тот же запрос через CLI:
sku-cli add item.json
# где item.json содержит:
# [
# { "id": "TEST-001", "name": "Болт М16х60 оцинкованный" }
# ]
# Или загрузите демо-каталог из архива (размер батча не более 500):
./bin/linux/sku-cli import --csv data/demo-data.csv --batch-size 500
Найдите его
curl -X POST http://localhost:8080/find \
-H "Content-Type: application/json" \
-d '{"items":[{"name":"болт м16"}]}'
# Или через CLI:
sku-cli search "болт м16"
Посмотрите результат
{
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"search_time_ms": 5,
"results": [
{
"query": { "name": "болт м16" },
"matches": [
{
"original": {
"name_out": "Болт М16х60 оцинкованный",
"id_out": "TEST-001",
"score": 1.0,
"matched_by": "fuzzy_text"
},
"cross_codes": []
}
]
}
]
}
Первоначальная загрузка данных
Как загрузить данные в сервис
- Подготовьте данные — каждый товар должен содержать уникальный идентификатор
id(ваш внутренний GUID из 1С) и названиеname. - Загрузите данные через API — отправьте
POST /addс массивом товаров. Размер пакета регулируется параметромserver.max_batch_sizeи лимитом HTTP-телаserver.max_request_body_size_mb. - Проверьте результат —
GET /healthпокажет количество загруженных записей.
id — важно:
id— ваш внутренний уникальный идентификатор товара (обычно GUID из 1С). Он обязателен при загрузке через/add.- Если вы повторно загрузите запись с тем же
id, но другим набором полей, старая запись будет полностью перезаписана новыми данными. Сервис сначала удаляет старую версию, затем добавляет новую. - Пустой или отсутствующий
idвызовет ошибку: товар не будет добавлен.
Способы загрузки
Способ 1: Загрузка из CSV файла (через CLI)
# Формат CSV: id,name,code,barcode,art
# Пример: 1C-GUID-001,Болт М16х60 оцинкованный,BOLT-001,4607000010015,78454
sku-cli import --csv items.csv
Если ваш CSV использует разделитель ; вместо ,, задайте его в config.toml:
[csv]
default_delimiter = ";"
sku-cli import при импорте учтет этот параметр.
Способ 2: Загрузка из JSON (через curl)
curl -X POST http://localhost:8080/add \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"id": "1C-GUID-001",
"name": "Болт М16х60 оцинкованный",
"code": "BOLT-001",
"barcode": "4607000010015",
"art": "78454"
},
{
"id": "1C-GUID-002",
"name": "Гайка М16 нержавеющая",
"code": "NUT-001"
}
]
}'
# Тот же запрос через CLI
# Создайте файл items.json:
[
{
"id": "1C-GUID-001",
"name": "Болт М16х60 оцинкованный",
"code": "BOLT-001",
"barcode": "4607000010015",
"art": "78454"
},
{
"id": "1C-GUID-002",
"name": "Гайка М16 нержавеющая",
"code": "NUT-001"
}
]
sku-cli add items.json
Способ 3: Прямо из 1С (через HTTP-запрос)
// Псевдокод 1С
Запрос = Новый HTTPЗапрос();
Запрос.Заголовки.Вставить("Content-Type", "application/json");
Данные = Новый Структура();
Данные.Вставить("items", СписокТоваров); // Массив структур с полями id, name, code...
Запрос.УстановитьТелоИзСтроки(JSON(Данные));
Соединение = Новый HTTPСоединение("localhost", 8080);
Ответ = Соединение.ОтправитьДляОбработки(Запрос, "/add");
Пример для тестовых данных
Если у вас нет своих данных, вот минимальный пример для тестирования:
curl -X POST http://localhost:8080/add \
-H "Content-Type: application/json" \
-d '{
"items": [
{"id": "TEST-001", "name": "Болт М6х30 оцинкованный"},
{"id": "TEST-002", "name": "Болт М8х40 оцинкованный"},
{"id": "TEST-003", "name": "Болт М10х50 нержавеющий"},
{"id": "TEST-004", "name": "Болт М12х60 нержавеющий"},
{"id": "TEST-005", "name": "Болт М16х80 оцинкованный"},
{"id": "TOOL-001", "name": "Перфоратор Bosch GBH 2-26"},
{"id": "FOOD-001", "name": "Молоко пастеризованное 3.2% 1л"}
]
}'
# Тот же запрос через CLI
# Создайте файл items.json:
[
{
"id": "TEST-001",
"name": "Болт М6х30 оцинкованный"
},
{
"id": "TEST-002",
"name": "Болт М8х40 оцинкованный"
},
{
"id": "TEST-003",
"name": "Болт М10х50 нержавеющий"
},
{
"id": "TEST-004",
"name": "Болт М12х60 нержавеющий"
},
{
"id": "TEST-005",
"name": "Болт М16х80 оцинкованный"
},
{
"id": "TOOL-001",
"name": "Перфоратор Bosch GBH 2-26"
},
{
"id": "FOOD-001",
"name": "Молоко пастеризованное 3.2% 1л"
}
]
sku-cli add items.json
Как проверить, что данные загружены
# Проверить количество записей
curl http://localhost:8080/health | jq .db_record_count
# Ожидаем: 7 (или сколько вы загрузили)
# Попробовать найти что-нибудь
curl -X POST http://localhost:8080/find \
-H "Content-Type: application/json" \
-d '{"items":[{"name":"болт"}]}' | jq .
# Тот же запрос через CLI
sku-cli health
- Пустой id — сервис вернет ошибку. Каждый товар должен иметь уникальный идентификатор.
- 413 Request Entity Too Large — тело запроса превысило
server.max_request_body_size_mb(по умолчанию 20 МБ). Уменьшите batch или увеличьте лимит вconfig.tomlиnginx.conf. - 400 Bad Request — batch превысил
server.max_batch_size(по умолчанию 500). Разбейте на части или увеличьтеmax_batch_size. - Rate limiting — при большой загрузке установите в конфиге
rate_limit_per_minute = 0. - Данные не сохранились после перезапуска — проверьте путь к индексу в конфиге. Индекс сохраняется на диск.
Производительность массовой загрузки
Размер batch и пропускная способность зависят от:
server.max_batch_size— сколько товаров принимается в одномPOST /add;server.max_request_body_size_mb— максимальный размер HTTP-тела, который готов принять сервис (и reverse proxy, например nginx);- длины полей товаров (
name,supplier, описания и т.п.); - включен ли семантический поиск (PRO) и успевают ли фоновые воркеры строить векторы.
Результаты внутреннего бенчмарка (PRO-версия, семантический поиск отключен, 100 000 товаров):
| batch | items/s | 100k за | Примечание |
|---|---|---|---|
| 500 | 866 | ~115 с | Базовый режим |
| 1 000 | 1 703 | ~58 с | |
| 2 000 | 3 380 | ~29 с | |
| 5 000 | 8 016 | ~12.5 с | |
| 10 000 | 14 121 | ~7.1 с | |
| 30 000 | 37 602 | ~2.7 с | При лимите тела 20 МБ — это максимальный работающий batch |
| 50 000 | 49 354 | ~2.0 с | Требует поднять max_request_body_size_mb и client_max_body_size до ~64 МБ |
| 100 000 | 52 476 | ~1.9 с | Один запрос на всю пачку; предел CPU/RAM и дисковой подсистемы сервера |
- Начните с
max_batch_size = 500иmax_request_body_size_mb = 20(по умолчанию). - Если 1С формирует пакеты крупнее 500 и CPU/SSD сервера позволяют — поднимайте
max_batch_sizeдо 1 000–5 000. - Batch больше 5 000 дает выигрыш только при отключенных эмбеддингах и большом лимите тела. При включенных эмбеддингах (PRO) скорость будет ограничена фоновой индексацией векторов, а не размером HTTP-пакета.
- При увеличении
max_request_body_size_mbне забудьте поднятьclient_max_body_sizeв nginx (или другом reverse proxy). - На массовой загрузке отключайте rate limiting:
rate_limit_per_minute = 0, а после загрузки возвращайте обратно. - Абсолютный потолок без семантического поиска — ~50–52k items/s. Дальше рост batch не ускоряет загрузку.
При первоначальной загрузке большого справочника (100 000+ записей):
- Временно отключите rate limiting:
rate_limit_per_minute = 0 - Разбейте данные на пакеты по 300–1 000 записей (можно больше, если позволяют настройки и железо).
- Добавьте небольшую паузу между пакетами (0.1–0.3 сек), чтобы не перегружать CPU.
- После загрузки верните настройки rate limiting обратно.
API
id— ваш уникальный идентификатор товара (обычно GUID из 1С). Без него сервис не сможет добавить или обновить товар. Повторная загрузка с тем жеidполностью перезаписывает записьname— наименование товара. Обязательно только для нечеткого поиска- Сервис ищет по полям:
name,code,barcode,art, а также динамическим полям в PRO-режиме (см. Расширенный поиск) - Ответ приходит за 2-10 мс — ваша 1С не зависнет
- Если сервис недоступен — ставьте таймаут 1-2 секунды и создавайте новый товар без проверки
security.api_key, все запросы должны содержать заголовок X-API-Key: <ключ>. Без него сервис вернет 401 Unauthorized. При превышении лимита запросов сервис вернет 429 Too Many Requests.
site.enabled = true). Все мутации (/add, /delete, /admin/*, /feedback, /synonyms POST/DELETE и др.) возвращают 403 Forbidden. Доступны только чтение: /find, /export, /health, /api/search-config, /api/config и статика.
Поля в запросах и ответах
| Поле | Где используется | Что значит | Тип | Обязательность |
|---|---|---|---|---|
name | в запросе /find, /add | Наименование товара | string | для нечеткого поиска, для точного (можно пустым) |
id | в запросе /add, /delete, /find | Ваш GUID из 1С | string | обязательно для /add |
id_out | в ответе /find | GUID найденного товара | string | — |
name_out | в ответе /find | Наименование найденного товара | string | — |
score | в ответе /find | Уверенность (0-1). 1.0 = идеальное совпадение | float | — |
matched_by | в ответе /find | Способ поиска (см. таблицу ниже) | string | — |
search_time_ms | в ответе /find | Общее время выполнения поиска в миллисекундах | integer | — |
code | в запросе/ответе | Код товара / производителя | string | опционально |
barcode | в запросе/ответе | Штрихкод (строка или массив строк) | string или array | опционально |
art | в запросе/ответе | Дополнительный артикул | string | опционально |
fields | в запросе /find | Какие дополнительные поля вернуть в ответе | object | опционально |
(динамические поля) | в запросе/ответе | Любые дополнительные реквизиты передаются плоско, на одном уровне с name, id, code (например: material, color, supplier, brand). Все значения хранятся и сравниваются как строки — числа и даты тоже становятся строками. | string | опционально (PRO only) |
strategy | в запросе /find | Стратегия поиска: "fs_only", "embedding_only", "hybrid", "parallel" | string | опционально; принимается в обеих редакциях, эффект только в PRO |
parent_id | в запросе /add | ID родительского элемента (для иерархии) | string | опционально (PRO only) |
group_id | в запросе /add | ID группы аналогов (кросс-коды) | string | опционально (PRO only) |
is_original | в запросе /add | Является ли товар оригиналом в группе | boolean | опционально (PRO only) |
limit | в запросе /find | Максимальное количество результатов (не более 1000) | integer | опционально (по умолчанию из конфига, максимум 1000) |
type | /find, /add, ответ | Тип номенклатуры (1С) | string | опционально |
desc | /find, /add, ответ | Описание/реквизиты (1С) | string | опционально |
ref | /find, /add, ответ | Ссылка/UUID 1С (точный поиск) | string | опционально |
| fs_only | /add (body) | Пропустить эмбеддинги (экономия CPU) | bool | по умолчанию false |
success | в ответе /add, /delete | Успешно ли выполнена операция | boolean | — |
error / reason | в ответе /add, /delete | Причина ошибки | string | — |
Значения поля matched_by
| Значение | Что значит |
|---|---|
exact_id | Точное совпадение по id |
exact_id_from_name | UUID из поля name распознан как id |
exact_id_fallback | Точное совпадение по id после fallback-поиска |
exact_barcode / exact_barcode_from_name | Точное совпадение по штрихкоду (в том числе из name) |
exact_code / exact_code_from_name | Точное совпадение по коду товара (в том числе из name) |
exact_art / exact_art_from_name | Точное совпадение по артикулу (в том числе из name) |
exact_type | Точное совпадение по типу 1С |
exact_ref | Точное совпадение по ссылке/UUID 1С |
exact_name / exact_name_raw | Точное совпадение по наименованию (стеммированное / исходное) |
exact_fields | Совпадение по комбинации полей запроса (code, art, type, ref, barcode) |
exact_dynamic | Точное совпадение по динамическим полям (PRO only) |
exact_identifier | Точное совпадение по идентификатору 1С или внешнему коду |
exact_parent_id | Найден по parent_id (иерархия) |
exact_1c_full_match | Полное совпадение по 1С-идентификаторам |
ngram | Найдено через Edge N-gram (по части слова) |
phrase / phrase_fallback | Найдено по точной фразе |
fuzzy_text | Найдено через нечеткий поиск (с опечатками) |
fuzzy_boolean | Найдено через булевый нечеткий поиск |
dynamic_only | Найдено только по динамическим полям (без name, PRO only) |
embedding | Найдено через семантический поиск (PRO only) |
fs_only | Найдено через полнотекстовый поиск |
fs+embedding | Гибридный поиск (PRO only) |
hierarchy_child | Найден как потомок в иерархии (PRO only) |
hierarchy_default | Значение по умолчанию при иерархическом поиске (PRO only) |
POST /find — поиск
Поиск по названию (основной сценарий):
curl -X POST http://localhost:8080/find \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"name": "Болт М16 цинк"
}
],
"limit": 3
}'
# Тот же запрос через CLI
sku-cli search "Болт М16 цинк" --limit 3
Точный поиск по ID (name можно передать пустым):
curl -X POST http://localhost:8080/find \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"name": "",
"id": "1C-GUID-0001"
}
]
}'
# Тот же запрос через CLI
sku-cli search --id "1C-GUID-0001"
Поиск по артикулу (name можно передать пустым):
curl -X POST http://localhost:8080/find \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"name": "",
"code": "BOLT-M16-60-ZN"
}
]
}'
# Тот же запрос через CLI
sku-cli search --code "BOLT-M16-60-ZN"
Поиск по динамическим полям (PRO only):
curl -X POST http://localhost:8080/find \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"name": "",
"supplier": "ООО Ромашка",
"brand": "Bosch"
}
],
"limit": 20
}'
# Тот же запрос через CLI
sku-cli search "" --field supplier="ООО Ромашка" --field brand="Bosch" --dynamic-only --limit 20
extended_search_enabled = true и require_name_for_fuzzy = false.
Смешанный поиск (название + артикул + динамические поля):
curl -X POST http://localhost:8080/find \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"name": "Болт М16",
"art": "78454",
"color": "оцинкованный"
}
],
"limit": 5
}'
# Тот же запрос через CLI
sku-cli search "Болт М16" --art "78454" --field color="оцинкованный" --limit 5
С указанием стратегии поиска:
curl -X POST http://localhost:8080/find \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"name": "шуруповерт аккумуляторный"
}
],
"strategy": "hybrid",
"limit": 5
}'
# Тот же запрос через CLI
sku-cli search "шуруповерт аккумуляторный" --limit 5 --strategy hybrid
Поиск по type (фильтр по типу объекта 1С):
curl -X POST http://localhost:8080/find \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"name": "болт",
"type": "Справочник.Номенклатура"
}
]
}'
# Тот же запрос через CLI
sku-cli search "болт" --type "Справочник.Номенклатура"
Поиск по ref (прямая ссылка на объект 1С):
curl -X POST http://localhost:8080/find \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"name": "",
"ref": "e1cib/data/Справочник.Номенклатура?ref=a1b2c3d4e5f6..."
}
]
}'
# Тот же запрос через CLI
sku-cli search --ref "e1cib/data/Справочник.Номенклатура?ref=a1b2c3d4e5f6..."
Транслитерация и неправильная раскладка:
# Русская фраза, набранная латиницей на русской раскладке
curl -X POST http://localhost:8080/find \
-H "Content-Type: application/json" \
-d '{"items":[{"name":"ljvbr d lthtdyt"}]}'
# Латинская транслитерация
curl -X POST http://localhost:8080/find \
-H "Content-Type: application/json" \
-d '{"items":[{"name":"domik v derevne"}]}'
# Тот же запрос через CLI
sku-cli search "ljvbr d lthtdyt"
sku-cli search "domik v derevne"
config.toml включен параметр search.transliteration_enabled = true.
Ответ (когда товар найден):
{
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"search_time_ms": 12,
"results": [
{
"query": {
"name": "Болт М16 цинк",
"id": null,
"code": null,
"art": null,
"barcode": [],
"fields": {},
"parent_id": null,
"group_id": null,
"is_original": false,
"type": null,
"desc": null,
"ref": null
},
"matches": [
{
"original": {
"name_out": "Болт М16х60 оцинкованный ГОСТ 7798-70",
"id_out": "1C-GUID-0001",
"code": "BOLT-M16-60-ZN",
"barcode": ["4607000010015"],
"art": "78454",
"score": 0.94,
"matched_by": "fuzzy_text",
"is_original": false,
"matched_tokens": ["болт", "м16", "оцинкованный"]
}
}
]
}
]
}
cross_codes возвращается только при непустом списке кросс-кодов; если аналогов нет, ключ в JSON отсутствует.
Ответ с кросс-кодами (PRO only):
Параметр include_cross_codes по умолчанию true. Кросс-коды возвращаются только если в конфигурации включены pro.enabled и pro.cross_codes.enabled.
{
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"search_time_ms": 8,
"results": [
{
"query": { ... },
"matches": [
{
"original": {
"name_out": "Болт М16х60 оцинкованный",
"id_out": "DETAL-BOSCH-001",
"group_id": "GROUP-BOSCH-001",
"is_original": true,
"score": 0.94
},
"cross_codes": [
{
"name_out": "Болт М16х60 оцинкованный аналог",
"id_out": "DETAL-BOSCH-002",
"score": 0.89,
"is_original": false
}
]
}
]
}
]
}
Ответ (когда ничего не найдено):
{
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"search_time_ms": 3,
"results": [
{
"query": { "name": "несуществующий товар", ... },
"matches": []
}
]
}
- Обычные extra-поля (не описанные в
<code>[[search.extended_fields]]</code>) сохраняются вf_attrsи возвращаются автоматически, если они проиндексированы. - Расширенные поля (
<code>[[search.extended_fields]]</code>, PRO) сохраняются в отдельные поля схемы поискового индекса. Они возвращаются в ответе, только когда поиск выполняется именно по этому полю (обычно с пустымname). Если товар найден по названию или эмбеддингам, расширенное поле может отсутствовать. - «Технические» 1С-значения (GUID, строки только из цифр/дефисов/точек/слешей) не попадают в
f_attrsи не возвращаются как extra-атрибуты.
POST /add — добавить или обновить
Запрос:
curl -X POST http://localhost:8080/add \
-H "Content-Type: application/json" \
-d '{
"items": [{
"name": "Гайка М16 нержавеющая",
"id": "1C-GUID-0002",
"code": "NUT-M16-A2",
"barcode": ["4607000010022"],
"art": "GN-16",
"supplier": "ООО Ромашка",
"brand": "Nord"
}]
}'
# Тот же запрос через CLI
# Создайте файл item.json:
[
{
"name": "Гайка М16 нержавеющая",
"id": "1C-GUID-0002",
"code": "NUT-M16-A2",
"barcode": [
"4607000010022"
],
"art": "GN-16",
"supplier": "ООО Ромашка",
"brand": "Nord"
}
]
sku-cli add item.json
id: Если в индексе уже есть товар с таким id, сервис удалит старую запись и запишет новую. Это позволяет обновлять товары, но требует, чтобы id был уникальным и не менялся между загрузками.
Ответ (успех):
{
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"success": true,
"error": null,
"results": [
{
"id": "1C-GUID-0002",
"success": true,
"error": null
}
]
}
Запрос с ошибкой (пустой id):
curl -X POST http://localhost:8080/add \
-H "Content-Type: application/json" \
-d '{
"items": [{
"name": "Товар без GUID",
"id": ""
}]
}'
# Тот же запрос через CLI
# Создайте файл item.json:
[
{
"name": "Товар без GUID",
"id": ""
}
]
sku-cli add item.json
Ответ (ошибка):
{
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"success": false,
"error": "Batch validation failed",
"results": [
{
"id": "",
"success": false,
"error": "id missing or empty"
}
]
}
POST /add?dry_run=true — проверить без записи
curl -X POST "http://localhost:8080/add?dry_run=true" \
-H "Content-Type: application/json" \
-d '{"items":[{"name":"Новый товар","id":"NEW-001"}]}'
# Тот же запрос через CLI
# Создайте файл item.json:
[
{
"name": "Новый товар",
"id": "NEW-001"
}
]
sku-cli add item.json --dry-run
Ответ:
{
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"success": true,
"error": null,
"results": [
{
"id": "NEW-001",
"success": true,
"error": "would_insert"
}
]
}
POST /delete — удалить
curl -X POST http://localhost:8080/delete \
-H "Content-Type: application/json" \
-d '{"items":[{"id":"1C-GUID-OLD-001"}]}'
# Тот же запрос через CLI
sku-cli delete "1C-GUID-OLD-001"
Ответ:
{
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"success": true,
"error": null,
"results": [
{
"id": "1C-GUID-OLD-001",
"success": true,
"error": null
}
]
}
POST /synonyms — управление синонимами
curl -X POST http://localhost:8080/synonyms \
-H "Content-Type: application/json" \
-d '{"items":[{"word":"цинк","expansions":["оцинкованный","zn","zinc"]}]}'
# Тот же запрос через CLI
sku-cli synonyms add --word "цинк" --expansions "оцинкованный,zn,zinc"
Ответ:
{
"results": [
{
"word": "цинк",
"success": true,
"reason": null
}
]
}
GET /synonyms — получить все синонимы
curl http://localhost:8080/synonyms
# Тот же запрос через CLI
sku-cli synonyms list
Ответ:
{
"synonyms": {
"цинк": ["оцинкованный", "zn", "zinc"],
"болт": ["болтик", "винт"]
}
}
POST /feedback — обратная связь для обучения
curl -X POST http://localhost:8080/feedback \
-H "Content-Type: application/json" \
-d '{
"query_name": "болт м16 цинк",
"matched_id_out": "1C-GUID-0001",
"action": "reject"
}'
# Тот же запрос через CLI
sku-cli feedback --query "болт м16 цинк" --matched-id "1C-GUID-0001" --action reject
Ответ:
{
"status": "logged"
}
query_name, matched_id_out и action, запрос может содержать опциональные поля:
product_name, product_art, product_barcode, matched_by, position, session_id, expected_id.
GET /health — проверить состояние сервиса
curl http://localhost:8080/health
# Тот же запрос через CLI
sku-cli health
Ответ:
{
"status": "ok",
"version": "0.2.9",
"edition": "pro",
"uptime_seconds": 3600,
"config_path": "/app/config.toml",
"config_hash": "a2476ef4e23468b5",
"db_record_count": 20001,
"db_size_mb": 142.5,
"disk_free_gb": 180.2,
"synonyms_count": 42,
"cpu_usage_percent": 2.1,
"ram_used_mb": 11912,
"backups_count": 3,
"cache_hits": 1234,
"cache_misses": 567,
"cache_size": 1000,
"qps": 12.5,
"index_status": {
"optimized": true,
"segments_count": 1
},
"orchestrator": {
"strategy": "hybrid",
"fs_enabled": true,
"embedding_enabled": true,
"total_searches": 0,
"avg_time_ms": 0.0
},
"embedding": {
"enabled": true,
"model_loaded": true,
"vector_count": 20399,
"vector_dimension": 1024,
"avg_search_time_ms": 0.0
},
"embedding_queue": {
"enabled": true,
"pending": 0,
"processing": 0,
"completed": 20399,
"failed": 0,
"dead": 0,
"total": 20399
},
"background_workers": {
"worker_count": 2,
"active_workers": 2,
"total_restarts": 0,
"restarts_last_minute": 0,
"max_restarts_per_minute": 8,
"monitor_interval_secs": 2
},
"license": {
"enabled": true,
"active": true,
"fingerprint": "sha256:..."
},
"last_error": null
}
{"enabled": false, "model_loaded": false, "vector_count": 0} (для embedding) или {"enabled": false} (для остальных). Поле last_error всегда присутствует и равно null, если ошибок не было.
GET /export — выгрузить все данные
curl "http://localhost:8080/export?format=csv&limit=1000&offset=0" -o export.csv
Параметры:
format—"json"(по умолчанию) или"csv"limit— количество строк (макс. из конфига, по умолчанию 1000)offset— смещение (для пагинации)
Ответ (CSV):
id_out,name,code,barcode,art
1C-GUID-0001,Болт М16х60 оцинкованный ГОСТ 7798-70,BOLT-M16-60-ZN,"[""4607000010015""]",78454
1C-GUID-0002,Гайка М16 нержавеющая,NUT-M16-A2,"[""4607000010022""]",GN-16
GET /api/search-config — получить конфигурацию поиска (для UI)
curl http://localhost:8080/api/search-config
# Тот же запрос через CLI
sku-cli config
Ответ:
{
"extended_fields": [
{"field": "supplier", "weight": 0.7, "stemmed": true, "fuzzy": true},
{"field": "brand", "weight": 0.6, "stemmed": true, "fuzzy": true}
],
"extended_search_enabled": true,
"require_name_for_fuzzy": false,
"pro_enabled": true,
"embedding_enabled": true,
"search_strategy": "parallel",
"pro_info_url": "https://example.com/pro",
"purchase_url": "https://example.com/purchase"
}
extended_fields возвращается пустым массивом, pro_enabled и embedding_enabled — false. Поля pro_info_url и purchase_url присутствуют в обеих редакциях, если заданы в конфигурации.
PRO API — Аналитика и тюнинг (только PRO версия)
pro.analytics_enabled = true.
GET /admin/analytics/stats — статистика поиска
curl http://localhost:8080/admin/analytics/stats
# Тот же запрос через CLI
sku-cli analytics stats
POST /admin/analytics/reset — сброс статистики
GET /admin/analytics/tuning — рекомендации по настройке
PRO API — Иерархия (BOM / Where-Used)
pro.plugins.hierarchy.enabled = true.
POST /hierarchy — построение дерева спецификаций
Запрос (разузлование вниз):
curl -X POST http://localhost:8080/hierarchy \
-H "Content-Type: application/json" \
-d '{
"root_id": "DETAL-001",
"direction": "down",
"max_depth": 5,
"include_cross_codes": true,
"limit": 100
}'
Параметры:
root_id— ID корневого элемента (обязательно). Дляdirection=downэто начало дерева; дляdirection=up— деталь, для которой ищутся родители.direction— только строчные значения:"down"(разузлование, от изделия к деталям) или"up"(Where-Used, от детали к изделиям). Значения с заглавной буквы ("Down","UP") вернут ошибку 422.max_depth— максимальное количество уровней от корня. Корень имеет уровень 0, его прямые потомки/родители — уровень 1 и т.д. По умолчанию 5 (из конфигаpro.plugins.hierarchy.default_max_depth).include_cross_codes— включать аналоги для каждого узла. Сам узел в свой список аналогов не попадает. По умолчаниюfalse(из конфигаpro.plugins.hierarchy.include_cross_codes_by_default).limit— общий лимит узлов во всем дереве или цепочке, включая корень. Не «на уровень». По умолчанию 1000 (из конфигаpro.plugins.hierarchy.default_limit).
PRO API — Управление эмбеддингами
GET /admin/embedding/stats — статистика эмбеддингов
POST /admin/embedding/reindex — переиндексация векторов
POST /admin/embedding/reindex/background — фоновая переиндексация
POST /admin/embedding/clear — очистить векторное хранилище
POST /admin/embedding/optimize — оптимизировать HNSW индекс
POST /admin/embedding/cache/clear — очистить кэш эмбеддингов
Административные эндпоинты
POST /admin/clear — очистить весь индекс (PRO only)
GET /admin/learn — анализ фидбека и предложение синонимов
GET /admin/index/stats — статистика индекса
POST /admin/index/optimize — оптимизация индекса (мерж сегментов)
GET /admin/queries/top — топ запросов
POST /admin/cache/clear — очистить кэш запросов
POST /admin/config/update — обновить конфигурацию на лету
curl -X POST http://localhost:8080/admin/config/update \\
-H "Content-Type: application/json" \\
-d '{"search":{"auto_match_threshold":0.9}}'
GET /admin/config — текущая конфигурация
GET /admin/config и GET /api/config возвращают одинаковую редактируемую конфигурацию (секции search, pro, csv, export). Различий между «admin» и «public» представлением нет.
GET /api/config — текущая конфигурация
GET /swagger-ui — интерактивная документация OpenAPI
GET / — embedded GUI
Возвращает HTML-страницу встроенного интерфейса (Free/PRO GUI или страница активации), а не JSON-статус.
Полный список эндпоинтов
/add, /delete, /feedback, /synonyms, /admin/config/update, /admin/clear, /admin/index/optimize, /admin/cache/clear, /admin/analytics/reset, /admin/embedding/*, /hierarchy и др.) регистрируются только при allow_mutations = true. В режиме «только для сайта» (site.enabled = true) мутации возвращают 403 Forbidden.
| Метод | Путь | Описание | Free | PRO |
|---|---|---|---|---|
| GET | / | Embedded GUI (Free/PRO/активация) | ✓ | ✓ |
| GET | /sample_queries | Примеры поисковых запросов | ✓ | ✓ |
| GET | /docs/* | Встроенная документация (mdbook) | ✓ | ✓ |
| GET | /static/* | Статические файлы GUI | ✓ | ✓ |
| GET | /swagger-ui | Интерактивная документация OpenAPI | ✓ | ✓ |
| GET | /api-docs/openapi.json | OpenAPI-спецификация | ✓ | ✓ |
| GET | /api/update-check | Проверка обновлений | ✓ | ✓ |
| POST | /find | Поиск номенклатуры | ✓ | ✓ |
| POST | /add | Добавить/обновить товары | ✓ | ✓ |
| POST | /delete | Удалить товары | ✓ | ✓ |
| POST | /synonyms | Управление синонимами | ✓ | ✓ |
| DELETE | /synonyms/:word | Удалить синоним | ✓ | ✓ |
| GET | /synonyms | Получить синонимы | ✓ | ✓ |
| POST | /feedback | Обратная связь для обучения | ✓ | ✓ |
| GET | /health | Состояние сервиса | ✓ | ✓ |
| GET | /export | Экспорт данных | ✓ | ✓ |
| GET | /api/search-config | Конфигурация для UI | ✓ | ✓ |
| GET | /api/config | Текущая конфигурация | ✓ | ✓ |
| POST | /api/activate | Активация PRO-лицензии | — | ✓ |
| GET | /api/license/status | Статус лицензии | — | ✓ |
| GET | /api/license/fingerprint | Fingerprint сервера | — | ✓ |
| POST | /api/license/upload | Загрузка файла лицензии | — | ✓ |
| GET | /api/license/heartbeat | Heartbeat лицензии | — | ✓ |
| GET | /api/license/crl | Список отозванных лицензий | — | ✓ |
| POST | /api/license/recover | Восстановление лицензии | — | ✓ |
| GET | /api/subscription-status | Статус подписки | — | ✓ |
| GET | /admin | Admin GUI | ✓ | ✓ |
| POST | /admin/clear | Очистить индекс | — | ✓ |
| GET | /admin/learn | Анализ фидбека | — | ✓ |
| GET | /admin/index/stats | Статистика индекса | — | ✓ |
| POST | /admin/index/optimize | Оптимизация индекса | — | ✓ |
| GET | /admin/queries/top | Топ-10 запросов | — | ✓ |
| POST | /admin/cache/clear | Очистить кэш | — | ✓ |
| POST | /admin/config/update | Обновить конфигурацию на лету | ✓ | ✓ |
| GET | /admin/config | Текущая конфигурация | ✓ | ✓ |
| POST | /hierarchy | Иерархия (BOM) | — | ✓ |
| GET | /admin/analytics/stats | Статистика аналитики | — | ✓ |
| POST | /admin/analytics/reset | Сброс аналитики | — | ✓ |
| GET | /admin/analytics/tuning | Рекомендации по настройке | — | ✓ |
| GET | /admin/embedding/stats | Статистика эмбеддингов | — | ✓ |
| POST | /admin/embedding/reindex | Переиндексация векторов | — | ✓ |
| POST | /admin/embedding/reindex/background | Фоновая переиндексация | — | ✓ |
| POST | /admin/embedding/clear | Очистить векторное хранилище | — | ✓ |
| POST | /admin/embedding/optimize | Оптимизировать HNSW | — | ✓ |
| POST | /admin/embedding/cache/clear | Очистить кэш эмбеддингов | — | ✓ |
| GET | /admin/background/stats | Статистика фоновой очереди | — | ✓ |
Динамические поля и расширенный поиск
[[search.extended_fields]] индексируются и участвуют в поиске только в PRO-сборке. В Free-режиме секция [[search.extended_fields]] игнорируется; произвольные атрибуты можно индексировать через enable_attrs_indexing (см. раздел «Произвольные поля (attrs)» ниже).
Представьте: вам нужно найти всех поставщиков, у которых есть товар с определенным кодом ТН ВЭД, или найти все позиции конкретного бренда. Вы не знаете точное название — знаете только характеристики. Сервис ищет по ним, даже если name пустой.
Как это работает: два режима
В конфиге есть два ключевых параметра, которые управляют расширенным поиском:
true— сервис индексирует и ищет по дополнительным полям (бренд, поставщик, цвет, размер и т.д.)false— секция[[search.extended_fields]]игнорируется
false. Во встроенных production-конфигах (deployment/sku-pro/config.toml) параметр установлен в true.
true(режим совместимости по умолчанию) — нечеткий поиск поnameтребует непустогоname. Поиск ТОЛЬКО по динамическим полям приname = ""работает независимо от этого флагаfalse— разрешает нечеткий поиск поnameдаже с пустымname(полностью коммерческий режим)
supplier, brand, tnved и другим расширенным полям работает при любом значении, если extended_search_enabled = true.
Таблица режимов работы
extended_search_enabled | require_name_for_fuzzy | Что получаем | Когда использовать |
|---|---|---|---|
false | true или false | Базовые поля (name, code, barcode, art) + attrs при enable_attrs_indexing = true. Расширенные поля [[search.extended_fields]] не используются | Простая база, не нужны доп. реквизиты |
true | true | Расширенные поля работают как доп. фильтр; нечеткий поиск по name требует непустого name. Поиск только по динамическим полям при name = "" работает | Нужны доп. характеристики, но название всегда известно |
true | false | ПОЛНЫЙ КОММЕРЧЕСКИЙ РЕЖИМ — можно искать ТОЛЬКО по динамическим полям; нечеткий поиск по name также доступен с пустым name | Поставщики, бренды, ТН ВЭД, характеристики — название не требуется |
Что такое динамические поля и как их настроить
Динамические поля — это любые дополнительные реквизиты товара. Например:
supplier— поставщик ("ООО Ромашка")brand— бренд ("Bosch", "Samsung")tnved— код ТН ВЭД (для таможни)color— цвет ("красный", "синий")size— размер ("42", "XL")diagonal— диагональ телевизора ("55")material— материал ("нержавеющая сталь")
Главная особенность: разные товары могут иметь РАЗНЫЕ наборы полей. Обувь — цвет и размер. Холодильники — диагональ и бренд. Сервис индексирует только те поля, которые есть у конкретного товара. Поиск работает по любым полям, независимо от их наличия у других товаров.
Настройка в config.toml
# Включаем расширенный поиск
[search]
extended_search_enabled = true
require_name_for_fuzzy = false # ← коммерческий режим
# Описываем все возможные поля (один раз для всей базы)
[[search.extended_fields]]
field = "supplier" # Имя поля в JSON
weight = 0.7 # Сохраняется в конфиге; в текущей версии не влияет на FS-ранжирование
stemmed = true # Приводить к основе слова ("поставщики" → "поставщик")
fuzzy = true # Разрешить опечатки ("Ромашкаа" → "Ромашка")
[[search.extended_fields]]
field = "tnved"
weight = 0.9 # Сохраняется в конфиге; в текущей версии не влияет на FS-ранжирование
stemmed = false # Код должен совпадать ТОЧНО
fuzzy = false
[[search.extended_fields]]
field = "size"
weight = 0.4 # Сохраняется в конфиге; в текущей версии не влияет на FS-ранжирование
stemmed = false
fuzzy = true
[[search.extended_fields]]
field = "color"
weight = 0.3 # Сохраняется в конфиге; в текущей версии не влияет на FS-ранжирование
stemmed = true
fuzzy = true
Параметры поля ([[search.extended_fields]])
/api/search-config, но в текущей версии не используется при вычислении итогового скора в FS-поиске. Ранжирование по динамическим полям определяется глобальными настройками [search.weights] и фиксированными бустами.
- Для кодов (tnved, артикулы производителя): 0.8-1.0 — точное совпадение критично
- Для названий (бренд, поставщик): 0.5-0.7 — важно, но не фатально
- Для характеристик (цвет, размер): 0.3-0.5 — уточняющие параметры
true: "поставщики", "поставщиком", "поставщиках" → все найдет "поставщик"false: только точное совпадение (для кодов, артикулов)
true: "Ромашкаа" найдет "Ромашка", "Samsng" найдет "Samsung"false: только точное совпадение
Сценарии использования с примерами
Сценарий 1: Поиск всех товаров поставщика
Найти все товары от "ООО Ромашка", даже не зная названий:
curl -X POST http://localhost:8080/find \
-H "Content-Type: application/json" \
-d '{
"items": [{
"name": "",
"supplier": "ООО Ромашка"
}],
"limit": 50
}'
# Тот же запрос через CLI
sku-cli search "" --field supplier="ООО Ромашка" --dynamic-only --limit 50
Сценарий 2: Фильтрация по характеристикам
Найти все телевизоры Samsung с диагональю 55 дюймов:
curl -X POST http://localhost:8080/find \
-H "Content-Type: application/json" \
-d '{
"items": [{
"name": "",
"brand": "Samsung",
"diagonal": "55"
}],
"limit": 20
}'
# Тот же запрос через CLI
sku-cli search "" --field brand="Samsung" --field diagonal="55" --dynamic-only --limit 20
Сценарий 3: Поиск по коду ТН ВЭД
Точный поиск по классификатору (без опечаток, точное совпадение):
curl -X POST http://localhost:8080/find \
-H "Content-Type: application/json" \
-d '{
"items": [{
"name": "",
"tnved": "7307 19 100 0"
}]
}'
# Тот же запрос через CLI
sku-cli search "" --field tnved="7307 19 100 0" --dynamic-only
Сценарий 4: Смешанный поиск (название + характеристики)
Ищем красные кроссовки 42 размера:
curl -X POST http://localhost:8080/find \
-H "Content-Type: application/json" \
-d '{
"items": [{
"name": "кроссовки",
"color": "красный",
"size": "42"
}],
"limit": 10
}'
# Тот же запрос через CLI
sku-cli search "кроссовки" --field color="красный" --field size="42" --limit 10
Сценарий 5: Пакетный поиск для сверки с прайсом поставщика
Загружаете прайс от поставщика и хотите найти, какие товары уже есть в вашей базе:
curl -X POST http://localhost:8080/find \
-H "Content-Type: application/json" \
-d '{
"items": [
{"name": "", "supplier_code": "ART-001"},
{"name": "", "supplier_code": "ART-002"},
{"name": "", "supplier_code": "ART-003"}
],
"limit": 1
}'
Как это работает внутри (для понимания)
- Настройка полей в конфиге — вы описываете все возможные поля один раз. Сервис запоминает их.
- Загрузка товара — вы отправляете товар с любым набором полей. Сервис индексирует ТОЛЬКО те поля, которые есть у этого товара.
- Поиск — вы отправляете запрос с любыми полями. Сервис ищет среди товаров, у которых эти поля есть. Если поля нет у товара — он просто не попадет в результаты.
- Точные совпадения —
id,barcode,code,art(1-2 мс) - Точные совпадения по динамическим полям —
supplier,tnvedи т.д. (1-2 мс) - Нечеткий поиск — по названию и динамическим полям (3-10 мс)
color и brand), сервис вернет только товары, у которых совпали ВСЕ указанные поля. ExtendedField.fuzzy определяет, разрешен ли нечеткий поиск для конкретного поля.
Пример: разные товары — разные поля
Представьте, что у вас в базе есть и обувь, и холодильники. У них совершенно разные характеристики:
# Загружаем кроссовки (есть цвет, размер, бренд)
{
"id": "SHOE-001",
"name": "Кроссовки спортивные",
"color": "красный",
"size": "42",
"brand": "Nike"
}
# Загружаем холодильник (есть бренд, диагональ, объем)
{
"id": "FRIDGE-001",
"name": "Холодильник Side-by-Side",
"brand": "Samsung",
"diagonal": "55",
"volume": "600"
}
Что получится при поиске:
- Запрос
{"color": "красный"}→ найдет ТОЛЬКО кроссовки - Запрос
{"diagonal": "55"}→ найдет ТОЛЬКО холодильник - Запрос
{"brand": "Samsung"}→ найдет холодильник (и ничего больше) - Запрос
{"color": "красный", "brand": "Samsung"}→ ничего не найдет (нет товара, у которого есть оба поля)
Частые ошибки и как их исправить
Решение: Проверьте, что extended_search_enabled = true и поля описаны в [[search.extended_fields]]. Поиск только по динамическим полям работает независимо от require_name_for_fuzzy. Если же нужен нечеткий поиск по пустому name — установите require_name_for_fuzzy = false.
Решение: Проверьте, описано ли поле в [[search.extended_fields]]. Если поле не описано — оно не индексируется и не участвует в поиске.
Решение: Проверьте настройки стемминга и fuzzy, а также глобальные веса в секции [search.weights] (см. config.md). Индивидуальный параметр weight в [[search.extended_fields]] в текущей версии не влияет на ранжирование.
Решение: Включите fuzzy = true для этого поля. Без него только точные совпадения.
Рекомендации для коммерческого внедрения
- Включите
extended_search_enabled = true - Опишите ВСЕ поля, которые есть в вашей базе (даже если они есть не у всех товаров)
- Для коммерческого режима установите
require_name_for_fuzzy = false - Настройте стемминг и fuzzy-логику полей в зависимости от их важности:
- Ключевые идентификаторы (ТН ВЭД, артикул производителя) →
stemmed = false,fuzzy = false - Бренд, поставщик →
stemmed = true,fuzzy = true - Характеристики (цвет, размер) →
stemmed = false или true(по ситуации),fuzzy = true
- Ключевые идентификаторы (ТН ВЭД, артикул производителя) →
- Протестируйте поиск: отправьте запросы, которые реально делают ваши операторы
- При необходимости скорректируйте глобальные веса в секции
[search.weights](см.config.md)
Произвольные поля (attrs) — Атрибуты и реквизиты 1С
Для разработчиков и аналитиков: В 1С у номенклатуры могут быть сотни произвольных реквизитов (цвет, материал, ГОСТ, гарантия, страна и т.д.). Сервис умеет автоматически индексировать и искать по ним, даже если они не описаны в конфиге явно.
Как это работает под капотом: API → Индекс
При загрузке товара через POST /add вы передаете произвольные ключи плоско, на одном уровне с name, id, code (они попадают в ItemQuery.extra благодаря #[serde(flatten)]). Не заворачивайте их во вложенный объект extra — это вызовет ошибку 422. Сервис автоматически разделяет их на два потока индексации:
attrs— для точной фильтрации. Идеально для кодов, размеров, артикулов производителей, нормативных документов.attrs_flat— для нечеткого и стеммированного поиска. Сервис склеивает значения в строку, применяет стеммер и стоп-слова. Идеально для названий брендов, материалов, характеристик.
Важно: Механизм управляется параметром enable_attrs_indexing в секции [search]. При false произвольные поля игнорируются на уровне индексации.
Настройка в config.toml
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
enable_attrs_indexing | bool | false | Включить индексацию произвольных полей. true активирует двойную запись: attrs (точное) + attrs_flat (текстовое). |
search.weights.attrs_exact_weight | f32 | 1.0 | Множитель к скору при точном совпадении по JSON-атрибутам (динамическим extra-полям). |
search.weights.attrs_flat_weight | f32 | 0.7 | Вес текстового буста через плоские атрибуты. Влияет на скор при нечетком/тематическом поиске по характеристикам. |
Пример загрузки товара с атрибутами
curl -X POST http://localhost:8080/add \
-H "Content-Type: application/json" \
-d '{
"items": [{
"name": "Болт М16х60",
"id": "1C-GUID-0001",
"code": "BOLT-16-60",
"color": "оцинкованный",
"material": "сталь 35",
"gost": "7798-70",
"warranty_months": "24",
"manufacturer": "Уралметиз"
}]
}'
Что произойдет внутри сервиса:
- Известные поля (
name,id,code) пойдут в стандартные индексы. - Остальные поля (
color,material,gostи др.) автоматически попадут вattrs(для точного поиска) иattrs_flat(для текстового поиска с учетом опечаток и словоформ). - Авто-фильтрация мусора: Поля с техническими значениями (только цифры, символы
- . / пробел _ </code>, GUID, внутренние коды 1С) и значения короче 2 символов не участвуют в полнотекстовом поиске: они пропускаются изattrs_flat. Однако после версии с исправлением ATTR-001 они все равно сохраняются в JSONattrsи возвращаются в ответе — это позволяет использовать их как точный фильтр (например,"size": "42"или"internal_code": "000000042").
Как искать по атрибутам
Сервис автоматически ищет по attrs_flat, если вы передаете текст в запросе. Для точной фильтрации по конкретному атрибуту передавайте ключ в теле запроса:
# Поиск товаров из "стали 35" (с учетом опечаток: "сталь", "стл", "стали 35")
curl -X POST http://localhost:8080/find \
-H "Content-Type: application/json" \
-d '{"items": [{"name": "", "material": "сталь"}], "limit": 10}'
# Точный поиск по ГОСТу (через attrs JSON, без стемминга и fuzzy)
curl -X POST http://localhost:8080/find \
-H "Content-Type: application/json" \
-d '{"items": [{"name": "", "gost": "7798-70"}], "limit": 5}'
# Комбинированный запрос: название + произвольный атрибут
curl -X POST http://localhost:8080/find \
-H "Content-Type: application/json" \
-d '{"items": [{"name": "болт", "color": "оцинк"}], "limit": 20}'
Совет: При включенном enable_attrs_indexing атрибуты участвуют в поиске автоматически. Вам не нужно описывать их в [[search.extended_fields]], если не требуется индивидуальная настройка веса, стемминга или fuzzy-логики.
Ограничения и рекомендации для продакшена
- Рост индекса: Индексация
attrsиattrs_flatувеличивает размер индекса на ~10-15%. Для баз >1 млн позиций с редким поиском по реквизитам рекомендуетсяfalse. - Приоритет полей: Если поле явно описано в
[[search.extended_fields]], оно будет индексироваться строго по правилам конфига и не попадет в автоматическийattrs_flat. - Post-фильтрация: Результаты, найденные через
attrs, проходят строгую проверку на совпадение ключ-значение. Если в запросе"gost": "7798-70", товар с"gost": "7798"не пройдет фильтр, даже если скор высокий. - Производительность: Поиск по
attrs_flatиспользует тот же стеммер и стоп-слова, что иname. Убедитесь, что отраслевой шум (упак, кор, шт) добавлен вstopwords.txt.
Что класть в extra, а что нет
Произвольные атрибуты удобны для коротких характеристик товара. Но не все стоит туда складывать — важно понимать, что попадает в поисковый индекс, а что только хранится.
Подходит для extra:
- Краткие характеристики:
color,size,brand,material,gost,country,warranty. - Идентификаторы, которые нужны в ответе или как фильтр:
supplier_code,internal_code,oem,serial. - Короткие текстовые метки и теги:
series,line,segment. - Значения, по которым может искать оператор:
diagonal,voltage,weight.
Не подходит для extra:
- Большие тексты документов (текст договора, ТЗ, спецификации, инструкции). Они сохранятся, но и будут токенизироваться в индексе — индекс сильно разрастется, а поиск замедлится.
- Бинарные данные в base64, JSON-объекты, HTML-разметка.
- Длинные табличные данные (например, прайс-лист внутри одного поля).
- Поля, которые нужно искать по диапазонам: даты, цены (если требуется «от/до»).
Правило: если значение больше пары строк или им не ищут как по обычному слову — храните его в 1С / файловом хранилище / S3, а в SKU Search передавайте только метаданные и ключевые слова.
42), даты (2026-09-29), булевы значения (true) передаются JSON-корректно, но внутри сервиса и при сравнении они являются строками. Это значит: нет числовых операций («цена от 1000 до 2000»), нет диапазонов дат, нет математики. Если нужно искать по диапазону — используйте 1С или нормальную базу данных.
extra при включенном enable_attrs_indexing индексируются. Для таких сценариев используйте внешнее хранилище и передавайте в сервис только ссылку или краткое содержание.
Режимы фильтрации по extra
По умолчанию параметры extra работают как жесткий фильтр: товар не попадает в результаты, если хотя бы одно указанное поле не совпадает. Это поведение можно изменить без переиндексации.
| Параметр | Значение | Эффект |
|---|---|---|
search.extra_filter_mode = "hard" | По умолчанию | Точный фильтр: только товары со всеми совпавшими extra-полями. |
search.extra_filter_mode = "soft" | Требует enable_attrs_indexing = true | Совпадение по extra увеличивает скор, но не отсекает остальные товары. Силу буста задает search.weights.extra_match_boost. |
search.extra_filter_mode = "off" | Требует enable_attrs_indexing = true | extra игнорируется при поиске; поля по-прежнему сохраняются и возвращаются в ответе. |
Пример: мягкое бустирование по бренду
Допустим, оператор ищет "кроссовки" и хочет, чтобы товары Nike шли первыми, но не хочет терять Adidas:
# config.toml
[search]
enable_attrs_indexing = true
extra_filter_mode = "soft"
[search.weights]
extra_match_boost = 0.5
curl -X POST http://localhost:8080/find \
-H "Content-Type: application/json" \
-d '{"items": [{"name": "кроссовки", "brand": "Nike"}], "limit": 20}'
В ответе Nike будет выше, чем Adidas, но Adidas тоже останется в списке. При extra_filter_mode = "hard" в ответе остались бы только Nike.
"soft" и "off" работают только при enable_attrs_indexing = true. Если индексация атрибутов выключена, extra не влияет на поиск в любом режиме.
Кросс-коды (аналоги и заменители) — PRO only
Что это такое: Когда один товар можно заменить другим. Например, у вас есть оригинальная деталь "Болт М16х60 оцинкованный", а в базе есть три аналога от разных производителей. При поиске по артикулу ЛЮБОГО аналога — сервис вернет ВСЮ группу, и оператор увидит все доступные варианты.
Как это устроено в данных
При загрузке товаров вы указываете для каждого:
group_id— идентификатор группы аналогов (одинаковый для всех взаимозаменяемых товаров)is_original— является ли этот товар оригиналом (true/false)
# Загружаем группу аналогов
curl -X POST http://localhost:8080/add \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"id": "DETAL-001",
"name": "Болт М16х60 оцинкованный оригинал",
"group_id": "GROUP-BOLT-001",
"is_original": true
},
{
"id": "DETAL-002",
"name": "Болт М16х60 оцинкованный аналог (Nord)",
"group_id": "GROUP-BOLT-001",
"is_original": false
},
{
"id": "DETAL-003",
"name": "Болт М16х60 оцинкованный аналог (DIN)",
"group_id": "GROUP-BOLT-001",
"is_original": false
}
]
}'
# Тот же запрос через CLI
# Создайте файл items.json:
[
{
"id": "DETAL-001",
"name": "Болт М16х60 оцинкованный оригинал",
"group_id": "GROUP-BOLT-001",
"is_original": true
},
{
"id": "DETAL-002",
"name": "Болт М16х60 оцинкованный аналог (Nord)",
"group_id": "GROUP-BOLT-001",
"is_original": false
},
{
"id": "DETAL-003",
"name": "Болт М16х60 оцинкованный аналог (DIN)",
"group_id": "GROUP-BOLT-001",
"is_original": false
}
]
sku-cli add items.json
Как это выглядит в ответе поиска
При поиске по любому из товаров группы вы получите оригинал + список аналогов:
{
"search_time_ms": 10,
"results": [{
"query": { "name": "болт м16" },
"matches": [{
"original": {
"name_out": "Болт М16х60 оцинкованный оригинал",
"id_out": "DETAL-001",
"group_id": "GROUP-BOLT-001",
"is_original": true,
"score": 0.94
},
"cross_codes": [
{
"name_out": "Болт М16х60 оцинкованный аналог (Nord)",
"id_out": "DETAL-002",
"score": 0.89,
"is_original": false
},
{
"name_out": "Болт М16х60 оцинкованный аналог (DIN)",
"id_out": "DETAL-003",
"score": 0.85,
"is_original": false
}
]
}]
}]
}
Настройка в config.toml
[pro.cross_codes]
enabled = true # Включить поддержку кросс-кодов
max_group_size = 30 # Максимум аналогов в ответе
include_in_response = true # Возвращать кросс-коды в /find
sort_within_group = true # Сортировать аналоги по релевантности
include_original_in_cross_list = false # Не дублировать оригинал в массиве аналогов
- 10 — для быстрых ответов (магазины)
- 30 — стандарт (рекомендуется)
- 50-100 — для автозапчастей (много аналогов)
Иерархия (BOM / Where-Used) — PRO only
pro.plugins.hierarchy.enabled = true. Для построения нетривиальных деревьев (связей parent_id) необходимо также включить pro.cross_codes.enabled = true; иначе /hierarchy вернет вырожденное дерево (root без детей).
Что это такое: Возможность строить деревья спецификаций (BOM — Bill of Materials) и находить, где используется деталь (Where-Used).
- "Вниз" (down) — берем готовое изделие и смотрим, из каких деталей оно состоит (разузлование)
- "Вверх" (up) — берем деталь и ищем, в каких изделиях она используется (где применяется)
Как это устроено в данных
При загрузке товаров вы указываете для каждого:
parent_id— ID родительского товара (для которого эта деталь является составной частью)
# Загружаем иерархию: Изделие → Деталь1 → Деталь2
curl -X POST http://localhost:8080/add \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"id": "PROD-001",
"name": "Изделие (корневая сборка)",
"parent_id": null
},
{
"id": "PART-001",
"name": "Деталь А",
"parent_id": "PROD-001"
},
{
"id": "PART-002",
"name": "Деталь Б",
"parent_id": "PROD-001"
},
{
"id": "SUB-001",
"name": "Поддеталь А.1",
"parent_id": "PART-001"
}
]
}'
# Тот же запрос через CLI
# Создайте файл items.json:
[
{
"id": "PROD-001",
"name": "Изделие (корневая сборка)",
"parent_id": null
},
{
"id": "PART-001",
"name": "Деталь А",
"parent_id": "PROD-001"
},
{
"id": "PART-002",
"name": "Деталь Б",
"parent_id": "PROD-001"
},
{
"id": "SUB-001",
"name": "Поддеталь А.1",
"parent_id": "PART-001"
}
]
sku-cli add items.json
Пример 1: Разузлование (direction = "down")
Показать, из чего состоит изделие "PROD-001":
curl -X POST http://localhost:8080/hierarchy \
-H "Content-Type: application/json" \
-d '{
"root_id": "PROD-001",
"direction": "down",
"max_depth": 5,
"include_cross_codes": false,
"limit": 100
}'
Ответ (дерево):
{
"root": {
"name_out": "Изделие (корневая сборка)",
"id_out": "PROD-001"
},
"tree": {
"item": { "name_out": "Изделие (корневая сборка)", "id_out": "PROD-001" },
"level": 0,
"children": [
{
"item": { "name_out": "Деталь А", "id_out": "PART-001" },
"level": 1,
"children": [
{
"item": { "name_out": "Поддеталь А.1", "id_out": "SUB-001" },
"level": 2,
"children": []
}
]
},
{
"item": { "name_out": "Деталь Б", "id_out": "PART-002" },
"level": 1,
"children": []
}
]
},
"stats": {
"total_nodes": 4,
"max_depth_reached": 2,
"cross_codes_found": 0,
"elapsed_ms": 8
}
}
Пример 2: Where-Used (direction = "up")
Найти, где используется деталь "PART-001":
curl -X POST http://localhost:8080/hierarchy \
-H "Content-Type: application/json" \
-d '{
"root_id": "PART-001",
"direction": "up",
"max_depth": 5
}'
Ответ (цепочка родителей):
{
"root": {
"name_out": "Изделие (корневая сборка)",
"id_out": "PROD-001"
},
"tree": {
"item": { "name_out": "Изделие (корневая сборка)", "id_out": "PROD-001" },
"level": 0,
"children": [
{
"item": { "name_out": "Деталь А", "id_out": "PART-001" },
"level": 1,
"children": []
}
]
},
"stats": {
"total_nodes": 2,
"max_depth_reached": 1,
"elapsed_ms": 5
}
}
Пример 3: С включением кросс-кодов
При разузловании можно также получить аналоги для каждой детали:
curl -X POST http://localhost:8080/hierarchy \
-H "Content-Type: application/json" \
-d '{
"root_id": "PROD-001",
"direction": "down",
"include_cross_codes": true
}'
Ответ (с аналогами):
{
"tree": {
"item": { ... },
"cross_codes": [], // у корневого изделия может не быть аналогов
"children": [
{
"item": { "name_out": "Деталь А", "id_out": "PART-001" },
"cross_codes": [
{ "name_out": "Деталь А аналог (Nord)", "id_out": "PART-001-ALT" }
],
"children": [ ... ]
}
]
}
}
Настройка в config.toml
[pro.plugins.hierarchy]
enabled = true # Включить плагин иерархии
default_max_depth = 5 # Максимальная глубина по умолчанию
default_limit = 1000 # Лимит узлов в ответе
include_cross_codes_by_default = false # Включать кросс-коды по умолчанию
Когда это нужно
| Сценарий | Что делаем | Направление |
|---|---|---|
| Производство (спецификация изделия) | Показать из каких деталей состоит продукт | down |
| Ремонт (запчасти) | Найти, в каких изделиях используется деталь | up |
| Закупки | При поиске аналога детали — показать все родительские изделия | down + include_cross_codes |
| Конструкторская документация | Экспорт полного дерева в JSON или CSV | down |
⌨ CLI-утилита
Общие параметры
# Указать сервер явно
sku-cli -s http://localhost:8081 health
# Или через переменную окружения
export SKU_CLI_SERVER=http://localhost:8082
sku-cli health
# Тихий режим (только JSON) — только флаг `-q`, env не поддерживается
sku-cli -q search "болт" --format json
# Авторизация по API-ключу (если включена) — только флаг `-a`, env не поддерживается
sku-cli -a secret-key search "болт"
# Загрузить config.toml (нужен для csv.default_delimiter и др.)
sku-cli --config /etc/sku-search/config.toml health
-s / --server, -a / --api-key, -q / --quiet, -c / --config. Переменные окружения поддерживаются только для SKU_CLI_SERVER.
1. Поиск
Команда search принимает позиционный запрос (название) и дополнительные фильтры. UUID длиной 36 символов автоматически интерпретируется как id.
# Обычный поиск
sku-cli search "болт м16" --limit 5
# Поиск по коду, артикулу, штрихкоду
sku-cli search --code CSV-001 --art ART-42
sku-cli search --barcode 4601234567890 --barcode 4609876543210
sku-cli search --id 550e8400-e29b-41d4-a716-446655440000
# Поиск с указанием стратегии (принимается везде, эффект только в PRO)
sku-cli search "шуруповерт" --strategy hybrid --limit 10
# Поиск только по динамическим/extra-полям (PRO only)
sku-cli search "" --field supplier="ООО Ромашка" --dynamic-only
sku-cli search "болт" --field material=сталь --field color=черный
# Поиск с дополнительными 1С-полями
sku-cli search "болт" --type "крепеж" --desc "оцинкованный" --ref "uuid-123"
# Поиск с детальной статистикой
sku-cli search "болт" --show-stats --format json
Стратегии поиска
Флаг --strategy принимается в обеих редакциях, но эффект имеет только в PRO (во Free любое значение сводится к fs_only).
fs_only— только полнотекстовый поиск (default).embedding_only— только векторный (семантический) поиск (PRO).hybrid— полнотекстовый поиск + эмбеддинги, взвешенный скор (PRO).parallel— полнотекстовый поиск и эмбеддинги параллельно, быстрее, но выше нагрузка (PRO).
2. Добавление товаров
Команда add загружает JSON-файл с массивом объектов ItemQuery.
# Прямая загрузка
sku-cli add items.json
# Проверка без записи
sku-cli add items.json --dry-run
# Только полнотекстовый индекс (без эмбеддингов, быстрее)
sku-cli add items.json --fs-only
Пример файла items.json:
[
{
"id": "ID-001",
"name": "Болт М6x20",
"code": "CSV-001",
"art": "ART-001",
"barcode": ["4601234567890"],
"type": "крепеж",
"material": "сталь",
"color": "оцинкованный"
}
]
3. Импорт из CSV
Команда import преобразует CSV в батчи /add.
# Базовый импорт (батчи по 500 строк — лимит сервера)
sku-cli import --csv items.csv
# Импорт демо-данных из архива
sku-cli import --csv data/demo-data.csv --batch-size 500
# Сухой запуск
sku-cli import --csv items.csv --dry-run
# Пропуск ошибок и свой размер батча
sku-cli import --csv items.csv --skip-errors --batch-size 500
# Нестандартный разделитель через флаг
sku-cli import --csv items.csv --delimiter ';'
# Разделитель по умолчанию из config.toml
sku-cli --config config.toml import --csv items.csv
# Без эмбеддингов
sku-cli import --csv items.csv --fs-only
CSV должен содержать колонки id и name. Рекомендуемые колонки: code, art, barcode, type. Остальные колонки импортируются как extra-атрибуты. Сервер принимает батчи максимум 500 строк; CLI по умолчанию использует 500.
4. Экспорт
# JSON в stdout
sku-cli export --format json --limit 1000 > backup.json
# CSV в файл
sku-cli export --format csv --limit 10000 --output export.csv
# Постранично
sku-cli export --format json --offset 1000 --limit 1000 > page2.json
5. Удаление
# По одному ID
sku-cli delete "ID-001"
# Несколько ID
sku-cli delete "ID-001" "ID-002" "ID-003"
# Из файла (один ID на строку)
sku-cli delete --file ids.txt
6. Обратная связь (feedback)
Помогает обучению поиска. Регистрирует, какой результат был выбран для запроса.
# Подтвердить релевантность (для обучения синонимов важно передать product_name)
sku-cli feedback --query "болт м10" --matched-id "ID-001" --action accept --product-name "Болт М10х40 оцинкованный"
# Отметить нерелевантность
sku-cli feedback --query "болт м10" --matched-id "ID-002" --action reject
# Запрос без результатов
sku-cli feedback --query "перфоратор бош" --action zero_result
# Дополнительные опции: --product-art, --product-barcode, --matched-by, --position, --session-id, --expected-id
7. Синонимы
# Добавить синонимы
sku-cli synonyms add --word "цинк" --expansions "оцинкованный,zn,zinc"
# Список всех синонимов
sku-cli synonyms list
# Фильтр по слову
sku-cli synonyms list --word "цинк"
# Удалить синоним
sku-cli synonyms remove --word "цинк"
8. Администрирование индекса (PRO only)
# Статистика индекса
sku-cli index-stats
# Оптимизация индекса (мерж сегментов)
sku-cli optimize
# Очистка кэша запросов
sku-cli cache-clear
# Полная очистка индекса (ОСТОРОЖНО!)
sku-cli clear --force
404.
9. Аналитика и обучение (PRO)
# Топ запросов
sku-cli top-queries --limit 20
# Статистика аналитики
sku-cli analytics stats
# Рекомендации по тюнингу
sku-cli analytics tuning
# Сброс накопленной аналитики
sku-cli analytics reset
# Предложения синонимов на основе фидбека
sku-cli learn
10. Управление эмбеддингами (PRO)
# Статистика эмбеддинг-движка
sku-cli embedding stats
# Статус фоновой очереди
sku-cli background
# Переиндексация векторов (синхронно)
sku-cli embedding reindex
# Переиндексация векторов в фоне
sku-cli embedding reindex --background
# Оптимизация векторного индекса
sku-cli embedding optimize
# Очистка кэша модели
sku-cli embedding cache-clear
# Удалить все векторы (ОСТОРОЖНО!)
sku-cli embedding clear
11. Иерархия (PRO)
# Разузлование (дети)
sku-cli hierarchy "ID-001" --direction down --depth 3
# Where-used (родители)
sku-cli hierarchy "ID-001" --direction up --depth 3
# С кросс-кодами и JSON-выводом
sku-cli hierarchy "ID-001" --include-cross --format json
12. Утилиты
# Проверка здоровья сервера
sku-cli health
sku-cli health | jq .db_record_count
# Текущая конфигурация поиска
sku-cli config
# Версия CLI
sku-cli version
# Автодополнение для bash
sku-cli completion -s bash > /etc/bash_completion.d/sku-cli
# Автодополнение для zsh
sku-cli completion -s zsh > ~/.zsh/completions/_sku-cli
# Man-страница
sku-cli man > /tmp/sku-cli.1
Быстрый справочник по переменным окружения
SKU_CLI_SERVER. API-ключ (-a) и тихий режим (-q) задаются только флагами, переменных окружения для них нет.
#!/bin/bash
# Автоматическая синхронизация с 1С
# Выгружаем все товары в CSV
sku-cli export --format csv --limit 50000 --output /tmp/export.csv
# Импортируем новые товары из прайса
sku-cli import --csv /tmp/price.csv --skip-errors
# Оптимизируем индекс
sku-cli optimize
# Получаем статистику
sku-cli health | jq .db_record_count
Архитектура и продвинутые концепции
Три уровня поиска — от простого к сложному
Сервис ищет товары на трех уровнях:
id, barcode, code, art. Скорость: 1-2 мс. Нашел → сразу возвращает результат.id/code/art/barcode. Однако в стратегиях hybrid и parallel семантический поиск запускается параллельно с полнотекстовым всегда, а их результаты объединяются по весам.
- Пользователи используют сленг ("шурик", "макака", "холодильник самсунг")
- В названиях товаров нет точных совпадений со словами пользователей
- Вы хотите поднять точность поиска с 85% до 92-95%
- Если все товары ищутся по артикулу/штрихкоду — эмбеддинги не дадут выигрыша
Принципиальная схема поиска
┌─────────────────────────────────────────────────────────────┐
│ sku-search │
├─────────────────────────────────────────────────────────────┤
│ │
│ Запрос пользователя: "шурик бош" │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Маршрутизатор запросов │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌────────────┐ │ │
│ │ │ Точный поиск │ │ N-gram/Fuzzy │ │ Векторный │ │ │
│ │ │ (ID/артикул) │ │ (текст) │ │ поиск │ │ │
│ │ └──────┬───────┘ └──────┬───────┘ └─────┬──────┘ │ │
│ │ │ │ │ │ │
│ │ └─────────────────┼─────────────────┘ │ │
│ │ ▼ │ │
│ │ ┌─────────────────────┐ │ │
│ │ │ Слияние и ранжирование │ │
│ │ └─────────────────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Результат: "шуруповерт бош" (score: 0.94) │
└─────────────────────────────────────────────────────────────┘
Семантический поиск (эмбеддинги) — как это работает
[pro.search] и подготовленной ONNX-модели.
Простая аналогия: Представьте, что каждый товар и каждый поисковый запрос получают свой «отпечаток пальца» — набор из 1024 чисел (для модели bge-m3; e5-small дает 384 числа). Похожие по смыслу товары имеют похожие отпечатки. Сервис умеет находить «отпечатки», которые ближе всего к отпечатку запроса, за 5-10 мс.
Как это работает на практике
multilingual-e5-small) в формат ONNX с INT8-квантованием. Получаем файл model.onnx (30-40 МБ).Гибридный поиск — как объединяются результаты
Когда включены оба движка (полнотекстовый + семантический поиск), сервис запускает их параллельно и объединяет результаты с весами:
Итоговый score = normalize(текстовый_score) × weight_tantivy + normalize(семантический_score) × weight_embedding
Перед объединением скоры обоих источников нормализуются (`min_max`, `robust`, `z_score`, `softmax` или `none`). Метод задается в `pro.search.orchestrator.score_normalization` (default: `robust`).
Упрощенный пример (значения до нормализации):
Полнотекстовый поиск нашел товар с score = 0.70
Семантический поиск нашел тот же товар с score = 0.92
Веса: weight_tantivy = 0.6, weight_embedding = 0.4
Итоговый score ≈ 0.70 × 0.6 + 0.92 × 0.4 = 0.42 + 0.368 = 0.788
Рекомендуемые веса
| Режим | tantivy_weight | embedding_weight | Когда использовать |
|---|---|---|---|
| Преимущество полнотекстового поиска (баланс) | 0.6 | 0.4 | Большинство кейсов, рекомендуется по умолчанию |
| Равные права | 0.5 | 0.5 | Текст и семантика одинаково важны |
| Преимущество семантики | 0.4 | 0.6 | Сленг и семантика важнее точных кодов |
| Только полнотекстовый поиск | 1.0 | 0.0 | Эмбеддинги выключены |
| Только семантический поиск | 0.0 | 1.0 | Экспериментальный режим |
Как подготовить ONNX-модель с INT8-квантованием
# 1. Установка зависимостей (одна команда)
pip install optimum[exporters] onnxruntime transformers
# 2. Конвертация E5-small в ONNX с INT8-квантованием
optimum-cli export onnx --model intfloat/multilingual-e5-small --task feature-extraction --optimize O2 ./models/e5
optimum-cli onnxruntime quantize --onnx_model ./models/e5 -o ./models/e5-int8 --avx512
# Готово! В папке ./models/e5-int8 вы найдете model.onnx (30-40 МБ)
Доступные модели для конвертации
| Модель | Размер | Скорость инференса | Точность |
|---|---|---|---|
| multilingual-e5-small | 30-40 МБ | Быстро (10-30 мс) | Хорошая |
| multilingual-e5-base | ~280 МБ | Средне (20-60 мс) | Очень хорошая |
| multilingual-e5-large | ~560 МБ | Медленно (50-150 мс) | Отличная |
Векторное хранилище — что хранит и как ищет
Векторы товаров хранятся во встраиваемой векторной базе данных. Она не требует отдельного сервера — это просто файлы на диске.
- Хранит пары:
(ID товара, вектор) - Тип индекса задается параметром
pro.search.embedding.vector_index_type(default:"auto"). Рекомендуется оставить"auto"— хранилище само выберет подходящий индекс. При смене типа нужно выполнитьsku-cli embedding clearи переиндексировать товары, иначе останется старый индекс. - Поиск: 1-10 мс для 1 млн векторов
- Не требует держать все векторы в RAM — данные на диске
# Что хранится в векторном хранилище (упрощенно)
┌─────────────────────────────────────┐
│ id: "DETAL-001" │
│ vector: [0.123, -0.456, 0.789...] │ ← 1024 числа (bge-m3)
├─────────────────────────────────────┤
│ id: "DETAL-002" │
│ vector: [0.111, -0.444, 0.777...] │
└─────────────────────────────────────┘
Как эмбеддинги работают с дополнительными полями
Простой принцип: перед отправкой в модель вы склеиваете ВСЕ значимые поля в один «супер-текст». Модель сама «поймет», где бренд, где название, где артикул.
Пример супер-текста для эмбеддинга:
"Шуруповерт аккумуляторный Bosch GSR 12V-15 электроинструмент"
Что дает:
• Запрос "шурик" → найдет этот товар (сленг)
• Запрос "bosch gsr" → найдет (артикул)
• Запрос "аккумуляторный инструмент" → найдет (категория)
Настройка весов для каждого поля не нужна — модель сама научилась определять важность разных частей текста.
Скорость работы: честные цифры
| Операция | Время | Примечание |
|---|---|---|
| Точный поиск (ID/штрихкод) | 1-2 мс | Всегда работает быстро |
| N-gram / Fuzzy поиск | 3-10 мс | Основной режим |
| Эмбеддинг (вектор запроса) | 10-30 мс | С INT8-квантованием |
| Поиск в векторном хранилище (1 млн векторов) | 5-10 мс | Автоматически выбранный индекс |
| Гибридный поиск (текст + векторы) | 20-50 мс | Параллельный запуск |
| Индексация 10 000 товаров (с эмбеддингами) | ~2 минуты | Фоновая пакетная обработка |
Сравнение подходов (для принятия решений)
| Функция | Только полнотекстовый поиск | Текст + эмбеддинги |
|---|---|---|
| Точный поиск (ID/штрихкод) | ✓ | ✓ |
| Fuzzy / опечатки | ✓ | ✓ |
| N-gram (короткие запросы) | ✓ | ✓ |
| Семантика / сленг ("шурик" → "шуруповерт") | — | ✓ |
| Синонимы без ручного ввода | — | ✓ |
| Скорость поиска | 2-10 мс | 15-50 мс |
| Память (RAM) | 200-500 МБ | 500-1000 МБ |
| Размер дистрибутива | 10-20 МБ | 60-80 МБ |
- Основная логика стабильна
- Есть реальные запросы пользователей, которые текущий поиск не покрывает
- Вы готовы добавить «AI-усиление» как PRO-фичу
Частые вопросы по архитектуре и эмбеддингам
1. Нужно ли переиндексировать все товары при смене модели?
Да. У каждой модели свое «понимание» смысла — векторы будут разными. Если вы меняете модель, векторное хранилище нужно перезаполнить.
2. А если я хочу попробовать несколько моделей?
Сервис хранит векторы только в одной таблице на инстанс (задается через lancedb.path + collection_name). Переход на другую модель делается в четыре шага:
- Удалите старые векторы:
sku-cli embedding clear(или вручную удалите файл векторного хранилища). - В
config.tomlизменитеactive_backend,model_pathиvector_dimensionна новую модель. - Перезапустите сервис.
- Заново загрузите все товары — сервис пересчитает эмбеддинги для каждого добавленного товара. Старые векторы при этом будут перезаписаны.
3. Как эмбеддинги влияют на время индексации?
Добавляет ~20-30 мс на товар (вычисление вектора). Для массовой загрузки используйте пакетную обработку (batch-инференс) — это ускорит процесс в 5-10 раз.
4. Мой сервер слабый — потянет ли он эмбеддинги?
Минимальные требования для E5-small с INT8:
- RAM: 500 МБ свободной (модель 30 МБ + векторное хранилище)
- CPU: любой с поддержкой AVX2 (Intel Core i3 / AMD Ryzen 3 и выше)
- Диск: SSD (для векторного хранилища)
Если сервер очень слабый — используйте только полнотекстовый поиск (эмбеддинги отключите).
ONNX Runtime в PRO-редакции
sku-search-pro.zip, распаковали его и не планируете использовать GPU, дополнительно ничего скачивать не нужно — нужные .so/.dll уже лежат в папке runtime/.
PRO-версия загружает ONNX Runtime из папки runtime/ рядом с исполняемым файлом. Это позволяет:
- поставлять один исполняемый файл для всех конфигураций;
- использовать разные рантаймы: CPU, CUDA, DirectML, ROCm;
- не качать лишние сотни мегабайт GPU-зависимостей, если GPU не нужен.
Что входит в поставку
В архиве PRO уже есть CPU-рантайм, а также созданы пустые папки для GPU-рантаймов. После распаковки структура выглядит так:
sku-search-pro/
├── sku-service
├── config.toml
└── runtime/
├── linux-cpu/
│ ├── libonnxruntime.so.1.23.2
│ ├── libonnxruntime.so.1 -> libonnxruntime.so.1.23.2
│ ├── libonnxruntime.so -> libonnxruntime.so.1
│ └── libonnxruntime_providers_shared.so
├── linux-cuda/ # пустая, сюда кладется CUDA-рантайм
├── linux-rocm/ # пустая, сюда кладется ROCm-рантайм
├── win-cpu/ # уже содержит onnxruntime.dll с CPU + DirectML
└── win-cuda/ # пустая, сюда кладется CUDA-рантайм
Если вы не используете GPU, ничего докачивать не нужно — сервис сразу загрузит CPU-рантайм из папки runtime/linux-cpu/ (или runtime/win-cpu/ на Windows). В win-cpu/onnxruntime.dll уже встроен DirectML execution provider; отдельно докачивать DirectML не требуется. Для CUDA нужно положить файлы в runtime/win-cuda/ или runtime/linux-cuda/.
Как подключить GPU-рантайм
- Скачайте нужный архив с официальной страницы ONNX Runtime 1.23.2:
| Платформа | Вариант | Ссылка | Размер архива |
|---|---|---|---|
| Linux | CPU (уже в поставке) | onnxruntime-linux-x64-1.23.2.tgz | ~8 МБ |
| Linux | GPU (CUDA) | onnxruntime-linux-x64-gpu-1.23.2.tgz | ~230 МБ |
| Windows | CPU / DirectML (уже в поставке) | onnxruntime-win-x64-1.23.2.zip | ~78 МБ |
| Windows | GPU (CUDA) | onnxruntime-win-x64-gpu-1.23.2.zip | ~326 МБ |
| macOS | CPU | onnxruntime-osx-universal2-1.23.2.tgz | — |
libonnxruntime.so и положить ее в runtime/linux-rocm/.
- Распакуйте архив и перенесите библиотеки в папку
runtime/рядом сsku-service:
Linux CUDA:
tar -xzf onnxruntime-linux-x64-gpu-1.23.2.tgz
mkdir -p runtime/linux-cuda
cp onnxruntime-linux-x64-gpu-1.23.2/lib/libonnxruntime.so* runtime/linux-cuda/
cp onnxruntime-linux-x64-gpu-1.23.2/lib/libonnxruntime_providers_* runtime/linux-cuda/
Windows CUDA:
unzip onnxruntime-win-x64-gpu-1.23.2.zip
mkdir runtime\win-cuda
xcopy onnxruntime-win-x64-gpu-1.23.2\lib\*.dll runtime\win-cuda\
Execution providers (провайдеры инференса)
PRO-сервис пытается зарегистрировать execution providers в следующем порядке:
- CUDA (Linux), DirectML / CUDA (Windows) — если в конфиге
use_gpu = true. - CPU (всегда доступен).
use_gpu = false в секции [pro.search.embedding].
Как сервис выбирает рантайм
Сервис ищет папку runtime/ в нескольких местах:
- В директории, заданной переменной окружения
SKU_ORT_RUNTIME_DIR. - Рядом с исполняемым файлом
sku-service. - В текущей рабочей директории.
Внутри runtime/ проверяются подпапки в порядке приоритета:
- Linux:
linux-cuda→linux-rocm→linux-cpu. - Windows:
win-cuda→win-cpu(вwin-cpuуже есть DirectML). - macOS:
macos-cpu.
Если найден GPU-рантайм и он успешно загрузился — используется GPU. Если нет — сервис пытается загрузить CPU-рантайм. Если ни один не загрузился, embedding-движок отключается, но текстовый поиск продолжает работать.
Настройка производительности embedding-бэкенда
PRO-версия позволяет управлять тем, сколько ресурсов CPU/GPU тратить на вычисление эмбеддингов. Основные параметры — в секциях [pro.search.embedding] и [pro.search.embedding.<backend>] (где <backend> = e5 или bge_m3):
| Параметр | Где задается | Значение | Эффект |
|---|---|---|---|
use_gpu | [pro.search.embedding] | true / false | При true сервис пытается использовать CUDA/DirectML, при false — только CPU. |
intra_op_threads | [pro.search.embedding] и бэкенд | 0 или число | Число потоков внутри одного сеанса ONNX Runtime. 0 означает «авто» — рантайм сам выберет под доступное железо. |
inter_op_threads | [pro.search.embedding] и бэкенд | 0 или число | Потоки между операциями графа. Для embedding-моделей обычно достаточно 0 или 1. |
session_pool_size | бэкенд | 1, 2, 4… | Число независимых сеансов ONNX Runtime. Увеличение ускоряет параллельные запросы, но умножает потребление RAM. |
background.batch_size | [pro.background] | 100, 200, 500… | Сколько задач воркер забирает из очереди за один инференс. Больший батч снижает накладные расходы, но увеличивает задержку первого результата. |
background.worker_count | [pro.background] | 1, 2, 4… | Число фоновых воркеров. Каждый воркер держит свою ONNX-сессию и потоки intra_op_threads. |
Рекомендации по CPU
- Хотите максимальную скорость индексации: установите
intra_op_threads = 0(авто),background.worker_count = 2иbackground.batch_size = 500дляe5-small(или100дляbge-m3). На 16-ядерном процессоре (AMD Ryzen 9 7945HX) фоновая индексация 10 000 коротких товаров черезe5-small-int8занимает ~51 с, то есть ~195 vec/s. Точные цифры для разных моделей и настроек приведены в разделе Производительность. - Нужно оставить ресурсы поиску/сайту: задайте фиксированное
intra_op_threads = 4иbackground.worker_count = 2. Скорость индексации упадет, но поиск останется отзывчивым. - Слабый CPU / Colab / VPS с 2 vCPU: оставьте
intra_op_threads = 0иbackground.worker_count = 1, иначе воркеры будут бороться за ядра. В Colab CPU-индексация 10 000 товаров занимает ~12–13 минут.
intra_op_threads = 4 на 16-ядерном сервере и на 2-vCPU VPS даст совершенно разную скорость. Поэтому по умолчанию в dev-конфиге используется 0 — сервис сам подстроится под железо.
Рекомендации по GPU
- В Colab / на сервере с CUDA установите
use_gpu = trueиintra_op_threads = 0. GPU берет основную нагрузку инференса, а CPU-потоки остаются на токенизацию и пост-обработку. - Если CUDA/DirectML не зарегистрируется, сервис автоматически fallback'ится на CPU — индексация замедлится, но не сломается.
Проверка
После запуска сервиса посмотрите ответ /health:
curl -s http://localhost:8080/health | jq .ort_runtime
# Или через CLI:
sku-cli health
Пример с загруженным CPU-рантаймом:
{
"loaded": true,
"path": "/opt/sku-search-pro/runtime/linux-cpu/libonnxruntime.so",
"build_info": "ORT Build Info: git-branch=HEAD, git-commit-id=a83fc4d58c, build type=Release"
}
Если рантайм не найден:
{
"loaded": false
}
Совместимость версий
Если установить другую версию (например, 1.18 или 1.30), возможны следующие сценарии:
- Рантайм не загрузится совсем — в логах будет ошибка,
/healthпокажет"loaded": false, embedding отключится, но текстовый поиск останется работать. - Рантайм загрузится, но модель или execution provider будут вести себя непредсказуемо — возможны сбои при индексации эмбеддингов или при поиске.
Если вы хотите использовать версию, отличную от 1.23.2, проверьте работу эмбеддингов на копии данных перед переводом в production.
Частые причины, почему рантайм не загружается
- Папка
runtime/не найдена. Убедитесь, что она лежит рядом сsku-serviceили в текущей директории. - Неправильная структура подпапок. Для Linux CPU должно быть
runtime/linux-cpu/libonnxruntime.so, для Windows —runtime/win-cpu/onnxruntime.dll. - Потеряны symlink'и. Если архив распаковывался на Windows/FAT,
libonnxruntime.soможет оказаться битой ссылкой. Запуститеsetup.sh— он восстановит symlink'и. - Несовпадение версий. Установлен ONNX Runtime не версии 1.23.2.
- Отсутствует MSVC Redistributable на Windows. PRO-версия требует установленного Visual C++ Redistributable x64.
- На Linux не найдены зависимости CUDA. Убедитесь, что в системе установлены совместимые CUDA/cuDNN-библиотеки, либо добавьте их в переменную окружения
LD_LIBRARY_PATH. - На Windows не найдены зависимости CUDA. Убедитесь, что DLL CUDA (
cudart64_*.dll,cublas64_*.dllи др.) доступны либо рядом сonnxruntime.dll, либо в системной переменнойPATH. - Проблемы с правами. Библиотека должна быть доступна для чтения пользователю, от имени которого запущен сервис.
Пользовательская директория для рантайма
Если по какой-то причине рантайм нельзя положить рядом с исполняемым файлом, можно задать собственный путь через переменную окружения:
export SKU_ORT_RUNTIME_DIR=/opt/onnxruntime
./sku-service
В этом случае сервис будет искать подпапки linux-cpu, linux-cuda и т.д. именно в /opt/onnxruntime.
Подготовка ONNX-моделей для семантического поиска
[pro.search], подготовленной ONNX-модели и загруженного ONNX Runtime.
Семантический поиск (эмбеддинги) требует предварительно подготовленную ONNX-модель. Сервис поддерживает модели семейства E5 (e5-small, e5-base, e5-large) и BGE-M3. CLI-утилита может автоматически скачать, экспортировать и квантовать модель.
Быстрый старт: подготовка E5-small модели
# 1. Установка зависимостей (один раз)
pip install optimum[onnxruntime] transformers torch onnx huggingface-hub --break-system-packages
# 2. Подготовка модели (скачать, экспортировать, квантовать в INT8)
# CLI-утилита sku-cli (входит в поставку) поддерживает команду model prepare.
sku-cli model prepare \
--model intfloat/e5-small-v2 \
--output ./models/e5 \
--quantize int8 \
--optimize
# 3. Проверка, что модель готова
ls -la ./models/e5/int8/
# Должны быть: model.onnx, tokenizer.json, config.json
# 4. Настройка config.toml:
# model_path = "./models/e5/int8/model.onnx"
# vector_dimension = 384
Доступные модели
| Модель | Размер (INT8) | Размерность | Скорость | Точность | Для каких задач |
|---|---|---|---|---|---|
| intfloat/e5-small-v2 | ~120 MB | 384 | очень быстро | средне | Общие задачи, быстрый поиск |
| intfloat/e5-base-v2 | ~440 MB | 768 | средне | высокая | Точный поиск, сложные запросы |
| intfloat/e5-large-v2 | ~1.3 GB | 1024 | медленно | макс. точность | Научные тексты, медицинские базы |
| BAAI/bge-m3 | ~650 MB | 1024 | средне | макс. точность | Мультиязычный поиск, редкие слова |
Подготовка BGE-M3 (особый случай)
# Скачать INT8 модель (рекомендуется)
huggingface-cli download skatzR/USER-BGE-M3-ONNX-INT8 --local-dir ./models/bge-m3/int8
# Или FP16 модель
huggingface-cli download hotchpotch/vespa-onnx-BAAI-bge-m3-only-dense --local-dir ./models/bge-m3/fp16
# Настройка config.toml:
# [pro.search.embedding]
# active_backend = "bge_m3"
# vector_db_path = "./data/vectors.lance"
# query_cache_enabled = true
# session_pool_size задается только в секции бэкенда (.bge_m3 / .e5)
#
# [pro.search.embedding.bge_m3]
# enabled = true
# model_path = "./models/bge-m3/int8/model.onnx"
# vector_dimension = 1024
# enable_sparse = false
# pooling = "mean"
# intra_op_threads = 4
# inter_op_threads = 1
# session_pool_size = 1
Параметры команды model prepare
none— сохранить FP32 (оригинальный размер, максимальная точность)int8— 8-битное целочисленное (рекомендуется для CPU, размер в 4 раза меньше)fp16— 16-битное с плавающей точкой (баланс, размер в 2 раза меньше)
Проверка модели
После подготовки модели рекомендуется проверить, что она корректно загружается:
# Запускаем сервис
./sku-service --config config.toml
# В другом терминале проверяем статус эмбеддингов
curl http://localhost:8080/health | jq .embedding
# Ожидаемый ответ:
# {
# "enabled": true,
# "model_loaded": true,
# "vector_count": 50000,
# "vector_dimension": 384,
# "avg_search_time_ms": 12.3
# }
# Тот же запрос через CLI
sku-cli health
- Embedding включен, но
/healthпоказываетembedding.loaded = false— проверьте, что папкаruntime/с нужным рантаймом лежит рядом сsku-service. Подробности в разделе ONNX Runtime в PRO. - "No module named 'optimum'" — установите зависимости:
pip install optimum[onnxruntime] transformers torch onnx huggingface-hub - "File too large" / protobuf limit — используйте
use_external_data_format=True(скрипт делает это автоматически) - Модель не загружается в сервис — проверьте путь в
config.toml. Должен быть абсолютный или относительно корня проекта. - Медленный инференс — включите квантование (
--quantize int8) и настройтеintra_op_threads = 4в конфиге. - Out of memory — уменьшите
batch_sizeв конфиге (например, с 64 до 32).
- Для большинства задач —
intfloat/e5-small-v2+ INT8 квантование. Баланс скорости и качества. - Для высокой точности —
intfloat/e5-base-v2+ INT8. Требует больше RAM, но точнее. - Для мультиязычного поиска (русский, английский, немецкий) —
BAAI/bge-m3(готовая INT8 модель с Hugging Face). - Для слабых серверов (2-4 GB RAM) — только полнотекстовый поиск, эмбеддинги отключить.
Пример полной настройки config.toml для E5-small
[pro]
enabled = true
[pro.search.orchestrator]
fs_enabled = true
embedding_enabled = true
strategy = "hybrid"
tantivy_weight = 0.6
embedding_weight = 0.4
[pro.search.embedding]
active_backend = "bge_m3" # или "e5"
vector_db_path = "./data/vectors.lance"
query_cache_enabled = true
# session_pool_size задается только в секции бэкенда (.bge_m3 / .e5)
[pro.search.embedding.bge_m3]
enabled = true
model_path = "./models/bge-m3-int8/model.onnx"
vector_dimension = 1024
enable_sparse = false
pooling = "mean"
intra_op_threads = 4
inter_op_threads = 1
session_pool_size = 1
# Для E5-small раскомментируйте и выключите bge_m3 выше:
# [pro.search.embedding.e5]
# enabled = true
# model_path = "./models/e5-small-int8/model.onnx"
# model_type = "e5-small"
# vector_dimension = 384
# pooling = "mean"
# intra_op_threads = 4
# inter_op_threads = 1
# session_pool_size = 1
Конфигурация
config.toml, в котором большинство параметров уже заполнены разумными значениями. Вы можете запустить сервис «из коробки» или изменить конфигурацию под свои задачи.
service/config.toml все обязательные поля уже заполнены. Для остальных параметров, если в конфиге значение не задано, используется встроенное значение по умолчанию, указанное на этой странице.
[pro]; PRO-версия включает ее принудительно. Значение pro.enabled в config.toml игнорируется.
config.toml. Параметры без встроенного значения по умолчанию обязательны и должны быть указаны в конфиге.
[csv] — Параметры CSV-импорта
sku-cli import при запуске с флагом --config config.toml, если не переопределен явно через --delimiter.
- Обычно
","или";" - Применяется только в CLI; сам сервис CSV не парсит
[server] — Сетевые параметры
- Локальная разработка:
"127.0.0.1:8080" - Production / Docker:
"0.0.0.0:8080" - IPv6:
"[::1]:8080"
"Slow request". Полноценный HTTP-таймаут с возвратом 504 Gateway Timeout в текущей версии не реализован.
- Пакетная загрузка (до 500 элементов): 3000-5000 мс
- Интерактивный поиск: 1000-2000 мс
- Рекомендуется: 100-300 для 1С, до 500 для фоновой синхронизации, 1 000-5 000 для массовой первичной загрузки при мощном CPU/SSD.
- При превышении вернется 400 Bad Request.
- Защита от перегрузки CPU и RAM.
- Большой batch требует большего
max_request_body_size_mbи соответствующегоclient_max_body_sizeв nginx. Подробные замеры и рекомендации — в разделе Производительность массовой загрузки.
- Влияет на лимит тела, который накладывает Axum до разбора JSON
- Должен быть достаточен для
max_batch_sizeс самыми длинными полями (name,supplier, описания и т.п.) - При превышении вернется 413 Request Entity Too Large
- nginx (или другой reverse proxy) должен иметь не меньший
client_max_body_size
[site] — Режим работы "Только для сайта" (API Gateway / Frontend Proxy)
enabled = true и readonly = true. Остаются только чтение: /find, /export, /health, /api/search-config и статика.
enabled. Когда enabled = true и readonly = true, все мутации отключены. При enabled = false значение readonly не влияет на доступность мутаций.
[license] — Лицензирование PRO
[index] — Полнотекстовый индекс
- Создается автоматически при старте
- Размещайте на быстром SSD (NVMe предпочтительно)
- Не используйте NFS, SMB — резкое падение производительности
- < 100 000 записей: 50-100 МБ
- 100 000-500 000: 150-250 МБ
- > 500 000: 300-500 МБ
- Больше = реже диск I/O, быстрее массовая загрузка
[pro.index].writer_ram_budget_mb. Если [pro.index].writer_ram_budget_mb = 0 (default), используется верхнеуровневый параметр [index].writer_ram_budget_mb.
0: только точные совпадения (макс. скорость)1: одна опечатка (рекомендуется для production)2: две опечатки (+10-15% recall, но в 1.5-2 раза медленнее)
fuzzy_max_distance. С типовым значением 1 поиск хорошо ловит отдельные опечатки («блот» → «болт»), но может пропускать разные падежные формы («болтом», «болты»), потому что после стемминга они дают один и тот же корень, а расстояние Левенштейна между исходными формами бывает равно 2.Если в вашей предметной области важен поиск в разных падежах (например, пользователи вводят «стальным болтом» вместо «стальной болт»), установите
fuzzy_max_distance = 2. В наших замерах скорость при этом остается в диапазоне 15–20 мс на 50 000 товаров.
false(по умолчанию): сервис не запустится и сообщит об ошибкеSchemaError. Используйте, если потеря индекса недопустима.true: старый индекс переименовывается в./data/index.bak.<timestamp>, создается новый пустой индекс. Сервис продолжит работу, но данные нужно будет загрузить заново.
[search] — Алгоритмы предобработки и фильтрации
[search.field_weights] — Веса базовых полей
1: включает однобуквенные коды ("М", "А", "Б"), но добавляет шум2: оптимально для номенклатуры (отсекает предлоги, частицы)3: максимальная скорость, если нет значимых коротких кодов
- Критично для точности BM25 и скорости
- Отсекает высокочастотные слова: "упак", "кор", "ящ", "комплект", "набор"
- Рекомендуется:
true
- Загружается при старте. Если не найден — используется встроенный набор
- Добавляйте отраслевой шум: "упак", "кор", "ящ", "комплект", "набор"
- Финальный порог вычисляется как
max(relative_threshold, max_score × auto_match_threshold), т.е. результат должен быть не хуже доли от максимального скора - Рекомендуется: 0.85-0.90 для баланса точности/полноты
- Много ложных срабатываний → увеличьте до 0.92
- Ничего не находится → снизьте до 0.75-0.80
true: оператор может ввести "болт м16 78454" в одно поле — найдетсяfalse: поиск только по полюname(быстрее, но менее гибко)- Рекомендуется:
trueдля интеграции с 1С/Excel
"ghbdtn"→"привет"- Экспериментальная функция, может давать ложные срабатывания
- Включайте, если пользователи часто забывают переключить раскладку
Результаты с score < (max_score × relative_score_threshold) отфильтровываются.
0.50: мягкая фильтрация (default, оставляет 50% от лучшего)0.55: баланс точности/полноты (рекомендуется для production)0.60-0.70: агрессивно, только очень релевантные
max(relative_threshold, max_score × auto_match_threshold). Результат должен превышать оба значения.
Критичные токены — короткие (≤ critical_token_max_length) ИЛИ содержащие цифры.
Захватывает: М16, 42р, XL, 220V, IP67, DIN125 и т.д.
- Включите для "грязных" баз, где важно точное совпадение по коротким кодам
- Отключите, если короткие коды встречаются в каждом товаре (теряете полноту)
- М16 (3 символа) — критичный при значении 4
- DIN125 (5 символов) — НЕ критичный при значении 4
- Увеличьте до 5, если у вас есть значимые 5-символьные коды (DIN125, ГОСТ7798)
Доля токенов запроса, которые должны совпасть в результате.
0.5: минимум 50% токенов должны совпасть (рекомендуется)0.7: строже, 70% токенов (меньше ложных срабатываний)0.0: отключить фильтрацию (старое поведение)
- Больше → выше точность, но медленнее
- Меньше → быстрее, но можно пропустить релевантные результаты
- Рекомендуемый диапазон: 2-5
true: можно искать поsupplier,brand,colorи т.д.false: динамические поля игнорируются
[[search.extended_fields]]` игнорируются, даже если они описаны в конфиге.
true: для нечеткого поиска обязательно наличиеname(режим совместимости, default)false: можно искать ТОЛЬКО по динамическим полям, без указанияname(коммерческий режим)
[[search.extended_fields]]` в PRO.
Защита от ситуации, когда все результаты имеют очень низкий score и относительный порог отбрасывает все.
- Рекомендуемое значение: 0.01-0.05
- Не меняйте без крайней необходимости
Защита от экспоненциального роста запросов при сложных поисках.
- Рекомендуемое: 300-1000
- 500 — баланс безопасности и точности
- При превышении — лишние клаузулы отбрасываются с предупреждением
- Если
trueиnameпустой — позволяет добавить/найти товар без названия, отключая стемминг/эмбеддинги для пустого имени. - Документ индексируется только по ID, code, art и т.п.
- Рекомендация:
trueдля справочников, где название неизвестно при загрузке;falseдля стандартного каталога.
- Включает точный поиск по реквизитам. Требует больше места на диске.
extra при поиске. Работает только при enable_attrs_indexing = true.
"hard"— точный фильтр: в результаты попадают только товары, у которых ВСЕ указанные в запросеextra-поля совпадают точно. Это поведение по умолчанию."soft"— бустирование: совпадение поextraувеличивает скор товара, но не отсекает товары без совпадения. Регулируется весомsearch.weights.extra_match_boost."off"— игнорироватьextraпри поиске: поля возвращаются в ответе, но не влияют на выборку и ранжирование.
- Автоматически извлекает ID из строк вида "e1cib/data/...?ref=..." или "{GUID} Название".
- Рекомендация: включайте, если в
nameчасто приходят 1С-ссылки; иначе оставьтеfalseдля экономии CPU.
- Обычно используется для артикулов, штрихкодов, внутренних кодов.
- Влияет только на прямой поиск по полю:
query.code,query.art,query.barcode. Например, приcase_sensitive_fields = ["code"]запросcode="ABC-123"найдет толькоABC-123, аcode="abc-123"— нет. - Поиск через общее поле
name(unified search) остается регистронезависимым: запросname="abc-123"найдет товар сcode="ABC-123". - Чтобы регистрозависимость exact-поиска заработала для уже загруженных данных, выполните переиндексацию после изменения этого параметра.
- Поиск по каким дополнительным полям будет РЕГИСТРОЗАВИСИМЫМ при прямом указании в запросе.
- Как и
case_sensitive_fields, не влияет на поиск через общее полеname.
408 Request Timeout не возвращается.
[search.weights] — Веса разных типов совпадений
- Увеличить (1.2-1.5) → точные совпадения выше в результатах
- Уменьшить (0.8-0.9) → точные совпадения менее важны
- Много опечаток → увеличьте до 0.8-0.85
- Нечеткий поиск часто ошибается → уменьшите до 0.5-0.6
Пример: запрос "болты" ищет "болты" (точное совпадение), а не "болт" (основа).
Формула: score × (1.0 + matched_tokens × coverage_boost_per_token)
- Запрос "болт м16 оцинкованный" (3 слова). Товар содержит все 3 → бонус 3×0.2=+60%
0.1— слабый бонус,0.2— баланс,0.3— сильный
- Запрос "болт" → "Болт м16" получает бонус
- "Гайка болт" — не получает (начинается с "гайка")
1.0— без бонуса,1.15— +15%,1.3— +30%
type.
- Поле
desc(точное совпадение) - Точное совпадение по полю
descполучает дополнительный бонус
ref.
extra-полям), найденным через search_by_attrs.
- Текстовый буст через плоские атрибуты
extra в режиме extra_filter_mode = "soft".
0.0— совпадение поextraне влияет на скор.0.3-0.5— мягкое приоритезирование: подходящие товары поднимаются выше, но не вытесняют релевантные по названию.1.0-2.0— сильный буст: товары с совпавшимиextraмогут оказаться на первых местах даже при менее точном совпадении поname.
"soft". В режиме "hard" совпадение по extra является обязательным фильтром, а в режиме "off" поля игнорируются при поиске.
\[search.fuzzy\] — Тонкая настройка нечеткого поиска
fuzzy_max_distance + 1.
1: минимальное расширение2: стандартное (рекомендуется)3: максимальное (может давать ложные срабатывания)
fuzzy_max_distance = 1 эффективное расстояние будет 2, т.е. пример "перф" → "перфоратор" (расстояние 5) недостижим. Если много ложных срабатываний — уменьшите до 2.
3: "пер" (короткий), "перф" (обычный)4: "перф" (короткий), "перфо" (обычный)5: "перфо" (короткий)
1.0: без буста2.0: умеренный буст4.0: сильный буст (рекомендуется)8.0: экстремальный (только для точных совпадений)
[search.strategy] — Как сервис ищет: быстро или точно
true: короткие запросы (1-3 слова) → последовательный (точнее), длинные (4+) → булевый (быстрее)false: всегда последовательный (максимальная точность)
2: быстрый переход на булевый (меньше точности)3: сбалансированный (рекомендуется)5: максимальная точность для длинных запросов (медленнее)
[search.edge_ngram] — Префиксный поиск для коротких запросов
2: найдет даже по 2 буквам ("пе" → "перфоратор"), но больше шума3: оптимально для русского языка (рекомендуется)4: только по 4 буквам ("перф" → "перфоратор")
5-6: оптимально (баланс)8-10: точнее, но индекс больше15: индекс в 2 раза больше
[search.extended_fields] — Динамические поля
- Для кодов (tnved, артикулы):
0.8-1.0 - Для названий (бренд, поставщик):
0.5-0.7 - Для характеристик (цвет, размер):
0.3-0.5
true: "поставщики" найдет "поставщик"false: только точное совпадение (для кодов и артикулов)
true: "Ромашка" найдет "Ромашкаа"false: только точное совпадение
[pro] — Профессиональные фичи (Enterprise)
pro.enabled в конфиге игнорируется.
[pro.index] — Продвинутые настройки индексации (PRO)
0 — используется значение из [index].writer_ram_budget_mb. Положительное значение переопределяет верхнеуровневый параметр только в PRO-сборке. При возникновении OOM на индексации установите 0, чтобы вернуться к общему значению.
commit_mode, commit_docs_threshold и commit_timeout_secs пока не реализованы; коммиты идут по стандартной политике поискового индекса.
[pro.cross_codes] — Кросс-коды и аналоги
10: для быстрых ответов30: стандарт (рекомендуется)50-100: для автозапчастей (много аналогов)
[pro.search.embedding] — Семантический поиск (векторные эмбеддинги)
[pro.search.embedding] — Общие настройки эмбеддингов
"bge_m3" или "e5" (default: "bge_m3"). "gemini" не реализован.
"./data/vectors.lance"
"lancedb" (default: "lancedb"). Параметр vector_db_type — устаревший синоним, игнорируется.
"auto"). Если не уверены — оставьте "auto".
| Значение | Когда использовать |
|---|---|
"auto" | По умолчанию. Хранилище само выберет индекс. |
"ivf_pq" | Большой каталог и ограниченная память. |
"ivf_hnsw_sq" | Нужна высокая скорость поиска, ресурсов достаточно. |
"ivf_hnsw_pq" | Компромисс между скоростью и размером индекса. |
"ivf_hnsw_flat" | Максимальная точность при поиске, но больше места на диске. |
"hnsw" | Для Qdrant — обычный HNSW-граф. Для встроенного векторного хранилища работает как "ivf_hnsw_sq". |
При смене значения старый индекс не перестраивается автоматически. Чтобы применить новый тип, выполните sku-cli embedding clear и снова проиндексируйте товары.
query_cache_size и query_cache_ttl_seconds не реализованы; размер и TTL общего кэша задаются через cache_max_size / cache_ttl_seconds.
"f32", "f16", "i8" (default: "f16"). В текущей версии векторное хранилище всегда хранит векторы как Float32; само значение к хранению не применяется.
fs_fuzzy_fallback_enabled не реализован.
bge_m3 или e5).
[pro.search.embedding.bge_m3] — Бэкенд BGE-M3
active_backend = "bge_m3", должен быть true, иначе сервис не запустится.
"./models/bge-m3-int8/model.onnx" (обязательный)
""). Пустая строка в pooling молча преобразуется в "mean".
"mean": усреднение векторов всех токенов. Самый стабильный вариант, рекомендуется для E5 и bge-m3."cls": вектор специального[CLS]-токена. Иногда чуть лучше для классификации, но может быть нестабилен для поиска."last": вектор последнего токена. Редко используется; может подойти для декодер-подобных моделей.
0 означает автовыбор эвристикой ONNX Runtime.
[pro.search.embedding.e5] — Бэкенд E5
"./models/e5-small-int8/model.onnx" (обязательный)
"e5-small", "e5-base", "e5-large" (default: "e5-small")
"mean", "cls", "last" (default: "", молча преобразуется в "mean")
[pro.search.embedding.gemini] — Бэкенд Google Gemini
active_backend = "gemini" сервис молча переключается на bge_m3. Секция сохранена для обратной совместимости, но не влияет на поведение.
[pro.search.embedding.hnsw] — Настройки HNSW индекса
num_edges).
[pro.search.embedding.lancedb] — Встроенное векторное хранилище
"./data/vectors.lance").
"embeddings").
"f32", "f16", "i8" (default: "f16").
[pro.search.embedding.qdrant] — Qdrant
use_grpc не реализован.
[pro.search.embedding.text_config] — Состав текста для эмбеддинга
" ")
# Пример конфигурации PRO с BGE-M3
[pro.search.embedding]
active_backend = "bge_m3"
vector_db_path = "./data/vectors.lance"
query_cache_enabled = true
[pro.search.embedding.bge_m3]
enabled = true
model_path = "./models/bge-m3-int8/model.onnx"
vector_dimension = 1024
enable_sparse = false
pooling = ""
intra_op_threads = 0
inter_op_threads = 0
session_pool_size = 1
# Пример переключения на E5-small
# [pro.search.embedding.e5]
# enabled = true
# model_path = "./models/e5-small-int8/model.onnx"
# model_type = "e5-small"
# vector_dimension = 384
# pooling = ""
# intra_op_threads = 0
# inter_op_threads = 0
# session_pool_size = 1
active_mode = "legacy") удален. Используйте active_backend и секции [pro.search.embedding.bge_m3] или [pro.search.embedding.e5]. Бэкенд Gemini не реализован.
[pro.search.orchestrator] — Оркестратор поиска
"fs_only": только полнотекстовый поиск (быстро, точно, default)"embedding_only": только эмбеддинги (семантический)"hybrid": последовательный гибрид"parallel": параллельный гибрид (максимальная скорость)
- Преимущество полнотекстового поиска:
0.8 / 0.2 - Сбалансированный (по умолчанию):
0.6 / 0.4 - Преимущество эмбеддингов:
0.4 / 0.6
"min_max": масштабирование на [0,1] по максимуму."robust": нормализация на 95-й перцентиль, менее чувствительна к выбросам (default)."z_score": центрирование на среднем и деление на std, затем масштабирование z-значений на [0,1]."softmax": экспоненциальное преобразование, сумма скоров после нормализации = 1."none": не нормализовать, использовать сырые скоры источника.
Режим влияет только на гибридный/параллельный поиск в PRO, когда скоры полнотекстового поиска и эмбеддингов объединяются.
[pro.background] — Фоновая очередь эмбеддингов
[pro.plugins.hierarchy] — Иерархические группы (PRO)
POST /hierarchy).
[security] — Безопасность и лимиты
[admin] — Администрирование
[feedback] — Обратная связь
POST /feedback
query_name— исходный запрос пользователя.matched_id_out— ID найденного товара.action—accept,rejectилиzero_result.
И опциональные поля для улучшенного анализа:
product_name,product_art,product_barcode— данные найденного товара.matched_by— поле, по которому произошло совпадение (name,code,art,barcode,attrs).position— позиция товара в выдаче.session_id— ID сессии для анализа реформулировок.expected_id— ID товара, который ожидал увидеть пользователь.
Пример:
{
"query_name": "болт м10",
"matched_id_out": "ID-001",
"action": "accept",
"product_name": "Болт М10х50 DIN 933",
"matched_by": "name",
"position": 1
}
GET /admin/learn — рекомендации
curl 'http://localhost:8080/admin/learn?type=synonym'
Типы рекомендаций:
synonym— предлагаемые синонимы на основе accept-записей.missing_product— популярные запросы без результатов (zero_result).check_product— товары с высоким reject rate.typo— возможные опечатки и альтернативные написания.
Каждая рекомендация содержит recommendation_type, title, description, confidence (0.0–1.0) и data.
[export] — Экспорт данных
[logging] — Логирование
file_rotate_max_size_mb > 0, ротация выполняется по размеру (имеет приоритет над file_rotate_by_age). Если 0 — ротация только по времени.
file_rotate_max_size_mb = 0): hourly, daily, weekly.
0 старые файлы не удаляются.
[files] — Внешние словари
Обучение на обратной связи
Сервис умеет учиться на ваших действиях. Не ML, а просто и понятно: вы отправляете обратную связь, сервис анализирует ее и предлагает улучшения.
/admin/learn, /feedback и управление синонимами через API работают только в PRO-сборке. В Free-редакции /admin/learn вернет 404.
accept и zero_result. Записи с reject сохраняются, но пока не используются для построения синонимов. При accept сервис учит, что искали одно, а выбрали другое, и предлагает синонимы; при zero_result — частые запросы, по которым ничего не нашлось.
Как это работает — по шагам
В 1С после поиска сервис показал товары. Оператор либо выбирает подходящий и подтверждает (
accept), либо сообщает, что ничего не нашлось (zero_result).1С формирует запрос: что искали, какой товар выбрали, и действие оператора.
Каждая запись выглядит так:
{"timestamp":"2024-01-15T10:30:00Z","query_name":"болт м16 цинк","matched_1c_id_out":"1C-GUID-0001","product_name":"Болт М16х60 оцинкованный ГОСТ 7798-70","action":"accept"}
Раз в неделю (или чаще) администратор выполняет команду:
# Через CLI:
sku-cli learn
# Или через API:
curl http://localhost:8080/admin/learn
Доступно только в PRO.
Сервис ищет закономерности: если оператор искал слово "цинк", а в выбранном товаре встречается "оцинкованный", значит, это хорошая пара для синонима. Анализ строится по полю
product_name, поэтому без него предложения не генерируются.Сервис возвращает JSON с рекомендациями:
{
"recommendations": [
{
"recommendation_type": "synonym",
"title": "Синоним: цинк → оцинкованный",
"description": "В 82% accept-запросов со словом 'цинк' выбран товар, содержащий 'оцинкованный'",
"confidence": 0.82,
"data": {
"query_token": "цинк",
"product_token": "оцинкованный"
}
},
{
"recommendation_type": "missing_product",
"title": "Возможно отсутствующий товар: болт м16 din933",
"description": "15 zero_result запросов за период",
"confidence": 0.95,
"data": {
"query": "болт м16 din933",
"count": 15
}
},
{
"recommendation_type": "check_product",
"title": "Проверьте товар 1C-GUID-0001",
"description": "Высокий reject rate (45%)",
"confidence": 0.45,
"data": {
"id": "1C-GUID-0001",
"reason": "Высокий reject rate (45%)"
}
},
{
"recommendation_type": "typo",
"title": "Опечатка: перфоротар → перфоратор",
"description": "Похоже на опечатку, найден канонический вариант",
"confidence": 0.78,
"data": {
"query": "перфоротар",
"canonical": "перфоратор"
}
}
],
"processed_entries": 42
}
Если предложение хорошее — добавляем его вручную через API:
# Через CLI:
sku-cli synonyms add --word "цинк" --expansions "оцинкованный,zn,zinc"
# Или через API:
curl -X POST http://localhost:8080/synonyms \
-H "Content-Type: application/json" \
-d '{"items":[{"word":"цинк","expansions":["оцинкованный","zn","zinc"]}]}'
С этого момента все новые поиски "цинк" будут находить "оцинкованный". Перезапуск сервиса не нужен.
Как настроить 1С для отправки feedback
В 1С, после того как сервис вернул результаты поиска, добавьте кнопки "Выбрать этот товар" (accept) и "Ничего не нашлось" (zero_result). При нажатии вызывайте:
// Псевдокод 1С
Процедура ВыбранТовар(НайденныйТовар, ПоисковыйЗапрос)
Данные = Новый Структура;
Данные.Вставить("query_name", ПоисковыйЗапрос.Наименование);
Данные.Вставить("matched_id_out", НайденныйТовар.Идентификатор);
Данные.Вставить("product_name", НайденныйТовар.Наименование);
Данные.Вставить("action", "accept"); // "accept" — выбрали этот товар
// Отправка POST /feedback
Ответ = ОтправитьHTTPЗапрос("/feedback", Данные);
КонецПроцедуры
Процедура НичегоНеНашлось(ПоисковыйЗапрос)
Данные = Новый Структура;
Данные.Вставить("query_name", ПоисковыйЗапрос.Наименование);
Данные.Вставить("action", "zero_result"); // ничего не нашлось
Ответ = ОтправитьHTTPЗапрос("/feedback", Данные);
КонецПроцедуры
Автоматизация обучения
Можно настроить cron для регулярного анализа:
# Запускать каждую неделю в понедельник в 9 утра
0 9 * * 1 cd /opt/sku-search && ./sku-cli learn >> /var/log/learn.log 2>&1
Полный цикл обучения на примере
- Оператор ищет "болт м16 цинк", сервис предлагает "Болт М16 черный"
- Оператор выбирает "Болт М16х60 оцинкованный ГОСТ 7798-70" и нажимает "Это тот товар", 1С отправляет feedback с
action: "accept" - В лог попадает запись: искали "цинк", выбрали товар с "оцинкованный"
- Администратор через неделю запускает
sku-cli learn - Сервис анализирует: в нескольких accept-запросах со словом "цинк" выбран товар, содержащий "оцинкованный"
- Сервис предлагает синоним: "цинк" → "оцинкованный"
- Администратор добавляет синоним через API
- Теперь "болт м16 цинк" находит "Болт М16х60 оцинкованный" с высоким score
Что происходит в feedback.log
Файл data/feedback.log накапливает все действия операторов. Пример содержимого:
{"timestamp":"2024-01-15T10:30:00.123Z","query_name":"болт м16 цинк","matched_1c_id_out":"1C-GUID-0001","product_name":"Болт М16х60 оцинкованный ГОСТ 7798-70","action":"accept"}
{"timestamp":"2024-01-15T10:32:15.456Z","query_name":"гайка нерж м16","matched_1c_id_out":"1C-GUID-0002","product_name":"Гайка М16 нержавеющая","action":"accept"}
{"timestamp":"2024-01-15T10:35:22.789Z","query_name":"шайба м8","matched_1c_id_out":"1C-GUID-0003","action":"zero_result"}
Вы можете анализировать его вручную:
# Сколько раз отклонили
grep '"action":"reject"' data/feedback.log | wc -l
# Сколько подтверждений
grep '"action":"accept"' data/feedback.log | wc -l
# Какие запросы чаще всего отклоняют
grep '"action":"reject"' data/feedback.log | jq -r '.query_name' | sort | uniq -c | sort -rn | head -10
Частые вопросы про обучение на фидбеке
1. Как часто нужно запускать sku-cli learn?
Зависит от объема данных. Если у вас 1000 новых feedback-записей в день — запускайте раз в неделю. Если 100 — раз в месяц. Сервис анализирует все записи с момента последнего запуска (он не хранит состояние, просто читает весь файл каждый раз).
2. Почему сервис предлагает синоним, который уже есть?
Алгоритм ищет пересечения токенов в query_name и названии выбранного товара (product_name). Если синоним уже есть в synonyms.json, он все равно может предложить его снова. Просто игнорируйте такие предложения.
3. Как удалить синоним, который добавили по ошибке?
Через API:
curl -X DELETE http://localhost:8080/synonyms/цинк
# или через CLI:
sku-cli synonyms remove --word цинк
Также можно отредактировать data/synonyms.json вручную и перезапустить сервис, но это не требуется.
4. Что означает confidence в предложениях?
Число от 0 до 1, которое показывает долю accept-запросов, содержащих query-токен, в которых выбран товар с product-токеном. 0.7 = в 70% таких accept-запросов выбран товар с предложенным токеном. Можете использовать как фильтр: добавлять только те, у кого confidence > 0.5. Внутренний порог генерации синонимов — 0.3.
5. А сервис сам добавит синоним, если я не проверю?
Нет. Сервис только предлагает. Решение всегда за администратором. Это сделано специально — чтобы вы случайно не испортили поиск автоматическими правками.
6. Почему после добавления синонима поиск не изменился?
Проверьте:
- Синоним добавился (проверьте
GET /health— там естьsynonyms_count) - Ваш запрос содержит слово, которое совпадает с ключом синонима (регистр не важен, все приводится к нижнему)
- Синоним не конфликтует со стоп-словом (стоп-слова фильтруются ДО применения синонимов)
7. Можно ли автоматизировать добавление синонимов?
Можно, но осторожно. Напишите скрипт, который раз в неделю:
- Вызывает
GET /admin/learn - Фильтрует предложения с
confidence > 0.8 - Отправляет их через
POST /synonyms
Рекомендуем сначала хотя бы месяц смотреть вручную, чтобы понять, какие предложения действительно полезны.
8. Что делать, если feedback.log слишком большой?
Сервис сам не ротирует этот файл. Добавьте в crontab:
# Каждую неделю архивировать и очищать
0 2 * * 0 mv /path/to/data/feedback.log /path/to/data/feedback.log.$(date +\%Y\%m\%d) && touch /path/to/data/feedback.log
9. Почему GET /admin/learn возвращает пустые предложения?
Причины:
- Нет accept-записей с
product_nameвfeedback.log(для синонимов) - Нет zero_result-записей (для missing_product)
- Нет повторяющихся accept/reject по одному товару (для check_product)
- Включен параметр
enabled = falseв конфиге (фидбек не пишется) - В запросах все токены короче
learn_min_token_len(по умолчанию 3 символа). "М6", "шт", "уп" — игнорируются
10. Можно ли обучить сервис на "хороших" совпадениях (action: "accept")?
Да. Алгоритм синонимов как раз работает на accept-записях с заполненным product_name. Хорошие совпадения показывают, что искали одно, а выбрали другое — и это самый надежный источник для предложений синонимов.
11. Как проверить, что обучение реально помогло?
Возьмите 100 запросов, на которых раньше были ошибки. Запустите поиск до добавления синонимов и после. Сравните результаты. Эффект зависит от качества и полноты feedback.
12. Что делать, если операторы нажимают "неверно" случайно?
Алгоритм ищет повторяющиеся паттерны. Для синонима нужно минимум 2 accept-записи с одним query-токеном и confidence ≥ 0.3. Случайные нажатия не наберут статистику.
Продвинутая настройка обучения
В config.toml есть параметры:
[feedback]
enabled = true # Включить сбор фидбека
log_path = "./data/feedback.log" # Путь к логу
learn_min_token_len = 3 # Игнорировать слова короче 3 букв
learn_max_suggestions = 20 # Максимум предложений за раз
Что изменить, если...
| Ситуация | Решение |
|---|---|
| Слишком много мусорных предложений | Увеличьте learn_min_token_len до 4-5 |
| Сервис не видит короткие важные слова ("М6", "din") | Уменьшите learn_min_token_len до 2 |
| Приходит 100+ предложений, трудно читать | Уменьшите learn_max_suggestions до 10 |
| Нет предложений, хотя ошибки есть | Проверьте, что длина токенов >= learn_min_token_len, и что в accept-записях передано product_name |
Производительность
Ниже — реальные замеры на двух разных стендах. Free-версия тестировалась на скромном железе 2014 года, PRO-версия — на современном сервере с эмбеддингами. Все цифры приблизительные и зависят от CPU, RAM, диска и загрузки системы.
Free: поиск без эмбеддингов
Стенд: Mac mini 2014, Linux, 4 CPU, 8 ГБ RAM. Загружено 50 000 тестовых позиций. Цифры взяты из замеров для статьи на Infostart.
| Показатель | Значение | Что это значит |
|---|---|---|
| Точный поиск по id | 1.85 мс / p95 3.40 мс | Мгновенный ответ, 1С не заметит задержки |
| Точный поиск по штрихкоду | 1.35 мс / p95 1.65 мс | Еще быстрее — обычно по индексу |
| Fuzzy по названию | 2.35 мс / p95 2.86 мс | С учетом опечаток и форм слов |
| Загрузка 50 000 позиций | ~39 с | ~1275 записей/сек |
| Размер индекса | ~3.4 МБ | Очень компактно |
Free: QPS поиска по названию на 10 000 товаров
Замер через wrk на стенде AMD Ryzen 7 5700X, 64 ГБ RAM, NVMe SSD. 1000 случайных поисковых фраз из демо-каталога, limit=10, нагрузка: 2 потока, 10 соединений, 30 с.
| Конфигурация | QPS | Средняя latency | p50 | p99 |
|---|---|---|---|---|
| Production-конфиг (по умолчанию) | ~242 | ~41 мс | ~40 мс | ~83 мс |
| Tuned-конфиг (отключены дорогие фичи поиска) | ~664 | ~15 мс | ~14 мс | ~30 мс |
Tuned-конфиг дает примерно в 2,7 раза больше QPS за счет отключения дорогих search-time фич. Это максимум throughput; production-конфиг сохраняет более высокое качество поиска.
Что именно изменено в tuned-конфиге и зачем
| Параметр | Production | Tuned | Почему влияет на скорость |
|---|---|---|---|
max_candidates_for_fuzzy | 500 | 100 | Меньше fuzzy-кандидатов — быстрее пост-фильтрация |
use_unified_search | true | false | Нет лишнего Boolean OR по exact-полям (code, art, barcode) |
transliteration_enabled | true | false | Не генерируются транслитерированные варианты токенов |
enable_critical_token_filtering | true | false | Отключен пост-фильтр по «критичным» токенам |
extended_search_enabled | true | false | Не ищем по extra-полям (color, material и т.д.) |
enable_deep_1c_parsing | true | false | Нет препроцессинга 1С-запросов |
min_token_match_ratio | 0.5 | 0.0 | Не отсекаем результаты по минимальному покрытию токенов |
candidate_multiplier | 2 | 1 | Меньше кандидатов на ранжирование |
logging.level | info | warn | Меньше логов на каждый запрос |
console_enabled / file_enabled | true | false | Нет pretty-вывода и записи логов на диск |
Побочный эффект: может снизиться recall на опечатках, разных падежах и поиске по атрибутам. Используйте tuned-конфиг только как потолок производительности или для нагрузочных тестов, где важен максимум RPS.
PRO: гибридный поиск с эмбеддингами
20 000 товаров, bge-m3-int8
Стенд: AMD Ryzen 9 7945HX, 16 cores / 32 threads, 92 ГБ RAM. Модель bge-m3-int8 (1024 dim).
| Показатель | Значение | Примечание |
|---|---|---|
| Вставка 20 000 товаров | 3.75 с | ~5339 записей/сек. Эмбеддинги строятся асинхронно. |
| Фоновая индексация 20 000 векторов | ~4 мин | Два фоновых воркера, batch 100. Скорость вычисления эмбеддингов — главный лимит. |
| Среднее время поиска | 3.86 мс | Гибридный поиск: текст + эмбеддинги |
| p50 | 2.28 мс | Большинство запросов мгновенны |
| p95 | 6.39 мс | Тяжелые запросы остаются быстрыми |
| p99 | 62.20 мс | Холодный старт / прогрев кэша первых запросов |
| QPS | ~254 | 1000 тестовых запросов за 3.94 с |
100 000 товаров, bge-m3-int8
Стенд: тот же AMD Ryzen 9 7945HX. Batch 500.
| Показатель | Значение |
|---|---|
| Вставка 100 000 товаров | 20.77 с (~4813 записей/сек) |
| Поиск (warm cache) | QPS 1603, avg 0.60 мс, p95 0.74 мс |
| Поиск (cold cache) | QPS 460, avg 2.14 мс, p99 75.5 мс |
| Размер data/ | 506.87 МБ |
| Размер полнотекстового индекса | 37.05 МБ |
| Размер векторного хранилища | 415.31 МБ |
Ресурсы PRO
| Ресурс | 20k товаров | 100k товаров |
|---|---|---|
| RAM после загрузки + модель | ~9.2 ГБ | ~20.2 ГБ |
| Рост RAM относительно старта | ~0.5 ГБ | ~2.3 ГБ |
| Диск (полнотекстовый индекс + векторное хранилище + очередь) | ~119 МБ | ~507 МБ |
| CPU в поиске | ~13% | ~5% |
Рекомендуемые настройки фоновой индексации
| Параметр | bge-m3 (по умолчанию) | E5-small (быстрее) |
|---|---|---|
pro.background.worker_count | 2 | 2 |
pro.background.batch_size | 100 | 500 |
pro.background.lance_write_buffer_size | 512 | 1000 |
| Ожидаемая скорость индексации | ~69 items/s | ~195 items/s |
Какую модель выбрать?
В PRO-режиме семантический поиск работает через ONNX Runtime. По умолчанию активна модель bge-m3, также из коробки настроена e5. Оба варианта конфигурируются через config.toml: вы указываете путь к model.onnx, размерность вектора и стратегию объединения токенов.
Скорость и размер (100k товаров)
| Модель | Вставка 100k | QPS | Avg latency | Диск data/ | Полнотекстовый индекс | Векторное хранилище |
|---|---|---|---|---|---|---|
| bge-m3 | 16.11 с (~6208/s) | 456 | 2.17 мс | 506.52 МБ | 37.05 МБ | 415.23 МБ |
| e5-small | 25.02 с (~3997/s) | 839 | 1.16 мс | 241.98 МБ | 37.04 МБ | 152.99 МБ |
Точность (synthetic dataset, 1500 товаров, 120 запросов)
| Модель | strategy | Hit Rate@1 | Hit Rate@5 | MRR |
|---|---|---|---|---|
| bge-m3 | embedding_only | 65.83% | 77.50% | 0.7156 |
| e5-small | embedding_only | 36.67% | 58.33% | 0.4699 |
| bge-m3 | hybrid | 74.17% | 88.33% | 0.7994 |
| e5-small | hybrid | 61.67% | 80.83% | 0.7079 |
- важна высокая точность семантического поиска;
- в каталоге много синонимов и опечаток;
- нужна многоязычность (русский + английский + артикулы);
- ресурсы сервера позволяют — 20+ ГБ RAM и SSD.
- основной режим — hybrid с сильным полнотекстовым сигналом;
- критичны скорость индексации и размер диска;
- запросы в основном точные или содержат артикулы/штрихкоды;
- сервер ограничен по RAM.
Часто задаваемые вопросы
Что делать, если поиск не работает?
Пошаговый чек-лист:
curl http://localhost:8080/health | jq .status — ожидаем "ok".Через CLI:
sku-cli health | jq .statuscurl http://localhost:8080/health | jq .db_record_count — ожидаем > 0. Если 0 — загрузите данные через POST /add.Через CLI:
sku-cli health | jq .db_record_countВ
config.toml: api_key = "" — если не пустой, нужен заголовок X-API-Key. Ошибка 401 → проверьте ключ и заголовки.В
config.toml: rate_limit_per_minute = 0 — временно отключите для теста. Ошибка 429 → увеличьте лимит.Поиск не находит то, что должен
Запрос без name возвращает пустой результат
Причина: require_name_for_fuzzy = true.
[search]
require_name_for_fuzzy = false # ← измените на false
При false можно искать только по динамическим полям (поставщик, бренд, ТН ВЭД).
Поиск по динамическим полям (supplier, brand) не работает
Причина 1 (PRO): extended_search_enabled = false.
[search]
extended_search_enabled = true # ← включите
Причина 2 (PRO): поле не описано в [[search.extended_fields]].
[[search.extended_fields]]
field = "supplier" # ← добавьте поле
weight = 0.7
stemmed = true
fuzzy = true
Сервис находит только идеальные совпадения, ничего "похожего"
Причина: слишком высокий порог auto_match_threshold.
[search]
auto_match_threshold = 0.85 # ← попробуйте снизить до 0.75-0.80
relative_score_threshold = 0.55
Слишком высокий порог (0.95+) — находим только идеальные совпадения. Слишком низкий (0.5) — много мусора.
Короткие запросы ("перф", "цинк") ничего не находят
Причина: edge_ngram.enabled = false или слишком высокий min_token_length.
[search]
min_token_length = 2 # ← должно быть 2 или меньше
[search.edge_ngram]
enabled = true # ← включите N-gram поиск
min_gram = 3
max_gram = 6
Опечатки не исправляются ("блот" не находит "болт")
Причина: fuzzy_max_distance = 0.
[index]
fuzzy_max_distance = 2 # ← разрешить до 2 опечаток
Расстояние 1 уже находит "блот" → "болт" (перестановка соседних букв). Расстояние 2 дополнительно находит "блоот" → "болт", но делает поиск в 1.5-2 раза медленнее.
Поиск в другом падеже не работает ("болтом" не находит "болт")
Причина: при fuzzy_max_distance = 1 сервис не успевает «согласовать» стеммированный корень с исходной формой в документе. Русский стеммер приводит «болтом» и «болт» к одному корню, но пост-фильтр ориентируется на расстояние Дамерау-Левенштейна (Damerau-Levenshtein), которое между этими формами равно 2.
[index]
fuzzy_max_distance = 2 # ← позволяет находить разные падежи и числа
Рекомендация:
- Используйте
1, если основная задача — исправлять опечатки и искать по точным артикулам/штрихкодам. - Используйте
2, если пользователи часто вводят названия в разных падежах или числах ("болтом", "болты", "краской" → "краска"), а также при работе с кириллическими названиями, где встречаются опечатки в два символа: "краскаа" → "краска", "крска" → "краска", "телефоон" → "телефон". - Значения
>2не рекомендуются — значительно растет задержка (+50–100% latency) и количество ложных срабатываний.
Товар с 2 совпавшими словами из 3 не показывается
Причина: слишком высокий min_token_match_ratio.
[search]
min_token_match_ratio = 0.5 # ← 50% совпадений достаточно
# Если 0.7 — нужно 70% (3 из 3 или 2 из 3 не хватит)
Сервис падает при старте с ошибкой SchemaError
Ошибка: SchemaError("An index exists but the schema does not match.")
Причина: поисковый индекс был создан другой версией сервиса или с другой схемой полей.
Решение: удалите старый индекс и перезапустите сервис. Данные в индексе будут потеряны — перезагрузите их через POST /add.
# Остановите сервис
rm -rf ./data/index
# Перезапустите сервис — индекс создастся заново
./data/index можно восстановить только из исходного источника.
PRO: почему не работает PRO-версия?
В /health показывает edition: "free" или license.active: false.
Возможные причины:
-
Используется Free-версия вместо PRO. Редакция определяется поставляемым исполняемым файлом и не переключается через
config.toml. Проверьте:curl http://localhost:8080/health | jq .edition # Ожидаем: "pro" # Или через CLI: sku-cli health | jq .editionЕсли
"free"— вы запустили Free-версию вместо PRO. -
Нет файла лицензии. PRO требует валидный файл
./data/license.lic. Получите его через сервер активации или админ-панель. -
PRO-файл поврежден или не соответствует вашей платформе. Скачайте архив PRO-версии заново и убедитесь, что исполняемый файл запускается без ошибок.
PRO: Как активировать PRO-версию?
-
Получите пин-код PRO-лицензии (после оплаты или от администратора).
-
Откройте в браузере страницу активации:
- в сервисе — корень
/, если PRO-версия еще не активирована; - на сайте —
/license/activate/pro.
- в сервисе — корень
-
Введите полученный пин-код и нажмите кнопку активации.
-
Сервис проверит пин-код и сам установит лицензию по пути
license_path(обычно./data/license.lic). Ручное скачивание или копирование файла не требуется. -
Проверьте статус:
curl http://localhost:8080/health | jq .license.active # Ожидаем: true # Или через CLI: sku-cli health | jq .license.active
license.active: false — проверьте, что папка data/ (параметр license_path в config.toml) доступна сервису на запись и примонтирована как постоянный volume в Docker.
PRO: Кросс-коды и аналоги не показываются
Проверьте настройки кросс-кодов:
[pro.cross_codes]
enabled = true # ← должно быть true
include_in_response = true # ← должно быть true
max_group_size = 30
group_id. Если в ваших данных оно не заполнено — заполните его и перезагрузите товары. Переиндексация только из-за включения флага enabled не требуется.
PRO: Иерархия (/hierarchy) не работает
Проверьте настройки плагина:
[pro.plugins.hierarchy]
enabled = true # ← должно быть true
default_max_depth = 5
Также проверьте, что у товаров заполнено поле parent_id.
PRO: Эмбеддинги (семантический поиск) не работают
Чек-лист для эмбеддингов:
-
Включен PRO-режим:
[pro] enabled = true -
Включен оркестратор:
[pro.search.orchestrator] embedding_enabled = true -
Выбран активный бэкенд и указан путь к модели:
[pro.search.embedding] active_backend = "bge_m3" [pro.search.embedding.bge_m3] enabled = true model_path = "./models/bge-m3-int8/model.onnx" vector_dimension = 1024 -
Модель существует и валидна:
ls -la ./models/bge-m3-int8/model.onnx # Должен быть файл размером ~343 МБ (INT8) -
Проверьте health-статус:
curl http://localhost:8080/health | jq .embedding # Ожидаем: "enabled": true, "model_loaded": true # Или через CLI: sku-cli health | jq .embedding -
Проверьте, что векторы построены:
curl http://localhost:8080/health | jq .embedding.vector_count # Должно совпадать с .db_record_count # Или через CLI: sku-cli health | jq .embedding.vector_count
model_loaded = false, но файл модели есть — проверьте логи сервиса: возможна несовместимость версии ONNX-модели или ошибка инициализации runtime.
Частые ошибки при загрузке данных
Ошибка: id missing or empty
Причина: поле id обязательно для каждого товара.
{
"name": "Болт М16",
"id": "1C-GUID-001" // ← обязательно!
}
Ошибка: rate limit exceeded (429)
Причина: слишком много запросов с одного IP.
[security]
rate_limit_per_minute = 0 # ← временно отключите для загрузки
Данные не сохраняются после перезапуска
Причина: неправильный путь к индексу или volume не примонтирован.
[index]
path = "./data/index" # ← относительный путь
./data. В production используйте абсолютный путь: path = "/var/lib/sku/index".
Фоновая индексация эмбеддингов идет слишком долго
Причина: маленький batch_size или мало worker_count.
[pro.background]
worker_count = 2
batch_size = 500 # ← для e5-small можно увеличить
lance_write_buffer_size = 1000
batch_size = 100 — он медленнее, но точнее. Для e5-small можно использовать batch_size = 500.
Как работают синонимы?
Синонимы хранятся в файле data/synonyms.json и загружаются при старте.
# Добавить синоним
sku-cli synonyms add --word "цинк" --expansions "оцинкованный,zn,zinc"
# Посмотреть список
sku-cli synonyms list
# Удалить синоним
sku-cli synonyms remove --word "цинк"
Общие рекомендации по настройке
Если сервис ничего не находит — снижайте пороги:
[search]
auto_match_threshold = 0.7
min_token_match_ratio = 0.4
relative_score_threshold = 0.4
Если сервис находит много мусора — повышайте пороги:
[search]
auto_match_threshold = 0.92
min_token_match_ratio = 0.7
relative_score_threshold = 0.6
Для массовой загрузки (100 000+ товаров):
[security]
rate_limit_per_minute = 0
rate_limit_burst = 0
[server]
max_batch_size = 500
[search]
Как получить поддержку
Если не нашли ответа на свой вопрос:
- Email: kadr78job@gmail.com
- Telegram: @kadr78job
- Документация: полный перечень эндпоинтов и параметров — в этом документе
- Версию сервиса:
sku-cli version - Логи: из папки
./data/logs/ - Пример запроса, который не работает
- Конфиг (без sensitive данных)
Глоссарий
1.0 = идеально, 0 = совсем не похоже.
["болт", "м16", "оцинкованный"].
id полностью перезаписывает запись. Пустой или отсутствующий id вызывает ошибку при добавлении.
Лицензия и условия использования
- Free Edition — полнотекстовый поиск + динамические поля
- PRO Edition — эмбеддинги, кросс-коды, иерархия, аналитика, фоновая очередь
Free Edition (бесплатно для всех)
- В коммерческих и некоммерческих организациях
- Внутри компании для своих нужд
- В составе своего продукта (CRM, ERP, интернет-магазин)
- Для тестирования и разработки
- Распространять бинарные версии с сохранением уведомления об авторских правах
- Полнотекстовый поиск — точный, fuzzy, n-gram
- Синонимы и стоп-слова
- Обратная связь (/feedback)
- Экспорт / импорт данных
- Базовое администрирование (создание папки
backup_dir, ротация, бэкап конфига при обновлении)
PRO Edition (требует покупки лицензии)
- Динамические поля (расширенный поиск по любым реквизитам)
- Семантический поиск (эмбеддинги)
- Кросс-коды (аналоги и заменители)
- Иерархия (BOM / Where-Used)
- Аналитика и тюнинг (/admin/analytics/*)
- Фоновая очередь эмбеддингов
- Кэширование результатов
- Очистка и оптимизация индекса (/admin/clear, /admin/index/optimize)
- Вы используете PRO-функции (эмбеддинги, кросс-коды, иерархия) в коммерческих целях
- Вы продаете продукт, в который встроен PRO-модуль
- Вы предоставляете SaaS / API с PRO-функциями
- Вы создаете конкурирующий продукт на основе PRO-модуля
| Сценарий | Free или PRO? | Нужна лицензия? |
|---|---|---|
| 1С-франчайзи, ищете товары в своей базе (Free-функции) | Free | Нет |
| 1С-франчайзи, используете эмбеддинги для поиска ("шурик" → "шуруповерт") | PRO | Да |
| Делаете CRM и встраиваете Free-поиск для поиска контактов | Free | Нет |
| Делаете CRM и встраиваете PRO-эмбеддинги | PRO | Да |
| Продаете SaaS "умный поиск для 1С" на PRO-функциях | PRO | Да |
|
Пин-коды активации и подписка на обновления
Для активации PRO-версии используются пин-коды. Существует два типа пинов:
- Дает право вечно использовать PRO-приложение на ограниченном числе ПК (по умолчанию до 3).
- Включает стартовую подписку на обновления (по умолчанию 6 месяцев).
- Привязывается к конкретному ПК через fingerprint.
- Не активирует приложение сам по себе, но продлевает подписку на обновления.
- Можно активировать на нескольких ПК в рамках лимита активаций.
- Привязывается к email пользователя и fingerprint ПК.
- Приложение продолжает работать — лицензия на использование вечная.
- Вы больше не можете скачивать обновления PRO-версии через личный кабинет{.text-primary}.
- Go UI показывает предупреждение «Срок подписки на обновления истек».
- Для возобновления доступа к обновлениям активируйте пин продления подписки в форме активации{.text-primary} или в приложении.
- Активировать PRO — получить
.lic-файл для ручной установки на сервер. - Скачать обновления PRO — проверить подписку и скачать последнюю версию.
- Веб-интерфейс PRO-сервиса (
http://<адрес-сервера>:8080/activation) — встроенная форма активации, работает даже если лицензия еще не установлена.
Стоимость PRO-лицензии
- Embedded (встраивание в свой продукт): от 19 900 ₽ / проект
- SaaS / API (предоставление как услуги): от 49 900 ₽ / год
- OEM (массовое распространение): договорная
- Email: kadr78job@gmail.com{style="color: var(--accent);"}
- Telegram: @kadr78job{style="color: var(--accent);" target="_blank"}
В письме укажите: название компании, сценарий использования, какие PRO-функции планируете использовать.
Что запрещено в любом случае
- Удалять уведомления об авторских правах из документации и исходников
- Перепродавать Free-версию как "свой продукт без изменений"
- Создавать прямого конкурента (продукт, чьей основной функцией является поиск номенклатуры)
Юридическая информация
Данный продукт использует следующие библиотеки с открытым исходным кодом:
- Поисковый движок (MIT License)
- Веб-фреймворк (MIT License)
- Асинхронный runtime (MIT License)
- Векторная база данных (Apache 2.0 License)
- ONNX Runtime — инференс моделей (MIT License)
Полный список зависимостей и их лицензий доступен в файле LICENSES.md в составе дистрибутива.
© 2026 sku-search Team. Все права защищены.
Free Edition распространяется под лицензией MIT. PRO Edition — по коммерческой лицензии.
Сервис распространяется "как есть" (AS IS), без каких-либо гарантий.
SKU Search
Документация для разработчиков и 1С-специалистов • MIT License