1- """
2- Standalone internet search function that can query multiple providers (Exa, Tavily, Linkup)
3- without coupling to the existing project code. No environment variables are read here; all
4- API keys must be provided via function parameters.
5-
6- Returned structure is a dictionary with one key:
7- - items: list of {fileName, url, text, source, published_date, summary}
8-
9- Example:
10-
11- from nexent.core.tools.standalone_web_search import internet_search
12-
13- result = internet_search(
14- query="OpenAI o4 mini update",
15- api_keys={
16- "exa": "EXA_API_KEY",
17- "tavily": "TAVILY_API_KEY",
18- "linkup": "LINKUP_API_KEY",
19- },
20- providers=["exa", "tavily", "linkup"],
21- max_results_per_provider=5,
22- )
23-
24- for item in result["items"]:
25- print(item["fileName"], item["url"], item["summary"]) # Display URLs and individual summaries
26- """
1+ # -- encoding: utf-8 --
2+ # Copyright (c) 2024 Huawei Technologies Co., Ltd. All Rights Reserved.
3+ # This file is a part of the ModelEngine Project.
4+ # Licensed under the MIT License. See License.txt in the project root for license information.
5+ # ======================================================================================================================
276import json
287from dataclasses import dataclass
298from typing import Dict , List , Optional , Sequence
@@ -98,51 +77,30 @@ def _truncate(text: str, max_chars: int) -> str:
9877
9978def _generate_individual_summary (text : str , max_chars : int = 200 ) -> str :
10079 """为单个搜索结果生成独立摘要
101-
102- 策略:
103- - 如果内容较短,直接返回
104- - 如果内容较长,提取前几个句子作为摘要
105- - 确保摘要不超过最大字符限制
80+
81+ 策略:截取前max_chars个字符,并在最后一个句子结束符处截断,确保语义完整
10682 """
10783 if not text :
10884 return ""
109-
85+
11086 # 如果内容已经很短,直接返回
11187 if len (text ) <= max_chars :
11288 return text
113-
114- # 按句子分割(简单按句号分割)
115- sentences = text .split ('. ' )
116-
117- # 收集句子直到达到字符限制
118- summary_parts = []
119- current_length = 0
120-
121- for sentence in sentences :
122- sentence = sentence .strip ()
123- if not sentence :
124- continue
125-
126- # 确保句子以句号结束
127- if not sentence .endswith ('.' ):
128- sentence += '.'
129-
130- sentence_length = len (sentence ) + 1 # +1 for space
131-
132- # 如果添加这个句子会超过限制,且已经有内容,就停止
133- if current_length + sentence_length > max_chars and summary_parts :
134- break
135-
136- summary_parts .append (sentence )
137- current_length += sentence_length
138-
139- summary = '. ' .join (summary_parts )
140-
141- # 确保不超过最大字符限制
142- if len (summary ) > max_chars :
143- summary = summary [:max_chars ].rstrip () + "…"
144-
145- return summary
89+
90+ # 截取前max_chars个字符
91+ truncated = text [:max_chars ]
92+
93+ # 找到最后一个句子结束符的位置(优先级:. > ! > ?)
94+ last_period = truncated .rfind ('.' )
95+ last_exclaim = truncated .rfind ('!' )
96+ last_question = truncated .rfind ('?' )
97+ last_sentence_end = max (last_period , last_exclaim , last_question )
98+
99+ # 如果找到了句子结束符,在该位置截断;否则直接截断并添加省略号
100+ if last_sentence_end > 0 :
101+ return truncated [:last_sentence_end + 1 ].rstrip ()
102+ else :
103+ return truncated .rstrip () + "…"
146104
147105
148106def _internet_search (
@@ -160,6 +118,7 @@ def _internet_search(
160118 if api_keys .get (name ):
161119 selected .append (name )
162120 items : List [SearchItem ] = []
121+ errors = [] # 记录失败的搜索工具
163122
164123 # Exa
165124 if "exa" in selected and api_keys .get ("exa" ):
@@ -188,8 +147,9 @@ def _internet_search(
188147 }
189148 )
190149 )
191- except Exception :
192- pass
150+ except Exception as e :
151+ sys_plugin_logger .warning (f'Failed to search in Exa tool: { str (e )} ' )
152+ errors .append ("exa" )
193153
194154 # Tavily
195155 if "tavily" in selected and api_keys .get ("tavily" ):
@@ -216,8 +176,9 @@ def _internet_search(
216176 }
217177 )
218178 )
219- except Exception :
220- pass
179+ except Exception as e :
180+ sys_plugin_logger .warning (f'Failed to search in Tavily tool: { str (e )} ' )
181+ errors .append ("tavily" )
221182
222183 # Linkup
223184 if "linkup" in selected and api_keys .get ("linkup" ):
@@ -245,8 +206,16 @@ def _internet_search(
245206 }
246207 )
247208 )
248- except Exception :
249- pass
209+ except Exception as e :
210+ sys_plugin_logger .warning (f'Failed to search in Linkup tool: { str (e )} ' )
211+ errors .append ("linkup" )
212+
213+ # 如果所有搜索都失败了,才抛出异常
214+ if not items and errors :
215+ raise FitException (
216+ InternalErrorCode .CLIENT_ERROR ,
217+ f'All search tools failed: { ", " .join (errors )} '
218+ )
250219
251220 # 去重逻辑
252221 seen = set ()
0 commit comments