From 1fc57ab1aed2089e11b187cf88b40f600ab17db8 Mon Sep 17 00:00:00 2001 From: Jorge Valdez Date: Wed, 9 Apr 2025 02:35:42 -0400 Subject: [PATCH] feat: add support for searxng (#1814) * Add support for searxng * body.results not body.web * type annotation * update docs --- README.md | 4 +++- README_zh.md | 5 +++-- lua/avante/config.lua | 22 ++++++++++++++++++++++ lua/avante/llm_tools/init.lua | 26 +++++++++++++++++++++++--- 4 files changed, 51 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index a28d73e..8a50cd4 100644 --- a/README.md +++ b/README.md @@ -810,12 +810,13 @@ Avante's tools include some web search engines, currently support: - Google's [Programmable Search Engine](https://developers.google.com/custom-search/v1/overview) - [Kagi](https://help.kagi.com/kagi/api/search.html) - [Brave Search](https://api-dashboard.search.brave.com/app/documentation/web-search/get-started) +- [SearXNG](https://searxng.github.io/searxng/) The default is Tavily, and can be changed through configuring `Config.web_search_engine.provider`: ```lua web_search_engine = { - provider = "tavily", -- tavily, serpapi, searchapi, google or kagi + provider = "tavily", -- tavily, serpapi, searchapi, google, kagi, brave, or searxng proxy = nil, -- proxy support, e.g., http://127.0.0.1:7890 } ``` @@ -830,6 +831,7 @@ Environment variables required for providers: - `GOOGLE_SEARCH_ENGINE_ID` as the [search engine](https://programmablesearchengine.google.com) ID - Kagi: `KAGI_API_KEY` as the [API Token](https://kagi.com/settings?p=api) - Brave Search: `BRAVE_API_KEY` as the [API key](https://api-dashboard.search.brave.com/app/keys) +- SearXNG: `SEARXNG_API_URL` as the [API URL](https://docs.searxng.org/dev/search_api.html) ## Disable Tools diff --git a/README_zh.md b/README_zh.md index b4e752d..7a2e142 100644 --- a/README_zh.md +++ b/README_zh.md @@ -807,14 +807,14 @@ Avante 的工具包括一些 Web 搜索引擎,目前支持: - Google's [Programmable Search Engine](https://developers.google.com/custom-search/v1/overview) - [Kagi](https://help.kagi.com/kagi/api/search.html) - [Brave Search](https://api-dashboard.search.brave.com/app/documentation/web-search/get-started) +- [SearXNG](https://searxng.github.io/searxng/) 默认是 Tavily,可以通过配置 `Config.web_search_engine.provider` 进行更改: ```lua web_search_engine = { - provider = "tavily", -- tavily, serpapi, searchapi, google 或 kagi + provider = "tavily", -- tavily, serpapi, searchapi, google, kagi, brave 或 searxng proxy = nil, -- proxy support, e.g., http://127.0.0.1:7890 - } ``` @@ -828,6 +828,7 @@ web_search_engine = { - `GOOGLE_SEARCH_ENGINE_ID` 作为 [搜索引擎](https://programmablesearchengine.google.com) ID - Kagi: `KAGI_API_KEY` 作为 [API 令牌](https://kagi.com/settings?p=api) - Brave Search: `BRAVE_API_KEY` 作为 [API 密钥](https://api-dashboard.search.brave.com/app/keys) +- SearXNG: `SEARXNG_API_URL` 作为 [API URL](https://docs.searxng.org/dev/search_api.html) ## 禁用工具 diff --git a/lua/avante/config.lua b/lua/avante/config.lua index f753203..0123ac0 100644 --- a/lua/avante/config.lua +++ b/lua/avante/config.lua @@ -186,6 +186,28 @@ M._defaults = { end ) + return vim.json.encode(jsn), nil + end, + }, + searxng = { + api_url_name = "SEARXNG_API_URL", + extra_request_body = { + format = "json", + }, + ---@type WebSearchEngineProviderResponseBodyFormatter + format_response_body = function(body) + if body.results == nil then return "", nil end + + local jsn = vim.iter(body.results):map( + function(result) + return { + title = result.title, + url = result.url, + snippet = result.content, + } + end + ) + return vim.json.encode(jsn), nil end, }, diff --git a/lua/avante/llm_tools/init.lua b/lua/avante/llm_tools/init.lua index 9faf166..08e616a 100644 --- a/lua/avante/llm_tools/init.lua +++ b/lua/avante/llm_tools/init.lua @@ -218,9 +218,9 @@ function M.web_search(opts, on_log) if on_log then on_log("query: " .. opts.query) end local search_engine = Config.web_search_engine.providers[provider_type] if search_engine == nil then return nil, "No search engine found: " .. provider_type end - if search_engine.api_key_name == "" then return nil, "No API key provided" end - local api_key = Utils.environment.parse(search_engine.api_key_name) - if api_key == nil or api_key == "" then + if provider_type ~= "searxng" and search_engine.api_key_name == "" then return nil, "No API key provided" end + local api_key = provider_type ~= "searxng" and Utils.environment.parse(search_engine.api_key_name) or nil + if provider_type ~= "searxng" and api_key == nil or api_key == "" then return nil, "Environment variable " .. search_engine.api_key_name .. " is not set" end if provider_type == "tavily" then @@ -338,6 +338,26 @@ function M.web_search(opts, on_log) if resp.status ~= 200 then return nil, "Error: " .. resp.body end local jsn = vim.json.decode(resp.body) return search_engine.format_response_body(jsn) + elseif provider_type == "searxng" then + local searxng_api_url = Utils.environment.parse(search_engine.api_url_name) + if searxng_api_url == nil or searxng_api_url == "" then + return nil, "Environment variable " .. search_engine.api_url_name .. " is not set" + end + local query_params = vim.tbl_deep_extend("force", { + q = opts.query, + }, search_engine.extra_request_body) + local query_string = "" + for key, value in pairs(query_params) do + query_string = query_string .. key .. "=" .. vim.uri_encode(value) .. "&" + end + local resp = curl.get(searxng_api_url .. "?" .. query_string, { + headers = { + ["Content-Type"] = "application/json", + }, + }) + if resp.status ~= 200 then return nil, "Error: " .. resp.body end + local jsn = vim.json.decode(resp.body) + return search_engine.format_response_body(jsn) end return nil, "Error: No search engine found" end