Skip to content

Vector Search

shaerware edited this page Mar 23, 2026 · 2 revisions

Vector Search (Векторный поиск)

Микросервис семантического поиска по текстовым документам. Работает полностью локально — без внешних API.

Скриншот

Vector Search

Концепция

Vector Search — отдельный HTTP-микросервис, который:

  • Принимает текст → разбивает на чанки → превращает в числовые векторы (embeddings)
  • Хранит векторы в ChromaDB (persistent, на диске)
  • Ищет по смыслу — cosine similarity между запросом и сохранёнными документами
  • Мультиязычный — русский, английский, казахский и 50+ языков (модель paraphrase-multilingual-mpnet-base-v2)
  • Самодостаточный — все вычисления локальные, без внешних сервисов

Зачем нужен

Задача Как решает
RAG Индексация документации/FAQ, поиск релевантного контекста для LLM
Каталог товаров Поиск по описаниям товаров «по смыслу» (а не по ключевым словам)
Дедупликация Обнаружение перефразов и дубликатов (similarity > 0.85)
Классификация Сравнение нового текста с эталонными категориями

Как это работает

Процесс загрузки (Upsert)

Текст документа
     │
     ▼
┌──────────┐     ┌──────────┐     ┌──────────┐
│ Chunker  │────►│ Embedder │────►│ ChromaDB │
│ (разбивка│     │ (векторы │     │ (хранение│
│ на части)│     │  768-dim) │     │ на диске)│
└──────────┘     └──────────┘     └──────────┘
  1. Текст нормализуется и разбивается на чанки (~2000 символов)
  2. Каждый чанк превращается в вектор из 768 чисел
  3. Вектор + метаданные сохраняются в ChromaDB
  4. При повторном upsert с тем же doc_id — старые чанки заменяются

Процесс поиска (Search)

"поисковый запрос"
     │
     ▼
┌──────────┐     ┌──────────┐     Результаты
│ Embedder │────►│ ChromaDB │────► (top-N по
│ (вектор  │     │ (cosine  │     similarity)
│ запроса) │     │ поиск)   │
└──────────┘     └──────────┘
  1. Запрос превращается в вектор
  2. ChromaDB находит ближайшие векторы (cosine similarity)
  3. Фильтрация по min_similarity, doc_id, group
  4. Пагинация и возврат результатов

Chunking (разбивка текста)

Шаг Описание
Нормализация Убирает лишние пробелы и переносы
Структурная разбивка По заголовкам (#), абзацам (\n\n), предложениям (.!?)
Сборка чанков Накапливает блоки до chunk_size символов (по умолчанию 2000)
Overlap 15% Конец предыдущего чанка дублируется в начале следующего
Word-safe Никогда не рвёт слово пополам

API

Base URL: https://ai-sekretar24.ru/vector-search Swagger UI: /vector-search/docs Auth: заголовок Authorization: Bearer <AUTH_KEY> (кроме /health)

Загрузка данных

Метод Путь Описание
POST /upsert Загрузить текст (авто-chunking + embedding)

Тело запроса:

{
  "text": "Текст документа...",
  "doc_id": "catalog-v2",
  "group": "products",
  "chunk_size": 2000
}
  • doc_id (опционально) — при повторном upsert с тем же ID старые чанки заменяются
  • group (опционально) — группировка для фильтрации при поиске
  • chunk_size (опционально) — размер чанка в символах (по умолчанию 2000)

Ответ: {"ids": ["uuid1", "uuid2", ...]}

Поиск

Метод Путь Описание
POST /search Семантический поиск по смыслу
POST /compare Сравнить текст с конкретной записью

Тело /search:

{
  "text": "поисковый запрос",
  "doc_id": "optional",
  "group": "optional",
  "min_similarity": 0.5,
  "limit": 10,
  "page": 1
}

Ответ:

{
  "items": [
    {
      "id": "uuid",
      "text": "найденный чанк...",
      "doc_id": "catalog-v2",
      "group": "products",
      "chunk_index": 0,
      "similarity": 0.87
    }
  ],
  "total": 5,
  "page": 1,
  "limit": 10
}

Тело /compare:

{
  "text": "текст для сравнения",
  "record_id": "uuid конкретной записи"
}

Ответ: {"similarity": 0.87}

Получение данных

Метод Путь Описание
GET /count Количество записей (query params: text?, doc_id?, group?, min_similarity?)
GET /ids Список ID записей (query params: doc_id?, group?)
GET /records Пагинированный список (query params: doc_id?, group?, limit?, page?)

Удаление

Метод Путь Описание
DELETE /record/{id} Удалить одну запись
DELETE /document/{doc_id} Удалить все чанки документа
DELETE /group/{group} Удалить все записи группы
DELETE /clear Очистить всё

Здоровье

Метод Путь Auth Описание
GET /health Нет {"status": "ok", "model": "paraphrase-multilingual-mpnet-base-v2"}

Деплой

Сервер: 155.212.231.7 (тот же VPS что AI Secretary) Порт: 8003 (проксируется nginx как /vector-search/) Systemd: vector-search.service Данные: /opt/vector-search/data/chroma/ Модель: /opt/vector-search/.cache/huggingface/ (~420MB)

Управление сервисом

systemctl status vector-search     # Статус
systemctl restart vector-search    # Перезапуск
journalctl -u vector-search -f     # Логи

Структура на сервере

/opt/vector-search/
├── app/                    # Исходный код
│   ├── main.py             # FastAPI + роуты
│   ├── auth.py             # Bearer token
│   ├── chunker.py          # Разбивка текста
│   ├── embedder.py         # sentence-transformers
│   ├── storage.py          # ChromaDB обёртка
│   └── config.py           # Настройки (.env)
├── tests/                  # Тесты (pytest)
├── data/chroma/            # Данные ChromaDB
├── .cache/huggingface/     # Кеш модели
├── venv/                   # Python виртуальное окружение
└── .env                    # Конфигурация (AUTH_KEY)

Стек

Компонент Технология
Язык Python 3.12
Фреймворк FastAPI + Uvicorn
Embeddings sentence-transformers (paraphrase-multilingual-mpnet-base-v2, 768d)
Векторная БД ChromaDB 0.6.x (persistent, cosine similarity)
Конфигурация pydantic-settings + .env
Потребление RAM ~1.1GB (модель + ChromaDB)

Примеры использования

Из curl

AUTH="Authorization: Bearer YOUR_KEY"
URL="https://ai-sekretar24.ru/vector-search"

# Загрузить документ
curl -X POST $URL/upsert \
  -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"text": "Текст документа...", "doc_id": "doc-1", "group": "knowledge"}'

# Поиск по смыслу
curl -X POST $URL/search \
  -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"text": "запрос", "min_similarity": 0.4}'

# Количество записей
curl "$URL/count" -H "$AUTH"

# Удалить документ
curl -X DELETE "$URL/document/doc-1" -H "$AUTH"

Интеграция с AI Secretary (RAG)

Начиная с PR #637, Vector Search автоматически интегрирован в WikiRAGService как параллельный движок поиска.

Архитектура

WikiRAGService.search_async() / retrieve_async()
      │
      ├── BM25 (всегда, sync → asyncio.to_thread)
      ├── Embeddings (Local/Gemini/OpenAI, sync → asyncio.to_thread)
      └── Vector Search (async HTTP → микросервис)
      │
      └── asyncio.gather → Merge + Deduplicate → Top-K results

Включение

  1. Задать переменные окружения:

    VECTOR_SEARCH_URL=http://localhost:8003  # или http://ai-secretary-vector-search:8000 в Docker
    VECTOR_SEARCH_TOKEN=your_secret_token    # опционально
  2. Запустить микросервис:

    docker compose --profile vector-search up -d
  3. Перезапустить orchestrator — клиент инициализируется автоматически.

  4. Синхронизировать данные:

    curl -X POST http://localhost:8002/admin/wiki-rag/vector-search/sync \
      -H "Authorization: Bearer YOUR_JWT"

Автоматическая синхронизация

  • При старте — фоновая задача vector-search-sync загружает все секции из WikiRAG
  • При обновлении — событие DatasetSynced триггерит инкрементальную синхронизацию
  • При удалении коллекции — группа в Vector Search удаляется автоматически

Admin API

Метод Путь Описание
GET /admin/wiki-rag/vector-search/status Health + статистика подключения
POST /admin/wiki-rag/vector-search/sync Ручная полная синхронизация

Health Check

В ответе /health появляется секция vector_search:

{
  "vector_search": {
    "status": "ok",
    "url": "http://localhost:8003",
    "model": "paraphrase-multilingual-mpnet-base-v2",
    "dims": 768,
    "collections": 3
  }
}

Прямое использование API (без интеграции)

import httpx

VECTOR_URL = "http://localhost:8003"
AUTH = {"Authorization": "Bearer YOUR_KEY"}

# 1. Индексация wiki-страниц
for page in wiki_pages:
    httpx.post(f"{VECTOR_URL}/upsert", headers=AUTH, json={
        "text": page.content,
        "doc_id": page.filename,
        "group": "wiki",
    })

# 2. Поиск контекста для вопроса пользователя
results = httpx.post(f"{VECTOR_URL}/search", headers=AUTH, json={
    "text": user_question,
    "group": "wiki",
    "min_similarity": 0.4,
    "limit": 5,
}).json()

# 3. Подставить в промпт LLM
context = "\n\n".join(r["text"] for r in results["results"])
prompt = f"Контекст:\n{context}\n\nВопрос: {user_question}"

Docker Compose

Микросервис доступен как Docker profile:

docker compose --profile vector-search up -d   # Запуск
docker compose --profile vector-search logs -f  # Логи
docker compose --profile vector-search down     # Остановка

Сервис vector-search слушает на порте 8003 (маппинг 8003:8000). Данные ChromaDB хранятся в volume vector_search_data.


См. также: Wiki-RAG, Cloud-LLM-Providers, Architecture

Clone this wiki locally