From f31f81ff6e8a23207c9f8ff31f226aee861d9c89 Mon Sep 17 00:00:00 2001 From: Connor Shorten Date: Wed, 8 Jul 2026 11:29:28 -0400 Subject: [PATCH 1/2] first look --- src/query/agent.test.ts | 106 ++++++++++++++++++++++++++++++++++++++++ src/query/agent.ts | 12 +++++ src/query/search.ts | 2 + 3 files changed, 120 insertions(+) diff --git a/src/query/agent.test.ts b/src/query/agent.test.ts index 88a799b..562d49f 100644 --- a/src/query/agent.test.ts +++ b/src/query/agent.test.ts @@ -640,6 +640,112 @@ it("search-only mode defaults filtering to recall", async () => { expect(capturedBodies[0].filtering).toBeUndefined(); }); +it("search-only mode sends ranking_instructions verbatim and persists through pagination", async () => { + const mockClient = { + getConnectionDetails: jest.fn().mockResolvedValue({ + host: "test-cluster", + bearerToken: "test-token", + headers: { "X-Provider": "test-key" }, + }), + } as unknown as WeaviateClient; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const capturedBodies: any[] = []; + + const apiSuccess: ApiSearchModeResponse = { + searches: [ + { + query: "search query", + collection: "test_collection", + }, + ], + usage: { + model_units: 1, + usage_in_plan: true, + remaining_plan_requests: 2, + }, + total_time: 1.0, + search_results: { objects: [] }, + }; + + global.fetch = jest.fn((url, init?: RequestInit) => { + if (init && init.body) { + capturedBodies.push(JSON.parse(init.body as string)); + } + return Promise.resolve({ + ok: true, + json: () => Promise.resolve(apiSuccess), + } as Response); + }) as jest.Mock; + + const agent = new QueryAgent(mockClient); + + const instructions = "Prioritize the most recent documents."; + const first = await agent.search("test query", { + collections: ["test_collection"], + rankingInstructions: instructions, + }); + + // First (generation) request should include the instructions verbatim + expect(capturedBodies[0].ranking_instructions).toBe(instructions); + + // Paginated (execution) request should also include them + await first.next({ limit: 20, offset: 1 }); + expect(capturedBodies[1].ranking_instructions).toBe(instructions); +}); + +it("search-only mode sends null ranking_instructions when not provided", async () => { + const mockClient = { + getConnectionDetails: jest.fn().mockResolvedValue({ + host: "test-cluster", + bearerToken: "test-token", + headers: { "X-Provider": "test-key" }, + }), + } as unknown as WeaviateClient; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const capturedBodies: any[] = []; + + const apiSuccess: ApiSearchModeResponse = { + searches: [ + { + query: "search query", + collection: "test_collection", + }, + ], + usage: { + model_units: 1, + usage_in_plan: true, + remaining_plan_requests: 2, + }, + total_time: 1.0, + search_results: { objects: [] }, + }; + + global.fetch = jest.fn((url, init?: RequestInit) => { + if (init && init.body) { + capturedBodies.push(JSON.parse(init.body as string)); + } + return Promise.resolve({ + ok: true, + json: () => Promise.resolve(apiSuccess), + } as Response); + }) as jest.Mock; + + const agent = new QueryAgent(mockClient); + + const first = await agent.search("test query", { + collections: ["test_collection"], + }); + + // The server treats null and absent identically; the client must not invent + // a default (e.g. empty string, which is semantically different server-side) + expect(capturedBodies[0].ranking_instructions).toBeNull(); + + await first.next({ limit: 20, offset: 1 }); + expect(capturedBodies[1].ranking_instructions).toBeNull(); +}); + it("search-only mode caches empty searches array for precision mode pagination", async () => { const mockClient = { getConnectionDetails: jest.fn().mockResolvedValue({ diff --git a/src/query/agent.ts b/src/query/agent.ts index b32fd47..d0270ab 100644 --- a/src/query/agent.ts +++ b/src/query/agent.ts @@ -606,6 +606,10 @@ export class QueryAgent { * Defaults to no diversity. * @param options.filtering - The filtering strategy: `"recall"` for broader retrieval or * `"precision"` for more targeted results. Defaults to `"recall"`. + * @param options.rankingInstructions - Optional natural language instructions for the + * instruction-following reranker, guiding which results to prioritize as most relevant. + * Only affects the ordering of results, not which results are retrieved. Limited to + * roughly 500 tokens, enforced server-side. * @returns A {@link SearchModeResponse} for the first page of results. Use * `response.next({ limit, offset })` to paginate. * @@ -624,6 +628,7 @@ export class QueryAgent { collections, diversityWeight, filtering, + rankingInstructions, }: QueryAgentSearchOnlyOptions = {}, ): Promise { const searcher = new QueryAgentSearcher( @@ -634,6 +639,7 @@ export class QueryAgent { this.agentsHost, diversityWeight, filtering, + rankingInstructions, ); return searcher.run({ limit, offset: 0 }); @@ -847,6 +853,12 @@ export type QueryAgentSearchOnlyOptions = { * (narrower, more targeted retrieval). */ filtering?: "recall" | "precision"; + /** + * Optional natural language instructions for the instruction-following reranker, guiding + * which results to prioritize as most relevant. Only affects the ordering of results, not + * which results are retrieved. Limited to roughly 500 tokens, enforced server-side. + */ + rankingInstructions?: string; }; /** Options for {@link QueryAgent.suggestQueries}. */ diff --git a/src/query/search.ts b/src/query/search.ts index 86edb53..8d17a65 100644 --- a/src/query/search.ts +++ b/src/query/search.ts @@ -29,6 +29,7 @@ export class QueryAgentSearcher { private agentsHost: string, private diversityWeight: number | undefined, private filtering: "recall" | "precision" | undefined, + private rankingInstructions: string | undefined, ) {} private buildRequestBody( @@ -43,6 +44,7 @@ export class QueryAgentSearcher { collections: mapCollections(this.collections), limit, offset, + ranking_instructions: this.rankingInstructions ?? null, } as const; if (this.cachedSearches === undefined) { return { From 896c906567db88e38a7b26ce79ce1e5d083edfdc Mon Sep 17 00:00:00 2001 From: Connor Shorten Date: Thu, 23 Jul 2026 08:24:04 -0400 Subject: [PATCH 2/2] remove unnecessary client-side default --- src/query/agent.test.ts | 6 +++--- src/query/search.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/query/agent.test.ts b/src/query/agent.test.ts index 562d49f..b9e6ddf 100644 --- a/src/query/agent.test.ts +++ b/src/query/agent.test.ts @@ -694,7 +694,7 @@ it("search-only mode sends ranking_instructions verbatim and persists through pa expect(capturedBodies[1].ranking_instructions).toBe(instructions); }); -it("search-only mode sends null ranking_instructions when not provided", async () => { +it("search-only mode omits ranking_instructions when not provided", async () => { const mockClient = { getConnectionDetails: jest.fn().mockResolvedValue({ host: "test-cluster", @@ -740,10 +740,10 @@ it("search-only mode sends null ranking_instructions when not provided", async ( // The server treats null and absent identically; the client must not invent // a default (e.g. empty string, which is semantically different server-side) - expect(capturedBodies[0].ranking_instructions).toBeNull(); + expect(capturedBodies[0]).not.toHaveProperty("ranking_instructions"); await first.next({ limit: 20, offset: 1 }); - expect(capturedBodies[1].ranking_instructions).toBeNull(); + expect(capturedBodies[1]).not.toHaveProperty("ranking_instructions"); }); it("search-only mode caches empty searches array for precision mode pagination", async () => { diff --git a/src/query/search.ts b/src/query/search.ts index 8d17a65..9355227 100644 --- a/src/query/search.ts +++ b/src/query/search.ts @@ -44,7 +44,7 @@ export class QueryAgentSearcher { collections: mapCollections(this.collections), limit, offset, - ranking_instructions: this.rankingInstructions ?? null, + ranking_instructions: this.rankingInstructions, } as const; if (this.cachedSearches === undefined) { return {