diff --git a/apps/sim/app/api/knowledge/search/route.ts b/apps/sim/app/api/knowledge/search/route.ts index 610507ea53e..66782134b26 100644 --- a/apps/sim/app/api/knowledge/search/route.ts +++ b/apps/sim/app/api/knowledge/search/route.ts @@ -17,7 +17,7 @@ export const POST = defineInternalJsonRoute({ 'Provider limits apply independently to each user grant; requests have bounded fanout and deadlines', }), errorPolicy: internalKnowledgeErrorPolicies.search, - mapInput: ({ body }, { request }) => ({ ...body, signal: request.signal }), + mapInput: ({ body }, { request }) => ({ ...body, rankByRelevance: true, signal: request.signal }), useCase: searchLiveKnowledge, present: (data) => ({ success: true as const, data }), }) diff --git a/apps/sim/lib/sim-search/live/README.md b/apps/sim/lib/sim-search/live/README.md index 2a81b679331..422fc7bc5fa 100644 --- a/apps/sim/lib/sim-search/live/README.md +++ b/apps/sim/lib/sim-search/live/README.md @@ -16,7 +16,7 @@ The Search assistant exposes the dedicated search/read tools. It cannot discover 1. Authorize the acting member against the canonical organization/workspace and resolve that member's current provider grant. 2. Load the integration's current mode and availability. In service mode, independently load and resolve the configured source credential within that organization. -3. Search the member's provider API or personal Coda MCP. Push resource restrictions into the provider query where supported to improve recall. Native queries may narrow these restrictions, but cannot override the independent checks. One call may send up to four native queries per account (`MAX_NATIVE_QUERIES_PER_ACCOUNT`), such as alternative phrasings (Slack keyword search does not honor OR) or several GitHub/GitLab kinds. The account opens one session whose request budget is one search's budget per query, shared by its queries, and each query reports its own status and cursor under its `queryIndex`. Results from every query and account merge by reciprocal rank fusion, so a document several queries return ranks above one found once. Fusion scores only compare ranks within one account, so a plain query orders the merged results by the query terms each result's title and preview hold, then the terms its title holds, then how much of its title the terms make up; fusion breaks the remaining ties. Date-sorted, queryless and native-query searches keep fusion order. +3. Search the member's provider API or personal Coda MCP. Push resource restrictions into the provider query where supported to improve recall. Native queries may narrow these restrictions, but cannot override the independent checks. One call may send up to four native queries per account (`MAX_NATIVE_QUERIES_PER_ACCOUNT`), such as alternative phrasings (Slack keyword search does not honor OR) or several GitHub/GitLab kinds. The account opens one session whose request budget is one search's budget per query, shared by its queries, and each query reports its own status and cursor under its `queryIndex`. Results from every query and account merge by reciprocal rank fusion, so a document several queries return ranks above one found once. Fusion scores only compare ranks within one account, so the Search page's result list orders a plain query's merged results by the query terms each result's title and preview hold, then the terms its title holds, then how much of its title the terms make up; fusion breaks the remaining ties. MCP and assistant searches keep fusion order, which interleaves sources: an end-to-end client benchmark found no gain from relevance order there, and the client searched less. Date-sorted, queryless and native-query searches keep fusion order everywhere. 4. In service mode, use the source credential to verify each candidate against the source's current permissions and resource settings. Content still comes from the member's connection. Drop candidates that cannot be verified before projecting titles, snippets, or citations to the assistant. 5. Bind document references to the member, owner scope, provider, and account. On a read, resolve access again, verify the source before reading, and check current source settings again before returning content. GitLab additionally validates ACLs against fresh content metadata. diff --git a/apps/sim/lib/sim-search/live/application.test.ts b/apps/sim/lib/sim-search/live/application.test.ts index c8e17b85a8d..2350562536b 100644 --- a/apps/sim/lib/sim-search/live/application.test.ts +++ b/apps/sim/lib/sim-search/live/application.test.ts @@ -557,7 +557,7 @@ describe('authorized live retrieval', () => { expect(statusOf('mail')?.nextCursor).toBeUndefined() expect(statusOf('account')).toMatchObject({ nextCursor: 'next' }) }) - it('ranks results across accounts by the query terms they hold, not by account order', async () => { + it('ranks across accounts by query terms only for a reader; agents keep fusion order', async () => { const gmail = { ...account, id: 'mail', provider: 'gmail', displayName: 'Mail' } mocks.accounts.mockResolvedValue([account, gmail]) const result = (id: string, title: string, content: string) => ({ @@ -576,15 +576,15 @@ describe('authorized live retrieval', () => { ] : [result('sheet', 'Question bank', 'Questions')], })) - const { results } = await searchLiveKnowledge.execute({ - principal, - input: { ...input, query: 'merge train' }, - }) - expect(results.map((row) => decodeLiveReference(row.documentId).id)).toEqual([ - 'named', - 'notes', - 'sheet', - ]) + const order = async (rankByRelevance?: boolean) => + ( + await searchLiveKnowledge.execute({ + principal, + input: { ...input, query: 'merge train', rankByRelevance }, + }) + ).results.map((row) => decodeLiveReference(row.documentId).id) + expect(await order(true)).toEqual(['named', 'notes', 'sheet']) + expect(await order()).toEqual(['sheet', 'notes', 'named']) }) it('applies date filters before spending provider verification', async () => { const verify = vi.fn(async () => true) diff --git a/apps/sim/lib/sim-search/live/application.ts b/apps/sim/lib/sim-search/live/application.ts index 98d58f20334..40c92c89a3f 100644 --- a/apps/sim/lib/sim-search/live/application.ts +++ b/apps/sim/lib/sim-search/live/application.ts @@ -167,6 +167,12 @@ interface LiveSearchOptions { topK: number filters?: WorkspaceSearchFilters nativeQueries?: NativeSearchQuery[] + /** + * Orders a plain query's results across sources by the query terms they hold, for a person + * reading the list. Agent clients keep fusion order, which interleaves sources: an end-to-end + * client benchmark found no gain from relevance order, and the agent searched less. + */ + rankByRelevance?: boolean signal?: AbortSignal resultSecretRegistry?: ResolvedSecretTraceRegistry } @@ -736,7 +742,9 @@ export const searchLiveKnowledge = defineAuthorizedKnowledgeUseCase({ * Native queries carry provider operators and keep fusion order. */ const relevanceOf = - dateSorted || queries || !input.query.trim() ? undefined : queryRelevance(input.query) + !input.rankByRelevance || dateSorted || queries || !input.query.trim() + ? undefined + : queryRelevance(input.query) const ranked = [...fused.values()] .map((entry) => ({ ...entry,