Skip to content
4 changes: 4 additions & 0 deletions src/app/api/search/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ interface ChatRequestBody {
history: Array<[string, string]>;
stream?: boolean;
systemInstructions?: string;
maxResultsPerQuery?: number; // Limits results per SearXNG query action; reduces LLM prompt size for local/small-context models
maxTotalResults?: number; // Limits total results across all engines post-merge; prevents context overflow when multiple engines are active
}

export const POST = async (req: Request) => {
Expand Down Expand Up @@ -60,6 +62,8 @@ export const POST = async (req: Request) => {
mode: body.optimizationMode,
fileIds: [],
systemInstructions: body.systemInstructions || '',
maxResultsPerQuery: body.maxResultsPerQuery,
maxTotalResults: body.maxTotalResults,
},
followUp: body.query,
chatId: crypto.randomUUID(),
Expand Down
6 changes: 4 additions & 2 deletions src/lib/agents/search/researcher/actions/academicSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ const academicSearchAction: ResearchAction<typeof schema> = {
engines: ['arxiv', 'google scholar', 'pubmed'],
});

const resultChunks: Chunk[] = res.results.map((r) => ({
const resultChunks: Chunk[] = res.results
.slice(0, additionalConfig.maxResultsPerQuery ?? res.results.length)
.map((r) => ({
content: r.content || r.title,
metadata: {
title: r.title,
Expand Down Expand Up @@ -121,7 +123,7 @@ const academicSearchAction: ResearchAction<typeof schema> = {

return {
type: 'search_results',
results,
results,
};
},
};
Expand Down
6 changes: 4 additions & 2 deletions src/lib/agents/search/researcher/actions/socialSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ const socialSearchAction: ResearchAction<typeof schema> = {
engines: ['reddit'],
});

const resultChunks: Chunk[] = res.results.map((r) => ({
const resultChunks: Chunk[] = res.results
.slice(0, additionalConfig.maxResultsPerQuery ?? res.results.length)
.map((r) => ({
content: r.content || r.title,
metadata: {
title: r.title,
Expand Down Expand Up @@ -121,7 +123,7 @@ const socialSearchAction: ResearchAction<typeof schema> = {

return {
type: 'search_results',
results,
results,
};
},
};
Expand Down
6 changes: 4 additions & 2 deletions src/lib/agents/search/researcher/actions/webSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,9 @@ const webSearchAction: ResearchAction<typeof actionSchema> = {
const search = async (q: string) => {
const res = await searchSearxng(q);

const resultChunks: Chunk[] = res.results.map((r) => ({
const resultChunks: Chunk[] = res.results
.slice(0, additionalConfig.maxResultsPerQuery ?? res.results.length)
.map((r) => ({
content: r.content || r.title,
metadata: {
title: r.title,
Expand Down Expand Up @@ -174,7 +176,7 @@ const webSearchAction: ResearchAction<typeof actionSchema> = {

return {
type: 'search_results',
results,
results,
};
},
};
Expand Down
34 changes: 28 additions & 6 deletions src/lib/agents/search/researcher/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,18 +167,40 @@ class Researcher {
session: session,
researchBlockId: researchBlockId,
fileIds: input.config.fileIds,
maxResultsPerQuery:
Number.isInteger(input.config.maxResultsPerQuery) &&
(input.config.maxResultsPerQuery as number) > 0
? input.config.maxResultsPerQuery
: undefined,
maxTotalResults:
Number.isInteger(input.config.maxTotalResults) &&
(input.config.maxTotalResults as number) > 0
? input.config.maxTotalResults
: undefined,
});

actionOutput.push(...actionResults);

actionResults.forEach((action, i) => {
agentMessageHistory.push({
role: 'tool',
id: finalToolCalls[i].id,
name: finalToolCalls[i].name,
content: JSON.stringify(action),
const totalCap =
Number.isInteger(input.config.maxTotalResults) &&
(input.config.maxTotalResults as number) > 0
? input.config.maxTotalResults
: Number.isInteger(input.config.maxResultsPerQuery) &&
(input.config.maxResultsPerQuery as number) > 0
? input.config.maxResultsPerQuery
: undefined;
const truncatedAction =
action.type === 'search_results' && totalCap
? { ...action, results: action.results.slice(0, totalCap) }
: action;
agentMessageHistory.push({
role: 'tool',
id: finalToolCalls[i].id,
name: finalToolCalls[i].name,
content: JSON.stringify(truncatedAction),
});
});
});
}

const searchResults = actionOutput
Expand Down
4 changes: 4 additions & 0 deletions src/lib/agents/search/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export type SearchAgentConfig = {
embedding: BaseEmbedding<any>;
mode: 'speed' | 'balanced' | 'quality';
systemInstructions: string;
maxResultsPerQuery?: number; // Limits results per SearXNG query action; reduces LLM prompt size for local/small-context models
maxTotalResults?: number; // Limits total results across all engines post-merge; prevents context overflow when multiple engines are active
};

export type SearchAgentInput = {
Expand Down Expand Up @@ -66,6 +68,8 @@ export type AdditionalConfig = {
llm: BaseLLM<any>;
embedding: BaseEmbedding<any>;
session: SessionManager;
maxResultsPerQuery?: number;
maxTotalResults?: number; // Limits total results across all engines post-merge
};

export type ResearcherInput = {
Expand Down