-
Notifications
You must be signed in to change notification settings - Fork 6
Add deep AI search to rerank all components in selected sources #2430
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: 06-18-build_broader_ai_candidate_pools_for_component_search
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -734,6 +734,26 @@ export const DashboardComponentsV2View = () => { | |
| ); | ||
| })(); | ||
|
|
||
| const deepAiCandidateMatches: LexicalMatch[] = (() => { | ||
| if (trimmedQuery.length === 0) return []; | ||
|
|
||
| const candidates: LexicalMatch[] = []; | ||
| const seenDigests = new Set<string>(); | ||
| const allLexicalMatches = lexicalSearch(filteredIndex, query, { | ||
| limit: filteredIndex.length, | ||
| }); | ||
| for (const match of allLexicalMatches) { | ||
| seenDigests.add(match.digest); | ||
| candidates.push(match); | ||
| } | ||
| for (const entry of sortedIndex) { | ||
| if (seenDigests.has(entry.digest)) continue; | ||
| seenDigests.add(entry.digest); | ||
| candidates.push(indexEntryToLexicalMatch(entry)); | ||
| } | ||
| return candidates; | ||
| })(); | ||
|
|
||
| const { | ||
| mutate: rerank, | ||
| data: rerankData, | ||
|
|
@@ -760,21 +780,29 @@ export const DashboardComponentsV2View = () => { | |
| } | ||
| }; | ||
|
|
||
| const handleSmartSearch = () => { | ||
| const startAiSearch = (matches: LexicalMatch[]) => { | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
[LOW] |
||
| const trimmed = query.trim(); | ||
| if (trimmed.length === 0 || aiCandidateMatches.length === 0) return; | ||
| if (trimmed.length === 0 || matches.length === 0) return; | ||
|
|
||
| const candidates = aiCandidateMatches | ||
| .map((m) => componentReferenceToCandidate(m.reference)) | ||
| const candidates = matches | ||
| .map((m) => componentReferenceToCandidate(m.reference, m.source)) | ||
| .filter((c): c is NonNullable<typeof c> => c !== null); | ||
|
|
||
| if (candidates.length === 0) return; | ||
|
|
||
| setRerankBaseMatches(aiCandidateMatches); | ||
| setRerankBaseMatches(matches); | ||
| setRerankedFor(trimmed); | ||
| rerank({ query: trimmed, candidates }); | ||
| }; | ||
|
|
||
| const handleSmartSearch = () => { | ||
| startAiSearch(aiCandidateMatches); | ||
| }; | ||
|
|
||
| const handleDeepAiSearch = () => { | ||
| startAiSearch(deepAiCandidateMatches); | ||
| }; | ||
|
|
||
| const handleSourceToggle = (sourceKey: string) => { | ||
| setDisabledSourceKeys((current) => | ||
| current.includes(sourceKey) | ||
|
|
@@ -1014,6 +1042,19 @@ export const DashboardComponentsV2View = () => { | |
| > | ||
| {isReranking ? <Spinner size={16} /> : <Icon name="Sparkles" />} | ||
| </Button> | ||
| <Button | ||
| variant="outline" | ||
| onClick={handleDeepAiSearch} | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
[MEDIUM] This "Deep AI search" Button gives no busy/in-progress feedback while |
||
| disabled={ | ||
| isReranking || | ||
| isEmpty || | ||
| deepAiCandidateMatches.length === 0 || | ||
| !isConfigured | ||
| } | ||
| title="Deep AI search — rerank all components in selected sources" | ||
| > | ||
| Deep AI search | ||
| </Button> | ||
| </InlineStack> | ||
| <SourceFilterBar | ||
| options={sourceFilterOptions} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -69,8 +69,10 @@ export interface ComponentSearchV2State { | |
| browseFolders: UIComponentFolder[]; | ||
| isLoading: boolean; | ||
| canRerank: boolean; | ||
| canDeepRerank: boolean; | ||
| isReranking: boolean; | ||
| rerank: () => void; | ||
| deepRerank: () => void; | ||
| } | ||
|
|
||
| export function registeredSource( | ||
|
|
@@ -294,12 +296,13 @@ function appendUniqueMatches( | |
| target: LexicalMatch[], | ||
| seenDigests: Set<string>, | ||
| matches: LexicalMatch[], | ||
| limit: number, | ||
| ) { | ||
| for (const match of matches) { | ||
| if (seenDigests.has(match.digest)) continue; | ||
| seenDigests.add(match.digest); | ||
| target.push(match); | ||
| if (target.length >= AI_CANDIDATE_LIMIT) return; | ||
| if (target.length >= limit) return; | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -344,19 +347,56 @@ export function buildAiCandidateMatches( | |
| limit: AI_LEXICAL_CANDIDATE_LIMIT, | ||
| minLength: 1, | ||
| }), | ||
| AI_CANDIDATE_LIMIT, | ||
| ); | ||
|
|
||
| appendUniqueMatches( | ||
| candidates, | ||
| seenDigests, | ||
| buildSourceDiverseBrowseMatches(index), | ||
| AI_CANDIDATE_LIMIT, | ||
| ); | ||
|
|
||
| const sortedIndex = [...index].sort((a, b) => a.name.localeCompare(b.name)); | ||
| appendUniqueMatches( | ||
| candidates, | ||
| seenDigests, | ||
| sampleEvenly(sortedIndex, AI_CANDIDATE_LIMIT).map(indexEntryToLexicalMatch), | ||
| AI_CANDIDATE_LIMIT, | ||
| ); | ||
|
|
||
| return candidates; | ||
| } | ||
|
|
||
| /** | ||
| * Explicit deep AI search candidate pool. Sends every searchable component, | ||
| * ordered with lexical hits first so truncating providers still see likely | ||
| * matches early. | ||
| */ | ||
| export function buildDeepAiCandidateMatches( | ||
| index: IndexEntry[], | ||
| trimmedQuery: string, | ||
| ): LexicalMatch[] { | ||
| if (trimmedQuery.length === 0) return []; | ||
|
|
||
| const candidates: LexicalMatch[] = []; | ||
| const seenDigests = new Set<string>(); | ||
| appendUniqueMatches( | ||
| candidates, | ||
| seenDigests, | ||
| lexicalSearch(index, trimmedQuery, { | ||
| limit: index.length, | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
[HIGH] Deep search builds an unbounded candidate pool: |
||
| minLength: 1, | ||
| }), | ||
| Number.MAX_SAFE_INTEGER, | ||
| ); | ||
|
|
||
| const sortedIndex = [...index].sort((a, b) => a.name.localeCompare(b.name)); | ||
| appendUniqueMatches( | ||
| candidates, | ||
| seenDigests, | ||
| sortedIndex.map(indexEntryToLexicalMatch), | ||
| Number.MAX_SAFE_INTEGER, | ||
| ); | ||
|
|
||
| return candidates; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,7 @@ import { | |
| } from "@/providers/ComponentLibraryProvider/libraries/storage"; | ||
| import { | ||
| buildAiCandidateMatches, | ||
| buildDeepAiCandidateMatches, | ||
| buildLexicalMatches, | ||
| buildRerankScoreByDigest, | ||
| buildResultFolders, | ||
|
|
@@ -175,6 +176,10 @@ export function useComponentSearchV2State( | |
| const trimmedQuery = query.trim(); | ||
| const lexicalMatches = buildLexicalMatches(index, trimmedQuery); | ||
| const aiCandidateMatches = buildAiCandidateMatches(index, trimmedQuery); | ||
| const deepAiCandidateMatches = buildDeepAiCandidateMatches( | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
[LOW] |
||
| index, | ||
| trimmedQuery, | ||
| ); | ||
|
|
||
| const { | ||
| mutate, | ||
|
|
@@ -202,23 +207,33 @@ export function useComponentSearchV2State( | |
| ? rerankedMatches(rerankData, rerankBaseMatches) | ||
| : lexicalMatches; | ||
|
|
||
| const rerank = () => { | ||
| if (!trimmedQuery || aiCandidateMatches.length === 0 || !isConfigured) { | ||
| return; | ||
| } | ||
| const startRerank = ( | ||
| matches: LexicalMatch[], | ||
| { scoreAllCandidates }: { scoreAllCandidates: boolean }, | ||
| ) => { | ||
| if (!trimmedQuery || matches.length === 0 || !isConfigured) return; | ||
|
|
||
| const candidates = aiCandidateMatches | ||
| .map((match) => componentReferenceToCandidate(match.reference)) | ||
| const candidates = matches | ||
| .map((match) => | ||
| componentReferenceToCandidate(match.reference, match.source), | ||
| ) | ||
| .filter((candidate): candidate is NonNullable<typeof candidate> => | ||
| Boolean(candidate), | ||
| ); | ||
|
|
||
| if (candidates.length === 0) return; | ||
|
|
||
| setRerankBaseMatches(aiCandidateMatches); | ||
| setRerankBaseMatches(matches); | ||
| setRerankedFor(trimmedQuery); | ||
| // Score every candidate so each displayed result shows a relevance %. | ||
| mutate({ query: trimmedQuery, candidates, scoreAllCandidates: true }); | ||
| mutate({ query: trimmedQuery, candidates, scoreAllCandidates }); | ||
| }; | ||
|
|
||
| const rerank = () => { | ||
| startRerank(aiCandidateMatches, { scoreAllCandidates: true }); | ||
| }; | ||
|
|
||
| const deepRerank = () => { | ||
| startRerank(deepAiCandidateMatches, { scoreAllCandidates: false }); | ||
| }; | ||
|
|
||
| const rerankScoreByDigest = buildRerankScoreByDigest( | ||
|
|
@@ -243,7 +258,12 @@ export function useComponentSearchV2State( | |
| isHydrating, | ||
| canRerank: | ||
| trimmedQuery.length > 0 && aiCandidateMatches.length > 0 && isConfigured, | ||
| canDeepRerank: | ||
| trimmedQuery.length > 0 && | ||
| deepAiCandidateMatches.length > 0 && | ||
| isConfigured, | ||
| isReranking, | ||
| rerank, | ||
| deepRerank, | ||
| }; | ||
| } | ||
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.
[HIGH] Same unbounded-deep-pool concern on the Dashboard surface:
lexicalSearch(filteredIndex, ..., { limit: filteredIndex.length })then every remainingsortedIndexentry is appended, and the whole pool is JSON.stringify-ed into a billed LLM rerank with no input cap or confirmation. Bound the deep pool to a finite N or confirm before sending a very large pool; document the worst-case prompt size.