mirror of
https://github.com/open-webui/open-webui
synced 2025-04-16 21:42:50 +00:00
feat: add web search via SerpApi
This commit is contained in:
parent
e4d7d41df6
commit
27d395ba06
@ -1803,6 +1803,18 @@ SEARCHAPI_ENGINE = PersistentConfig(
|
|||||||
os.getenv("SEARCHAPI_ENGINE", ""),
|
os.getenv("SEARCHAPI_ENGINE", ""),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
SERPAPI_API_KEY = PersistentConfig(
|
||||||
|
"SERPAPI_API_KEY",
|
||||||
|
"rag.web.search.serpapi_api_key",
|
||||||
|
os.getenv("SERPAPI_API_KEY", ""),
|
||||||
|
)
|
||||||
|
|
||||||
|
SERPAPI_ENGINE = PersistentConfig(
|
||||||
|
"SERPAPI_ENGINE",
|
||||||
|
"rag.web.search.serpapi_engine",
|
||||||
|
os.getenv("SERPAPI_ENGINE", ""),
|
||||||
|
)
|
||||||
|
|
||||||
BING_SEARCH_V7_ENDPOINT = PersistentConfig(
|
BING_SEARCH_V7_ENDPOINT = PersistentConfig(
|
||||||
"BING_SEARCH_V7_ENDPOINT",
|
"BING_SEARCH_V7_ENDPOINT",
|
||||||
"rag.web.search.bing_search_v7_endpoint",
|
"rag.web.search.bing_search_v7_endpoint",
|
||||||
|
@ -179,6 +179,8 @@ from open_webui.config import (
|
|||||||
JINA_API_KEY,
|
JINA_API_KEY,
|
||||||
SEARCHAPI_API_KEY,
|
SEARCHAPI_API_KEY,
|
||||||
SEARCHAPI_ENGINE,
|
SEARCHAPI_ENGINE,
|
||||||
|
SERPAPI_API_KEY,
|
||||||
|
SERPAPI_ENGINE,
|
||||||
SEARXNG_QUERY_URL,
|
SEARXNG_QUERY_URL,
|
||||||
SERPER_API_KEY,
|
SERPER_API_KEY,
|
||||||
SERPLY_API_KEY,
|
SERPLY_API_KEY,
|
||||||
@ -546,6 +548,8 @@ app.state.config.SERPLY_API_KEY = SERPLY_API_KEY
|
|||||||
app.state.config.TAVILY_API_KEY = TAVILY_API_KEY
|
app.state.config.TAVILY_API_KEY = TAVILY_API_KEY
|
||||||
app.state.config.SEARCHAPI_API_KEY = SEARCHAPI_API_KEY
|
app.state.config.SEARCHAPI_API_KEY = SEARCHAPI_API_KEY
|
||||||
app.state.config.SEARCHAPI_ENGINE = SEARCHAPI_ENGINE
|
app.state.config.SEARCHAPI_ENGINE = SEARCHAPI_ENGINE
|
||||||
|
app.state.config.SERPAPI_API_KEY = SERPAPI_API_KEY
|
||||||
|
app.state.config.SERPAPI_ENGINE = SERPAPI_ENGINE
|
||||||
app.state.config.JINA_API_KEY = JINA_API_KEY
|
app.state.config.JINA_API_KEY = JINA_API_KEY
|
||||||
app.state.config.BING_SEARCH_V7_ENDPOINT = BING_SEARCH_V7_ENDPOINT
|
app.state.config.BING_SEARCH_V7_ENDPOINT = BING_SEARCH_V7_ENDPOINT
|
||||||
app.state.config.BING_SEARCH_V7_SUBSCRIPTION_KEY = BING_SEARCH_V7_SUBSCRIPTION_KEY
|
app.state.config.BING_SEARCH_V7_SUBSCRIPTION_KEY = BING_SEARCH_V7_SUBSCRIPTION_KEY
|
||||||
|
48
backend/open_webui/retrieval/web/serpapi.py
Normal file
48
backend/open_webui/retrieval/web/serpapi.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from open_webui.retrieval.web.main import SearchResult, get_filtered_results
|
||||||
|
from open_webui.env import SRC_LOG_LEVELS
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
log.setLevel(SRC_LOG_LEVELS["RAG"])
|
||||||
|
|
||||||
|
|
||||||
|
def search_serpapi(
|
||||||
|
api_key: str,
|
||||||
|
engine: str,
|
||||||
|
query: str,
|
||||||
|
count: int,
|
||||||
|
filter_list: Optional[list[str]] = None,
|
||||||
|
) -> list[SearchResult]:
|
||||||
|
"""Search using serpapi.com's API and return the results as a list of SearchResult objects.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
api_key (str): A serpapi.com API key
|
||||||
|
query (str): The query to search for
|
||||||
|
"""
|
||||||
|
url = "https://serpapi.com/search"
|
||||||
|
|
||||||
|
engine = engine or "google"
|
||||||
|
|
||||||
|
payload = {"engine": engine, "q": query, "api_key": api_key}
|
||||||
|
|
||||||
|
url = f"{url}?{urlencode(payload)}"
|
||||||
|
response = requests.request("GET", url)
|
||||||
|
|
||||||
|
json_response = response.json()
|
||||||
|
log.info(f"results from serpapi search: {json_response}")
|
||||||
|
|
||||||
|
results = sorted(
|
||||||
|
json_response.get("organic_results", []), key=lambda x: x.get("position", 0)
|
||||||
|
)
|
||||||
|
if filter_list:
|
||||||
|
results = get_filtered_results(results, filter_list)
|
||||||
|
return [
|
||||||
|
SearchResult(
|
||||||
|
link=result["link"], title=result["title"], snippet=result["snippet"]
|
||||||
|
)
|
||||||
|
for result in results[:count]
|
||||||
|
]
|
@ -50,6 +50,7 @@ from open_webui.retrieval.web.duckduckgo import search_duckduckgo
|
|||||||
from open_webui.retrieval.web.google_pse import search_google_pse
|
from open_webui.retrieval.web.google_pse import search_google_pse
|
||||||
from open_webui.retrieval.web.jina_search import search_jina
|
from open_webui.retrieval.web.jina_search import search_jina
|
||||||
from open_webui.retrieval.web.searchapi import search_searchapi
|
from open_webui.retrieval.web.searchapi import search_searchapi
|
||||||
|
from open_webui.retrieval.web.serpapi import search_serpapi
|
||||||
from open_webui.retrieval.web.searxng import search_searxng
|
from open_webui.retrieval.web.searxng import search_searxng
|
||||||
from open_webui.retrieval.web.serper import search_serper
|
from open_webui.retrieval.web.serper import search_serper
|
||||||
from open_webui.retrieval.web.serply import search_serply
|
from open_webui.retrieval.web.serply import search_serply
|
||||||
@ -388,6 +389,8 @@ async def get_rag_config(request: Request, user=Depends(get_admin_user)):
|
|||||||
"tavily_api_key": request.app.state.config.TAVILY_API_KEY,
|
"tavily_api_key": request.app.state.config.TAVILY_API_KEY,
|
||||||
"searchapi_api_key": request.app.state.config.SEARCHAPI_API_KEY,
|
"searchapi_api_key": request.app.state.config.SEARCHAPI_API_KEY,
|
||||||
"searchapi_engine": request.app.state.config.SEARCHAPI_ENGINE,
|
"searchapi_engine": request.app.state.config.SEARCHAPI_ENGINE,
|
||||||
|
"serpapi_api_key": request.app.state.config.SERPAPI_API_KEY,
|
||||||
|
"serpapi_engine": request.app.state.config.SERPAPI_ENGINE,
|
||||||
"jina_api_key": request.app.state.config.JINA_API_KEY,
|
"jina_api_key": request.app.state.config.JINA_API_KEY,
|
||||||
"bing_search_v7_endpoint": request.app.state.config.BING_SEARCH_V7_ENDPOINT,
|
"bing_search_v7_endpoint": request.app.state.config.BING_SEARCH_V7_ENDPOINT,
|
||||||
"bing_search_v7_subscription_key": request.app.state.config.BING_SEARCH_V7_SUBSCRIPTION_KEY,
|
"bing_search_v7_subscription_key": request.app.state.config.BING_SEARCH_V7_SUBSCRIPTION_KEY,
|
||||||
@ -439,6 +442,8 @@ class WebSearchConfig(BaseModel):
|
|||||||
tavily_api_key: Optional[str] = None
|
tavily_api_key: Optional[str] = None
|
||||||
searchapi_api_key: Optional[str] = None
|
searchapi_api_key: Optional[str] = None
|
||||||
searchapi_engine: Optional[str] = None
|
searchapi_engine: Optional[str] = None
|
||||||
|
serpapi_api_key: Optional[str] = None
|
||||||
|
serpapi_engine: Optional[str] = None
|
||||||
jina_api_key: Optional[str] = None
|
jina_api_key: Optional[str] = None
|
||||||
bing_search_v7_endpoint: Optional[str] = None
|
bing_search_v7_endpoint: Optional[str] = None
|
||||||
bing_search_v7_subscription_key: Optional[str] = None
|
bing_search_v7_subscription_key: Optional[str] = None
|
||||||
@ -545,6 +550,13 @@ async def update_rag_config(
|
|||||||
form_data.web.search.searchapi_engine
|
form_data.web.search.searchapi_engine
|
||||||
)
|
)
|
||||||
|
|
||||||
|
request.app.state.config.SERPAPI_API_KEY = (
|
||||||
|
form_data.web.search.serpapi_api_key
|
||||||
|
)
|
||||||
|
request.app.state.config.SERPAPI_ENGINE = (
|
||||||
|
form_data.web.search.serpapi_engine
|
||||||
|
)
|
||||||
|
|
||||||
request.app.state.config.JINA_API_KEY = form_data.web.search.jina_api_key
|
request.app.state.config.JINA_API_KEY = form_data.web.search.jina_api_key
|
||||||
request.app.state.config.BING_SEARCH_V7_ENDPOINT = (
|
request.app.state.config.BING_SEARCH_V7_ENDPOINT = (
|
||||||
form_data.web.search.bing_search_v7_endpoint
|
form_data.web.search.bing_search_v7_endpoint
|
||||||
@ -604,6 +616,8 @@ async def update_rag_config(
|
|||||||
"serply_api_key": request.app.state.config.SERPLY_API_KEY,
|
"serply_api_key": request.app.state.config.SERPLY_API_KEY,
|
||||||
"serachapi_api_key": request.app.state.config.SEARCHAPI_API_KEY,
|
"serachapi_api_key": request.app.state.config.SEARCHAPI_API_KEY,
|
||||||
"searchapi_engine": request.app.state.config.SEARCHAPI_ENGINE,
|
"searchapi_engine": request.app.state.config.SEARCHAPI_ENGINE,
|
||||||
|
"serpapi_api_key": request.app.state.config.SERPAPI_API_KEY,
|
||||||
|
"serpapi_engine": request.app.state.config.SERPAPI_ENGINE,
|
||||||
"tavily_api_key": request.app.state.config.TAVILY_API_KEY,
|
"tavily_api_key": request.app.state.config.TAVILY_API_KEY,
|
||||||
"jina_api_key": request.app.state.config.JINA_API_KEY,
|
"jina_api_key": request.app.state.config.JINA_API_KEY,
|
||||||
"bing_search_v7_endpoint": request.app.state.config.BING_SEARCH_V7_ENDPOINT,
|
"bing_search_v7_endpoint": request.app.state.config.BING_SEARCH_V7_ENDPOINT,
|
||||||
@ -1127,6 +1141,7 @@ def search_web(request: Request, engine: str, query: str) -> list[SearchResult]:
|
|||||||
- TAVILY_API_KEY
|
- TAVILY_API_KEY
|
||||||
- EXA_API_KEY
|
- EXA_API_KEY
|
||||||
- SEARCHAPI_API_KEY + SEARCHAPI_ENGINE (by default `google`)
|
- SEARCHAPI_API_KEY + SEARCHAPI_ENGINE (by default `google`)
|
||||||
|
- SERPAPI_API_KEY + SERPAPI_ENGINE (by default `google`)
|
||||||
Args:
|
Args:
|
||||||
query (str): The query to search for
|
query (str): The query to search for
|
||||||
"""
|
"""
|
||||||
@ -1255,6 +1270,17 @@ def search_web(request: Request, engine: str, query: str) -> list[SearchResult]:
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise Exception("No SEARCHAPI_API_KEY found in environment variables")
|
raise Exception("No SEARCHAPI_API_KEY found in environment variables")
|
||||||
|
elif engine == "serpapi":
|
||||||
|
if request.app.state.config.SERPAPI_API_KEY:
|
||||||
|
return search_serpapi(
|
||||||
|
request.app.state.config.SERPAPI_API_KEY,
|
||||||
|
request.app.state.config.SERPAPI_ENGINE,
|
||||||
|
query,
|
||||||
|
request.app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
|
||||||
|
request.app.state.config.RAG_WEB_SEARCH_DOMAIN_FILTER_LIST,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise Exception("No SERPAPI_API_KEY found in environment variables")
|
||||||
elif engine == "jina":
|
elif engine == "jina":
|
||||||
return search_jina(
|
return search_jina(
|
||||||
request.app.state.config.JINA_API_KEY,
|
request.app.state.config.JINA_API_KEY,
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
'serper',
|
'serper',
|
||||||
'serply',
|
'serply',
|
||||||
'searchapi',
|
'searchapi',
|
||||||
|
'serpapi',
|
||||||
'duckduckgo',
|
'duckduckgo',
|
||||||
'tavily',
|
'tavily',
|
||||||
'jina',
|
'jina',
|
||||||
@ -268,6 +269,34 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{:else if webConfig.search.engine === 'serpapi'}
|
||||||
|
<div>
|
||||||
|
<div class=" self-center text-xs font-medium mb-1">
|
||||||
|
{$i18n.t('SerpApi API Key')}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<SensitiveInput
|
||||||
|
placeholder={$i18n.t('Enter SerpApi API Key')}
|
||||||
|
bind:value={webConfig.search.serpapi_api_key}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="mt-1.5">
|
||||||
|
<div class=" self-center text-xs font-medium mb-1">
|
||||||
|
{$i18n.t('SerpApi Engine')}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex w-full">
|
||||||
|
<div class="flex-1">
|
||||||
|
<input
|
||||||
|
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||||
|
type="text"
|
||||||
|
placeholder={$i18n.t('Enter SerpApi Engine')}
|
||||||
|
bind:value={webConfig.search.serpapi_engine}
|
||||||
|
autocomplete="off"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{:else if webConfig.search.engine === 'tavily'}
|
{:else if webConfig.search.engine === 'tavily'}
|
||||||
<div>
|
<div>
|
||||||
<div class=" self-center text-xs font-medium mb-1">
|
<div class=" self-center text-xs font-medium mb-1">
|
||||||
|
Loading…
Reference in New Issue
Block a user