diff --git a/docker/.env.example b/docker/.env.example index 8efd57cb..9b73a290 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -378,6 +378,9 @@ GID='1000' #------ Bing Search ----------- https://portal.azure.com/ # AGENT_BING_SEARCH_API_KEY= +#------ Baidu Search ----------- https://cloud.baidu.com/doc/qianfan-api/s/Wmbq4z7e5 +# AGENT_BAIDU_SEARCH_API_KEY= + #------ Serply.io ----------- https://serply.io/ # AGENT_SERPLY_API_KEY= @@ -448,4 +451,4 @@ GID='1000' # (optional) Comma-separated list of skills that are auto-approved. # This will allow the skill to be invoked without user interaction. -# AGENT_AUTO_APPROVED_SKILLS=create-pdf-file,create-word-file \ No newline at end of file +# AGENT_AUTO_APPROVED_SKILLS=create-pdf-file,create-word-file diff --git a/frontend/src/pages/Admin/Agents/WebSearchSelection/SearchProviderOptions/index.jsx b/frontend/src/pages/Admin/Agents/WebSearchSelection/SearchProviderOptions/index.jsx index 1d246bdc..17c63fbe 100644 --- a/frontend/src/pages/Admin/Agents/WebSearchSelection/SearchProviderOptions/index.jsx +++ b/frontend/src/pages/Admin/Agents/WebSearchSelection/SearchProviderOptions/index.jsx @@ -248,6 +248,43 @@ export function BingSearchOptions({ settings }) { ); } +export function BaiduSearchOptions({ settings }) { + return ( + <> +

+ You can get an API key{" "} + + from Baidu AI Cloud Qianfan. + +

+
+
+ + +
+
+ + ); +} + export function SerplySearchOptions({ settings }) { return ( <> diff --git a/frontend/src/pages/Admin/Agents/WebSearchSelection/icons/baidu.png b/frontend/src/pages/Admin/Agents/WebSearchSelection/icons/baidu.png new file mode 100644 index 00000000..7d57538d Binary files /dev/null and b/frontend/src/pages/Admin/Agents/WebSearchSelection/icons/baidu.png differ diff --git a/frontend/src/pages/Admin/Agents/WebSearchSelection/index.jsx b/frontend/src/pages/Admin/Agents/WebSearchSelection/index.jsx index 11a58edb..44803381 100644 --- a/frontend/src/pages/Admin/Agents/WebSearchSelection/index.jsx +++ b/frontend/src/pages/Admin/Agents/WebSearchSelection/index.jsx @@ -4,6 +4,7 @@ import SerpApiIcon from "./icons/serpapi.png"; import SearchApiIcon from "./icons/searchapi.png"; import SerperDotDevIcon from "./icons/serper.png"; import BingSearchIcon from "./icons/bing.png"; +import BaiduSearchIcon from "./icons/baidu.png"; import SerplySearchIcon from "./icons/serply.png"; import SearXNGSearchIcon from "./icons/searxng.png"; import TavilySearchIcon from "./icons/tavily.svg"; @@ -24,6 +25,7 @@ import { SearchApiOptions, SerperDotDevOptions, BingSearchOptions, + BaiduSearchOptions, SerplySearchOptions, SearXNGOptions, TavilySearchOptions, @@ -71,6 +73,14 @@ const SEARCH_PROVIDERS = [ options: (settings) => , description: "Web search powered by the Bing Search API (paid service).", }, + { + name: "Baidu Search", + value: "baidu-search", + logo: BaiduSearchIcon, + options: (settings) => , + description: + "Web search powered by Baidu Search for stronger zh-CN retrieval.", + }, { name: "Serply.io", value: "serply-engine", diff --git a/server/.env.example b/server/.env.example index bf5e519a..9c7878ad 100644 --- a/server/.env.example +++ b/server/.env.example @@ -382,6 +382,9 @@ TTS_PROVIDER="native" #------ Bing Search ----------- https://portal.azure.com/ # AGENT_BING_SEARCH_API_KEY= +#------ Baidu Search ----------- https://cloud.baidu.com/doc/qianfan-api/s/Wmbq4z7e5 +# AGENT_BAIDU_SEARCH_API_KEY= + #------ Serply.io ----------- https://serply.io/ # AGENT_SERPLY_API_KEY= diff --git a/server/models/systemSettings.js b/server/models/systemSettings.js index c54a3213..4518740d 100644 --- a/server/models/systemSettings.js +++ b/server/models/systemSettings.js @@ -147,6 +147,7 @@ const SystemSettings = { "searchapi", "serper-dot-dev", "bing-search", + "baidu-search", "serply-engine", "searxng-engine", "tavily-search", @@ -483,6 +484,7 @@ const SystemSettings = { AgentSearchApiEngine: process.env.AGENT_SEARCHAPI_ENGINE || "google", AgentSerperApiKey: !!process.env.AGENT_SERPER_DEV_KEY || null, AgentBingSearchApiKey: !!process.env.AGENT_BING_SEARCH_API_KEY || null, + AgentBaiduSearchApiKey: !!process.env.AGENT_BAIDU_SEARCH_API_KEY || null, AgentSerplyApiKey: !!process.env.AGENT_SERPLY_API_KEY || null, AgentSearXNGApiUrl: process.env.AGENT_SEARXNG_API_URL || null, AgentTavilyApiKey: !!process.env.AGENT_TAVILY_API_KEY || null, diff --git a/server/utils/agents/aibitat/plugins/web-browsing.js b/server/utils/agents/aibitat/plugins/web-browsing.js index 5f63fd21..23d5b38c 100644 --- a/server/utils/agents/aibitat/plugins/web-browsing.js +++ b/server/utils/agents/aibitat/plugins/web-browsing.js @@ -80,6 +80,9 @@ const webBrowsing = { case "bing-search": engine = "_bingWebSearch"; break; + case "baidu-search": + engine = "_baiduSearch"; + break; case "serply-engine": engine = "_serplyEngine"; break; @@ -604,6 +607,113 @@ const webBrowsing = { ); return result; }, + _baiduSearch: async function (query) { + if (!process.env.AGENT_BAIDU_SEARCH_API_KEY) { + this.super.introspect( + `${this.caller}: I can't use Baidu Search because the user has not defined the required API key.\nVisit: https://cloud.baidu.com/doc/qianfan-api/s/Wmbq4z7e5 to create the API key.` + ); + return `Search is disabled and no content was found. This functionality is disabled because the user has not set it up yet.`; + } + + this.super.introspect( + `${this.caller}: Using Baidu Search to search for "${ + query.length > 100 ? `${query.slice(0, 100)}...` : query + }"` + ); + + const { response, error } = await fetch( + "https://qianfan.baidubce.com/v2/ai_search/web_search", + { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${process.env.AGENT_BAIDU_SEARCH_API_KEY}`, + "X-Appbuilder-Authorization": `Bearer ${process.env.AGENT_BAIDU_SEARCH_API_KEY}`, + }, + body: JSON.stringify({ + messages: [{ role: "user", content: query }], + resource_type_filter: [{ type: "web", top_k: 10 }], + }), + } + ) + .then(async (res) => { + if (res.ok) return res.json(); + + const body = await res.text().catch(() => ""); + throw new Error( + `${res.status} - ${res.statusText}. params: ${JSON.stringify({ + auth: this.middleTruncate( + process.env.AGENT_BAIDU_SEARCH_API_KEY, + 5 + ), + q: query, + body: body.slice(0, 300), + })}` + ); + }) + .then((data) => { + return { response: data, error: null }; + }) + .catch((e) => { + this.super.handlerProps.log(`Baidu Search Error: ${e.message}`); + return { response: null, error: e.message }; + }); + + if (error) + return `There was an error searching for content. ${error}`; + + if ( + (response?.code || response?.message) && + !response?.references + ) { + return `There was an error searching for content. ${response?.message || response?.code}`; + } + + /** + * Normalize Baidu Search References to the expected search results format + * @param {Array} references - The references to normalize + * @returns {Array} The normalized references + */ + function normalizeBaiduSearchReferences(references = []) { + if (!Array.isArray(references)) return []; + + const seenLinks = new Set(); + return references + .filter((reference) => { + if (!reference) return false; + const referenceType = String( + reference.type || reference.resource_type || "web" + ).toLowerCase(); + return referenceType === "web"; + }) + .map((reference) => { + const title = String( + reference.title || reference.web_anchor || "" + ).trim(); + const link = String(reference.url || "").trim(); + const snippet = String( + reference.snippet || reference.content || "" + ).trim(); + + if (!title || !link || seenLinks.has(link)) return null; + seenLinks.add(link); + + return { title, link, snippet }; + }) + .filter(Boolean); + } + + const data = normalizeBaiduSearchReferences(response?.references); + if (data.length === 0) + return `No information was found online for the search query.`; + + this.reportSearchResultsCitations(data); + const result = JSON.stringify(data); + this.super.introspect( + `${this.caller}: I found ${data.length} results - reviewing the results now. (~${this.countTokens(result)} tokens)` + ); + return result; + }, _serplyEngine: async function ( query, language = "en", diff --git a/server/utils/helpers/updateENV.js b/server/utils/helpers/updateENV.js index ec171e05..de3630c1 100644 --- a/server/utils/helpers/updateENV.js +++ b/server/utils/helpers/updateENV.js @@ -586,6 +586,10 @@ const KEY_MAPPING = { envKey: "AGENT_BING_SEARCH_API_KEY", checks: [], }, + AgentBaiduSearchApiKey: { + envKey: "AGENT_BAIDU_SEARCH_API_KEY", + checks: [], + }, AgentSerplyApiKey: { envKey: "AGENT_SERPLY_API_KEY", checks: [],