diff --git a/.gitea/workflows/deploy-nas.yml b/.gitea/workflows/deploy-nas.yml index ae676b1..6288415 100644 --- a/.gitea/workflows/deploy-nas.yml +++ b/.gitea/workflows/deploy-nas.yml @@ -43,6 +43,7 @@ jobs: CLIENT_ID=${{ secrets.DISCORD_CLIENT_ID }} NANOGPT_API_KEY=${{ secrets.NANOGPT_API_KEY }} ZAI_API_KEY=${{ secrets.ZAI_API_KEY }} + BRAVE_API_KEY=${{ secrets.BRAVE_API_KEY }} NANO_MODEL=${{ secrets.NANO_MODEL }} NODE_ENV=production EOF diff --git a/docker-compose.yml b/docker-compose.yml index b7cc789..9888643 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,6 +10,7 @@ services: environment: - NANO_MODEL=${NANO_MODEL} - ZAI_API_KEY=${ZAI_API_KEY} + - BRAVE_API_KEY=${BRAVE_API_KEY} volumes: # Mount logs directory if you want to persist logs - ./logs:/app/logs diff --git a/utils/ai.js b/utils/ai.js index 725b4c2..b9e9888 100644 --- a/utils/ai.js +++ b/utils/ai.js @@ -7,6 +7,45 @@ const DEFAULT_MODEL = 'GLM-4.5-Air-Derestricted'; const FALLBACK_MODEL = 'GLM-4.7-Flash'; const FALLBACK_API_URL = 'https://api.z.ai/api/coding/paas/v4/chat/completions'; +// Brave Search API +const BRAVE_API_URL = 'https://api.search.brave.com/res/v1/web/search'; + +async function performBraveSearch(query, count = 10) { + const response = await axios.get(BRAVE_API_URL, { + params: { + q: query, + count: count, + extra_snippets: true + }, + headers: { + 'X-Subscription-Token': process.env.BRAVE_API_KEY, + 'Accept': 'application/json' + }, + timeout: 15000 + }); + + const results = response.data.web?.results || []; + + if (results.length === 0) { + return 'No search results found.'; + } + + let formatted = 'Search results:\n\n'; + results.slice(0, 8).forEach((result, i) => { + formatted += `${i + 1}. ${result.title}\n`; + formatted += ` ${result.url}\n`; + if (result.description) { + formatted += ` ${result.description.substring(0, 200)}${result.description.length > 200 ? '...' : ''}\n`; + } + if (result.extra_snippets && result.extra_snippets.length > 0) { + formatted += ` ${result.extra_snippets[0].substring(0, 150)}...\n`; + } + formatted += '\n'; + }); + + return formatted; +} + // Tool definitions for function calling const WEB_SEARCH_TOOL = { type: 'function', @@ -77,7 +116,15 @@ function buildMessages(history, prompt, mentionedUsername, systemPrompt) { return messages; } -async function performWebSearch(query) { +// Primary: Brave Search +async function webSearchWithBrave(query) { + console.log('Using Brave Search for:', query); + return await performBraveSearch(query); +} + +// Fallback: NanoGPT web search +async function webSearchWithNanoGPT(query) { + console.log('Using NanoGPT web search for:', query); const response = await axios.post( 'https://nano-gpt.com/api/web', { @@ -96,31 +143,52 @@ async function performWebSearch(query) { ); const data = response.data.data; - const metadata = response.data.metadata; - - // Format the response nicely - let formattedResult = ''; if (typeof data === 'string') { - formattedResult = data; + return data; } else if (data.answer) { - formattedResult = data.answer; + let result = data.answer; if (data.sources && data.sources.length > 0) { - formattedResult += '\n\nSources:\n'; + result += '\n\nSources:\n'; data.sources.forEach((source, i) => { - formattedResult += `${i + 1}. ${source.name || source.url}\n`; + result += `${i + 1}. ${source.name || source.url}\n`; }); } + return result; } else if (data.results) { - formattedResult = 'Search results:\n'; - data.results.slice(0, 5).forEach((result, i) => { - formattedResult += `${i + 1}. ${result.title || result.name}\n`; - if (result.url) formattedResult += ` ${result.url}\n`; - if (result.content) formattedResult += ` ${result.content.substring(0, 200)}...\n`; + let result = 'Search results:\n'; + data.results.slice(0, 8).forEach((r, i) => { + result += `${i + 1}. ${r.title || r.name}\n`; + if (r.url) result += ` ${r.url}\n`; + if (r.content) result += ` ${r.content.substring(0, 200)}...\n`; }); + return result; } - return formattedResult; + return 'Could not parse search results.'; +} + +// Unified web search function (Brave first, NanoGPT fallback) +async function performWebSearch(query) { + // Try Brave first if API key is available + if (process.env.BRAVE_API_KEY) { + try { + return await webSearchWithBrave(query); + } catch (error) { + console.error('Brave Search failed:', error.message); + } + } + + // Fallback to NanoGPT + if (process.env.NANOGPT_API_KEY) { + try { + return await webSearchWithNanoGPT(query); + } catch (error) { + console.error('NanoGPT search failed:', error.message); + } + } + + throw new Error('No web search provider available'); } async function callNanoGPTWithTools(messages, model, maxRetries = 2) { @@ -156,7 +224,7 @@ async function callNanoGPTWithTools(messages, model, maxRetries = 2) { const args = JSON.parse(toolCall.function.arguments); console.log('AI requested web search for:', args.query); - // Perform the search + // Perform the search (Brave primary, NanoGPT fallback) const searchResult = await performWebSearch(args.query); // Add tool result to messages and continue