diff --git a/src/lib/agents/media/video.ts b/src/lib/agents/media/video.ts index c8f19b69a..5703ebbbb 100644 --- a/src/lib/agents/media/video.ts +++ b/src/lib/agents/media/video.ts @@ -43,19 +43,31 @@ const searchVideos = async ( schema: schema, }); - const searchRes = await searchSearxng(res.query, { - engines: ['youtube'], - }); + let searchRes; + try { + searchRes = await searchSearxng(res.query, { + engines: ['youtube'], + }); + } catch (error) { + console.error(`Video search failed for query "${res.query}":`, error); + return []; + } const videos: VideoSearchResult[] = []; searchRes.results.forEach((result) => { if (result.thumbnail && result.url && result.title && result.iframe_src) { + // Normalize embed URL - some SearXNG instances return + // youtube-nocookie.com which doesn't resolve on all networks + const embedUrl = result.iframe_src.replace( + 'www.youtube-nocookie.com/embed', + 'www.youtube.com/embed', + ); videos.push({ img_src: result.thumbnail, url: result.url, title: result.title, - iframe_src: result.iframe_src, + iframe_src: embedUrl, }); } }); diff --git a/src/lib/agents/search/researcher/actions/academicSearch.ts b/src/lib/agents/search/researcher/actions/academicSearch.ts index 72e1f4b14..65c2adc6e 100644 --- a/src/lib/agents/search/researcher/actions/academicSearch.ts +++ b/src/lib/agents/search/researcher/actions/academicSearch.ts @@ -58,9 +58,17 @@ const academicSearchAction: ResearchAction = { let results: Chunk[] = []; const search = async (q: string) => { - const res = await searchSearxng(q, { - engines: ['arxiv', 'google scholar', 'pubmed'], - }); + let res; + try { + res = await searchSearxng(q, { + engines: ['arxiv', 'google scholar', 'pubmed'], + }); + } catch (error) { + console.error(`Academic search failed for query "${q}":`, error); + return; + } + + if (!res.results || res.results.length === 0) return; const resultChunks: Chunk[] = res.results.map((r) => ({ content: r.content || r.title, diff --git a/src/lib/agents/search/researcher/actions/scrapeURL.ts b/src/lib/agents/search/researcher/actions/scrapeURL.ts index c702a7014..774998afe 100644 --- a/src/lib/agents/search/researcher/actions/scrapeURL.ts +++ b/src/lib/agents/search/researcher/actions/scrapeURL.ts @@ -118,6 +118,7 @@ const scrapeURLAction: ResearchAction = { }, }); } catch (error) { + console.error(`Failed to scrape URL ${url}:`, error); results.push({ content: `Failed to fetch content from ${url}: ${error}`, metadata: { diff --git a/src/lib/agents/search/researcher/actions/socialSearch.ts b/src/lib/agents/search/researcher/actions/socialSearch.ts index 16468ab4c..645c506e4 100644 --- a/src/lib/agents/search/researcher/actions/socialSearch.ts +++ b/src/lib/agents/search/researcher/actions/socialSearch.ts @@ -58,9 +58,17 @@ const socialSearchAction: ResearchAction = { let results: Chunk[] = []; const search = async (q: string) => { - const res = await searchSearxng(q, { - engines: ['reddit'], - }); + let res; + try { + res = await searchSearxng(q, { + engines: ['reddit'], + }); + } catch (error) { + console.error(`Social search failed for query "${q}":`, error); + return; + } + + if (!res.results || res.results.length === 0) return; const resultChunks: Chunk[] = res.results.map((r) => ({ content: r.content || r.title, diff --git a/src/lib/agents/search/researcher/actions/webSearch.ts b/src/lib/agents/search/researcher/actions/webSearch.ts index 4d60b79f2..0184147cc 100644 --- a/src/lib/agents/search/researcher/actions/webSearch.ts +++ b/src/lib/agents/search/researcher/actions/webSearch.ts @@ -113,7 +113,15 @@ const webSearchAction: ResearchAction = { let results: Chunk[] = []; const search = async (q: string) => { - const res = await searchSearxng(q); + let res; + try { + res = await searchSearxng(q); + } catch (error) { + console.error(`SearXNG search failed for query "${q}":`, error); + return; + } + + if (!res.results || res.results.length === 0) return; const resultChunks: Chunk[] = res.results.map((r) => ({ content: r.content || r.title, diff --git a/src/lib/models/providers/ollama/ollamaLLM.ts b/src/lib/models/providers/ollama/ollamaLLM.ts index 3bcd3ccf1..998a09565 100644 --- a/src/lib/models/providers/ollama/ollamaLLM.ts +++ b/src/lib/models/providers/ollama/ollamaLLM.ts @@ -92,7 +92,9 @@ class OllamaLLM extends BaseLLM { temperature: input.options?.temperature ?? this.config.options?.temperature ?? 0.7, num_predict: input.options?.maxTokens ?? this.config.options?.maxTokens, - num_ctx: 32000, + num_ctx: + input.options?.contextWindowSize ?? + this.config.options?.contextWindowSize, frequency_penalty: input.options?.frequencyPenalty ?? this.config.options?.frequencyPenalty, @@ -105,15 +107,15 @@ class OllamaLLM extends BaseLLM { }); return { - content: res.message.content, + content: res.message?.content ?? '', toolCalls: - res.message.tool_calls?.map((tc) => ({ + res.message?.tool_calls?.map((tc) => ({ id: crypto.randomUUID(), name: tc.function.name, arguments: tc.function.arguments, })) || [], additionalInfo: { - reasoning: res.message.thinking, + reasoning: res.message?.thinking, }, }; } @@ -146,7 +148,9 @@ class OllamaLLM extends BaseLLM { top_p: input.options?.topP ?? this.config.options?.topP, temperature: input.options?.temperature ?? this.config.options?.temperature ?? 0.7, - num_ctx: 32000, + num_ctx: + input.options?.contextWindowSize ?? + this.config.options?.contextWindowSize, num_predict: input.options?.maxTokens ?? this.config.options?.maxTokens, frequency_penalty: input.options?.frequencyPenalty ?? @@ -161,9 +165,9 @@ class OllamaLLM extends BaseLLM { for await (const chunk of stream) { yield { - contentChunk: chunk.message.content, + contentChunk: chunk.message?.content ?? '', toolCallChunk: - chunk.message.tool_calls?.map((tc, i) => ({ + chunk.message?.tool_calls?.map((tc, i) => ({ id: crypto .createHash('sha256') .update( @@ -175,7 +179,7 @@ class OllamaLLM extends BaseLLM { })) || [], done: chunk.done, additionalInfo: { - reasoning: chunk.message.thinking, + reasoning: chunk.message?.thinking, }, }; } diff --git a/src/lib/models/providers/openai/openaiLLM.ts b/src/lib/models/providers/openai/openaiLLM.ts index 5ae1538a0..5ec65cf70 100644 --- a/src/lib/models/providers/openai/openaiLLM.ts +++ b/src/lib/models/providers/openai/openaiLLM.ts @@ -102,9 +102,9 @@ class OpenAILLM extends BaseLLM { if (response.choices && response.choices.length > 0) { return { - content: response.choices[0].message.content!, + content: response.choices[0].message?.content ?? '', toolCalls: - response.choices[0].message.tool_calls + response.choices[0].message?.tool_calls ?.map((tc) => { if (tc.type === 'function') { return { diff --git a/src/lib/models/types.ts b/src/lib/models/types.ts index 8abefd70a..8b27b0be8 100644 --- a/src/lib/models/types.ts +++ b/src/lib/models/types.ts @@ -35,6 +35,7 @@ type GenerateOptions = { stopSequences?: string[]; frequencyPenalty?: number; presencePenalty?: number; + contextWindowSize?: number; }; type Tool = { diff --git a/src/lib/searxng.ts b/src/lib/searxng.ts index a0bcd8355..d9dd7e8e9 100644 --- a/src/lib/searxng.ts +++ b/src/lib/searxng.ts @@ -39,10 +39,22 @@ export const searchSearxng = async ( } const res = await fetch(url); - const data = await res.json(); - const results: SearxngSearchResult[] = data.results; - const suggestions: string[] = data.suggestions; + if (!res.ok) { + throw new Error( + `SearXNG returned status ${res.status} for query: ${query}`, + ); + } + + let data; + try { + data = await res.json(); + } catch { + throw new Error(`SearXNG returned non-JSON response for query: ${query}`); + } + + const results: SearxngSearchResult[] = data.results ?? []; + const suggestions: string[] = data.suggestions ?? []; return { results, suggestions }; };