diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py
index ee540f5598..2084fc638a 100644
--- a/backend/open_webui/config.py
+++ b/backend/open_webui/config.py
@@ -1271,6 +1271,9 @@ AZURE_AI_SEARCH_ENDPOINT = os.getenv('AZURE_AI_SEARCH_ENDPOINT', '')
AZURE_AI_SEARCH_INDEX_NAME = os.getenv('AZURE_AI_SEARCH_INDEX_NAME', '')
EXA_API_KEY = os.getenv('EXA_API_KEY', '')
+EXA_MAX_CONTENT_LENGTH = int(os.environ['EXA_MAX_CONTENT_LENGTH']) if os.getenv('EXA_MAX_CONTENT_LENGTH') else None
+if EXA_MAX_CONTENT_LENGTH is not None and EXA_MAX_CONTENT_LENGTH <= 0:
+ raise ValueError('EXA_MAX_CONTENT_LENGTH must be a positive integer or unset')
PERPLEXITY_API_KEY = os.getenv('PERPLEXITY_API_KEY', '')
@@ -3001,6 +3004,7 @@ DEFAULT_CONFIG = {
'web.search.azure_ai_search_endpoint': AZURE_AI_SEARCH_ENDPOINT,
'web.search.azure_ai_search_index_name': AZURE_AI_SEARCH_INDEX_NAME,
'web.search.exa_api_key': EXA_API_KEY,
+ 'web.search.exa_max_content_length': EXA_MAX_CONTENT_LENGTH,
'web.search.perplexity_api_key': PERPLEXITY_API_KEY,
'web.search.perplexity_model': PERPLEXITY_MODEL,
'web.search.perplexity_search_context_usage': PERPLEXITY_SEARCH_CONTEXT_USAGE,
diff --git a/backend/open_webui/retrieval/web/exa.py b/backend/open_webui/retrieval/web/exa.py
index 068cc380b9..803428a342 100644
--- a/backend/open_webui/retrieval/web/exa.py
+++ b/backend/open_webui/retrieval/web/exa.py
@@ -1,6 +1,4 @@
import logging
-from dataclasses import dataclass
-from typing import Optional
import requests
from open_webui.retrieval.web.main import SearchResult
@@ -10,18 +8,12 @@ log = logging.getLogger(__name__)
EXA_API_BASE = 'https://api.exa.ai'
-@dataclass
-class ExaResult:
- url: str
- title: str
- text: str
-
-
def search_exa(
api_key: str,
query: str,
count: int,
- filter_list: Optional[list[str]] = None,
+ filter_list: list[str] | None = None,
+ max_content_length: int | None = None,
) -> list[SearchResult]:
"""Search using Exa Search API and return the results as a list of SearchResult objects.
@@ -29,7 +21,8 @@ def search_exa(
api_key (str): A Exa Search API key
query (str): The query to search for
count (int): Number of results to return
- filter_list (Optional[list[str]]): List of domains to filter results by
+ filter_list (list[str] | None): List of domains to filter results by
+ max_content_length (int | None): Maximum characters per result; None leaves text unlimited.
"""
log.info('Searching with Exa for query: %s', query)
@@ -39,7 +32,7 @@ def search_exa(
'query': query,
'numResults': count or 5,
'includeDomains': filter_list,
- 'contents': {'text': True, 'highlights': True},
+ 'contents': {'text': {'maxCharacters': max_content_length} if max_content_length is not None else True},
'type': 'auto', # Use the auto search type (keyword or neural)
}
@@ -48,22 +41,13 @@ def search_exa(
response.raise_for_status()
data = response.json()
- results = []
- for result in data['results']:
- results.append(
- ExaResult(
- url=result['url'],
- title=result['title'],
- text=result['text'],
- )
- )
-
+ results = data['results']
log.info('Found %s results', len(results))
return [
SearchResult(
- link=result.url,
- title=result.title,
- snippet=result.text,
+ link=result['url'],
+ title=result['title'],
+ snippet=(result.get('text') or '')[:max_content_length],
)
for result in results
]
diff --git a/backend/open_webui/routers/retrieval.py b/backend/open_webui/routers/retrieval.py
index 55813341a2..5c7a323565 100644
--- a/backend/open_webui/routers/retrieval.py
+++ b/backend/open_webui/routers/retrieval.py
@@ -129,7 +129,7 @@ from open_webui.utils.misc import (
calculate_sha256_string,
sanitize_text_for_db,
)
-from pydantic import BaseModel
+from pydantic import BaseModel, Field
from sqlalchemy.ext.asyncio import AsyncSession
log = logging.getLogger(__name__)
@@ -297,6 +297,7 @@ RETRIEVAL_CONFIG_KEYS = {
'ENABLE_WEB_SEARCH_CONFIRMATION': 'web.search.confirmation.enable',
'WEB_SEARCH_CONFIRMATION_CONTENT': 'web.search.confirmation.content',
'EXA_API_KEY': 'web.search.exa_api_key',
+ 'EXA_MAX_CONTENT_LENGTH': 'web.search.exa_max_content_length',
'EXTERNAL_DOCUMENT_LOADER_API_KEY': 'rag.external_document_loader_api_key',
'EXTERNAL_DOCUMENT_LOADER_HEADERS': 'rag.external_document_loader_headers',
'EXTERNAL_DOCUMENT_LOADER_URL': 'rag.external_document_loader_url',
@@ -745,6 +746,7 @@ async def get_rag_config(request: Request, user=Depends(get_admin_user)):
'BING_SEARCH_V7_ENDPOINT': config.BING_SEARCH_V7_ENDPOINT,
'BING_SEARCH_V7_SUBSCRIPTION_KEY': config.BING_SEARCH_V7_SUBSCRIPTION_KEY,
'EXA_API_KEY': config.EXA_API_KEY,
+ 'EXA_MAX_CONTENT_LENGTH': config.EXA_MAX_CONTENT_LENGTH,
'PERPLEXITY_API_KEY': config.PERPLEXITY_API_KEY,
'PERPLEXITY_MODEL': config.PERPLEXITY_MODEL,
'PERPLEXITY_SEARCH_CONTEXT_USAGE': config.PERPLEXITY_SEARCH_CONTEXT_USAGE,
@@ -824,6 +826,7 @@ class WebConfig(BaseModel):
BING_SEARCH_V7_ENDPOINT: str | None = None
BING_SEARCH_V7_SUBSCRIPTION_KEY: str | None = None
EXA_API_KEY: str | None = None
+ EXA_MAX_CONTENT_LENGTH: int | None = Field(default=None, gt=0, strict=True)
PERPLEXITY_API_KEY: str | None = None
PERPLEXITY_MODEL: str | None = None
PERPLEXITY_SEARCH_CONTEXT_USAGE: str | None = None
@@ -1342,6 +1345,7 @@ async def update_rag_config(request: Request, form_data: ConfigForm, user=Depend
config.BING_SEARCH_V7_ENDPOINT = form_data.web.BING_SEARCH_V7_ENDPOINT
config.BING_SEARCH_V7_SUBSCRIPTION_KEY = form_data.web.BING_SEARCH_V7_SUBSCRIPTION_KEY
config.EXA_API_KEY = form_data.web.EXA_API_KEY
+ config.EXA_MAX_CONTENT_LENGTH = form_data.web.EXA_MAX_CONTENT_LENGTH
config.PERPLEXITY_API_KEY = form_data.web.PERPLEXITY_API_KEY
config.PERPLEXITY_MODEL = form_data.web.PERPLEXITY_MODEL
config.PERPLEXITY_SEARCH_CONTEXT_USAGE = form_data.web.PERPLEXITY_SEARCH_CONTEXT_USAGE
@@ -1494,6 +1498,7 @@ async def update_rag_config(request: Request, form_data: ConfigForm, user=Depend
'BING_SEARCH_V7_ENDPOINT': config.BING_SEARCH_V7_ENDPOINT,
'BING_SEARCH_V7_SUBSCRIPTION_KEY': config.BING_SEARCH_V7_SUBSCRIPTION_KEY,
'EXA_API_KEY': config.EXA_API_KEY,
+ 'EXA_MAX_CONTENT_LENGTH': config.EXA_MAX_CONTENT_LENGTH,
'PERPLEXITY_API_KEY': config.PERPLEXITY_API_KEY,
'PERPLEXITY_MODEL': config.PERPLEXITY_MODEL,
'PERPLEXITY_SEARCH_CONTEXT_USAGE': config.PERPLEXITY_SEARCH_CONTEXT_USAGE,
@@ -2691,6 +2696,7 @@ async def search_web(request: Request, engine: str, query: str, user=None) -> li
query,
config.WEB_SEARCH_RESULT_COUNT,
config.WEB_SEARCH_DOMAIN_FILTER_LIST,
+ max_content_length=config.EXA_MAX_CONTENT_LENGTH,
)
else:
raise Exception('No EXA_API_KEY found in environment variables')
diff --git a/src/lib/components/admin/Settings/WebSearch.svelte b/src/lib/components/admin/Settings/WebSearch.svelte
index e77814f3a4..3570a03cde 100644
--- a/src/lib/components/admin/Settings/WebSearch.svelte
+++ b/src/lib/components/admin/Settings/WebSearch.svelte
@@ -98,7 +98,11 @@
: (webConfig.LINKUP_SEARCH_PARAMS ?? {});
const res = await updateRAGConfig(localStorage.token, {
- web: { ...webConfig, LINKUP_SEARCH_PARAMS: linkupParams }
+ web: {
+ ...webConfig,
+ EXA_MAX_CONTENT_LENGTH: webConfig.EXA_MAX_CONTENT_LENGTH ?? null,
+ LINKUP_SEARCH_PARAMS: linkupParams
+ }
});
// Convert arrays back to strings for display
@@ -711,6 +715,24 @@
bind:value={webConfig.EXA_API_KEY}
/>
+