Commit ed23330
authored
feat(knowledge): opt-in hybrid lexical + vector retrieval for KB search (#6124)
* feat(knowledge): hybrid lexical + vector retrieval for KB search
KB search ranked purely on pgvector cosine distance, which retrieves exact
tokens (error codes, ticket keys, identifiers, rare product names) poorly.
Add a full-text leg over the already-present generated `embedding.content_tsv`
column and its GIN index — no migration, no re-indexing — and fuse it with the
vector leg by reciprocal rank. Both legs run concurrently and share the same
visibility and tag-filter predicates; the lexical leg is best-effort and falls
back to vector-only on failure.
Hybrid is the default for every caller. `searchMode: 'vector'` on the internal
and v1 contracts (and an advanced Retrieval Mode dropdown on the Knowledge
block) restores the previous behavior.
Both search routes now share one `executeKnowledgeSearch` dispatch instead of
duplicating the three-branch retrieval logic.
* change(knowledge): make vector the default search mode, hybrid opt-in
Every existing caller — workflow block, v1 API, copilot, guardrail RAG — keeps
its current ranking. Hybrid retrieval is now requested explicitly via
`searchMode: 'hybrid'`.
Also routes the copilot knowledge tool through the shared
`executeKnowledgeSearch` dispatch so all four callers share one retrieval path,
and documents `searchMode` on the public v1 search endpoint in the OpenAPI spec.
* docs(knowledge): document the hybrid retrieval mode
Regenerates the knowledge integration reference for the new searchMode tool
param, and adds a Retrieval Mode section to the knowledge base workflow guide
explaining when hybrid beats vector-only.
* fix(knowledge): stop rank fusion from starving the lexical leg
Rank n in one leg always ties rank n in the other, so ordering the fused list
by score alone let whichever leg was scored first take every tied slot. At
topK=1 that meant a hybrid search returned exactly the vector-only result and
discarded the exact keyword match the mode exists to recover.
Selection now orders by score and drains each tie group round-robin, taking
from whichever leg has contributed fewest rows so far. The lexical leg is
passed first so it wins a total tie, since a chunk the vector leg ranked below
its distance threshold is the case hybrid was opted into for.
* fix(knowledge): credit a shared hit to every leg that returned it
Attributing a row found by both legs to a single leg left the round-robin
owing the other leg a slot it had already been served. With a shared rank-1
hit and topK 2, that evicted the lexical-only row — the exact match hybrid was
enabled to recover — in favor of the vector-only one.
A shared row satisfied every leg that returned it, so every one of them is now
charged for it. Tie-breaking prefers the candidate whose least-served leg has
been served least, which also removes the arbitrary best-rank attribution.
* fix(knowledge): reject a whitespace-only copilot query explicitly
The shared dispatch treats a whitespace-only query as absent and throws when no
tag filters accompany it, where the previous vector-only call would have
embedded the blank string and searched. Tighten the existing guard so the tool
returns its normal message instead.
* fix(knowledge): fan the keyword leg out per knowledge base
The vector leg caps candidates per base once getQueryStrategy sets useParallel,
but the keyword leg always ran one global query with a single LIMIT. Searching
several bases at once let whichever one ranks strongest lexically consume every
slot, so an exact-token hit in a smaller base never reached fusion — the case
hybrid exists to serve.
The keyword leg now uses the same strategy: per-base queries under the same
parallel limit, re-ranked globally on a selected ts_rank_cd. Both legs draw
candidates the same way, so fusion combines rankings over the same pool.
* perf(knowledge): stop the keyword leg detoasting every match's vector
Selecting the cosine distance in the ranking query made Postgres detoast the
1536-dimension embedding and compute a distance for every full-text match
before the LIMIT applied, so cost tracked how common the query term was rather
than topK. On a 20k-chunk base with a term matching every row that was 61,055
buffer hits against 1,030 for the same query without the projection.
Rank on ids and ts_rank_cd alone, then hydrate only the rows that survive the
limit. Same results, and the worst case drops to ~27ms end to end.1 parent 898a10d commit ed23330
16 files changed
Lines changed: 834 additions & 140 deletions
File tree
- apps
- docs
- content/docs/en
- integrations
- knowledgebase
- sim
- app/api
- knowledge/search
- v1/knowledge/search
- blocks/blocks
- lib
- api/contracts
- knowledge
- v1/knowledge
- copilot/tools/server/knowledge
- tools/knowledge
- packages/testing/src/mocks
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
43 | 43 | | |
44 | 44 | | |
45 | 45 | | |
| 46 | + | |
46 | 47 | | |
47 | 48 | | |
48 | 49 | | |
| |||
Lines changed: 14 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
37 | 37 | | |
38 | 38 | | |
39 | 39 | | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
40 | 53 | | |
41 | 54 | | |
42 | 55 | | |
| |||
95 | 108 | | |
96 | 109 | | |
97 | 110 | | |
| 111 | + | |
98 | 112 | | |
99 | 113 | | |
100 | 114 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
6040 | 6040 | | |
6041 | 6041 | | |
6042 | 6042 | | |
6043 | | - | |
| 6043 | + | |
6044 | 6044 | | |
6045 | 6045 | | |
6046 | 6046 | | |
| |||
6095 | 6095 | | |
6096 | 6096 | | |
6097 | 6097 | | |
| 6098 | + | |
| 6099 | + | |
| 6100 | + | |
| 6101 | + | |
| 6102 | + | |
| 6103 | + | |
6098 | 6104 | | |
6099 | 6105 | | |
6100 | 6106 | | |
6101 | 6107 | | |
6102 | 6108 | | |
6103 | 6109 | | |
6104 | 6110 | | |
6105 | | - | |
| 6111 | + | |
| 6112 | + | |
6106 | 6113 | | |
6107 | 6114 | | |
6108 | 6115 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
24 | | - | |
25 | | - | |
26 | | - | |
27 | | - | |
| 24 | + | |
28 | 25 | | |
29 | 26 | | |
30 | 27 | | |
31 | 28 | | |
32 | | - | |
33 | | - | |
34 | | - | |
35 | | - | |
| 29 | + | |
36 | 30 | | |
37 | 31 | | |
38 | 32 | | |
| |||
69 | 63 | | |
70 | 64 | | |
71 | 65 | | |
72 | | - | |
73 | | - | |
74 | | - | |
75 | | - | |
| 66 | + | |
76 | 67 | | |
77 | 68 | | |
78 | 69 | | |
| |||
118 | 109 | | |
119 | 110 | | |
120 | 111 | | |
121 | | - | |
122 | | - | |
123 | | - | |
124 | | - | |
125 | | - | |
126 | | - | |
127 | | - | |
128 | | - | |
129 | | - | |
| 112 | + | |
130 | 113 | | |
131 | 114 | | |
132 | 115 | | |
| |||
192 | 175 | | |
193 | 176 | | |
194 | 177 | | |
195 | | - | |
| 178 | + | |
196 | 179 | | |
197 | 180 | | |
198 | 181 | | |
| |||
212 | 195 | | |
213 | 196 | | |
214 | 197 | | |
215 | | - | |
| 198 | + | |
216 | 199 | | |
217 | 200 | | |
| 201 | + | |
| 202 | + | |
218 | 203 | | |
219 | | - | |
| 204 | + | |
220 | 205 | | |
221 | 206 | | |
222 | 207 | | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
223 | 242 | | |
224 | 243 | | |
225 | 244 | | |
| |||
239 | 258 | | |
240 | 259 | | |
241 | 260 | | |
242 | | - | |
| 261 | + | |
243 | 262 | | |
244 | 263 | | |
245 | 264 | | |
| |||
256 | 275 | | |
257 | 276 | | |
258 | 277 | | |
259 | | - | |
| 278 | + | |
260 | 279 | | |
261 | 280 | | |
| 281 | + | |
| 282 | + | |
262 | 283 | | |
263 | | - | |
| 284 | + | |
264 | 285 | | |
265 | 286 | | |
266 | 287 | | |
| |||
284 | 305 | | |
285 | 306 | | |
286 | 307 | | |
287 | | - | |
| 308 | + | |
288 | 309 | | |
289 | 310 | | |
290 | 311 | | |
| |||
348 | 369 | | |
349 | 370 | | |
350 | 371 | | |
351 | | - | |
| 372 | + | |
352 | 373 | | |
353 | 374 | | |
354 | 375 | | |
| |||
532 | 553 | | |
533 | 554 | | |
534 | 555 | | |
535 | | - | |
| 556 | + | |
536 | 557 | | |
537 | 558 | | |
538 | 559 | | |
| |||
750 | 771 | | |
751 | 772 | | |
752 | 773 | | |
753 | | - | |
| 774 | + | |
754 | 775 | | |
755 | 776 | | |
756 | 777 | | |
| |||
763 | 784 | | |
764 | 785 | | |
765 | 786 | | |
766 | | - | |
| 787 | + | |
767 | 788 | | |
768 | 789 | | |
| 790 | + | |
769 | 791 | | |
770 | 792 | | |
771 | 793 | | |
| |||
796 | 818 | | |
797 | 819 | | |
798 | 820 | | |
799 | | - | |
| 821 | + | |
800 | 822 | | |
801 | 823 | | |
802 | 824 | | |
| |||
816 | 838 | | |
817 | 839 | | |
818 | 840 | | |
819 | | - | |
| 841 | + | |
820 | 842 | | |
821 | 843 | | |
| 844 | + | |
| 845 | + | |
| 846 | + | |
822 | 847 | | |
823 | 848 | | |
824 | 849 | | |
825 | | - | |
826 | | - | |
827 | 850 | | |
828 | 851 | | |
829 | 852 | | |
| |||
987 | 1010 | | |
988 | 1011 | | |
989 | 1012 | | |
990 | | - | |
| 1013 | + | |
991 | 1014 | | |
992 | 1015 | | |
993 | 1016 | | |
| |||
1016 | 1039 | | |
1017 | 1040 | | |
1018 | 1041 | | |
1019 | | - | |
| 1042 | + | |
1020 | 1043 | | |
1021 | 1044 | | |
1022 | 1045 | | |
| |||
1034 | 1057 | | |
1035 | 1058 | | |
1036 | 1059 | | |
1037 | | - | |
1038 | | - | |
1039 | | - | |
1040 | | - | |
1041 | | - | |
1042 | | - | |
1043 | | - | |
1044 | 1060 | | |
1045 | 1061 | | |
1046 | 1062 | | |
| |||
1092 | 1108 | | |
1093 | 1109 | | |
1094 | 1110 | | |
1095 | | - | |
| 1111 | + | |
1096 | 1112 | | |
1097 | 1113 | | |
1098 | 1114 | | |
| |||
1110 | 1126 | | |
1111 | 1127 | | |
1112 | 1128 | | |
1113 | | - | |
1114 | | - | |
1115 | | - | |
1116 | | - | |
1117 | | - | |
1118 | | - | |
1119 | | - | |
1120 | 1129 | | |
1121 | 1130 | | |
1122 | 1131 | | |
| |||
1164 | 1173 | | |
1165 | 1174 | | |
1166 | 1175 | | |
1167 | | - | |
| 1176 | + | |
1168 | 1177 | | |
1169 | 1178 | | |
1170 | 1179 | | |
| |||
1182 | 1191 | | |
1183 | 1192 | | |
1184 | 1193 | | |
1185 | | - | |
1186 | | - | |
1187 | | - | |
1188 | | - | |
1189 | | - | |
1190 | | - | |
1191 | | - | |
1192 | 1194 | | |
1193 | 1195 | | |
1194 | 1196 | | |
| |||
0 commit comments