Add hybrid-search module: vector, tsvector and BM25 fused with Reciprocal Rank Fusion in Java and SQL over one PostgreSQL table; quality by query type, held-out k check, plans and latency at 50k rows
Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_0146opDKBYtADXvkc39zdiyw
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
# Reciprocal Rank Fusion on two short rankings
|
||||
|
||||
vector ranking : [A, B, C, D]
|
||||
keyword ranking: [C, A, E, B]
|
||||
|
||||
--- k = 1 ---
|
||||
A score 0.83333 (vector rank 1, keyword rank 2)
|
||||
C score 0.75000 (vector rank 3, keyword rank 1)
|
||||
B score 0.53333 (vector rank 2, keyword rank 4)
|
||||
E score 0.25000 (vector rank 0, keyword rank 3)
|
||||
D score 0.20000 (vector rank 4, keyword rank 0)
|
||||
|
||||
--- k = 60 ---
|
||||
A score 0.03252 (vector rank 1, keyword rank 2)
|
||||
C score 0.03227 (vector rank 3, keyword rank 1)
|
||||
B score 0.03175 (vector rank 2, keyword rank 4)
|
||||
E score 0.01587 (vector rank 0, keyword rank 3)
|
||||
D score 0.01563 (vector rank 4, keyword rank 0)
|
||||
|
||||
--- k = 1000 ---
|
||||
A score 0.00200 (vector rank 1, keyword rank 2)
|
||||
C score 0.00200 (vector rank 3, keyword rank 1)
|
||||
B score 0.00199 (vector rank 2, keyword rank 4)
|
||||
E score 0.00100 (vector rank 0, keyword rank 3)
|
||||
D score 0.00100 (vector rank 4, keyword rank 0)
|
||||
|
||||
--- the arithmetic for A at k = 60 ---
|
||||
rank 1 in the vector list: 1/(60+1) = 0.01639
|
||||
rank 2 in the keyword list: 1/(60+2) = 0.01613
|
||||
sum = 0.03252
|
||||
|
||||
--- an item in one list only ---
|
||||
E is rank 3 in the keyword list and absent from the vector list: 1/(60+3) = 0.01587
|
||||
D is rank 4 in the vector list and absent from the keyword list: 1/(60+4) = 0.01563
|
||||
@@ -0,0 +1,73 @@
|
||||
# Six rankers on 60 labelled queries over 94 passages (all-MiniLM-L6-v2, PostgreSQL 16, pgvector 0.8.7)
|
||||
|
||||
fusion: RRF with k=60 over the top 20 of each list. hit@1 and hit@5: the answer passage is first / in the first five. MRR@10: mean of 1/rank.
|
||||
|
||||
--- exact error code (12 queries) ---
|
||||
ranker hit@1 hit@5 MRR@10
|
||||
vector (pgvector cosine) 2/12 8/12 0.374
|
||||
keyword (tsvector, ts_rank_cd) 9/12 11/12 0.819
|
||||
keyword (BM25, in Java) 9/12 12/12 0.875
|
||||
hybrid: vector + tsvector, RRF 9/12 11/12 0.806
|
||||
hybrid: vector + BM25, RRF 10/12 11/12 0.883
|
||||
hybrid in one SQL statement 9/12 11/12 0.806
|
||||
|
||||
--- exact SKU or setting (12 queries) ---
|
||||
ranker hit@1 hit@5 MRR@10
|
||||
vector (pgvector cosine) 12/12 12/12 1.000
|
||||
keyword (tsvector, ts_rank_cd) 12/12 12/12 1.000
|
||||
keyword (BM25, in Java) 11/12 12/12 0.958
|
||||
hybrid: vector + tsvector, RRF 12/12 12/12 1.000
|
||||
hybrid: vector + BM25, RRF 12/12 12/12 1.000
|
||||
hybrid in one SQL statement 12/12 12/12 1.000
|
||||
|
||||
--- paraphrase (24 queries) ---
|
||||
ranker hit@1 hit@5 MRR@10
|
||||
vector (pgvector cosine) 15/24 23/24 0.767
|
||||
keyword (tsvector, ts_rank_cd) 5/24 11/24 0.296
|
||||
keyword (BM25, in Java) 4/24 13/24 0.290
|
||||
hybrid: vector + tsvector, RRF 13/24 20/24 0.663
|
||||
hybrid: vector + BM25, RRF 11/24 21/24 0.626
|
||||
hybrid in one SQL statement 13/24 20/24 0.663
|
||||
|
||||
--- code plus words (12 queries) ---
|
||||
ranker hit@1 hit@5 MRR@10
|
||||
vector (pgvector cosine) 12/12 12/12 1.000
|
||||
keyword (tsvector, ts_rank_cd) 10/12 12/12 0.896
|
||||
keyword (BM25, in Java) 11/12 12/12 0.958
|
||||
hybrid: vector + tsvector, RRF 12/12 12/12 1.000
|
||||
hybrid: vector + BM25, RRF 12/12 12/12 1.000
|
||||
hybrid in one SQL statement 12/12 12/12 1.000
|
||||
|
||||
--- all 60 queries ---
|
||||
ranker hit@1 hit@5 MRR@10
|
||||
vector (pgvector cosine) 41/60 55/60 0.782
|
||||
keyword (tsvector, ts_rank_cd) 36/60 46/60 0.662
|
||||
keyword (BM25, in Java) 35/60 49/60 0.674
|
||||
hybrid: vector + tsvector, RRF 46/60 55/60 0.826
|
||||
hybrid: vector + BM25, RRF 45/60 56/60 0.827
|
||||
hybrid in one SQL statement 46/60 55/60 0.826
|
||||
|
||||
--- the SQL version against the Java version ---
|
||||
queries whose top 5 are identical, in the same order, from both implementations: 60 of 60
|
||||
|
||||
--- one query of each kind: the top 3 from each ranker ---
|
||||
[exact error code] "What does error E-4106 mean?" -> answer is E-4106
|
||||
vector : E-4108, E-4105, E-4114
|
||||
tsvector : E-4106, E-4109, E-4139
|
||||
BM25 : E-4109, E-4106, E-4140
|
||||
hybrid : E-4108, E-4109, E-4111
|
||||
[exact SKU or setting] "Does OP-TERM-5520 work when the internet is down?" -> answer is OP-TERM-5520
|
||||
vector : OP-TERM-5520, OP-SUP-GOLD, G-30
|
||||
tsvector : OP-TERM-5520, OP-TERM-6100, OP-TERM-5530
|
||||
BM25 : OP-TERM-5520
|
||||
hybrid : OP-TERM-5520, OP-SUP-GOLD, OP-TERM-5530
|
||||
[paraphrase] "What happens when a customer's monthly payment does not go through?" -> answer is G-01
|
||||
vector : G-15, G-04, G-16
|
||||
tsvector : E-4110, E-4111, G-15
|
||||
BM25 : G-04, G-06, orbit.receipts.locale
|
||||
hybrid : G-15, G-16, G-04
|
||||
[code plus words] "E-4106 keeps appearing in our Express webhook handler, is the body being parsed?" -> answer is E-4106
|
||||
vector : E-4106, E-4107, E-4116
|
||||
tsvector : E-4106, E-4102, E-4107
|
||||
BM25 : E-4106, E-4102, E-4107
|
||||
hybrid : E-4106, E-4107, E-4102
|
||||
@@ -0,0 +1,26 @@
|
||||
# RRF parameters on the 60 queries: k, candidates per list, list weights
|
||||
|
||||
|
||||
--- k, with 20 candidates per list ---
|
||||
k hit@1 hit@5 MRR@10
|
||||
1 46/60 58/60 0.851
|
||||
10 45/60 55/60 0.826
|
||||
60 46/60 55/60 0.826
|
||||
200 46/60 55/60 0.825
|
||||
1000 46/60 55/60 0.825
|
||||
|
||||
--- candidates taken from each list, with k = 60 ---
|
||||
cands hit@1 hit@5 MRR@10
|
||||
3 41/60 57/60 0.802
|
||||
5 43/60 59/60 0.828
|
||||
10 45/60 57/60 0.823
|
||||
20 46/60 55/60 0.826
|
||||
50 46/60 55/60 0.829
|
||||
|
||||
--- weights (vector : keyword), k = 60, 20 candidates ---
|
||||
weights hit@1 hit@5 MRR@10
|
||||
1.0:0.0 41/60 55/60 0.782
|
||||
1.0:0.5 45/60 56/60 0.822
|
||||
1.0:1.0 46/60 55/60 0.826
|
||||
0.5:1.0 42/60 54/60 0.781
|
||||
0.0:1.0 37/60 50/60 0.692
|
||||
@@ -0,0 +1,15 @@
|
||||
# Choosing k on half of the queries and scoring it on the other half
|
||||
|
||||
candidates per list: 20. The 60 queries are split by position into two folds of 30; k comes from {1, 10, 60, 200}.
|
||||
|
||||
--- fold 1: train on 30, test on 30 ---
|
||||
k chosen on the training half (best MRR@10 0.872): 1
|
||||
held-out half, k=1 : 22/30 29/30 0.831
|
||||
held-out half, k=60 : 24/30 28/30 0.859
|
||||
held-out half, vector only: 21/30 30/30 0.831
|
||||
|
||||
--- fold 2: train on 30, test on 30 ---
|
||||
k chosen on the training half (best MRR@10 0.859): 60
|
||||
held-out half, k=60 : 22/30 27/30 0.794
|
||||
held-out half, k=60 : 22/30 27/30 0.794
|
||||
held-out half, vector only: 20/30 25/30 0.733
|
||||
@@ -0,0 +1,43 @@
|
||||
# HybridDocumentRetriever inside RetrievalAugmentationAdvisor (top 3 passages per question)
|
||||
|
||||
|
||||
--- what the retriever returns for "Why am I getting E-4116?" ---
|
||||
score 0.03279 vector rank 1 keyword rank 1 Error E-4116 - Charge already captured. A capture was attemp
|
||||
score 0.03125 vector rank 4 keyword rank 4 Error E-4111 - Authentication challenge failed. The customer
|
||||
score 0.02988 vector rank 9 keyword rank 5 Error E-4118 - Payout account not verified. Payouts are bloc
|
||||
|
||||
--- the prompt the model received ---
|
||||
Context information is below.
|
||||
|
||||
---------------------
|
||||
Error E-4116 - Charge already captured. A capture was attempted on a charge that has already been captured in full. Fetch the charge first and check its status instead of capturing blindly.
|
||||
Error E-4111 - Authentication challenge failed. The customer abandoned or failed the 3-D Secure challenge. The payment cannot be completed with this card; offer another payment method.
|
||||
Error E-4118 - Payout account not verified. Payouts are blocked because the bank account has not passed verification. Confirm the two micro-deposits shown in the dashboard to unlock transfers.
|
||||
---------------------
|
||||
|
||||
Given the context information and no prior knowledge, answer the query.
|
||||
|
||||
Follow these rules:
|
||||
|
||||
1. If the answer is not in the context, just say that you don't know.
|
||||
2. Avoid statements like "Based on the context..." or "The provided information...".
|
||||
|
||||
Query: Why am I getting E-4116?
|
||||
|
||||
Answer:
|
||||
|
||||
|
||||
--- does the answer passage reach the prompt? the 12 exact-code questions, top 3 passages ---
|
||||
E-4106 vector-only: false hybrid: false What does error E-4106 mean?
|
||||
E-4116 vector-only: true hybrid: true Why am I getting E-4116?
|
||||
E-4118 vector-only: false hybrid: true We see E-4118 in the logs, what should we do?
|
||||
E-4120 vector-only: true hybrid: true Explain E-4120
|
||||
E-4122 vector-only: false hybrid: true Support told me to look up E-4122
|
||||
E-4126 vector-only: true hybrid: true What is E-4126?
|
||||
E-4129 vector-only: false hybrid: true How do I fix E-4129?
|
||||
E-4131 vector-only: false hybrid: true Our service started returning E-4131 this morning
|
||||
E-4133 vector-only: false hybrid: true Meaning of E-4133
|
||||
E-4135 vector-only: true hybrid: true Is E-4135 our fault?
|
||||
E-4138 vector-only: false hybrid: true What causes E-4138 on the shop floor?
|
||||
E-4140 vector-only: true hybrid: true Customer reports E-4140, is the money safe?
|
||||
answer passage in the top 3: vector-only 5 of 12, hybrid 11 of 12
|
||||
@@ -0,0 +1,45 @@
|
||||
# 94 rows against 50,094 rows: plans, index sizes, latency (PostgreSQL 16, pgvector 0.8.7, 2 vCPU)
|
||||
|
||||
filler rows inserted: 50000 in 7819 ms. HNSW index built in 94632 ms, GIN index in 292 ms.
|
||||
rows in hybrid_scale: 50094
|
||||
heap 98 MB, HNSW index 98 MB, GIN index 2248 kB
|
||||
|
||||
--- plan for the vector query at 50,094 rows (scan nodes only) ---
|
||||
Limit (actual rows=20 loops=1)
|
||||
-> Index Scan using hybrid_scale_embedding_idx on hybrid_scale (actual rows=20 loops=1)
|
||||
|
||||
--- plan for the keyword query at 50,094 rows (scan nodes only) ---
|
||||
Limit (actual rows=20 loops=1)
|
||||
-> Sort (actual rows=20 loops=1)
|
||||
Sort Key: (ts_rank_cd(d.tsv, '''get'' | ''e'' | ''-4116'''::tsquery)) DESC
|
||||
Sort Method: quicksort Memory: 26kB
|
||||
-> Bitmap Heap Scan on hybrid_scale d (actual rows=40 loops=1)
|
||||
-> Bitmap Index Scan on hybrid_scale_tsv_idx (actual rows=40 loops=1)
|
||||
|
||||
--- plan for the same keyword query at 94 rows ---
|
||||
Limit (actual rows=20 loops=1)
|
||||
-> Sort (actual rows=20 loops=1)
|
||||
Sort Key: (ts_rank_cd(d.tsv, '''get'' | ''e'' | ''-4116'''::tsquery)) DESC
|
||||
Sort Method: quicksort Memory: 26kB
|
||||
-> Seq Scan on hybrid_docs d (actual rows=40 loops=1)
|
||||
Rows Removed by Filter: 54
|
||||
|
||||
--- rows the keyword query has to rank, by query kind (50,094 rows) ---
|
||||
exact error code average rows matching the OR query: 1547
|
||||
exact SKU or setting average rows matching the OR query: 1970
|
||||
paraphrase average rows matching the OR query: 3781
|
||||
code plus words average rows matching the OR query: 5220
|
||||
|
||||
--- latency in milliseconds over 60 queries x 3 runs after 2 warm-up passes (query embedding excluded unless stated) ---
|
||||
94 p50 94 p95 50k p50 50k p95
|
||||
embedding the question (MiniLM) 7.4 10.4 (same for both table sizes)
|
||||
vector, top 20 25.3 32.2 28.0 36.4
|
||||
keyword, top 20 27.0 39.3 85.5 177.1
|
||||
hybrid in Java (2 queries + fuse) 51.6 65.3 111.2 208.2
|
||||
hybrid in one SQL statement 31.5 40.1 132.3 226.4
|
||||
|
||||
--- keyword query latency at 50,094 rows by query kind (p50 / p95 ms) ---
|
||||
exact error code 51.1 / 124.0
|
||||
exact SKU or setting 35.7 / 223.0
|
||||
paraphrase 85.9 / 169.6
|
||||
code plus words 121.2 / 214.9
|
||||
Reference in New Issue
Block a user