13.2 网络搜索(WebSearch / WebFetch)
`rust-agent-websearch` 是一个纯 Rust 实现的网络搜索库,提供 WebSearch 工具(搜索引擎查询)、WebFetch 工具(网页内容获取)和自动搜索上下文提供器。无需 API Key,开箱即用。
架构概览
graph TB
subgraph "Agent 工具"
WS[WebSearch Tool]
WF[WebFetch Tool]
end
subgraph "搜索后端"
DDG[DuckDuckGo HTML]
DDGL[DuckDuckGo Lite]
DDGI[DuckDuckGo Instant Answer]
SX[SearXNG 实例]
BING[Bing API]
end
subgraph "内容获取"
SF[Servo-Fetch 浏览器引擎]
JS[JS 渲染]
LE[布局感知提取]
end
subgraph "防护机制"
AD[反检测]
UA[UA 轮换]
JT[随机抖动]
end
WS --> DDG
WS --> DDGL
WS --> DDGI
WS --> SX
WS --> BING
WF --> SF
SF --> JS
SF --> LE
WS --> AD
AD --> UA
AD --> JT
WebSearch 工具
`WebSearch` 实现 `ITool`,可以作为 Agent 工具直接使用:
use rust_agent_websearch::WebSearch;
use rust_agent_core::ITool;
let tool = WebSearch;
assert_eq!(tool.name(), "web_search");
assert_eq!(tool.description(), "搜索互联网获取最新信息");
// Agent 调用时
let result = tool.execute(serde_json::json!({
"query": "Rust async programming best practices"
})).await?;
多后端支持
WebSearch 支持多种搜索后端,通过 feature flags 切换:
| 后端 | 特点 | 是否需要 API Key | |------|------|-----------------| | DuckDuckGo HTML | 默认后端,HTML 抓取 | ❌ | | DuckDuckGo Lite | 轻量版,更快但结果较少 | ❌ | | DuckDuckGo Instant Answer | 即时答案 API | ❌ | | SearXNG | 自托管元搜索引擎 | ❌ (自建实例) | | Bing API | Microsoft Bing | ✅ |
反检测机制
为了确保稳定运行,内置了以下反检测策略:
- **User-Agent 轮换**:使用 `rand` crate 从预定义的 UA 池中随机选择 - **请求抖动**:在请求之间插入随机延迟 - **HTML 解析**:使用 `scraper` crate 进行 CSS 选择器解析,避免正则匹配
// 反检测策略(内部实现)
use rand::Rng;
fn rotate_user_agent() -> &'static str {
let agents = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ...",
"Mozilla/5.0 (X11; Linux x86_64) ...",
];
agents[rand::thread_rng().gen_range(0..agents.len())]
}
WebFetch 工具
`WebFetch` 使用基于 Servo 的浏览器引擎获取网页内容,支持 JS 渲染和布局感知的内容提取:
use rust_agent_websearch::WebFetch;
let tool = WebFetch;
let result = tool.execute(serde_json::json!({
"url": "https://example.com/article",
"mode": "cleaned" // 内容清洗模式
})).await?;
内容清洗模式
| 模式 | 说明 | |------|------| | `raw` | 原始 HTML | | `cleaned` | 移除脚本、样式、导航等非内容元素 | | `readability` | 类 Readability 算法提取正文 | | `markdown` | 转换为 Markdown 格式 | | `plaintext` | 纯文本提取 |
Servo-Fetch 引擎
`servo-fetch` 提供了完整的浏览器渲染能力:
- 执行 JavaScript 渲染单页应用(SPA) - 布局感知的内容提取(过滤导航、广告、页脚) - HTML 净化(移除 script、style、iframe 标签)
WebSearchContextProvider — 自动搜索
除了手动工具调用,还提供了自动搜索上下文提供器:
use rust_agent_websearch::{WebSearchContextProvider, WebSearch};
let provider = WebSearchContextProvider::new(WebSearch::default());
// 注册到 Agent
let agent = AgentBuilder::new("researcher")
.chat_client(client)
.instructions("...")
.with_context_provider(provider)
.build()?;
`WebSearchContextProvider` 在每次 Agent 调用前: 1. 分析用户消息中是否包含需要搜索的意图 2. 自动执行搜索 3. 将搜索结果作为上下文注入 system prompt
完整示例
use std::sync::Arc;
use rust_agent_core::{IAgent, ChatMessage};
use rust_agent_websearch::{WebSearch, WebFetch};
use rust_agent_framework::AgentBuilder;
use futures_util::StreamExt;
async fn research_agent() -> anyhow::Result<()> {
let client = DeepSeekChatClient::new(/* ... */)?;
let agent = AgentBuilder::new("researcher")
.chat_client(client)
.instructions(
"你是网络研究助手。使用 web_search 搜索信息,使用 web_fetch 获取网页详情。\n\
原则:\n\
1. 优先搜索获取最新信息\n\
2. 对重要来源使用 web_fetch 深入阅读\n\
3. 引用来源 URL\n\
4. 注明信息发布时间"
)
.with_tool(WebSearch)
.with_tool(WebFetch)
.max_tool_rounds(10)
.build()?;
let input = vec![ChatMessage::user(
"请研究 Rust 在 2025 年的生态系统发展情况,包括最新版本特性、\
热门 crate 和社区动态。"
)];
let mut stream = agent.run(input, None, None).await?;
while let Some(chunk) = stream.next().await {
if let Ok(result) = chunk {
for content in &result.contents {
if let rust_agent_core::Content::Text(ref t) = content {
print!("{}", t.delta);
}
}
}
}
Ok(())
}
Feature Flags
[dependencies]
rust-agent-websearch = { version = "0.1", features = ["rustls-tls"] }
# 或使用 native-tls
rust-agent-websearch = { version = "0.1", features = ["native-tls"] }
| Feature | 说明 | |---------|------| | `rustls-tls` (默认) | 使用 rustls TLS 后端 | | `native-tls` | 使用平台原生 TLS |
注意事项
1. **搜索频率限制**:高频搜索可能触发搜索引擎的限流机制 2. **结果缓存**:目前不包含内置缓存,重复搜索同一查询会重新请求 3. **SearXNG 实例**:使用 SearXNG 后端时需要自行部署或指定公开实例 4. **Servo 依赖**:WebFetch 依赖 `servo-fetch`,这是较重的依赖,仅在需要 JS 渲染时必要 5. **内容大小限制**:WebFetch 获取的页面内容可能被截断,大型页面建议指定 `mode: "readability"` 仅提取正文