From ac183a90e8e392d1930a81ee3bf062ec5afcb3bb Mon Sep 17 00:00:00 2001 From: ItzCrazyKns <95534749+ItzCrazyKns@users.noreply.github.com> Date: Thu, 18 Dec 2025 13:56:26 +0530 Subject: [PATCH] feat(academic-search): add academic search --- .../researcher/actions/academicSearch.ts | 129 ++++++++++++++++++ .../agents/search/researcher/actions/index.ts | 2 + 2 files changed, 131 insertions(+) diff --git a/src/lib/agents/search/researcher/actions/academicSearch.ts b/src/lib/agents/search/researcher/actions/academicSearch.ts index e69de29..72e1f4b 100644 --- a/src/lib/agents/search/researcher/actions/academicSearch.ts +++ b/src/lib/agents/search/researcher/actions/academicSearch.ts @@ -0,0 +1,129 @@ +import z from 'zod'; +import { ResearchAction } from '../../types'; +import { Chunk, SearchResultsResearchBlock } from '@/lib/types'; +import { searchSearxng } from '@/lib/searxng'; + +const schema = z.object({ + queries: z.array(z.string()).describe('List of academic search queries'), +}); + +const academicSearchDescription = ` +Use this tool to perform academic searches for scholarly articles, papers, and research studies relevant to the user's query. Provide a list of concise search queries that will help gather comprehensive academic information on the topic at hand. +You can provide up to 3 queries at a time. Make sure the queries are specific and relevant to the user's needs. + +For example, if the user is interested in recent advancements in renewable energy, your queries could be: +1. "Recent advancements in renewable energy 2024" +2. "Cutting-edge research on solar power technologies" +3. "Innovations in wind energy systems" + +If this tool is present and no other tools are more relevant, you MUST use this tool to get the needed academic information. +`; + +const academicSearchAction: ResearchAction = { + name: 'academic_search', + schema: schema, + getDescription: () => academicSearchDescription, + getToolDescription: () => + "Use this tool to perform academic searches for scholarly articles, papers, and research studies relevant to the user's query. Provide a list of concise search queries that will help gather comprehensive academic information on the topic at hand.", + enabled: (config) => + config.sources.includes('academic') && + config.classification.classification.skipSearch === false && + config.classification.classification.academicSearch === true, + execute: async (input, additionalConfig) => { + input.queries = input.queries.slice(0, 3); + + const researchBlock = additionalConfig.session.getBlock( + additionalConfig.researchBlockId, + ); + + if (researchBlock && researchBlock.type === 'research') { + researchBlock.data.subSteps.push({ + type: 'searching', + id: crypto.randomUUID(), + searching: input.queries, + }); + + additionalConfig.session.updateBlock(additionalConfig.researchBlockId, [ + { + op: 'replace', + path: '/data/subSteps', + value: researchBlock.data.subSteps, + }, + ]); + } + + const searchResultsBlockId = crypto.randomUUID(); + let searchResultsEmitted = false; + + let results: Chunk[] = []; + + const search = async (q: string) => { + const res = await searchSearxng(q, { + engines: ['arxiv', 'google scholar', 'pubmed'], + }); + + const resultChunks: Chunk[] = res.results.map((r) => ({ + content: r.content || r.title, + metadata: { + title: r.title, + url: r.url, + }, + })); + + results.push(...resultChunks); + + if ( + !searchResultsEmitted && + researchBlock && + researchBlock.type === 'research' + ) { + searchResultsEmitted = true; + + researchBlock.data.subSteps.push({ + id: searchResultsBlockId, + type: 'search_results', + reading: resultChunks, + }); + + additionalConfig.session.updateBlock(additionalConfig.researchBlockId, [ + { + op: 'replace', + path: '/data/subSteps', + value: researchBlock.data.subSteps, + }, + ]); + } else if ( + searchResultsEmitted && + researchBlock && + researchBlock.type === 'research' + ) { + const subStepIndex = researchBlock.data.subSteps.findIndex( + (step) => step.id === searchResultsBlockId, + ); + + const subStep = researchBlock.data.subSteps[ + subStepIndex + ] as SearchResultsResearchBlock; + + subStep.reading.push(...resultChunks); + + additionalConfig.session.updateBlock(additionalConfig.researchBlockId, [ + { + op: 'replace', + path: '/data/subSteps', + value: researchBlock.data.subSteps, + }, + ]); + } + }; + + await Promise.all(input.queries.map(search)); + + return { + type: 'search_results', + results, + }; + }, +}; + +export default academicSearchAction; diff --git a/src/lib/agents/search/researcher/actions/index.ts b/src/lib/agents/search/researcher/actions/index.ts index 6d7e58f..90559b9 100644 --- a/src/lib/agents/search/researcher/actions/index.ts +++ b/src/lib/agents/search/researcher/actions/index.ts @@ -1,3 +1,4 @@ +import academicSearchAction from './academicSearch'; import doneAction from './done'; import planAction from './plan'; import ActionRegistry from './registry'; @@ -10,5 +11,6 @@ ActionRegistry.register(doneAction); ActionRegistry.register(planAction); ActionRegistry.register(scrapeURLAction); ActionRegistry.register(uploadsSearchAction); +ActionRegistry.register(academicSearchAction); export { ActionRegistry };