Add Brave Search as primary web search with NanoGPT fallback
Deploy to NAS / deploy (push) Has been cancelled
Deploy to NAS / deploy (push) Has been cancelled
- Brave Search API is now primary (if BRAVE_API_KEY set) - NanoGPT web search is fallback if Brave fails - Add BRAVE_API_KEY to .env, docker-compose, and deploy workflow - Tool calling still works for AI to request searches
This commit is contained in:
@@ -43,6 +43,7 @@ jobs:
|
|||||||
CLIENT_ID=${{ secrets.DISCORD_CLIENT_ID }}
|
CLIENT_ID=${{ secrets.DISCORD_CLIENT_ID }}
|
||||||
NANOGPT_API_KEY=${{ secrets.NANOGPT_API_KEY }}
|
NANOGPT_API_KEY=${{ secrets.NANOGPT_API_KEY }}
|
||||||
ZAI_API_KEY=${{ secrets.ZAI_API_KEY }}
|
ZAI_API_KEY=${{ secrets.ZAI_API_KEY }}
|
||||||
|
BRAVE_API_KEY=${{ secrets.BRAVE_API_KEY }}
|
||||||
NANO_MODEL=${{ secrets.NANO_MODEL }}
|
NANO_MODEL=${{ secrets.NANO_MODEL }}
|
||||||
NODE_ENV=production
|
NODE_ENV=production
|
||||||
EOF
|
EOF
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- NANO_MODEL=${NANO_MODEL}
|
- NANO_MODEL=${NANO_MODEL}
|
||||||
- ZAI_API_KEY=${ZAI_API_KEY}
|
- ZAI_API_KEY=${ZAI_API_KEY}
|
||||||
|
- BRAVE_API_KEY=${BRAVE_API_KEY}
|
||||||
volumes:
|
volumes:
|
||||||
# Mount logs directory if you want to persist logs
|
# Mount logs directory if you want to persist logs
|
||||||
- ./logs:/app/logs
|
- ./logs:/app/logs
|
||||||
|
|||||||
+84
-16
@@ -7,6 +7,45 @@ const DEFAULT_MODEL = 'GLM-4.5-Air-Derestricted';
|
|||||||
const FALLBACK_MODEL = 'GLM-4.7-Flash';
|
const FALLBACK_MODEL = 'GLM-4.7-Flash';
|
||||||
const FALLBACK_API_URL = 'https://api.z.ai/api/coding/paas/v4/chat/completions';
|
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
|
// Tool definitions for function calling
|
||||||
const WEB_SEARCH_TOOL = {
|
const WEB_SEARCH_TOOL = {
|
||||||
type: 'function',
|
type: 'function',
|
||||||
@@ -77,7 +116,15 @@ function buildMessages(history, prompt, mentionedUsername, systemPrompt) {
|
|||||||
return messages;
|
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(
|
const response = await axios.post(
|
||||||
'https://nano-gpt.com/api/web',
|
'https://nano-gpt.com/api/web',
|
||||||
{
|
{
|
||||||
@@ -96,31 +143,52 @@ async function performWebSearch(query) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const data = response.data.data;
|
const data = response.data.data;
|
||||||
const metadata = response.data.metadata;
|
|
||||||
|
|
||||||
// Format the response nicely
|
|
||||||
let formattedResult = '';
|
|
||||||
|
|
||||||
if (typeof data === 'string') {
|
if (typeof data === 'string') {
|
||||||
formattedResult = data;
|
return data;
|
||||||
} else if (data.answer) {
|
} else if (data.answer) {
|
||||||
formattedResult = data.answer;
|
let result = data.answer;
|
||||||
if (data.sources && data.sources.length > 0) {
|
if (data.sources && data.sources.length > 0) {
|
||||||
formattedResult += '\n\nSources:\n';
|
result += '\n\nSources:\n';
|
||||||
data.sources.forEach((source, i) => {
|
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) {
|
} else if (data.results) {
|
||||||
formattedResult = 'Search results:\n';
|
let result = 'Search results:\n';
|
||||||
data.results.slice(0, 5).forEach((result, i) => {
|
data.results.slice(0, 8).forEach((r, i) => {
|
||||||
formattedResult += `${i + 1}. ${result.title || result.name}\n`;
|
result += `${i + 1}. ${r.title || r.name}\n`;
|
||||||
if (result.url) formattedResult += ` ${result.url}\n`;
|
if (r.url) result += ` ${r.url}\n`;
|
||||||
if (result.content) formattedResult += ` ${result.content.substring(0, 200)}...\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) {
|
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);
|
const args = JSON.parse(toolCall.function.arguments);
|
||||||
console.log('AI requested web search for:', args.query);
|
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);
|
const searchResult = await performWebSearch(args.query);
|
||||||
|
|
||||||
// Add tool result to messages and continue
|
// Add tool result to messages and continue
|
||||||
|
|||||||
Reference in New Issue
Block a user