enh: linkup

This commit is contained in:
Timothy Jaeryang Baek committed 2026-05-19 21:54:38 +04:00
1 parent 3c5e7968f0
commit 56c0d00e13
5 files changed
+158 -2

No files matched your search

+18
View File
@@ -1914,6 +1914,24 @@ YOUCOM_API_KEY = ConfigVar(
os.getenv('YOUCOM_API_KEY', ''),
)
LINKUP_API_KEY = ConfigVar(
'LINKUP_API_KEY',
'rag.web.search.linkup_api_key',
os.getenv('LINKUP_API_KEY', ''),
)
linkup_search_params = os.getenv('LINKUP_SEARCH_PARAMS', '')
try:
linkup_search_params = json.loads(linkup_search_params)
except json.JSONDecodeError:
linkup_search_params = {}
LINKUP_SEARCH_PARAMS = ConfigVar(
'LINKUP_SEARCH_PARAMS',
'rag.web.search.linkup_search_params',
linkup_search_params,
)
####################################
# Images
####################################
+4
View File
@@ -410,6 +410,8 @@ from open_webui.config import (
YANDEX_WEB_SEARCH_CONFIG,
YANDEX_WEB_SEARCH_URL,
YOUCOM_API_KEY,
LINKUP_API_KEY,
LINKUP_SEARCH_PARAMS,
YOUTUBE_LOADER_LANGUAGE,
YOUTUBE_LOADER_PROXY_URL,
AppConfig,
@@ -1130,6 +1132,8 @@ app.state.config.YANDEX_WEB_SEARCH_URL = YANDEX_WEB_SEARCH_URL
app.state.config.YANDEX_WEB_SEARCH_API_KEY = YANDEX_WEB_SEARCH_API_KEY
app.state.config.YANDEX_WEB_SEARCH_CONFIG = YANDEX_WEB_SEARCH_CONFIG
app.state.config.YOUCOM_API_KEY = YOUCOM_API_KEY
app.state.config.LINKUP_API_KEY = LINKUP_API_KEY
app.state.config.LINKUP_SEARCH_PARAMS = LINKUP_SEARCH_PARAMS
app.state.config.PLAYWRIGHT_WS_URL = PLAYWRIGHT_WS_URL
@@ -0,0 +1,76 @@
import logging
from typing import Optional
import requests
from open_webui.retrieval.web.main import SearchResult, get_filtered_results
log = logging.getLogger(__name__)
DEFAULT_LINKUP_PARAMS = {
'url': 'https://api.linkup.so/v1/search',
'depth': 'standard',
'outputType': 'sourcedAnswer',
}
def search_linkup(
api_key: str,
query: str,
count: int,
filter_list: Optional[list[str]] = None,
params: Optional[dict] = None,
) -> list[SearchResult]:
"""Search using the Linkup Search API.
``params`` is forwarded almost verbatim as the JSON body; only ``q``
and ``maxResults`` are injected automatically. The special key
``url`` (default ``https://api.linkup.so/v1/search``) is popped and
used as the endpoint.
"""
if hasattr(api_key, '__str__'):
api_key = str(api_key)
merged = {**DEFAULT_LINKUP_PARAMS, **(params or {})}
api_url = str(merged.pop('url', DEFAULT_LINKUP_PARAMS['url']))
payload = {**merged, 'q': query, 'maxResults': count}
try:
response = requests.post(
api_url,
headers={
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json',
},
json=payload,
timeout=30,
)
response.raise_for_status()
json_response = response.json()
output_type = merged.get('outputType', 'sourcedAnswer')
search_results = (
json_response.get('sources', [])
if output_type == 'sourcedAnswer'
else json_response.get('results', [])
)
if filter_list:
search_results = get_filtered_results(search_results, filter_list)
return [
SearchResult(
link=r.get('url', ''),
title=r.get('name') or r.get('title'),
snippet=r.get('content') or r.get('text') or r.get('snippet'),
)
for r in search_results
][:count]
except requests.exceptions.RequestException as e:
log.error(f'Linkup API request failed: {e}')
raise Exception(f'Linkup search failed: {str(e)}')
except Exception as e:
log.error(f'Error searching Linkup: {e}')
raise Exception(f'Linkup search error: {str(e)}')
+21
View File
@@ -107,6 +107,7 @@ from open_webui.retrieval.web.utils import get_web_loader
from open_webui.retrieval.web.yacy import search_yacy
from open_webui.retrieval.web.yandex import search_yandex
from open_webui.retrieval.web.ydc import search_youcom
from open_webui.retrieval.web.linkup import search_linkup
from open_webui.storage.provider import Storage
from open_webui.utils.access_control import has_permission
from open_webui.utils.access_control.files import has_access_to_file
@@ -553,6 +554,8 @@ async def get_rag_config(request: Request, user=Depends(get_admin_user)):
'YANDEX_WEB_SEARCH_API_KEY': request.app.state.config.YANDEX_WEB_SEARCH_API_KEY,
'YANDEX_WEB_SEARCH_CONFIG': request.app.state.config.YANDEX_WEB_SEARCH_CONFIG,
'YOUCOM_API_KEY': request.app.state.config.YOUCOM_API_KEY,
'LINKUP_API_KEY': request.app.state.config.LINKUP_API_KEY,
'LINKUP_SEARCH_PARAMS': request.app.state.config.LINKUP_SEARCH_PARAMS,
},
}
@@ -622,6 +625,8 @@ class WebConfig(BaseModel):
YANDEX_WEB_SEARCH_API_KEY: str | None = None
YANDEX_WEB_SEARCH_CONFIG: str | None = None
YOUCOM_API_KEY: str | None = None
LINKUP_API_KEY: str | None = None
LINKUP_SEARCH_PARAMS: dict | None = None
class ConfigForm(BaseModel):
@@ -1109,6 +1114,8 @@ async def update_rag_config(request: Request, form_data: ConfigForm, user=Depend
request.app.state.config.YANDEX_WEB_SEARCH_API_KEY = form_data.web.YANDEX_WEB_SEARCH_API_KEY
request.app.state.config.YANDEX_WEB_SEARCH_CONFIG = form_data.web.YANDEX_WEB_SEARCH_CONFIG
request.app.state.config.YOUCOM_API_KEY = form_data.web.YOUCOM_API_KEY
request.app.state.config.LINKUP_API_KEY = form_data.web.LINKUP_API_KEY
request.app.state.config.LINKUP_SEARCH_PARAMS = form_data.web.LINKUP_SEARCH_PARAMS
return {
'status': True,
@@ -1241,6 +1248,8 @@ async def update_rag_config(request: Request, form_data: ConfigForm, user=Depend
'YANDEX_WEB_SEARCH_API_KEY': request.app.state.config.YANDEX_WEB_SEARCH_API_KEY,
'YANDEX_WEB_SEARCH_CONFIG': request.app.state.config.YANDEX_WEB_SEARCH_CONFIG,
'YOUCOM_API_KEY': request.app.state.config.YOUCOM_API_KEY,
'LINKUP_API_KEY': request.app.state.config.LINKUP_API_KEY,
'LINKUP_SEARCH_PARAMS': request.app.state.config.LINKUP_SEARCH_PARAMS,
},
}
@@ -1876,6 +1885,7 @@ def search_web(request: Request, engine: str, query: str, user=None) -> list[Sea
- SOUGOU_API_SID + SOUGOU_API_SK
- SEARCHAPI_API_KEY + SEARCHAPI_ENGINE (by default `google`)
- SERPAPI_API_KEY + SERPAPI_ENGINE (by default `google`)
- LINKUP_API_KEY
Args:
query (str): The query to search for
"""
@@ -2166,6 +2176,17 @@ def search_web(request: Request, engine: str, query: str, user=None) -> list[Sea
request.app.state.config.WEB_SEARCH_RESULT_COUNT,
request.app.state.config.WEB_SEARCH_DOMAIN_FILTER_LIST,
)
elif engine == 'linkup':
if request.app.state.config.LINKUP_API_KEY:
return search_linkup(
api_key=request.app.state.config.LINKUP_API_KEY,
query=query,
count=request.app.state.config.WEB_SEARCH_RESULT_COUNT,
filter_list=request.app.state.config.WEB_SEARCH_DOMAIN_FILTER_LIST,
params=request.app.state.config.LINKUP_SEARCH_PARAMS,
)
else:
raise Exception('No LINKUP_API_KEY found in environment variables')
else:
raise Exception('No search engine API key found in environment variables')