feat(web_search): switch default backend from Bing to DuckDuckGo (#2132)

- Make DuckDuckGo the default search provider with Bing fallback
- Update tool description, config docs, TOOL_SURFACE, doctor output
- Update all search default tests and references
- Bing remains selectable via [search] provider = "bing"
This commit is contained in:
Hunter Bown
2026-05-26 16:37:53 -05:00
parent aa83446d6b
commit aeaf91d589
7 changed files with 23 additions and 20 deletions
+8 -8
View File
@@ -648,9 +648,9 @@ impl SnapshotsConfig {
#[serde(rename_all = "snake_case")]
pub enum SearchProvider {
/// Bing HTML scraping. No API key needed.
#[default]
Bing,
/// DuckDuckGo HTML scraping with Bing fallback. No API key needed.
#[default]
#[serde(alias = "duckduckgo")]
DuckDuckGo,
/// Tavily AI Search API (<https://tavily.com>). Requires api_key.
@@ -714,7 +714,7 @@ pub struct SearchProviderResolution {
/// Web search provider configuration (`[search]` table in config.toml).
#[derive(Debug, Clone, Deserialize, Default)]
pub struct SearchConfig {
/// Search provider: `bing` | `duckduckgo` | `tavily` | `bocha` | `metaso`. Default: `bing`.
/// Search provider: `bing` | `duckduckgo` | `tavily` | `bocha` | `metaso`. Default: `duckduckgo`.
#[serde(default)]
pub provider: Option<SearchProvider>,
/// API key for Tavily, Bocha, or Metaso. Not required for Bing or DuckDuckGo.
@@ -1105,9 +1105,9 @@ pub struct Config {
#[serde(default)]
pub snapshots: Option<SnapshotsConfig>,
/// Web search provider configuration. When absent, defaults to Bing.
/// Set `provider` to `duckduckgo`, `tavily`, or `bocha` to use those
/// services instead; Tavily and Bocha also require an `api_key`.
/// Web search provider configuration. When absent, defaults to DuckDuckGo.
/// Set `provider` to `bing`, `tavily`, or `bocha` to use those services
/// instead; Tavily and Bocha also require an `api_key`.
#[serde(default)]
pub search: Option<SearchConfig>,
@@ -4208,8 +4208,8 @@ mod tests {
}
#[test]
fn search_provider_defaults_to_bing() {
assert_eq!(SearchProvider::default(), SearchProvider::Bing);
fn search_provider_defaults_to_duckduckgo() {
assert_eq!(SearchProvider::default(), SearchProvider::DuckDuckGo);
}
#[test]
@@ -4254,7 +4254,7 @@ mod tests {
let resolution = Config::default().search_provider_resolution();
unsafe { EnvGuard::restore_var("DEEPSEEK_SEARCH_PROVIDER", prev) };
assert_eq!(resolution.provider, SearchProvider::Bing);
assert_eq!(resolution.provider, SearchProvider::DuckDuckGo);
assert_eq!(resolution.source, SearchProviderSource::Default);
}
+1 -1
View File
@@ -162,7 +162,7 @@ pub struct EngineConfig {
pub strict_tool_mode: bool,
/// Workshop / large-tool-output routing (#548). `None` disables routing.
pub workshop: Option<crate::tools::large_output_router::WorkshopConfig>,
/// Which search backend `web_search` should use. Default: Bing.
/// Which search backend `web_search` should use. Default: DuckDuckGo.
pub search_provider: crate::config::SearchProvider,
/// API key for Tavily, Bocha, or Metaso. `None` for Bing or DuckDuckGo.
/// Metaso also falls back to `METASO_API_KEY` env var, then a built-in key.
+5 -4
View File
@@ -3163,11 +3163,11 @@ fn doctor_search_provider_line(config: &Config) -> String {
let switch_hint = if matches!(
(search_provider.provider, search_provider.source),
(
crate::config::SearchProvider::Bing,
crate::config::SearchProvider::DuckDuckGo,
crate::config::SearchProviderSource::Default
)
) {
"; set [search] provider = \"duckduckgo\" | \"tavily\" | \"bocha\" to switch"
"; set [search] provider = \"bing\" | \"tavily\" | \"bocha\" to switch"
} else {
""
};
@@ -5701,7 +5701,7 @@ mod doctor_endpoint_tests {
}
#[test]
fn doctor_search_provider_line_includes_default_source_and_switch_hint() {
fn doctor_search_provider_line_includes_duckduckgo_default_source_and_switch_hint() {
let _guard = crate::test_support::lock_test_env();
let prev = std::env::var_os("DEEPSEEK_SEARCH_PROVIDER");
unsafe { std::env::remove_var("DEEPSEEK_SEARCH_PROVIDER") };
@@ -5712,9 +5712,10 @@ mod doctor_endpoint_tests {
Some(value) => unsafe { std::env::set_var("DEEPSEEK_SEARCH_PROVIDER", value) },
None => unsafe { std::env::remove_var("DEEPSEEK_SEARCH_PROVIDER") },
}
assert!(line.contains("search_provider: bing"));
assert!(line.contains("search_provider: duckduckgo"));
assert!(line.contains("source: default"));
assert!(line.contains("[search] provider"));
assert!(line.contains("provider = \"bing\""));
}
#[test]
+1 -1
View File
@@ -162,7 +162,7 @@ pub struct ToolContext {
/// routing (e.g. in sub-agents and test contexts to avoid recursion).
pub large_output_router: Option<crate::tools::large_output_router::LargeOutputRouter>,
/// Which search backend `web_search` should use. Default: Bing. Set via
/// Which search backend `web_search` should use. Default: DuckDuckGo. Set via
/// `[search] provider` in config.toml.
pub search_provider: crate::config::SearchProvider,
/// API key for Tavily, Bocha, or Metaso. `None` for Bing or DuckDuckGo.
+1 -1
View File
@@ -129,7 +129,7 @@ impl ToolSpec for WebSearchTool {
}
fn description(&self) -> &'static str {
"Search the web and return ranked results with URLs and snippets. Default backend is Bing; set `[search] provider = \"duckduckgo\" | \"tavily\" | \"bocha\"` in config.toml to switch backends. Use this instead of scraping search engines with `curl` in `exec_shell`. For a known canonical URL, prefer `fetch_url` directly."
"Search the web and return ranked results with URLs and snippets. Default backend is DuckDuckGo with Bing fallback; set `[search] provider = \"bing\" | \"tavily\" | \"bocha\"` in config.toml to switch backends. Use this instead of scraping search engines with `curl` in `exec_shell`. For a known canonical URL, prefer `fetch_url` directly."
}
fn input_schema(&self) -> Value {
+6 -4
View File
@@ -654,14 +654,16 @@ Use `codewhale-tui features list` to inspect known flags and their effective sta
## Web Search Provider
`web_search` uses Bing by default and does not require an API key. DuckDuckGo
remains selectable for users who explicitly want it, and Tavily or Bocha can be
selected when an API-backed provider is preferred. **Metaso** ([metaso.cn](https://metaso.cn))
`web_search` uses DuckDuckGo by default and does not require an API key. The
DuckDuckGo path keeps a Bing fallback when DDG returns a bot challenge or no
parseable results. Bing remains selectable for users who explicitly want it,
and Tavily or Bocha can be selected when an API-backed provider is preferred.
**Metaso** ([metaso.cn](https://metaso.cn))
100 searches/day free quota — set `METASO_API_KEY` or `[search] api_key` for a higher quota.
```toml
[search]
provider = "bing" # bing | duckduckgo | tavily | bocha | metaso
provider = "duckduckgo" # duckduckgo | bing | tavily | bocha | metaso
# api_key = "YOUR_KEY" # required for tavily and bocha; optional for metaso (100 searches/day free quota)
```
+1 -1
View File
@@ -35,7 +35,7 @@ chosen over the available shell equivalent. Companion to `crates/tui/src/prompts
|---|---|
| `grep_files` | Regex search file contents within the workspace; structured matches + context lines. Pure-Rust (`regex` crate), no `rg`/`grep` shell-out. |
| `file_search` | Fuzzy-match filenames (not contents). Use when you know roughly the name. |
| `web_search` | Bing by default; DuckDuckGo, Tavily, and Bocha are selectable in config. Ranked snippets + `ref_id` for citation. |
| `web_search` | DuckDuckGo by default with Bing fallback; Bing, Tavily, and Bocha are selectable in config. Ranked snippets + `ref_id` for citation. |
| `fetch_url` | Direct HTTP GET on a known URL. Faster than `web_search` when the link is already known. HTML stripped to text by default. |
### Shell