|
13 | 13 |
|
14 | 14 |
|
15 | 15 | 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') |
16 | 19 |
|
17 | 20 |
|
18 | 21 | @register_tool("search", allow_overwrite=True) |
@@ -106,7 +109,64 @@ def contains_chinese_basic(text: str) -> bool: |
106 | 109 |
|
107 | 110 |
|
108 | 111 |
|
| 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 | + |
109 | 167 | def search_with_serp(self, query: str): |
| 168 | + if SEARCH_PROVIDER == 'exa': |
| 169 | + return self.exa_search(query) |
110 | 170 | result = self.google_search_with_serp(query) |
111 | 171 | return result |
112 | 172 |
|
|
0 commit comments