npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

semidex-lite

v0.1.6

Published

Cloud RAG pipeline for Node.js with structural document chunking, Qdrant Cloud embeddings and hybrid search, and Gemini-grounded Ask APIs.

Readme

semidex-lite

English version

Semidex Lite — це хмарне RAG-ядро та JS/TS-клієнт для Node.js-застосунків, яким потрібно перетворити власні документи на базу знань із пошуком і відповідями на основі джерел. Воно є хмарною версією Semidex і закриває шлях від індексації документа до інтеграції пошуку або асистента у ваш backend.

Пакет уже надає:

  • ingestion для Markdown, PDF, plain text і підтримуваних Pandoc-форматів;
  • структурний skeleton-first чанкінг Markdown, детермінований контекст та інвентар документа;
  • dense і sparse ембединги через Qdrant Cloud Inference;
  • гібридний пошук і версійний POST /api/v1/search;
  • Ask API для одноходових і багатоходових відповідей із джерелами;
  • semidex-lite/client для Search, Ask v1 та Ask v2 у JavaScript/TypeScript;
  • CLI для індексації та операторську адмін-панель.

[!IMPORTANT] Статус: ранній pre-1.0 продукт. Semidex Lite активно розвивається, тому API, конфігурація та поведінка ще можуть змінюватися між релізами. Проєкт не пройшов незалежного security audit і постачається без гарантій; не використовуйте його як єдину межу безпеки або в критичних системах без власного аналізу ризиків, тестування й належного захисту deployment. Вихідний код доступний за ліцензією MIT: його можна безкоштовно використовувати, змінювати й поширювати, зокрема в комерційних продуктах, за умови дотримання тексту ліцензії. Ліцензія не надає гарантій.

Semidex Lite не є готовим SaaS, сховищем чатів або повною платформою керування користувачами. Ваш застосунок володіє інтерфейсом, користувачами, історією розмов і бізнес-правилами; Semidex Lite надає ingestion, retrieval та grounded answering як окреме backend-ядро. Qdrant Cloud використовується для зберігання й обчислення ембедингів, а Gemini — для генерації відповідей.

Повний цикл від документа до відповіді

semidex-lite — це не лише Ask endpoint або тонка обгортка над Qdrant. Пакет виконує повний хмарний RAG-цикл від вхідних документів до обґрунтованої відповіді:

документи
  -> парсинг і структурне розбиття на чанки
  -> детермінований контекст і структурний інвентар
  -> обчислення ембедингів через Qdrant Cloud
  -> dense + sparse індексація в Qdrant
  -> гібридний пошук
  -> відповідь Gemini з посиланнями на джерела

Markdown обробляється skeleton-first чанкером: ієрархія заголовків і структурні сутності, зокрема таблиці, блоки коду та чеклісти, залишаються доступними для перевірки, а не перетворюються на один плоский текст. Навігаційні вузли отримують детермінований інвентар навіть без необов'язкових LLM summary. PDF, plain-text і підтримувані Pandoc-формати використовують окремий шлях ingestion та поки не мають такої самої структурної точності, як Markdown.

Для чого використовувати semidex-lite

Пакет можна використовувати як хмарне RAG-ядро для застосунків, яким потрібно шукати у власних документах і формувати відповіді на їх основі. Наприклад:

  • асистент із документації або підтримки на сайті;
  • бот для Telegram чи іншого каналу спілкування;
  • пошук і Ask-інтерфейс для внутрішньої бази знань команди або організації;
  • навчальний чи дослідницький помічник для роботи з власними матеріалами;
  • retrieval-компонент більшої агентної системи або спеціалізованого продукту.

Для інтеграції з backend використовуйте версійні Search/Ask endpoints напряму або клієнт semidex-lite/client. Адмін-панель залишається операторським інструментом для локального налаштування, індексації та діагностики, а не публічним API вашого продукту.

semidex-lite відповідає за індексацію, пошук релевантного evidence і цикл Ask — включно з обчисленням обмеженого rolling summary для багатоходових розмов (/api/v2/ask, див. нижче), коли про це просять. Застосунок поверх нього володіє всім, що стосується самої розмови: власним інтерфейсом, автентифікацією, повною історією повідомлень, збереженням підсумку, який повертає Semidex, і застосуванням підтвердженої межі compaction, пам'яттю поза межами однієї розмови, додатковими інструментами та бізнес-правилами. Для інтеграції використовуйте HTTP Ask API через власний backend; поточний адмін-сервер не призначений для прямого відкриття в публічний інтернет.

Водночас системний prompt самого Ask у цьому MVP є внутрішнім і незмінним через публічний API або налаштування. Зовнішня надбудова може керувати контекстом до та після виклику Ask, але для зміни внутрішніх правил Gemini зараз потрібно змінити або форкнути вихідний код пакета. Конфігурований системний prompt може з'явитися в майбутньому, однак поки не є частиною публічного контракту.

Напрям розвитку

Найближчий фокус — стабільний публічний integration surface, відтворюваний end-to-end приклад, оцінювання retrieval/groundedness і подальше посилення безпеки. Наступні напрями включають якісніший ingestion для PDF, OCR та зображень, додаткових generation-провайдерів, agentic research/MCP facade, Codebase Memory і підключні механізми довготривалої пам'яті. Це напрями розвитку, а не обіцянка конкретних дат або складу наступного релізу.

Актуальний стан, пріоритети, non-goals і дослідницький backlog описані в canonical roadmap.

Чим semidex-lite відрізняється від повної версії semidex

| | semidex | semidex-lite | |---|---|---| | Сховище | Qdrant (локальний або Cloud) | Лише Qdrant Cloud | | Ембединги | Ollama, локальний ONNX (BGE-M3) або Qdrant Cloud Inference | Лише Qdrant Cloud Inference | | Генерація відповідей | Ollama або Gemini | Лише Gemini | | Контекст чанків (файли, відмінні від Markdown) | Генерується LLM (Ollama) | Детермінований (текст заголовка/секції, без викликів LLM) | | Генерація тегів, комбінований LLM-прохід | Підтримується (Ollama або локальний ONNX) | Недоступно | | Апаратні перевірки CUDA/DirectML | Підтримуються | Недоступні | | Розмір пакета / місце після встановлення | Містить onnxruntime-node і @huggingface/transformers | Не містить жодного з них — встановлення значно менше та швидше |

Все інше — гібридний dense+sparse retrieval, детермінований reranking, skeleton-first розбиття Markdown, підтримка PDF/Pandoc, Ask API, перегляд колекцій і пошук в адмін-панелі — працює так само.

[!NOTE] Повна версія semidex наразі доступна як вихідний код у GitHub-репозиторії, але ще не публікується як окремий готовий npm-пакет. Його планується підготувати після завершення тестування, стабілізації інсталяції та реалізації критично важливого функціоналу. До того часу для встановлення через npm використовуйте semidex-lite, а повну версію запускайте безпосередньо з репозиторію.

Встановлення

У проєкт (рекомендовано)

npm install semidex-lite

Пакет буде додано до залежностей поточного проєкту, а його версію зафіксують package.json і lockfile. Це рекомендований варіант для застосунків, контейнерів, серверів та відтворюваного розгортання: кожен проєкт використовує власну визначену версію semidex-lite.

Локально встановлену CLI запускайте через npx, наприклад npx semidex-lite doctor. Усі доступні команди та їх призначення наведено в розділі CLI.

Глобально (необов'язково)

npm install -g semidex-lite

Глобальне встановлення додає команду semidex-lite до системного PATH, тому її можна запускати з будь-якої директорії без npx. Цей варіант зручний для особистого використання на одному комп'ютері, навчальних або дослідницьких експериментів і ручної роботи з власними колекціями. Водночас версія пакета не фіксується окремо для кожного проєкту, тому для інтеграції та розгортання краще використовувати локальне встановлення.

[!NOTE] Поточний пакет semidex-lite не містить MCP-сервера. Для інтеграції із сайтами, ботами та іншими застосунками він надає HTTP Ask API. MCP-сервер наразі входить лише до повної версії semidex.

semidex-lite ніколи не записує дані у власну директорію встановлення, тому працює навіть із доступним лише для читання node_modules/. Усі дані стану (конфігурація, налаштування, кеш токенайзера) зберігаються в окремій для кожної ОС директорії даних застосунку — див. розділ SEMIDEX_HOME.

Налаштування

Створіть файл .env у директорії, з якої запускатимете semidex-lite, і заповніть:

  • QDRANT_URL, QDRANT_KEY — дані вашого кластера Qdrant Cloud;
  • GEMINI_API_KEY — ключ для Ask/генерації. Під час запуску він необов'язковий: serve, doctor та index працюють без нього, але запити Ask завершуватимуться помилкою, доки ключ не буде задано.
QDRANT_URL=https://your-cluster-id.your-region.cloud.qdrant.io
QDRANT_KEY=your-qdrant-cloud-api-key
GEMINI_API_KEY=your-gemini-api-key

Пакет постачається з повністю прокоментованим .env.example, що охоплює всі необов'язкові налаштування, включно з QDRANT_CLOUD_DENSE_MODEL, ASK_MODEL, ADMIN_HOST/ADMIN_PORT, налаштуваннями compaction Ask v2 та SEMIDEX_HOME. Якщо ви встановили semidex-lite як залежність проєкту (npm install semidex-lite), цей файл знаходиться за шляхом node_modules/semidex-lite/.env.example — копії у корені вашого власного проєкту немає, — тож або відкрийте його там, щоб побачити всі параметри, або почніть із мінімального блоку вище й додавайте параметри з цього README за потреби.

Облікові дані поки що налаштовуються поза адмін-панеллю. Додайте їх у локальний файл .env або у змінні оточення операційної системи перед запуском semidex-lite. Системні змінні оточення мають вищий пріоритет за значення з .env. Адмін-панель показує, чи налаштовані облікові дані, але в поточній версії не може додавати, показувати або змінювати QDRANT_URL, QDRANT_KEY чи GEMINI_API_KEY.

Приклад для поточної сесії PowerShell:

$env:QDRANT_URL='https://your-cluster.cloud.qdrant.io'
$env:QDRANT_KEY='your-qdrant-api-key'
$env:GEMINI_API_KEY='your-gemini-api-key'
npx semidex-lite serve

Значення, задані таким способом, діють лише у цьому процесі PowerShell і в запущених із нього програмах. Для локального налаштування проєкту краще використовувати файл .env, виключений із системи контролю версій. Ніколи не додавайте API-ключі до Git.

Під час запуску semidex-lite безумовно фіксує свою хмарну конфігурацію (DENSE_PROVIDER, SPARSE_PROVIDER, SEMIDEX_GENERATION_BACKEND, CONTEXT_MODE та перемикачі локальних runtime). Випадкова змінна локального провайдера, яка залишилася у .env від повної версії semidex, не може знову увімкнути локальний код. Settings API та API завдань індексації окремо відхиляють будь-які спроби змінити ці параметри під час роботи.

Моделі ембедінгу Qdrant Cloud

semidex-lite виконує retrieval через два окремі векторні канали, обидва обчислюються через Qdrant Cloud Inference:

  • dense-модель відповідає за семантичний (смисловий) пошук;
  • sparse-модель додає lexical/keyword retrieval — точні збіги термінів, ідентифікаторів та іншого, що чисто семантичний ембединг може пропустити.

Пошук у semidex-lite (аналог qdrant_search) завжди гібридний: він звертається до обох каналів і поєднує результати, а не обирає лише один із них.

Нижче наведено повний перелік моделей поточного каталогу. У цій версії реально можна вибрати лише моделі зі статусом supported — записи dedicated/planned є справжніми, live-перевіреними ідентифікаторами моделей Qdrant Cloud, але semidex-lite поки не визначає рівень (tier) конкретного кластера, тому ці моделі ніколи не доступні для вибору й ніколи не використовуються, незалежно від того, який план Qdrant Cloud у вас є.

| Модель | Тип | Розмірність | Контекстне вікно | Доступність | Примітки | |---|---|---|---|---|---| | intfloat/multilingual-e5-small | dense | 384 | 512 токенів | supported (free tier) | Багатомовна (100+ мов, включно з українською) | | sentence-transformers/all-minilm-l6-v2 | dense | 384 | 256 токенів | supported (free tier) | Налаштована на англійську; працює, але не оптимізована для інших мов | | qdrant/bm25 | sparse | n/a | n/a | supported (free tier) | Єдина sparse-модель, доступна для вибору в цій версії | | mixedbread-ai/mxbai-embed-large-v1 | dense | 1024 | невідомо | planned (потрібен dedicated-кластер) | Недоступна для вибору в цій версії | | prithivida/Splade_PP_en_v1 | sparse | n/a | 128 токенів | planned (потрібен dedicated-кластер) | Лише англійська; недоступна для вибору в цій версії |

Налаштування dense-моделі

Dense-модель для нових колекцій задається так:

QDRANT_CLOUD_DENSE_MODEL=intfloat/multilingual-e5-small

Про те, як завантажуються та пріоритезуються .env і системні змінні оточення, див. розділ Налаштування вище.

Sparse-модель

Наразі є лише одна sparse-модель зі статусом supportedqdrant/bm25, і немає користувацького вибору між кількома sparse-моделями. У коді існує розширена змінна оточення QDRANT_SPARSE_MODEL для сумісності наперед. Якщо її не задано, Semidex Lite використовує qdrant/bm25 за замовчуванням. Якщо задати модель без статусу supported, створення профілю нової колекції завершиться помилкою замість непомітного fallback до BM25. Адмін-панель показує активну sparse-модель лише для читання, оскільки другої sparse-моделі зі статусом supported поки немає.

Вибір dense-моделі через адмін-панель

Відкрийте адмін-панель (npx semidex-lite serve, потім http://127.0.0.1:8642) і перейдіть у Settings → Embeddings. Поле "Dense model (Qdrant Cloud)" перелічує всі dense-моделі зі статусом supported з таблиці вище, з позначенням розмірності та контекстного вікна. Налаштування застосовується до новостворених колекцій — зміна цього поля не впливає на колекції, які вже існують (див. Життєвий цикл колекції нижче). Облікові дані Qdrant/Gemini (QDRANT_URL, QDRANT_KEY, GEMINI_API_KEY) поки не можна додати, показати чи змінити з адмін-панелі — див. розділ Налаштування вище. Жодне налаштування в цьому розділі адмін-панелі не потребує перезапуску semidex-lite serve, щоб набути чинності.

Життєвий цикл колекції та зміна моделі

  • Dense/sparse-модель, активна на момент першого створення колекції, записується в metadata embedding-профілю саме цієї колекції в Qdrant.
  • Пошук у колекції автоматично використовує саме її власний записаний профіль — ніколи поточний глобальний default і ніколи іншу модель або розмірність векторів, ніж ту, з якою колекцію було створено.
  • Зміна QDRANT_CLOUD_DENSE_MODEL (або відповідного налаштування в адмін-панелі) впливає лише на колекції, створені після цієї зміни. Вона ніколи не перезаписує профіль чи збережені вектори вже наявної колекції.
  • Щоб перевести наявну колекцію на іншу модель, створіть нову колекцію під новою моделлю і переіндексуйте вихідні документи в неї (контрольована повна переіндексація) — вбудованої міграції моделі "на місці" немає.
  • Не редагуйте вручну розмір вектора чи metadata embedding-профілю колекції. Вони повністю визначаються моделлю, якою колекцію було створено, а пошук залежить від їх відповідності реальній векторній схемі колекції.

Перевірка, чи справді працює Cloud Inference

npx semidex-lite doctor --probe-inference

Команда виконує справжній, мінімальний цикл ембедингу через Qdrant Cloud Inference: створює тимчасову колекцію, обчислює нею ембединг і одразу видаляє цю колекцію. Повний опис команди — у розділі doctor нижче.

Який варіант обрати

  • Українські або інші багатомовні документи: intfloat/multilingual-e5-small (E5 Small).
  • Переважно англійські документи, особливо з короткими chunks: підходить будь-яка з моделей; sentence-transformers/all-minilm-l6-v2 (MiniLM) налаштована на англійську, але має менше контекстне вікно (256 токенів).

Жодна з моделей не визнана однозначно «кращою» — benchmark, що порівнював би їх для цього сценарію використання, поки не проводився. Обирайте, виходячи з мови ваших документів, і перевіряйте результат на власних даних.

Стан безпеки

[!IMPORTANT] Integration Ask API має автентифікацію, але Admin API — ні. Прочитайте цей розділ, перш ніж робити будь-яку частину semidex-lite serve доступною за межами власного комп'ютера.

Що захищено в цій версії:

  • Лише loopback за замовчуванням. Сервер слухає 127.0.0.1 і відмовляється від non-loopback ADMIN_HOST без ADMIN_ALLOW_REMOTE=1.
  • Cross-site запити з браузера відхиляються. Запити, які браузер позначає як cross-site (Sec-Fetch-Site), або які містять чужий чи opaque Origin, відхиляються ще до виконання логіки маршруту. Це закриває реальну проблему попередніх версій: стороння сторінка могла непомітно змусити ваш запущений інстанс стартувати індексацію, виконати платні Ask/search запити або змінити стан колекції — браузер блокував читання відповіді, але робота все одно виконувалася.
  • JSON-ендпоінти вимагають Content-Type: application/json. Будь-що інше з тілом запиту відхиляється кодом 415 до парсингу.
  • Заголовок Host валідується за loopback host/port (або вашим списком ADMIN_ALLOWED_HOSTS), що блокує атаки DNS-rebinding.
  • Ask v1/v2 вимагають bearer-ключ. Integration-ключі мають явні області дії за операціями й колекціями; відсутній, прострочений, відкликаний або непридатний для вибраної колекції ключ відхиляється до звернення в Qdrant чи Gemini. Якщо ключів немає, Integration API fail-closed повертає 503.
  • Ask має rate limiting для кожного ключа. Обидві версії Ask спільно використовують один token bucket ключа. Див. Обмеження частоти запитів нижче.
  • QDRANT_URL не можна спрямувати на адресу cloud-metadata, а зміна цього значення через HTTP вимагає прямого loopback-з'єднання. Кожне створення Qdrant-клієнта відхиляє не-http(s) схему, вбудовані облікові дані в URL або відому адресу cloud-metadata (169.254.169.254 та її документовані IPv6-форми, metadata.google.internal) ще до будь-якого мережевого запиту. Окремо PATCH /api/settings приймає зміну QDRANT_URL лише від прямого loopback-з'єднання до цього процесу — незалежно від ADMIN_ALLOW_REMOTE — тож віддалений викликач відкритого Admin API не може непомітно перенаправити Semidex на Qdrant-endpoint, контрольований атакувальником. Це не блокує loopback, LAN, RFC1918 чи Docker-internal адреси; самостійно розміщений Qdrant на таких адресах — звичайна підтримувана ціль, а не ризик, від якого захищає ця перевірка. Повний обсяг і обмеження — у §12j пов'язаного аудиту.
  • Індексація через дашборд/API обмежена схваленими оператором каталогами. POST /api/jobs/index канонічно визначає реальний шлях і приймає його лише всередині INDEX_ALLOWED_ROOTS. Якщо корені не налаштовані, індексація через HTTP/дашборд вимкнена. Пряме довірене CLI-індексування не обмежується.
  • Встановлено таймаути прийому запиту та обмеження кількості заголовків.

Чого поки немає — найважливіше:

  • Admin API не має автентифікації чи авторизації. Будь-який процес на машині може викликати маршрути дашборда, налаштувань, індексації, керування колекціями та неверсійований /api/search, включно з деструктивними. Bearer-ключі захищають лише POST /api/v1/ask і POST /api/v2/ask; вони не роблять Admin API безпечним для віддаленого адміністрування.
  • Адмін-маршрути не мають обмеження колекцій для окремих викликачів. Integration-ключі обмежують Ask точним переліком дозволених колекцій, але Admin API зберігає повний локальний доступ оператора до всіх налаштованих колекцій.
  • Немає rate limiting на Admin API. /api/search та інші адмін-маршрути необмежені. Rate limiting Ask захищає лише Integration-поверхню, не адмін/дашборд.

Практична порада: вважайте Admin-поверхню semidex-lite serve локальним сервісом для одного довіреного користувача. Для сайту, бота чи асистента викликайте автентифікований Ask API зі свого backend і залишайте там ідентифікацію та авторизацію кінцевих користувачів. Якщо використовується reverse proxy, відкривайте лише потрібні версійовані Ask endpoints; ніколи не проксіюйте весь Admin-порт в інтернет або недовірену локальну мережу.

Повний аналіз, помаршрутний інвентар і запланована послідовність хардненгу — у файлі docs/security/semidex-lite-public-api-audit-2026-08.md у репозиторії.

Дозволені корені індексації

Перед запуском індексації з дашборда або через POST /api/jobs/index налаштуйте каталоги, які Admin API може читати. У файлі середовища значенням є JSON-масив (зворотні слеші Windows потрібно екранувати):

INDEX_ALLOWED_ROOTS=["C:\\Users\\me\\Documents\\knowledge","D:\\shared-docs"]

У Settings → System вводьте по одному абсолютному каталогу в рядку. Налаштування застосовується одразу у Full і Lite; порожній список працює fail-closed і вимикає індексацію через HTTP/дашборд. Вибір папки лише заповнює поле цільового шляху й ніколи не розширює allow-list.

Налаштовані корені та цільовий шлях мають існувати. Semidex визначає їх через реальну файлову систему до порівняння компонентів шляху, тому symbolic link або Windows junction, що веде за межі дозволеного кореня, відхиляється. Це перевірка стану на конкретний момент, а не повноцінна race-proof пісочниця: інший локальний процес, здатний замінювати файли чи каталоги під час обходу дочірнім індексатором, може створити TOCTOU-гонку. Дозволяйте запис у ці каталоги лише довіреним користувачам.

Обмеження стосується тільки Admin HTTP route. Локальний оператор, який прямо запускає npx semidex-lite index <path>, сам відповідає за вибраний шлях.

Відкриття сервера за межі loopback

Якщо ви свідомо встановлюєте ADMIN_ALLOW_REMOTE=1, потрібно також задати ADMIN_ALLOWED_HOSTS — точні хости, які використовуватимуть клієнти. Інакше сервер не запуститься:

ADMIN_ALLOW_REMOTE=1
ADMIN_ALLOWED_HOSTS=semidex.example.com,192.168.1.10:8642

Вказуйте порт, якщо клієнти підключаються не на стандартний. Це список дозволених Host, а не контроль доступу — він запобігає DNS-rebinding та атакам через заголовок Host, але не робить API безпечним для відкриття без вашого власного автентифікованого шару попереду.

За reverse proxy з термінацією TLS origin браузера (https://ваш-домен) відрізняється від того, що бачить цей процес на незашифрованому сокеті, тому додатково вкажіть точні дозволені origin:

ADMIN_ALLOWED_ORIGINS=https://semidex.example.com

X-Forwarded-Proto і X-Forwarded-Host свідомо не є довіреними — вони контролюються атакувальником на безпосередньо доступному listener — тож це потрібно налаштувати явно, а не виводити автоматично.

CLI

Нижче наведено рекомендований запуск із локальної залежності проєкту через npx. Так проєкт використовує версію semidex-lite, зафіксовану у власному package.json і lockfile, а не випадкову системну версію. Якщо ви свідомо встановили пакет глобально через npm install -g semidex-lite, можете прибрати npx і запускати ті самі команди як semidex-lite ....

npx semidex-lite --help                       # перелік хмарних команд
npx semidex-lite doctor [--probe-inference]   # перевірка середовища без змін
npx semidex-lite serve                        # запуск адмін-API та панелі
npx semidex-lite index <path>                 # індексація файла або папки

doctor

За замовчуванням працює лише на читання: перевіряє версію Node, наявність .env, наявність облікових даних Qdrant Cloud/Gemini та доступність Qdrant Cloud за допомогою дешевого запиту. Команда нічого не створює, не змінює і не видаляє.

Параметр --probe-inference виконує справжній цикл обчислення ембедингу через тимчасову колекцію Qdrant Cloud. Колекція створюється і видаляється в межах одного запуску команди. Це перевіряє, чи справді Cloud Inference працює з вибраною dense-моделлю. Перед виконанням команда показує попередження.

serve

Запускає адмін-API та панель на ADMIN_HOST:ADMIN_PORT (за замовчуванням 127.0.0.1:8642). Якщо QDRANT_URL, QDRANT_KEY або GEMINI_API_KEY відсутні чи недоступні, сервер запускається в обмеженому режимі: панель повідомляє, що саме не налаштовано, замість того щоб відмовитися від запуску. До виправлення конфігурації не працюють лише залежні операції: пошук, індексація або Ask.

index

COLLECTION=my-docs npx semidex-lite index ./docs
COLLECTION=my-docs npx semidex-lite index ./docs --prune-stale

У PowerShell змінну колекції потрібно задати окремо:

$env:COLLECTION='my-docs'
npx semidex-lite index ./docs

Індексує файл або папку у вказану колекцію Qdrant Cloud. Підтримує Markdown, звичайний текст, PDF та формати, які може конвертувати Pandoc. Параметр --prune-stale видаляє точки файлів, яких більше немає за вказаним шляхом. Використовуйте його лише під час індексації повного кореня джерел, а не його частини.

Параметрів --onnx-embed, --llm-summaries і --tag-gen немає: це локальні можливості, які не входять до цього пакета.

key

Керує ключами Integration API — bearer-токенами, якими ваш backend викликає Ask. Повна модель — у розділі Автентифікація Integration API нижче.

npx semidex-lite key add --name assistant-backend --collection my-docs
npx semidex-lite key list
npx semidex-lite key revoke <id>

key add показує сирий токен один раз — він ніколи не зберігається (зберігається лише SHA-256 digest) і повторно показаний бути не може. key list показує лише публічні метадані, ніколи токен чи digest. key revoke діє негайно, без перезапуску.

Автентифікація Integration API

[!IMPORTANT] Примітка про міграцію для наявних користувачів Ask API. Ask тепер потребує bearer-токена. Доки ви не створите перший ключ, POST /api/v1/search, POST /api/v1/ask і POST /api/v2/ask повертають 503 integration_auth_not_configured. Створіть ключ командою semidex-lite key add … і надсилайте його як Authorization: Bearer <token>. Більше нічого не змінюється: адмін-панель, налаштування, індексація та перегляд колекцій працюють як раніше, без жодних облікових даних.

Admin API проти Integration API

semidex-lite обслуговує дві різні поверхні з навмисно різними правилами. Сам Integration API складається з двох незалежних груп ендпоінтів — Search (/api/v1/search, лише Qdrant, без виклику генерації) і Ask (/api/v1/ask//api/v2/ask, платна генерація через Gemini) — і ключ треба явно налаштувати на кожну з них окремо (див. Створення ключа нижче):

| | Admin API | Integration API | |---|---|---| | Маршрути | Панель, налаштування, завдання індексації, колекції, проби, /api/search (неверсійний, лише для панелі) | POST /api/v1/search, POST /api/v1/ask, POST /api/v2/ask | | Хто викликає | Ви, з браузера на цій машині | Ваш backend, server-to-server | | Обліковий запис | Немає — захист через loopback-прив'язку | Bearer-ключ, обов'язково | | Відкритість | Ніколи не відкривати поза loopback | Доступний через ваш backend |

Admin-маршрути ніколи не блокуються integration-ключем: відсутній або пошкоджений key store вимикає Search/Ask, а не вашу панель.

/api/search і /api/v1/search виглядають схожими, але це РІЗНІ контракти:

  • /api/search — неверсійний, використовується лише вбудованою панеллю цього пакета (ui-src/search.js). Без bearer-ключа, лише loopback, без публічної гарантії сумісності — форма відповіді може змінитися в будь-якому релізі без попередження.
  • /api/v1/search — версійний, автентифікований і стабільний. Саме цей ендпоінт має викликати ваш backend. Див. Search: POST /api/v1/search нижче.

Створення ключа

npx semidex-lite key add --name assistant-backend \
  --collection my-docs \
  --collection support-docs \
  --expires 90d

Параметри:

  • --name — мітка, обов'язково.

  • --collection — можна повторювати, обов'язково. Ключ без жодної колекції відхиляється: порожня область дії ніколи не повинна тихо означати необмежений доступ. Щоб явно надати всі колекції, вкажіть --collection "*".

  • --operation — можна повторювати. За замовчуванням generate (Ask v1/v2), якщо прапорець не вказано — наявний ключ, створений до появи Search, ніколи автоматично не отримує доступ до неї: доступ до Search з'являється лише в ключа, явно створеного (або перествореного) з --operation search. Вкажіть --operation search лише для Search, --operation generate лише для Ask, або обидва прапорці разом для ключа, який може викликати обидва:

    # Лише Search — цей backend ніколи не викликає Ask/Gemini.
    npx semidex-lite key add --name search-widget --collection my-docs --operation search
    
    # Лише Ask/generate (попередня поведінка за замовчуванням, без змін).
    npx semidex-lite key add --name chat-backend --collection my-docs --operation generate
    
    # Обидва — один ключ для backend, який робить і Search, і Ask.
    npx semidex-lite key add --name full-backend --collection my-docs \
      --operation search --operation generate
  • --expires — ISO-дата (2027-01-01) або тривалість (90d, 12h). Пропустіть, щоб ключ не мав терміну дії.

  • --requests-per-minute — стійкий ліміт частоти для цього ключа, ціле число від 1 до 6000. Пропустіть для значення за замовчуванням (30/хв). Див. Обмеження частоти запитів нижче.

  • --burst — місткість token bucket (burst) для цього ключа, ціле число від 1 до 1000. Пропустіть для значення за замовчуванням (5).

Токен друкується один раз. Зберігайте його в секрет-менеджері вашого backend — ніколи у браузерному JavaScript, localStorage, URL чи системі контролю версій.

Надсилання токена

curl -N -X POST "http://127.0.0.1:8642/api/v1/ask" \
  -H "Authorization: Bearer $SEMIDEX_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"collection":"my-docs","question":"Які умови повернення товару?"}'
const response = await fetch('http://127.0.0.1:8642/api/v1/ask', {
  method: 'POST',
  headers: {
    // Читайте з власного сховища секретів — ніколи не вписуйте токен у код.
    Authorization: `Bearer ${process.env.SEMIDEX_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ collection, question: userMessage }),
});

Токен приймається лише у заголовку Authorization. Він ігнорується в query-рядку, cookie та тілі запиту, бо ці місця логуються, кешуються й поширюються так, як для облікових даних неприпустимо.

Обмеження ключа колекціями

Ключ має доступ лише до тих колекцій, з якими його створено. Збіг точний: --collection docs дає доступ до docs, але не до docs-a. Колекція поза областю дії та колекція, якої не існує, повертають однаковий 403, тож викликач не може дізнатися, які колекції у вас є.

Саме цей механізм слід використовувати, коли один інстанс semidex-lite обслуговує кількох асистентів: дайте кожному backend власний ключ із власними колекціями.

Коди відповідей

| Статус | Код | Значення | |---|---|---| | 503 | integration_auth_not_configured | Ключів не налаштовано (або key store нечитабельний). Створіть ключ. | | 401 | unauthorized | Відсутній, некоректний, невідомий, хибний, відкликаний або прострочений токен. Навмисно нерозрізнювані — щоб не можна було перебирати ключі. | | 429 | rate_limited | Автентифіковано, але цей ключ перевищив свій ліміт частоти запитів. Див. Обмеження частоти запитів нижче. | | 403 | forbidden | Автентифіковано, але цей ключ не має доступу до цієї колекції чи операції. | | 200 | — | Авторизовано; починається SSE-потік. |

401, 429 або 403 визначаються до будь-якого запиту в Qdrant, будь-якого обчислення ембедингів і будь-якого виклику Gemini — відхилений запит нічого вам не коштує. 429 визначається навіть раніше за 403 (і до того, як взагалі розбирається тіло запиту): автентифікований запит завжди витрачає одну одиницю ліміту, навіть якщо він виявиться некоректним або націленим на колекцію поза областю дії ключа.

Обмеження частоти запитів

Кожен автентифікований запит Integration API (/api/v1/search, /api/v1/ask, /api/v2/ask) обмежується за ключем через token bucket: 30 запитів/хв, burst 5, за замовчуванням. Усі три ендпоінти використовують ОДИН спільний bucket на ключ — виклики Search і Ask по черзі не подвоюють ефективну частоту.

Задайте інший ліміт для ключа при його створенні:

npx semidex-lite key add --name high-volume-backend \
  --collection my-docs \
  --requests-per-minute 300 \
  --burst 20

--requests-per-minute приймає 1–6000; --burst приймає 1–1000. Ліміт ключа фіксується при створенні — команди key edit не існує; щоб змінити ліміт, відкличте й створіть ключ заново. key list завжди показує ефективний ліміт кожного ключа (30/хв burst 5 для ключа, створеного без цих прапорців), а не сире незадане значення.

При перевищенні ліміту запит повертає 429 з тілом { "error": { "code": "rate_limited", "message": "..." } } і заголовком Retry-After (ціле число секунд — зачекайте щонайменше стільки перед повторною спробою). Жодна деталь відповіді не розкриває ідентичність ключа чи його налаштований ліміт.

Семантика скидання та ротації:

  • Стан ліміту частоти живе лише в пам'яті процесу сервера. Перезапуск semidex-lite serve скидає bucket кожного ключа до повного — немає збереженого лічильника "використано цієї хвилини", який переживає перезапуск.
  • Відкликання ключа не потребує явного очищення: відкликаний токен не проходить автентифікацію (401) ще до того, як запускається етап обмеження частоти, тож він більше ніколи не торкається bucket'а цього ключа. Сам bucket пізніше автоматично видаляється збирачем сміття після простою.
  • Створення нового ключа завжди починається з повного bucket'а (burst токенів доступні одразу) — немає спільного чи успадкованого стану між ключами, навіть для однойменної інтеграції, повторно створеної після відкликання.

Обмеження — прочитайте перед плануванням потужності:

  • Немає спільного стану між процесами/репліками. Лімітер зберігається у пам'яті одного процесу. Якщо ви запускаєте кілька процесів semidex-lite serve за балансувальником навантаження, кожен процес застосовує налаштований ліміт незалежно — реальна сумарна частота для ключа стає requestsPerMinute × кількість процесів, а не тим числом, яке ви налаштували.
  • Це не захист від DDoS. Це обмежує частоту запитів легітимного, вже автентифікованого ключа. Це не захищає від переповнення з'єднаннями чи неавтентифікованого трафіку (він відхиляється раніше, на етапі 401/503, до запуску цього етапу) і не зупиняє того, хто має доступ оператора, від створення нових ключів.
  • Немає гарантії витрат. Ліміт кількості запитів — не стеля витрат: Ask-запити мають різну вартість Gemini/Qdrant Cloud залежно від вмісту. Для реальної стелі витрат використовуйте власні сповіщення про витрати у провайдера.

Чим semidex-lite і далі не володіє

Автентифікація не змінює власності над розмовою: ваш застосунок і далі володіє історією чату та зберігає її. semidex-lite не зберігає розмови для жодного з endpoint'ів — див. Інтеграція backend: багатоходовий Ask. Ключ ідентифікує backend, що викликає, а не кінцевого користувача; зіставлення користувачів із правами лишається завданням вашого backend.

Ще не реалізовано: віддалена автентифікація Admin API та керування ключами з панелі.

JS-клієнт (semidex-lite/client)

Це рекомендований спосіб інтеграції з semidex-lite — причини нижче. semidex-lite постачає невеликий, без жодних залежностей ESM-клієнт, що покриває всі три ендпоінти Integration API (Search, Ask v1, Ask v2). Це рекомендований спосіб викликати semidex-lite з backend на Node.js — приклади з прямим fetch/curl теж наведені в цьому README, для інших мов або коли потрібен повний контроль, але клієнт уже коректно обробляє тонкі місця: розбір SSE через довільні межі мережевих фрагментів, тайм-аути запитів без витоку таймерів, підтримку AbortSignal і один типізований клас помилки на всі випадки збою замість перевірки кодів статусу вручну.

import { createSemidexClient } from 'semidex-lite/client';

const semidex = createSemidexClient({
  baseUrl: 'http://127.0.0.1:8642',   // без query-рядка, фрагмента чи userinfo
  apiKey: process.env.SEMIDEX_TOKEN,  // ніколи не передавайте це в браузерний JavaScript
});

// Search — звичайний Promise.
const result = await semidex.search({ collection: 'my-docs', query: 'умови повернення', top: 5 });
console.log(result.results.map((r) => r.sourceFile));

// Ask v1 — асинхронний генератор: sources, нуль або більше answer_delta, потім done.
for await (const event of semidex.askV1({ collection: 'my-docs', question: 'Який термін повернення?' })) {
  if (event.type === 'answer_delta') process.stdout.write(event.text);
  if (event.type === 'done') console.log('\nцитати:', event.citations);
}

// Ask v2 — той самий контракт подій, плюс блок `conversation`, яким володіє викликач.
// conversationId/summary/recentMessages — ваша відповідальність зберігати —
// див. "Чим semidex-lite і далі не володіє" вище та
// examples/backend-integration-server.mjs, де реальний застосунок зберігав би це.
for await (const event of semidex.askV2({
  collection: 'my-docs',
  question: 'А які є винятки з цього правила?',
  conversation: { conversationId, summary, recentMessages },
})) {
  // ... такі самі форми подій, як у askV1(), плюс event.conversation у `done`
}

Обробка помилок. Будь-яка помилка — відповідь не 2xx, термінальна SSE-подія error, тайм-аут, скасований запит, мережева помилка — постає як ОДИН і той самий типізований SemidexApiError, а не окрема перевірка коду статусу чи різна форма залежно від того, який саме ендпоінт відмовив:

import { createSemidexClient, SemidexApiError } from 'semidex-lite/client';

try {
  await semidex.search({ collection: 'my-docs', query: 'x' });
} catch (err) {
  if (err instanceof SemidexApiError) {
    console.error(err.status, err.code, err.retryable, err.message);
    // err ніколи не містить apiKey чи будь-який інший секрет.
  }
}

Редіректи ніколи не виконуються. Якщо налаштований baseUrl (або що-небудь перед ним) відповідає 3xx, клієнт відхиляє запит з SemidexApiError (retryable: true) замість того, щоб перейти за редіректом — ваш apiKey/заголовок Authorization та тіло запиту ніколи не повторюються за адресою з Location, на будь-якому origin, включно з тим самим.

Повний робочий приклад — мінімальний backend, який викликає браузер, який сам володіє ключем Semidex та явним зіставленням теми на колекцію, викликає semidex-lite/client і транслює в браузер одну розмову Ask v2 як власний потік Server-Sent Events — постачається у файлі examples/backend-integration-server.mjs. Він конкретно демонструє архітектуру, яку передбачає кожна інтеграція в цьому README:

браузер (без секретів)  -->  ВАШ backend (тримає SEMIDEX_TOKEN, володіє зіставленням колекцій)  -->  Semidex Lite

Запустіть його проти власного semidex-lite serve:

npx semidex-lite key add --name demo-backend --collection my-docs --operation search --operation generate
SEMIDEX_TOKEN=<token> SEMIDEX_BASE_URL=http://127.0.0.1:8642 SEMIDEX_DOCS_COLLECTION=my-docs \
  node examples/backend-integration-server.mjs

Оголошення типів (.d.ts) постачаються разом із клієнтом для редакторів/ tsc — без кроку збірки, без компіляції TypeScript у цьому репозиторії.

Search: POST /api/v1/search

Версійний, автентифікований аналог внутрішнього пошуку панелі. Використовує ту саму реалізацію retrieval, що й /api/search (див. Admin API проти Integration API вище щодо відмінності) — той самий гібридний dense+sparse ранжування, ті самі обмежені семантики top/window, та сама фільтрація за тегами/файлом — але за bearer-ключем замість довіри лише до loopback.

curl -N -X POST "http://127.0.0.1:8642/api/v1/search" \
  -H "Authorization: Bearer $SEMIDEX_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"collection":"my-docs","query":"умови повернення","top":5}'
const response = await fetch('http://127.0.0.1:8642/api/v1/search', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.SEMIDEX_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ collection: 'my-docs', query: 'умови повернення', top: 5 }),
});
const { results } = await response.json();

Для backend на Node.js надавайте перевагу JS-клієнту (semidex.search({ collection, query, top })) замість прямого fetch — він перевіряє вхідні дані та безкоштовно дає типізовані помилки.

Поля запиту:

| Поле | Тип | За замовчуванням | Примітка | |---|---|---|---| | collection | рядок | — | обов'язково | | query | рядок | — | обов'язково | | top | ціле 1–20 | 3 | | | window | ціле 0–5 | 0 | додаткові фрагменти до/після кожного збігу | | windowFormat | "compact" | "full" | "compact", коли window > 0, інакше ігнорується | | | sourceFile | рядок | — | фільтр за одним відомим файлом | | tags | рядок[] | — | фільтр за тегом (будь-який збіг) |

На відміну від /api/search, /api/v1/search відхиляє будь-яке поле тіла поза цим списком з 400 bad_request — свідомо жорсткіший публічний контракт із самого початку, тож помилка в назві поля ніколи мовчки нічого не робить.

Відповідь містить apiVersion: "v1" і явну, задокументовану форму результату (sourceFile, chunkIndex, totalChunks, section, text, context, tags, score, nodeId, nodePath, nodeType, isMatch, а також windowChunks, коли window > 0) — ніколи сирий внутрішній об'єкт. Помилки використовують той самий конверт { error: { apiVersion, code, message, retryable } }, що й Ask v1/v2 (див. Коди відповідей вище щодо кодів автентифікації/області дії/обмеження частоти, спільних для всіх трьох ендпоінтів Integration API); специфічні для Search коди — це bad_request, not_found, forbidden, not_implemented, embedding_failed, embedding_unresolved і embedding_unsupported — Search ніколи не повертає код, пов'язаний із генерацією (busy, dependency_unavailable, жоден код budget_*), бо ніколи не викликає провайдера генерації.

Ключ має бути налаштований на операцію search, щоб викликати цей ендпоінт — див. Створення ключа вище. Наявний ключ, створений до появи Search, без прапорця --operation, доступний лише для generate і отримає тут 403 forbidden, доки його не перествориять із --operation search.

Ask: відповіді на основі вашої бази знань

Ask дає змогу поставити запитання до однієї проіндексованої колекції та отримати відповідь, сформовану Gemini на основі знайдених у ній фрагментів. Модель не отримує всю колекцію: semidex-lite спочатку знаходить обмежений набір релевантних джерел і лише потім передає їх разом із запитанням у Gemini.

Як формується відповідь

Запитання користувача
  -> ембединг запиту через модель, закріплену за колекцією
  -> гібридний dense+sparse пошук у Qdrant Cloud
  -> відбір і нумерація релевантних фрагментів
  -> складання обмеженого evidence-контексту
  -> Gemini отримує системні правила + evidence + запитання
  -> streaming-відповідь із посиланнями [1], [2] ...

Модель ембедингів, використана під час запиту, визначається профілем самої колекції. Це важливо: пошук не повинен використовувати іншу модель або іншу розмірність векторів, ніж індексація цієї колекції.

Ask передає у Gemini дві окремі частини:

  • системний prompt — правила поведінки моделі;
  • користувацький prompt — пронумеровані знайдені фрагменти та запитання.

Системний prompt надсилається через нативний systemInstruction Gemini. Він вимагає відповідати лише за наданими джерелами, додавати посилання до фактичних тверджень, відповідати мовою запитання, ігнорувати інструкції всередині проіндексованих документів і відмовлятися від відповіді, якщо evidence не містить достатньої інформації. Це зменшує ризик вигаданих відповідей і prompt injection, але не дає абсолютної гарантії: результат LLM усе одно потрібно оцінювати за наведеними джерелами.

У поточній версії системний prompt є внутрішньою частиною Ask runtime. Його не можна змінити через адмін-панель, .env або запит Ask API. Можливість безпечно задавати власні інструкції може бути додана пізніше разом із валідацією та обмеженнями, але зараз це не частина публічного контракту.

Інтеграція Ask через власний backend

Основний спосіб використання Ask - виклик із backend вашого асистента. Так сайт, бот або застосунок може вибрати потрібну базу знань, автентифікувати користувача, вести історію діалогу та застосувати власні правила доступу перед зверненням до Semidex. Ask доступний через версійований endpoint POST /api/v1/ask.

Після локального запуску serve backend може викликати його через fetch:

const collectionByAssistant = {
  support: 'company-support',
  education: 'course-materials',
};

const collection = collectionByAssistant[assistantId];
if (!collection) throw new Error('Unknown assistant');

const response = await fetch('http://127.0.0.1:8642/api/v1/ask', {
  method: 'POST',
  headers: {
    // Зчитуйте зі свого сховища секретів — ніколи не вписуйте токен у код.
    // Див. розділ "Автентифікація Integration API" вище.
    Authorization: `Bearer ${process.env.SEMIDEX_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ collection, question: userMessage }),
});

Для backend на Node.js надавайте перевагу JS-клієнту (for await (const event of semidex.askV1({ collection, question }))) замість прямого fetch — він коректно розбирає SSE-потік через межі фрагментів і дає один типізований клас помилки на всі випадки збою замість самописної перевірки Content-Type.

Визначайте або перевіряйте дозволену колекцію на своєму backend. Не дозволяйте неавтентифікованому браузеру передавати довільну назву колекції, інакше він може отримати доступ до бази знань іншого асистента.

Для ручної перевірки API можна використати curl:

curl.exe -N -X POST "http://127.0.0.1:8642/api/v1/ask" `
  -H "Authorization: Bearer $env:SEMIDEX_TOKEN" `
  -H "Content-Type: application/json" `
  -d '{"collection":"my-docs","question":"Які основні вимоги описані в документації?"}'

Обов'язкові поля запиту:

  • collection — назва однієї колекції;
  • question — непорожнє запитання.

Необов'язкове поле scope.sourceFile обмежує пошук одним відомим файлом:

{
  "collection": "my-docs",
  "question": "Які умови повернення товару?",
  "scope": {
    "sourceFile": "policies/returns.md"
  }
}

Відповідь передається як Server-Sent Events (SSE):

  • sources — фрагменти, відібрані як evidence;
  • answer_delta — частини відповіді під час генерації;
  • done — фінальна відповідь, citations, модель, використання токенів і час;
  • error — структурована помилка.

Ask API v1 є stateless: кожен запит незалежний, sessionId, історія діалогу й довгострокова пам'ять поки не підтримуються. Він вимагає bearer-токен і обмежується за частотою на рівні ключа (див. Автентифікація Integration API та Обмеження частоти запитів вище). Не відкривайте порт адмін-сервера безпосередньо в інтернет; для зовнішньої інтеграції розміщуйте перед ним власний автентифікований backend або reverse proxy з відповідними обмеженнями.

Інтеграція backend: багатоходовий Ask (/api/v2/ask)

/api/v2/askосновний спосіб інтеграції багатоходового розмовного асистента із Semidex Lite — використовуйте його, коли користувач може ставити уточнювальні запитання. /api/v1/ask залишається доступним і є правильним вибором для stateless одноходових запитів без жодної історії розмови (див. вище); v2 є додатковим, а не заміною. v2 так само stateless, як і v1 — різниця в тому, що у кожному запиті ВАШ backend надсилає обмежений підсумок і вікно останніх повідомлень як об'єкт conversation, і Semidex використовує його лише для цього одного запиту: щоб переформулювати неоднозначні уточнювальні питання перед retrieval, дати моделі контекст розмови і — лише коли історія стала достатньо довгою — повернути свіжо перерахований підсумок для збереження. Semidex Lite не зберігає діалоги на сервері в цій версії, для жодного з endpoint'ів. Дашборд наразі не має UI для Ask (див. нижче) — можливо, його буде реалізовано пізніше, — тож наразі /api/v1/ask і /api/v2/ask, викликані напряму або через власний backend, є способами використати Ask.

{
  "collection": "company-support",
  "question": "А які винятки?",
  "conversation": {
    "id": "conv_123",
    "summary": "Обговорювали 14-денний термін повернення.",
    "recentMessages": [
      { "role": "user", "content": "Скільки часу є на повернення товару?" },
      { "role": "assistant", "content": "У вас є 14 днів від дати доставки." }
    ]
  }
}

Хто володіє чатом і хто керує контекстним вікном

Ваш backend є джерелом істини для розмови. Щонайменше він має зберігати append-only архів повідомлень, поточний rolling summary та індекс першого повідомлення архіву, яке ще не покрите цим підсумком. Перед кожним запитом він формує recentMessages від цієї межі й надсилає Semidex підсумок та обмежене вікно. Semidex не завантажує попередні ходи за conversation.id і не може відновити чат, який викликач не передав у запиті.

Керування token budget у межах одного запиту виконує Semidex. Викликачу не потрібно самостійно токенізувати повідомлення або знати точний розмір контекстного вікна налаштованої моделі Gemini. Semidex:

  1. отримує ліміт контексту активної моделі генерації;
  2. рахує питання, summary, останні повідомлення, службову частину prompt і retrieval evidence в межах одного бюджету;
  3. резервує місце для evidence та згенерованої відповіді;
  4. вилучає найстаріші raw-повідомлення лише з поточного prompt, якщо вся передана історія не вміщується (збережений архів викликача не змінюється);
  5. за потреби переформульовує контекстне уточнення в самостійний retrieval query, але залишає оригінальне питання для фінальної відповіді;
  6. після успішної відповіді намагається стиснути summary, коли досягнуто налаштованого порога.

Викликач усе одно контролює, що потрапляє в цей процес: яку розмову й колекцію авторизовано, який збережений summary і recent messages передано та чи буде збережено повернений стан. Налаштування compaction, які надає Semidex (ASK_SUMMARY_COMPACTION_THRESHOLD, ASK_SUMMARY_RETAINED_MESSAGES і ASK_SUMMARY_COMPACTION_TIMEOUT_MS), керують моментом і способом спроби стиснення, але не перетворюють Semidex на базу даних чатів.

  • ASK_SUMMARY_COMPACTION_THRESHOLD за замовчуванням дорівнює 8 повідомленням і визначає, коли Semidex починає намагатися виконати compaction.
  • ASK_SUMMARY_RETAINED_MESSAGES за замовчуванням дорівнює 4 і визначає, скільки найновіших raw-повідомлень залишаються поза оновленим summary.
  • ASK_SUMMARY_COMPACTION_TIMEOUT_MS за замовчуванням дорівнює 6000; timeout залишає поточний summary і boundary без змін та не скасовує вже успішно згенеровану відповідь.

Ці операційні налаштування відрізняються від фіксованих протокольних меж, описаних нижче. Збільшення порога compaction не збільшує ліміт запиту у 200 повідомлень.

Життєвий цикл успішного ходу виглядає так:

backend завантажує архів + summary + boundary
  -> формує нестиснені recentMessages
  -> POST /api/v2/ask
  -> Semidex обмежує контекст, шукає evidence і стримить відповідь
  -> Semidex може повернути updatedSummary + compactedMessageCount
  -> backend атомарно додає user/assistant хід та оновлює summary + boundary
  • conversation.id — непрозорий рядок, який генерує й контролює ВАШ backend (наприклад, UUID). Semidex лише повертає його назад у полі conversation.id події done — він ніколи не використовується для пошуку, авторизації чи збереження чогось на сервері.
  • conversation.recentMessages — найновіші ходи {role, content} з ВАШОЇ власної збереженої історії, role обмежено "user"/"assistant". Semidex додатково обрізає це під реальне вікно контексту моделі за потреби і не зберігає це на диску та не передає стороннім сервісам понад те, що необхідно для обробки цього одного запиту — але "цей один запит" може включати до трьох окремих викликів Gemini, кожен з яких бачить частину або весь цей вміст: необов'язковий виклик переформулювання запиту (щоб уточнити неоднозначне уточнювальне запитання перед retrieval), основний виклик відповіді та необов'язковий виклик compaction підсумку (лише коли розмова перевищила ASK_SUMMARY_COMPACTION_THRESHOLD). Semidex сам нічого з цього не записує на диск чи в будь-яке сховище даних, і нічого з цього не переживає межі цього одного HTTP-запиту в самому процесі Semidex — але воно виходить за межі процесу Semidex, щоб дістатися API Gemini, і підпорядковується власним умовам обробки даних Google для цього API. Якщо це неприйнятно для ваших даних, не надсилайте їх як recentMessages/summary взагалі.
  • conversation.summary — ваш раніше збережений підсумок розмови (пропустіть на першому ході нової розмови). Semidex трактує його, як і recentMessages, як недовірений контекст розмови — ніколи як retrieval evidence, ніколи як перевірений факт, він ніколи не може перевизначити власні системні інструкції Semidex.
  • done.conversation.summaryChanged / updatedSummary / compactedMessageCount — Semidex перераховує підсумок лише коли розмова перевищує налаштовувану довжину (ASK_SUMMARY_COMPACTION_THRESHOLD), а не в кожному запиті. Коли summaryChanged дорівнює true: збережіть updatedSummary як нове значення для наступного ходу, і посуньте власну межу request-view рівно на compactedMessageCount — кількість НАЙСТАРІШИХ повідомлень із recentMessages, які ви щойно надіслали і які тепер покриті updatedSummary. Пропуск цього кроку означає, що ви й далі надсилатимете повідомлення, які Semidex уже згорнув у підсумок, тож compaction фактично ніколи не зменшить обсяг того, що ви надсилаєте, і розмова зростатиме без обмежень. Виконуйте це як одне атомарне оновлення разом зі збереженням підсумку — наприклад, summarizedThroughArchiveIndex += compactedMessageCount у examples/conversation-manager.mjs, який зберігає повний, лише-для-додавання архів і виводить обмежене вікно recentMessages із цієї межі, а не змінює один масив на місці. Коли summaryChanged дорівнює false — використовуйте той підсумок і ту межу, які у вас вже є; compactedMessageCount у цьому випадку відсутній.

Протокольні межі. Це фіксовані, не налаштовувані обмеження, які /api/v2/ask перевіряє на етапі парсингу запиту — запит, що перевищує будь-яке з них, відхиляється з кодом 400 ще до будь-якого retrieval чи генерації:

  • conversation.recentMessages — не більше 200 елементів.
  • content кожного повідомлення — не більше 50 000 символів.
  • conversation.summary — не більше 8000 символів.
  • conversation.id — не більше 256 символів.

Коли compaction не встигає. Compaction — best-effort: якщо провайдер генерації недоступний або постійно падає саме на виклику compaction, summaryChanged залишається false хід за ходом, тож вікно recentMessages, яке ваш backend продовжує надсилати, ніколи не зменшується — тоді як ваш архів продовжує зростати на один хід щоразу, коли сама відповідь успішна. Врешті це вікно перевищить протокольну межу у 200 елементів, наведену вище. examples/conversation-manager.mjs виявляє це локально, перед відправкою, і повертає помилку client_bounded_context_exceeded замість того, щоб або мовчки обрізати історію, або дозволити /api/v2/ask відхилити завеликий запит. Звичайний retry не відновлює цей стан — те саме завелике вікно знову буде відхилено локально щоразу, тож жоден запит не досягає Semidex, і провайдер, що відновився, не отримує запиту для compaction. Єдині виходи — почати нову розмову або застосувати власне ручне/позасистемне відновлення compaction (самостійно підсумувати й обрізати збережений архів) — жоден із них не реалізований у цьому демо.

У цьому пакеті постачається готовий до запуску, без залежностей клієнт і демо — див. examples/ask-v2-sse-client.mjs (невеликий SSE-streaming клієнт: відкриває запит, коректно парсить sources/ answer_delta/done/error навіть коли фрейм розділений між мережевими chunk'ами, і повертає простий об'єкт результату) та examples/conversation-manager.mjs (мінімальний приклад ВОЛОДІННЯ станом розмови навколо цього клієнта). Запустіть CLI-демо напряму проти власного запущеного сервера.

Якщо ви клонували цей репозиторій або працюєте всередині самого packages/lite/:

QDRANT_URL=... QDRANT_KEY=... GEMINI_API_KEY=... npx semidex-lite serve &
npx semidex-lite key add --name demo --collection my-docs   # скопіюйте виведений токен
SEMIDEX_TOKEN=<token> node examples/run-conversation-demo.mjs my-docs "Скільки часу є на повернення товару?" "А які винятки?"

Якщо ви встановили semidex-lite як залежність власного проєкту (npm install semidex-lite), приклад знаходиться всередині node_modules/, тож шлях інший:

QDRANT_URL=... QDRANT_KEY=... GEMINI_API_KEY=... npx semidex-lite serve &
npx semidex-lite key add --name demo --collection my-docs   # скопіюйте виведений токен
SEMIDEX_TOKEN=<token> node node_modules/semidex-lite/examples/run-conversation-demo.mjs my-docs "Скільки часу є на повернення товару?" "А які винятки?"

Окремої підкоманди semidex-lite CLI для цього демо немає — це вихідний код прикладу, який ви запускаєте напряму через node, а не бінарник пакета.

Компактна форма того, що робить ваш backend навколо цього клієнта:

// Власна персистентність — НЕ частина Semidex. Див.
// examples/conversation-manager.mjs для повної (in-memory, лише демо)
// реалізації цієї форми.
const conversation = await chatStore.loadConversation(conversationId, userId);
const collection = assistantRegistry.resolveAllowedCollection(assistantId); // ніколи не довіряйте назві колекції з браузера
const token = await secrets.getIntegrationApiToken(); // власне сховище секретів — ніколи не вписуйте в код і не логуйте
const result = await askV2({ baseUrl, collection, question: userMessage, conversation, token });
await chatStore.commitTurn({
  conversationId, ownerId: userId, expectedVersion: conversation.version,
  messages: [{ role: 'user', content: userMessage }, { role: 'assistant', content: result.answer }],
  // Посунення межі — НЕ опціональне. Пропуск означає, що кожен наступний
  // запит продовжуватиме надсилати повідомлення, які Semidex уже згорнув
  // у підсумок, і compaction ніколи фактично не зменшить обсяг того, що
  // ви надсилаєте.
  ...(result.summaryChanged ? {
    updatedSummary: result.updatedSummary,
    summarizedThroughArchiveIndex: conversation.summarizedThroughArchiveIndex + result.compactedMessageCount,
  } : {}),
});

examples/conversation-manager.mjs зберігає стан в in-memory Map — це демо, а не production-персистентність. Вона втрачається при кожному перезапуску і ніколи не поширюється між кількома репліками сервера. Реальний backend має замінити її на PostgreSQL, Redis, MongoDB, SQLite чи будь-яке інше сховище, яке вже використовується, бажано через один атомарний виклик у формі commitTurn (ніколи не через два окремі виклики з перевіркою версії для "додати повідомлення" та "оновити підсумок" — якщо перший виклик інкрементує збережену версію, аргумент версії другого виклику вже застарілий, і повідомлення можуть зберегтися без відповідного підсумку).

/api/v1/ask залишається доступним без змін для stateless одноходових запитів без жодного стану розмови — v2 є додатковим, а не заміною.

Див. Ask API v2 — Bounded Conversational Context для повного обґрунтування дизайну, контракту ConversationStore, який Semidex розроблений підтримувати в майбутньому релізі, і правил token-budgeting/rewriting/compaction. Design-документи в цьому репозиторії — лише англійською.

Ручна перевірка без побудови інтеграції

Вбудований дашборд наразі не має панелі Ask — можливо, її буде реалізовано пізніше, але поки що він надає лише ручний пошук по проіндексованій колекції через власний внутрішній, неверсійний /api/search (без генерації, без citations, без SSE, без bearer-ключа — див. Admin API проти Integration API вище). Наразі немає браузерного способу викликати версійні /api/v1/search, /api/v1/ask чи /api/v2/ask; приклади curl вище та runnable-приклади в examples/ — це способи перевірити їх вручну до побудови повної інтеграції:

  1. Налаштуйте QDRANT_URL, QDRANT_KEY і GEMINI_API_KEY.
  2. Проіндексуйте файл або папку в колекцію через semidex-lite index.
  3. Запустіть сервер командою npx semidex-lite serve.
  4. Створіть ключ (semidex-lite key add ...) і викличте /api/v1/search, /api/v1/ask чи /api/v2/ask напряму через curl, або запустіть examples/backend-integration-server.mjs / examples/run-conversation-demo.mjs, щоб перевірити їх через JS-клієнт.

Live acceptance релізу лише для супровідників

Супровідники репозиторію можуть перевірити саме запакований і чисто встановлений npm-артефакт, а не вихідний код checkout:

SEMIDEX_LITE_RELEASE_LIVE=1 npm run accept:lite-release-live

У Windows PowerShell:

$env:SEMIDEX_LITE_RELEASE_LIVE = "1"
npm run accept:lite-release-live

Ця перевірка навмисно не входить до npm test, CI чи опублікованого пакета. Вона потребує справжніх QDRANT_URL, QDRANT_KEY і GEMINI_API_KEY, завантажує npm-залежності, виконує реальні Qdrant Cloud inference та Gemini generation, створює два ключі Integration API в ізольованому тимчасовому SEMIDEX_HOME, а також створює й видаляє одну унікальну колекцію harness (doctor probe окремо керує власною короткочасною probe-колекцією). Harness відмовляється працювати при збігу назви, видаляє лише точну колекцію власного запуску, не друкує bearer-токени чи ключі провайдерів і повертає ACCEPT лише після успішного cleanup. JSON-звіт за замовчуванням записується в .tmp/semidex-lite-release-live-report.json.

Це доповнення до scripts/ask-v2-live-acceptance.mjs: старий скрипт тестує розширену багатокрокову розмову й compaction безпосередньо з вихідного коду, а release harness перевіряє пакування, чисту інсталяцію, CLI, автентифікацію/scoping/rate limiting та Ask v1/v2 як один готовий продукт.

SEMIDEX_HOME

Дані застосунку (config.json, settings.json, кеш токенайзера) зберігаються поза встановленим пакетом у стандартній для відповідної ОС директорії:

  • Windows: %LOCALAPPDATA%\semidex-lite
  • macOS: ~/Library/Application Support/semidex-lite
  • Linux: $XDG_DATA_HOME/semidex-lite (або ~/.local/share/semidex-lite, якщо змінну не задано)

Шлях можна змінити за допомогою змінної оточення SEMIDEX_HOME. Ця директорія належить лише Lite і ніколи не використовується спільно з повною версією semidex.

Обмеження

  • Немає локальних провайдерів ембедингів або генерації (Ollama, локальний ONNX): підтримуються лише Qdrant Cloud Inference та Gemini.
  • Немає генерації тегів, комбінованого LLM-проходу context+tags і перевірок CUDA/DirectML.
  • Розширені налаштування chunking/retrieval із Settings UI повної версії semidex тут не показуються. Dense-модель і підтримувані несекретні параметри можна налаштувати, але облікові дані Qdrant та Gemini поки потрібно задавати через системні змінні оточення або локальний .env.
  • Settings API надає менший дозволений перелік параметрів, ніж повна версія semidex. Спроба записати непідтримуваний параметр повертає not_available_in_lite.

Якщо вам потрібні можливості поза цими межами, використовуйте повну версію semidex.

Ліцензія

Semidex Lite поширюється за ліцензією MIT. Ви можете використовувати, копіювати, змінювати, публікувати й поширювати код, зокрема у комерційних продуктах, за умовами ліцензії та зі збереженням необхідного повідомлення про авторські права. Програмне забезпечення надається «як є», без гарантій.