dead/packages/: qdrant-hybrid-search-0.1.0 metadata and description

Simple index

Hybrid search service using Qdrant vector database

description_content_type text/markdown
requires_dist
  • httpx==0.28.1
  • langchain-core==1.2.7
  • langgraph==1.0.7
  • pydantic-settings==2.12.0
  • pydantic==2.12.0
  • python-dotenv==1.2.1
  • qdrant-client==1.16.2
  • pytest==9.0.2; extra == 'dev'
  • ruff==0.14.14; extra == 'dev'
requires_python >=3.10

Because this project isn't in the mirror_whitelist, no releases from root/pypi are included.

File Tox results History
qdrant_hybrid_search-0.1.0-py3-none-any.whl
Size
24 KB
Type
Python Wheel
Python
3
qdrant_hybrid_search-0.1.0.tar.gz
Size
108 KB
Type
Source

Qdrant Hybrid Search

Універсальний пакет для гібридного пошуку з використанням Qdrant. Поєднує:

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

pip install qdrant-hybrid-search
# або
uv add qdrant-hybrid-search

Швидкий старт

1. Запуск Qdrant

docker-compose up -d

Або окремо:

docker run -p 6333:6333 -p 6334:6334 qdrant/qdrant

2. Створення індексу

from qdrant_hybrid_search import SearchConfig, IndexConfig, QdrantIndexer

# Конфігурація Qdrant та embeddings
search_config = SearchConfig(
    qdrant_url="http://localhost:6333",
    collection_name="api_endpoints",
    embedding_api_key="your-openrouter-key",
)

# Конфігурація індексу:
# - text_fields: поля для semantic + BM25 пошуку
# - payload_indexes: поля для фільтрації
index_config = IndexConfig(
    id_field="operation_id",
    text_fields=["summary", "path", "description", "tags"],
    payload_indexes={
        "resource_type": "keyword",
        "effect_type": "keyword",
        "method": "keyword",
    },
)

# Створення індексу
indexer = QdrantIndexer(search_config, index_config)
indexer.create_collection(reset=True)
indexer.create_payload_indexes()

# Індексація з JSONL файлу
indexer.index_from_jsonl("api-endpoints.jsonl")

3. Пошук

from qdrant_hybrid_search import SearchConfig, QdrantHybridSearch

config = SearchConfig(
    qdrant_url="http://localhost:6333",
    collection_name="api_endpoints",
    embedding_api_key="your-openrouter-key",
    top_k=10,
)

search = QdrantHybridSearch(config)

# Простий пошук
results = search.search("send document for signature")

# Пошук з фільтрами
results = search.search(
    "get user documents",
    filters={"resource_type": "document", "effect_type": "read"},
)

for r in results:
    print(f"[{r.score:.3f}] {r.payload['operation_id']}: {r.payload['summary']}")

Інтеграція з LangGraph

Проста нода (без генерації запитів)

from langgraph.graph import StateGraph, END
from qdrant_hybrid_search import create_simple_search_node, SearchConfig

class MyState(TypedDict):
    query: str
    search_results: list

config = SearchConfig(
    collection_name="api_endpoints",
    embedding_api_key="...",
)

search_node = create_simple_search_node(config)

graph = StateGraph(MyState)
graph.add_node("search", search_node)
graph.set_entry_point("search")
graph.add_edge("search", END)

app = graph.compile()
result = app.invoke({"query": "send reminder to signer"})

Повна нода з LLM генерацією запитів

from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, END
from qdrant_hybrid_search import (
    create_search_node,
    SearchNodeConfig,
    SearchConfig,
)

class AgentState(TypedDict):
    user_command: str
    context: dict
    search_queries: list
    search_results: list

node_config = SearchNodeConfig(
    search_config=SearchConfig(
        collection_name="api_endpoints",
        embedding_api_key="...",
        top_k=15,
    ),
    generate_queries=True,      # LLM генерує пошукові запити
    max_queries=3,              # Макс. кількість запитів
    state_input_key="user_command",
    state_output_key="search_results",
)

search_node = create_search_node(node_config)
llm = ChatOpenAI(model="gpt-4o-mini")

graph = StateGraph(AgentState)
graph.add_node("search", lambda state: search_node(state, llm))
graph.set_entry_point("search")
graph.add_edge("search", END)

app = graph.compile()
result = app.invoke({
    "user_command": "відправ нагадування підписанту документа",
    "context": {"tenant_id": "123"},
})

print(f"Згенеровані запити: {result['search_queries']}")
print(f"Знайдено результатів: {len(result['search_results'])}")

Конфігурація

SearchConfig

Параметр Тип Default Опис
qdrant_url str http://localhost:6333 URL Qdrant сервера
collection_name str search_index Назва колекції
embedding_api_key str - API ключ для embeddings
embedding_base_url str https://openrouter.ai/api/v1 Base URL API
embedding_model str openai/text-embedding-3-small Модель для embeddings
embedding_dims int 1536 Розмірність векторів
top_k int 10 Кількість результатів
min_score float 0.1 Мін. поріг релевантності
use_reranking bool True Використовувати LLM reranking
rerank_model str anthropic/claude-3-haiku Модель для reranking
matryoshka_dims list[int] [64, 128, 256, 512] Розміри matryoshka
rrf_k int 60 K параметр для RRF fusion

Всі параметри можна встановити через env змінні з префіксом SEARCH_:

SEARCH_QDRANT_URL=http://localhost:6333
SEARCH_COLLECTION_NAME=my_index
SEARCH_EMBEDDING_API_KEY=sk-...

IndexConfig

Параметр Тип Default Опис
text_fields list[str] ["content"] Поля для пошуку (dense + BM25)
id_field str id Поле з ID документа
payload_indexes dict[str, str] {} Поля для фільтрації
store_text bool True Зберігати об'єднаний текст
batch_size int 50 Розмір батча при індексації

Типи для payload_indexes: keyword, integer, float, bool, text

Кастомні синоніми

from qdrant_hybrid_search import SynonymExpander, QdrantHybridSearch

# Власні синоніми для вашого домену
synonyms = {
    "контракт": ["договір", "угода", "документ"],
    "підписати": ["завізувати", "затвердити", "виконати"],
    "надіслати": ["відправити", "переслати"],
}

expander = SynonymExpander(synonyms)

search = QdrantHybridSearch(
    config=config,
    synonym_expander=expander,
)

CLI

# Створити колекцію та проіндексувати
python -m qdrant_hybrid_search.cli index \
    --collection api_endpoints \
    --create \
    --file api-endpoints.jsonl \
    --id-field operation_id \
    --text-fields summary,path,description,tags \
    --payload-indexes resource_type:keyword,effect_type:keyword

 qdrant_hybrid_search.cli index \
    --collection api_endpoints \
    --create \
    --file api-endpoints.jsonl \
    --id-field operation_id \
    --text-fields summary,path,description,tags \
    --payload-indexes resource_type:keyword,effect_type:keyword

# Інтерактивний пошук
python -m qdrant_hybrid_search.cli search --collection api_endpoints

# Пошук з запитом
python -m qdrant_hybrid_search.cli search \
    --collection api_endpoints \
    --query "send document" \
    --top-k 5

# Інформація про колекцію
python -m qdrant_hybrid_search.cli info --collection api_endpoints

Приклад: API Endpoints

# index_api.py
from qdrant_hybrid_search import SearchConfig, IndexConfig, QdrantIndexer

search_config = SearchConfig(
    collection_name="signnow_api",
    embedding_api_key=os.environ["OPENROUTER_API_KEY"],
)

index_config = IndexConfig(
    id_field="operation_id",
    text_fields=["summary", "path", "description", "method", "tags"],
    payload_indexes={
        "resource_type": "keyword",
        "effect_type": "keyword",
        "method": "keyword",
    },
)

indexer = QdrantIndexer(search_config, index_config)
indexer.create_collection(reset=True)
indexer.create_payload_indexes()
indexer.index_from_jsonl("api-endpoints.jsonl")

print(indexer.get_collection_info())
# search_api.py
from qdrant_hybrid_search import SearchConfig, QdrantHybridSearch

config = SearchConfig(
    collection_name="signnow_api",
    embedding_api_key=os.environ["OPENROUTER_API_KEY"],
)

search = QdrantHybridSearch(config)

# Семантичний + keyword пошук
results = search.search("resend signing invitation reminder")

# З фільтрами
results = search.search(
    "get documents",
    filters={"effect_type": "read", "resource_type": "document"},
    top_k=5,
)

Як це працює

Гібридний пошук

  1. Dense Search: Семантичний пошук через embedding вектори
  2. Sparse Search (BM25): Keyword пошук через sparse vectors
  3. Matryoshka Search: Прогресивний пошук - спочатку швидкий з малими векторами, потім точніший
  4. RRF Fusion: Об'єднання результатів через Reciprocal Rank Fusion
  5. LLM Reranking: Фінальний перерейтинг через LLM (опційно)

BM25 та text_fields

Поля вказані в text_fields об'єднуються в один текст, який використовується для:

Sparse vector будується через хешування слів, що дозволяє точний keyword matching паралельно з семантичним пошуком.

Payload Indexes

Індекси на payload полях дозволяють ефективну фільтрацію:

results = search.search("query", filters={"status": "active"})

Структура проекту

qdrant-hybrid-search/
├── src/
│   ├── __init__.py         # Public API
│   ├── config.py           # SearchConfig, IndexConfig
│   ├── models.py           # SearchResult, SearchQuery, IndexDocument
│   ├── embeddings.py       # Embedding client
│   ├── synonyms.py         # Synonym expansion
│   ├── indexer.py          # QdrantIndexer
│   ├── search.py           # QdrantHybridSearch
│   ├── langgraph_node.py   # LangGraph integration
│   └── cli.py              # CLI
├── docker-compose.yml
├── pyproject.toml
└── README.md

Ліцензія

MIT