Skip to content

Commit d59cdf4

Browse files
committed
Add Exa search backend
1 parent f72f75d commit d59cdf4

3 files changed

Lines changed: 69 additions & 0 deletions

File tree

.env.example

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,18 @@ MAX_WORKERS=30
4646
# API Keys and External Services
4747
# =============================================================================
4848

49+
# Web search provider for the `search` tool: serper (default) or exa
50+
# SEARCH_PROVIDER=exa
51+
4952
# Serper API for web search and Google Scholar
5053
# Get your key from: https://serper.dev/
5154
SERPER_KEY_ID=your_key
5255

56+
# Exa API for neural web search (used when SEARCH_PROVIDER=exa, or when this is
57+
# the only search key set)
58+
# Get your key from: https://dashboard.exa.ai/api-keys
59+
# EXA_API_KEY=your_key
60+
5361
# Jina API for web page reading
5462
# Get your key from: https://jina.ai/
5563
JINA_API_KEYS=your_key

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ cp .env.example .env
100100
Edit the `.env` file and provide your actual API keys and configuration values:
101101

102102
- **SERPER_KEY_ID**: Get your key from [Serper.dev](https://serper.dev/) for web search and Google Scholar
103+
- **EXA_API_KEY**: Get your key from [Exa](https://dashboard.exa.ai/api-keys) to use Exa neural search for the `search` tool. Set `SEARCH_PROVIDER=exa` to select it, or leave `SEARCH_PROVIDER` unset and it is used automatically when `SERPER_KEY_ID` is absent
103104
- **JINA_API_KEYS**: Get your key from [Jina.ai](https://jina.ai/) for web page reading
104105
- **API_KEY/API_BASE**: OpenAI-compatible API for page summarization from [OpenAI](https://platform.openai.com/)
105106
- **DASHSCOPE_API_KEY**: Get your key from [Dashscope](https://dashscope.aliyun.com/) for file parsing

inference/tool_search.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313

1414

1515
SERPER_KEY=os.environ.get('SERPER_KEY_ID')
16+
EXA_API_KEY=os.environ.get('EXA_API_KEY')
17+
# "serper" (default) or "exa"; falls back to exa when only EXA_API_KEY is set.
18+
SEARCH_PROVIDER=os.environ.get('SEARCH_PROVIDER') or ('exa' if EXA_API_KEY and not SERPER_KEY else 'serper')
1619

1720

1821
@register_tool("search", allow_overwrite=True)
@@ -106,7 +109,64 @@ def contains_chinese_basic(text: str) -> bool:
106109

107110

108111

112+
def exa_search(self, query: str):
113+
"""Exa neural search: one call returns ranked results with highlights."""
114+
payload = {
115+
"query": query,
116+
"type": "auto",
117+
"numResults": 10,
118+
"contents": {"highlights": True},
119+
}
120+
headers = {
121+
'x-api-key': EXA_API_KEY,
122+
'Content-Type': 'application/json',
123+
'x-exa-integration': 'Alibaba-NLP/DeepResearch-integration',
124+
}
125+
126+
results = None
127+
for i in range(5):
128+
try:
129+
response = requests.post(
130+
"https://api.exa.ai/search",
131+
json=payload,
132+
headers=headers,
133+
timeout=30,
134+
)
135+
response.raise_for_status()
136+
results = response.json()
137+
break
138+
except Exception as e:
139+
print(e)
140+
if i == 4:
141+
return f"Exa search Timeout, return None, Please try again later."
142+
continue
143+
144+
try:
145+
web_snippets = list()
146+
for idx, page in enumerate(results.get("results", []), start=1):
147+
date_published = ""
148+
if page.get("publishedDate"):
149+
date_published = "\nDate published: " + page["publishedDate"]
150+
151+
source = ""
152+
if page.get("author"):
153+
source = "\nSource: " + page["author"]
154+
155+
snippet = ""
156+
if page.get("highlights"):
157+
snippet = "\n" + " ... ".join(page["highlights"])
158+
159+
web_snippets.append(
160+
f"{idx}. [{page.get('title') or page.get('url')}]({page.get('url')}){date_published}{source}\n{snippet}"
161+
)
162+
163+
return f"An Exa search for '{query}' found {len(web_snippets)} results:\n\n## Web Results\n" + "\n\n".join(web_snippets)
164+
except:
165+
return f"No results found for '{query}'. Try with a more general query."
166+
109167
def search_with_serp(self, query: str):
168+
if SEARCH_PROVIDER == 'exa':
169+
return self.exa_search(query)
110170
result = self.google_search_with_serp(query)
111171
return result
112172

0 commit comments

Comments
 (0)