v0.12: stabilize search routing and profile-driven phase4 pipeline
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
"""通用搜索客户端(Exa 优先,Tavily fallback)。
|
||||
"""通用搜索客户端(Serper / Exa / Tavily 路由)。
|
||||
|
||||
为 build_glossary.py 这类术语核查场景服务。
|
||||
|
||||
关键设计:
|
||||
- `trust_env=False` 绕开系统 socks 代理(Clash on macOS 配 socks5 时 httpx 会 TLS EOF)
|
||||
- Exa 优先:LinkedIn / 官网 / 百度百科返回质量最高
|
||||
- 遇到配额问题自动降级到 Tavily 或返回 empty
|
||||
- 专利 / Scholar / News 优先 Serper,保证 Google Patents / Google Scholar 路径被真正调用
|
||||
- 通用网页 Exa 优先,Tavily fallback
|
||||
- 遇到配额问题自动降级或返回 empty
|
||||
- 不做深度 crawl,只要摘要
|
||||
"""
|
||||
|
||||
@@ -125,10 +126,11 @@ class SearchClient:
|
||||
所有客户端都延迟导入 serper_client,避免没装 SERPAPI_KEY 时 import 炸。
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
def __init__(self, *, strict_specialized: bool = True) -> None:
|
||||
self._exa: ExaClient | None = None
|
||||
self._tavily: TavilyClient | None = None
|
||||
self._serper = None # 惰性实例化
|
||||
self.strict_specialized = strict_specialized
|
||||
try:
|
||||
self._exa = ExaClient()
|
||||
except SearchError:
|
||||
@@ -137,10 +139,9 @@ class SearchClient:
|
||||
self._tavily = TavilyClient()
|
||||
except SearchError:
|
||||
pass
|
||||
if not (self._exa or self._tavily):
|
||||
raise SearchError(
|
||||
"neither EXA_API_KEY nor TAVILY_API_KEY available"
|
||||
)
|
||||
self._has_serper_key = bool(os.environ.get("SERPER_API_KEY") or os.environ.get("SERPAPI_KEY"))
|
||||
if not (self._exa or self._tavily or self._has_serper_key):
|
||||
raise SearchError("no search API key available: set SERPER_API_KEY, SERPAPI_KEY, EXA_API_KEY, or TAVILY_API_KEY")
|
||||
|
||||
def _get_serper(self):
|
||||
"""惰性创建 SerperClient。没 key 时返回 None。"""
|
||||
@@ -190,9 +191,12 @@ class SearchClient:
|
||||
try:
|
||||
hits = serper.patents(query, num_results=num_results)
|
||||
return [SearchHit(h.title, h.url, h.snippet) for h in hits]
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as exc:
|
||||
if self.strict_specialized:
|
||||
raise SearchError(f"serper patents failed: {exc}") from exc
|
||||
# 降级:通用搜索加 site 限定
|
||||
if self.strict_specialized:
|
||||
raise SearchError("serper unavailable for patents route; refusing silent fallback")
|
||||
return self.search(f"site:patents.google.com {query}", num_results=num_results)
|
||||
|
||||
def scholar(
|
||||
@@ -215,8 +219,11 @@ class SearchClient:
|
||||
)
|
||||
for h in hits
|
||||
]
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as exc:
|
||||
if self.strict_specialized:
|
||||
raise SearchError(f"serper scholar failed: {exc}") from exc
|
||||
if self.strict_specialized:
|
||||
raise SearchError("serper unavailable for scholar route; refusing silent fallback")
|
||||
return self.search(query, num_results=num_results)
|
||||
|
||||
def news(
|
||||
@@ -239,8 +246,11 @@ class SearchClient:
|
||||
)
|
||||
for h in hits
|
||||
]
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as exc:
|
||||
if self.strict_specialized:
|
||||
raise SearchError(f"serper news failed: {exc}") from exc
|
||||
if self.strict_specialized:
|
||||
raise SearchError("serper unavailable for news route; refusing silent fallback")
|
||||
return self.search(query, num_results=num_results)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user