diff --git a/apps/mana/apps/web/src/lib/modules/news-research/tools.ts b/apps/mana/apps/web/src/lib/modules/news-research/tools.ts index ce9d9c0a2..056363abe 100644 --- a/apps/mana/apps/web/src/lib/modules/news-research/tools.ts +++ b/apps/mana/apps/web/src/lib/modules/news-research/tools.ts @@ -12,9 +12,18 @@ import type { ModuleTool } from '$lib/data/tools/types'; import { discoverByQuery, searchFeeds } from './api'; +import { compareResearch } from '$lib/modules/research-lab/api'; +import type { AgentAnswer } from '$lib/modules/research-lab/types'; const MAX_RESULTS = 15; +const DEEP_AGENT_PREFERENCE = [ + 'perplexity-sonar', + 'gemini-grounding', + 'openai-responses', + 'claude-web-search', +] as const; + function formatContext( query: string, feedCount: number, @@ -45,6 +54,28 @@ function formatContext( return lines.join('\n'); } +function formatDeepContext(query: string, providers: string[], answers: AgentAnswer[]): string { + const lines = [ + `# Deep Research — Query: ${query}`, + `Agents consulted: ${providers.join(', ')}`, + '', + ]; + for (let i = 0; i < answers.length; i++) { + const provider = providers[i]; + const answer = answers[i]; + if (!answer) continue; + lines.push(`## ${provider}`, '', answer.answer, ''); + if (answer.citations.length > 0) { + lines.push('### Sources'); + for (const cit of answer.citations) { + lines.push(`- [${cit.title || cit.url}](${cit.url})`); + } + lines.push(''); + } + } + return lines.join('\n'); +} + export const newsResearchTools: ModuleTool[] = [ { name: 'research_news', @@ -58,16 +89,23 @@ export const newsResearchTools: ModuleTool[] = [ description: 'Thema oder Stichworte (z.B. "KI-Regulierung EU")', required: true, }, + { + name: 'depth', + type: 'string', + description: + '"shallow" (Standard, kostenlos — RSS-Feeds durchsuchen) oder "deep" (kostet Credits — fragt 1–2 Research-Agents wie Perplexity/Gemini mit web_search).', + required: false, + }, { name: 'language', type: 'string', - description: 'Sprache der Feeds (de/en); optional', + description: 'Sprache der Feeds (de/en); optional, nur für shallow', required: false, }, { name: 'limit', type: 'number', - description: `Maximale Anzahl Treffer (Standard ${MAX_RESULTS})`, + description: `Maximale Anzahl Treffer im shallow-Modus (Standard ${MAX_RESULTS})`, required: false, }, ], @@ -76,9 +114,42 @@ export const newsResearchTools: ModuleTool[] = [ if (!query) { return { success: false, message: 'query is required' }; } + const depth = String(params.depth ?? 'shallow').toLowerCase(); const language = typeof params.language === 'string' ? params.language : undefined; const limit = Math.min(Math.max(Number(params.limit) || MAX_RESULTS, 1), 50); + if (depth === 'deep') { + // Ask 2 research agents in parallel; prefer cheapest-first order. + const providers = [...DEEP_AGENT_PREFERENCE].slice(0, 2); + try { + const res = await compareResearch(query, providers); + const answers = res.results.map((r) => r.data?.answer).filter(Boolean) as AgentAnswer[]; + const successful = res.results.filter((r) => r.success); + const context = formatDeepContext( + query, + successful.map((r) => r.provider), + answers + ); + return { + success: true, + message: `Deep Research: ${successful.length} / ${res.results.length} Agents geantwortet.`, + data: { + context, + runId: res.runId, + answers: answers.map((a, i) => ({ + provider: successful[i]?.provider, + answer: a.answer, + citations: a.citations, + })), + }, + }; + } catch (err) { + const message = err instanceof Error ? err.message : 'Deep research fehlgeschlagen'; + return { success: false, message }; + } + } + + // shallow (default): RSS discovery + keyword search const discovered = await discoverByQuery(query, language); const feedUrls = discovered.feeds.slice(0, 10).map((f) => f.url); if (feedUrls.length === 0) {