diff --git a/crates/zclaw-hands/src/hands/researcher.rs b/crates/zclaw-hands/src/hands/researcher.rs index 829ae7a..742c910 100644 --- a/crates/zclaw-hands/src/hands/researcher.rs +++ b/crates/zclaw-hands/src/hands/researcher.rs @@ -324,11 +324,13 @@ impl ResearcherHand { self.search_native(&query.query, max_results).await? } SearchEngine::DuckDuckGo => { - self.search_duckduckgo_html(&query.query, max_results).await? + // DDG在国内不可用,降级到百度 + tracing::warn!(target: "researcher", "DuckDuckGo在国内不可用,降级到百度"); + self.search_baidu(&query.query, max_results).await? } SearchEngine::Google => { - tracing::warn!(target: "researcher", "Google 不支持直接搜索,降级到 Bing"); - self.search_bing(&query.query, max_results).await? + tracing::warn!(target: "researcher", "Google在国内不可用,降级到百度"); + self.search_baidu(&query.query, max_results).await? } SearchEngine::Bing => { self.search_bing(&query.query, max_results).await? @@ -348,48 +350,32 @@ impl ResearcherHand { Ok(results) } - /// Rust-native multi-engine search with Chinese auto-detection + /// Rust-native multi-engine search — optimized for China mainland users + /// Priority: Baidu + Bing CN (both always work in China) + /// DuckDuckGo as optional fallback (may be blocked by GFW) async fn search_native(&self, query: &str, max_results: usize) -> Result> { - let has_cjk = query.chars().any(|c| is_cjk_char(c)); - - // Strategy: try multiple engines in parallel, merge results let mut all_results = Vec::new(); - if has_cjk { - // Chinese query: Bing CN + Baidu + DuckDuckGo in parallel - let bing_fut = self.search_bing(query, max_results); - let baidu_fut = self.search_baidu(query, max_results); - let ddg_fut = self.search_duckduckgo_html(query, max_results); + // Always use Baidu + Bing CN in parallel (both work in China) + let baidu_fut = self.search_baidu(query, max_results); + let bing_fut = self.search_bing(query, max_results); - let (bing_res, baidu_res, ddg_res) = tokio::join!( - async { bing_fut.await }, - async { baidu_fut.await }, - async { ddg_fut.await }, - ); + let (baidu_res, bing_res) = tokio::join!( + async { baidu_fut.await }, + async { bing_fut.await }, + ); - if let Ok(r) = bing_res { - all_results.extend(r); - } - if let Ok(r) = baidu_res { - all_results.extend(r); - } - if let Ok(r) = ddg_res { - all_results.extend(r); - } - } else { - // English query: DuckDuckGo HTML first, then Bing - let ddg_fut = self.search_duckduckgo_html(query, max_results); - let bing_fut = self.search_bing(query, max_results); + if let Ok(r) = baidu_res { + all_results.extend(r); + } + if let Ok(r) = bing_res { + all_results.extend(r); + } - let (ddg_res, bing_res) = tokio::join!( - async { ddg_fut.await }, - async { bing_fut.await }, - ); - - if let Ok(r) = ddg_res { - all_results.extend(r); - } - if let Ok(r) = bing_res { + // If both primary engines returned nothing, try DDG as last resort + if all_results.is_empty() { + tracing::info!(target: "researcher", "Primary engines empty, trying DuckDuckGo as fallback"); + if let Ok(r) = self.search_duckduckgo_html(query, max_results).await { all_results.extend(r); } }