diff --git a/src/app/api/search/route.ts b/src/app/api/search/route.ts index 099126840..732bef7d0 100644 --- a/src/app/api/search/route.ts +++ b/src/app/api/search/route.ts @@ -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) => { @@ -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(), diff --git a/src/lib/agents/search/researcher/actions/academicSearch.ts b/src/lib/agents/search/researcher/actions/academicSearch.ts index 72e1f4b14..3d848b2d4 100644 --- a/src/lib/agents/search/researcher/actions/academicSearch.ts +++ b/src/lib/agents/search/researcher/actions/academicSearch.ts @@ -62,7 +62,9 @@ const academicSearchAction: ResearchAction = { 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, @@ -121,7 +123,7 @@ const academicSearchAction: ResearchAction = { return { type: 'search_results', - results, + results, }; }, }; diff --git a/src/lib/agents/search/researcher/actions/socialSearch.ts b/src/lib/agents/search/researcher/actions/socialSearch.ts index 16468ab4c..25b20175c 100644 --- a/src/lib/agents/search/researcher/actions/socialSearch.ts +++ b/src/lib/agents/search/researcher/actions/socialSearch.ts @@ -62,7 +62,9 @@ const socialSearchAction: ResearchAction = { 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, @@ -121,7 +123,7 @@ const socialSearchAction: ResearchAction = { return { type: 'search_results', - results, + results, }; }, }; diff --git a/src/lib/agents/search/researcher/actions/webSearch.ts b/src/lib/agents/search/researcher/actions/webSearch.ts index 4d60b79f2..ae51eadc9 100644 --- a/src/lib/agents/search/researcher/actions/webSearch.ts +++ b/src/lib/agents/search/researcher/actions/webSearch.ts @@ -115,7 +115,9 @@ const webSearchAction: ResearchAction = { 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, @@ -174,7 +176,7 @@ const webSearchAction: ResearchAction = { return { type: 'search_results', - results, + results, }; }, }; diff --git a/src/lib/agents/search/researcher/index.ts b/src/lib/agents/search/researcher/index.ts index d6532819e..69565cb2e 100644 --- a/src/lib/agents/search/researcher/index.ts +++ b/src/lib/agents/search/researcher/index.ts @@ -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 diff --git a/src/lib/agents/search/types.ts b/src/lib/agents/search/types.ts index 64c967eab..481fa743f 100644 --- a/src/lib/agents/search/types.ts +++ b/src/lib/agents/search/types.ts @@ -13,6 +13,8 @@ export type SearchAgentConfig = { embedding: BaseEmbedding; 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 = { @@ -66,6 +68,8 @@ export type AdditionalConfig = { llm: BaseLLM; embedding: BaseEmbedding; session: SessionManager; + maxResultsPerQuery?: number; + maxTotalResults?: number; // Limits total results across all engines post-merge }; export type ResearcherInput = {