-
-
Notifications
You must be signed in to change notification settings - Fork 361
fix(search): wire search_engines parameter through to engine dispatch #414
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?
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 |
|---|---|---|
|
|
@@ -211,6 +211,10 @@ export interface OrchestratorInput { | |
| * result whose title+snippet does not contain the unquoted query as a | ||
| * case-insensitive substring is dropped post-rerank. */ | ||
| exactMatch?: boolean; | ||
| /** Caller-supplied engine allowlist. When non-empty, only engines whose | ||
| * name matches an entry (case-insensitive) are dispatched. Wired from | ||
| * SearchInput.search_engines via the MCP schema and CLI --search-engines. */ | ||
| engineFilter?: string[]; | ||
| } | ||
|
|
||
| export interface OrchestratorOutput { | ||
|
|
@@ -303,6 +307,12 @@ interface RunV1SearchOptions { | |
| _isFallback?: boolean; | ||
| } | ||
|
|
||
| function applyEngineAllowlist(entries: EngineEntry[], allowlist: string[]): EngineEntry[] { | ||
| const lowered = allowlist.map((n) => n.toLowerCase()); | ||
| const filtered = entries.filter((e) => lowered.includes(e.engine.name.toLowerCase())); | ||
| return filtered.length > 0 ? filtered : entries; | ||
| } | ||
|
Comment on lines
+310
to
+314
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. 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift Compute the no-match fallback at roster scope.
When Determine whether the allowlist matches 🤖 Prompt for AI Agents |
||
|
|
||
| export async function runV1Search( | ||
| input: OrchestratorInput, | ||
| opts: RunV1SearchOptions = {}, | ||
|
|
@@ -365,9 +375,16 @@ export async function runV1Search( | |
| // Probe-only engines are held back from the primary wave: they are a | ||
| // per-call latency/failure tax on the happy path but still an independent | ||
| // signal the degraded-recovery wave can pull in when the pool collapses. | ||
| const entries = allEntries.filter((e) => e.probeOnly !== true); | ||
| let entries = allEntries.filter((e) => e.probeOnly !== true); | ||
| const probeEntries = allEntries.filter((e) => e.probeOnly === true); | ||
|
|
||
| // Apply caller-supplied engine allowlist (SearchInput.search_engines). | ||
| // Case-insensitive match against engine name. Unknown names are silently | ||
| // ignored — if no entries match, fall back to the full roster. | ||
| if (input.engineFilter && input.engineFilter.length > 0) { | ||
| entries = applyEngineAllowlist(entries, input.engineFilter); | ||
| } | ||
|
|
||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| const options: SearchEngineOptions = { | ||
| maxResults: input.maxResults ?? DEFAULT_MAX_RESULTS, | ||
| timeoutMs: input.timeoutMs ?? DEFAULT_TIMEOUT_MS, | ||
|
|
@@ -623,10 +640,13 @@ export async function runV1Search( | |
| const skippedPrimary = outcomes | ||
| .filter((o) => o.skipped) | ||
| .map((o) => o.engine); | ||
| const recoveryEntries = [ | ||
| let recoveryEntries = [ | ||
| ...probeEntries, | ||
| ...entries.filter((e) => skippedPrimary.includes(e.engine.name)), | ||
| ]; | ||
| if (input.engineFilter && input.engineFilter.length > 0) { | ||
| recoveryEntries = applyEngineAllowlist(recoveryEntries, input.engineFilter); | ||
| } | ||
| if ( | ||
| outcomes.length > 0 && | ||
| primaryHealthy < poolHealthFloor(outcomes.length) && | ||
|
|
@@ -684,7 +704,10 @@ export async function runV1Search( | |
| vertical !== 'images' && | ||
| !opts._isFallback | ||
| ) { | ||
| const generalEntries = getGeneralEngines(); | ||
| let generalEntries = getGeneralEngines(); | ||
| if (input.engineFilter && input.engineFilter.length > 0) { | ||
| generalEntries = applyEngineAllowlist(generalEntries, input.engineFilter); | ||
| } | ||
| if (generalEntries.length > 0) { | ||
| log.info('vertical starved below floor, backfilling from general', { | ||
| from: vertical, | ||
|
|
||
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.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Include
search_enginesinhasAnyFilter.When
search_enginesis the only filter,hasAnyFilterreturns false because it omits this field.buildSearchCacheKeythen returns the bare query at Line 391, so the fingerprint at Lines 404-406 is never used.A cached unfiltered response can therefore satisfy a filtered request and return results and
engines_usedfrom unselected engines. Add(filters.search_engines?.length ?? 0) > 0tohasAnyFilter.🤖 Prompt for AI Agents