पाठ 15 / 28
Hybrid Search: Full-Text और Vectors
एक query में keyword प्रासंगिकता और vector similarity मिलाएँ।
एक ORDER BY में दो संकेत
Vector similarity अर्थ समझती है; keyword खोज सटीक शब्दों (नाम, कोड, दुर्लभ शब्द) में माहिर है। Hybrid search दोनों उपयोग करती है। PostgreSQL में आप full-text index वाला tsvector column (यहाँ generated column) जोड़ सकते हैं, ts_rank से rank कर सकते हैं, और भारित योग से उसी ORDER BY में vector similarity के साथ मिला सकते हैं, या दोनों queries चलाकर application या SQL में Reciprocal Rank Fusion से सूचियाँ मिला सकते हैं। समर्पित engines आम तौर पर sparse vectors (BM25-शैली) या अंतर्निहित fusion के साथ hybrid queries देते हैं। भार अपने labelled सवालों पर recall मापकर चुनें; दोनों systems के scores अलग पैमानों पर हैं, इसीलिए rank-आधारित fusion लोकप्रिय है।
Vector similarity और keyword boost, चलाकर
मैंने यह SQL Docker container में pgvector extension संस्करण 0.8.6 के साथ PostgreSQL 16 पर चलाया। "leave" और "travel" के बीच के query vector के लिए दस्तावेज़ों 1 और 3 के vector scores पास-पास हैं (0.781 और 0.776)। शब्द "hotels" का full-text rank, 5 से भारित, जोड़ने पर travel-and-hotels दस्तावेज़ पहले स्थान पर आ जाता है।
ALTER TABLE docs ADD COLUMN IF NOT EXISTS tsv tsvector GENERATED ALWAYS AS (to_tsvector('english', body)) STORED;
SELECT id, body,
round((1 - (embedding <=> '[0.5,0.5,0]'))::numeric, 3) AS vec_sim,
round(ts_rank(tsv, plainto_tsquery('english', 'hotels'))::numeric, 3) AS keyword_rank
FROM docs
WHERE tenant = 'acme'
ORDER BY (1 - (embedding <=> '[0.5,0.5,0]')) + 5 * ts_rank(tsv, plainto_tsquery('english', 'hotels')) DESC
LIMIT 3;
Output:
id | body | vec_sim | keyword_rank ----+-------------------+---------+-------------- 3 | travel and hotels | 0.776 | 0.061 2 | old leave policy | 0.851 | 0.000 1 | leave policy 2025 | 0.781 | 0.000 (3 rows)
Reciprocal Rank Fusion, चलाकर
मैंने यह सादा-Python (सिर्फ़ standard library) उदाहरण चलाया। दस्तावेज़ a vector सूची में पहला और keyword सूची में तीसरा है; c तीसरा और पहला। RRF a को पहले और c को दूसरे, फिर b, e, d रखता है। यह सिर्फ़ ranks उपयोग करता है, इसलिए असंगत score पैमाने मायने नहीं रखते।
def rrf(*rankings, k=60):
scores = {}
for ranking in rankings:
for rank, doc in enumerate(ranking, start=1):
scores[doc] = scores.get(doc, 0) + 1 / (k + rank)
return [d for d, _ in sorted(scores.items(), key=lambda x: -x[1])]
vector_hits = ["a", "b", "c", "d"]
keyword_hits = ["c", "e", "a"]
print("RRF:", rrf(vector_hits, keyword_hits))
Output:
RRF: ['a', 'c', 'b', 'e', 'd']
Normalise करें या rank से fuse करें
कच्चे keyword और vector scores सीधे जोड़ना नाज़ुक है। उन्हें normalise करें, या RRF जैसा rank-आधारित fusion उपयोग करें।
त्वरित जाँच: Hybrid search के लिए rank-आधारित fusion लोकप्रिय क्यों है?
- Keyword और vector scores अलग पैमानों पर हैं; ranks तुलनीय हैं
- इसे ranking नहीं चाहिए
- यह vectors की ज़रूरत हटाता है
- यह जानबूझकर queries धीमी करता है
Answer
Keyword और vector scores अलग पैमानों पर हैं; ranks तुलनीय हैं — RRF असंगत scores के calibration से बचाता है।