-
-
Notifications
You must be signed in to change notification settings - Fork 85
feat(search): hybrid Fuse + embedding ranking with better relevance #307
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
The preview deployment failed. 🔴 Last updated at: 2025-10-26 23:19:48 CET |
|
build not pass @intlayer/backend:build:ci: src/controllers/search.controller.ts(33,36): error TS2339: Property 'similarity' does not exist on type 'VectorStoreEl'. |
aymericzip
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
code good to me, can merge once the build pass
| const fuseScore = normalizeFuse(fuseItem.score); | ||
| const backendScore = backendMap.get(doc.docKey); | ||
| const combinedScore = backendScore | ||
| ? 0.7 * backendScore + 0.3 * fuseScore |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
avoid magic numbers
| const backendResultUrls = new Set( | ||
| backendResults.map((doc) => doc.docKey) | ||
| const backendDocsWithScore = | ||
| (searchDocData?.data ?? []).map((d: any) => ({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
restore abbreviations to plain variable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
avoid any
| {results.map((result, i) => ( | ||
| <li key={result.url}> | ||
| <SearchResultItem doc={result} onClickLink={onClickLink} /> | ||
| <p className="text-gray-400 text-xs">Rank #{i + 1}</p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
debug only
to remove
| allDocs: DocMetadata[] | ||
| ): DocMetadata[] { | ||
| const normalizeFuse = (score?: number) => 1 - Math.min((score ?? 1) / 0.5, 1); // invert Fuse score | ||
| const normalizeBackend = (score: number) => Math.min(score / 1.0, 1); // already cosine-like |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no need to divide by 1
|
fixed the build errors and code review requests |
aymericzip
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good to me
Hey @dfordp , thanks it works well! It's much more precise to search a doc using fuse however, as showed in the screenshot, when fuse.js do not retrieve result, the backend result are not showed anymore |
|
Hey @aymericzip, good catch Thanks for spotting this edge case 🙌 |
dfordp
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i feel the changes are all as per the requirement
| {results.map((result) => ( | ||
| <li key={result.url}> | ||
| <SearchResultItem doc={result} onClickLink={onClickLink} /> | ||
| {results.map((r) => ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no abreviation
| ...r, | ||
| similarity: | ||
| selection.find( | ||
| (s) => s.fileKey === r.fileKey && s.chunkNumber === r.chunkNumber |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no abreviation
|
|
||
| type BackendDocResult = { fileKey: string; similarityScore: number }; | ||
|
|
||
| function mergeHybridResults( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
prefer arrow function
| const inputRef = useRef<HTMLInputElement>(null); | ||
| const searchQueryParam = useSearchParams().get('search'); | ||
| const [results, setResults] = useState<DocMetadata[]>([]); | ||
| const [currentQuery, setCurrentQuery] = useState<string | null>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
probably not necessary
use search
|
Hey @dfordp I gave another review, Please make sure the flow has been tested and works for next submission |
dfordp
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this fixes your required changes
|
The preview deployment failed. 🔴 Last updated at: 2025-11-04 01:38:52 CET |


🧠 Summary
Closes #282
This PR upgrades the documentation search experience by introducing a hybrid ranking system that combines Fuse.js fuzzy search (local metadata-based) with backend embedding similarity scores (semantic relevance).
The result is a much more accurate and natural search experience, balancing instant responsiveness with deeper semantic understanding.
✨ What’s Changed
Fuse.js configuration tuned
threshold(0.25) andignoreLocation: truefor flexible word orderexcerptfield to provide lightweight contextincludeScorefor hybrid rankingBackend integration upgraded
/searchDocUtilnow returns bothfileKeyandsimilarityScore(cosine similarity)Hybrid ranking system added
0.7 * backend + 0.3 * fuse)Improved user experience
🔍 Why This Matters
🧪 Testing Instructions
Run the website locally (
pnpm devoryarn dev).Open the search modal and try queries such as:
Observe that:
🧩 Affected Areas
apps/website/src/components/DocPage/Search/SearchView.tsxapps/backend/src/controllers/search.controller.ts@utils/AI/askDocQuestion/askDocQuestion(used indirectly)🚀 Future Improvements
0.6 / 0.4)