Skip to content

Commit 498a659

Browse files
Dan JonesDan Jones
authored andcommitted
Update to new search/next design
1 parent 62dd66d commit 498a659

File tree

5 files changed

+42
-55
lines changed

5 files changed

+42
-55
lines changed

src/query/agent.test.ts

Lines changed: 16 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ import { ApiQueryAgentResponse } from "./response/api-response.js";
44
import {
55
QueryAgentResponse,
66
ComparisonOperator,
7-
SearchModeResponse,
87
} from "./response/response.js";
9-
import { QueryAgentSearcher } from "./search.js";
108
import { ApiSearchModeResponse } from "./response/api-response.js";
119
import { QueryAgentError } from "./response/error.js";
1210

@@ -101,23 +99,6 @@ it("runs the query agent", async () => {
10199
});
102100
});
103101

104-
it("prepareSearch returns a QueryAgentSearcher", async () => {
105-
const mockClient = {
106-
getConnectionDetails: jest.fn().mockResolvedValue({
107-
host: "test-cluster",
108-
bearerToken: "test-token",
109-
headers: { "X-Provider": "test-key" },
110-
}),
111-
} as unknown as WeaviateClient;
112-
113-
const agent = new QueryAgent(mockClient, {
114-
systemPrompt: "test system prompt",
115-
});
116-
117-
const searcher = agent.configureSearch("test query");
118-
expect(searcher).toBeInstanceOf(QueryAgentSearcher);
119-
});
120-
121102
it("search-only mode success: caches searches and sends on subsequent request", async () => {
122103
const mockClient = {
123104
getConnectionDetails: jest.fn().mockResolvedValue({
@@ -195,14 +176,10 @@ it("search-only mode success: caches searches and sends on subsequent request",
195176
} as Response);
196177
}) as jest.Mock;
197178

198-
const agent = new QueryAgent(mockClient);
199-
const searcher = agent.configureSearch("test query", {
200-
collections: ["test_collection"],
201-
});
202-
203-
const first = await searcher.run({ limit: 2, offset: 0 });
179+
const agent = new QueryAgent(mockClient)
204180

205-
expect(first).toEqual<SearchModeResponse<undefined>>({
181+
const first = await agent.search("test query", { limit: 2, collections: ["test_collection"] });
182+
expect(first).toMatchObject({
206183
originalQuery: apiSuccess.original_query,
207184
searches: [
208185
{
@@ -231,14 +208,24 @@ it("search-only mode success: caches searches and sends on subsequent request",
231208
totalTime: 1.5,
232209
searchResults: apiSuccess.search_results,
233210
});
211+
expect(typeof first.next).toBe("function");
234212

235213
// First request should have searches: null (generation request)
236214
expect(capturedBodies[0].searches).toBeNull();
237-
const second = await searcher.run({ limit: 2, offset: 1 });
215+
216+
// Second request uses the next method on the first response
217+
const second = await first.next({ limit: 2, offset: 1 });
238218
// Second request should include the original searches (execution request)
239219
expect(capturedBodies[1].searches).toEqual(apiSuccess.searches);
240220
// Response mapping should be the same (because response is mocked)
241-
expect(second).toEqual(first);
221+
expect(second).toMatchObject({
222+
originalQuery: apiSuccess.original_query,
223+
searches: first.searches,
224+
usage: first.usage,
225+
totalTime: first.totalTime,
226+
searchResults: first.searchResults,
227+
});
228+
expect(typeof second.next).toBe("function");
242229
});
243230

244231
it("search-only mode failure propagates QueryAgentError", async () => {
@@ -266,12 +253,8 @@ it("search-only mode failure propagates QueryAgentError", async () => {
266253
) as jest.Mock;
267254

268255
const agent = new QueryAgent(mockClient);
269-
const searcher = agent.configureSearch("test query", {
270-
collections: ["test_collection"],
271-
});
272-
273256
try {
274-
await searcher.run({ limit: 2, offset: 0 });
257+
await agent.search("test query", { limit: 2, collections: ["test_collection"] });
275258
} catch (err) {
276259
expect(err).toBeInstanceOf(QueryAgentError);
277260
expect(err).toMatchObject({

src/query/agent.ts

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { fetchServerSentEvents } from "./response/server-sent-events.js";
1515
import { mapCollections, QueryAgentCollectionConfig } from "./collection.js";
1616
import { handleError } from "./response/error.js";
1717
import { QueryAgentSearcher } from "./search.js";
18+
import { SearchModeResponse } from "./response/response.js";
1819

1920
/**
2021
* An agent for executing agentic queries against Weaviate.
@@ -188,29 +189,22 @@ export class QueryAgent {
188189
}
189190

190191
/**
191-
* Configure a QueryAgentSearcher for the search-only mode of the query agent.
192+
* Run the Query Agent search-only mode.
192193
*
193-
* This returns a configured QueryAgentSearcher, but does not send any requests or
194-
* run the agent. To do that, you should call the `run` method on the searcher.
195-
*
196-
* This allows you to paginate through a consistent results set, as calling the
197-
* `run` method on the searcher multiple times will result in the same underlying
198-
* searches being performed each time.
199-
*
200-
* @param query - The natural language query string for the agent.
201-
* @param options - Additional options for configuring the searcher.
202-
* @param options.collections - The collections to query. Will override any collections if passed in the constructor.
203-
* @returns A configured QueryAgentSearcher for the search-only mode of the query agent.
194+
* Sends the initial search request and returns the first page of results.
195+
* The returned response includes a `next` method for pagination which
196+
* reuses the same underlying searches to ensure consistency across pages.
204197
*/
205-
configureSearch<T = undefined>(
198+
async search<T = undefined>(
206199
query: string,
207-
{ collections }: QueryAgentSearchOnlyOptions = {},
208-
): QueryAgentSearcher<T> {
209-
return new QueryAgentSearcher(this.client, query, {
200+
{ limit, collections }: QueryAgentSearchOnlyOptions = {},
201+
): Promise<SearchModeResponse<T>> {
202+
const searcher = new QueryAgentSearcher<T>(this.client, query, {
210203
collections: collections ?? this.collections,
211204
systemPrompt: this.systemPrompt,
212205
agentsHost: this.agentsHost,
213-
});
206+
})
207+
return searcher.run({ limit: limit ?? 20, offset: 0 });
214208
}
215209
}
216210

@@ -246,6 +240,8 @@ export type QueryAgentStreamOptions = {
246240

247241
/** Options for the QueryAgent search-only run. */
248242
export type QueryAgentSearchOnlyOptions = {
243+
/** The maximum number of results to return. */
244+
limit?: number;
249245
/** List of collections to query. Will override any collections if passed in the constructor. */
250246
collections?: (string | QueryAgentCollectionConfig)[];
251247
};

src/query/response/response-mapping.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
StreamedTokens,
1010
ProgressMessage,
1111
DateFilterValue,
12-
SearchModeResponse,
12+
MappedSearchModeResponse,
1313
} from "./response.js";
1414

1515
import {
@@ -305,11 +305,11 @@ export const mapResponseFromSSE = (
305305
export const mapSearchOnlyResponse = <T>(
306306
response: ApiSearchModeResponse<T>,
307307
): {
308-
mappedResponse: SearchModeResponse<T>;
308+
mappedResponse: MappedSearchModeResponse<T>;
309309
apiSearches: ApiSearchResult[] | undefined;
310310
} => {
311311
const apiSearches = response.searches;
312-
const mappedResponse: SearchModeResponse<T> = {
312+
const mappedResponse: MappedSearchModeResponse<T> = {
313313
originalQuery: response.original_query,
314314
searches: apiSearches ? mapInnerSearches(apiSearches) : undefined,
315315
usage: mapUsage(response.usage),

src/query/response/response.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,10 +263,14 @@ export type StreamedTokens = {
263263
delta: string;
264264
};
265265

266-
export type SearchModeResponse<T> = {
266+
export type MappedSearchModeResponse<T> = {
267267
originalQuery: string;
268268
searches?: SearchResult[];
269269
usage: Usage;
270270
totalTime: number;
271271
searchResults: WeaviateReturn<T>;
272272
};
273+
274+
export type SearchModeResponse<T> = MappedSearchModeResponse<T> & {
275+
next: (options?: { limit?: number; offset?: number }) => Promise<SearchModeResponse<T>>;
276+
};

src/query/search.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,11 @@ export class QueryAgentSearcher<T> {
126126
if (mappedResponse.searches) {
127127
this.cachedSearches = apiSearches;
128128
}
129-
return mappedResponse;
129+
return {
130+
...mappedResponse,
131+
next: async ({ limit: nextLimit = 20, offset: nextOffset = 0 } = {}) =>
132+
this.run({ limit: nextLimit, offset: nextOffset }),
133+
};
130134
}
131135
}
132136

0 commit comments

Comments
 (0)