1pub mod brave;
2pub mod config;
3pub mod duckduckgo;
4pub mod exa;
5pub mod jina;
6pub mod providers;
7pub mod search_result;
8pub mod serpapi;
9pub mod serper;
10pub mod tavily;
11
12use anyhow::Result;
13
14pub use config::SearchConfig;
15pub use providers::{SearchProviderConfig, SearchProviderType};
16pub use search_result::{SearchResult, SearchResults};
17
18pub struct SearchEngine {
20 config: SearchConfig,
21}
22
23impl SearchEngine {
24 pub fn new() -> Result<Self> {
25 let config = SearchConfig::load()?;
26 Ok(Self { config })
27 }
28
29 pub async fn search(
30 &self,
31 provider_name: &str,
32 query: &str,
33 count: Option<usize>,
34 ) -> Result<SearchResults> {
35 let provider_config = self.config.get_provider(provider_name)?;
36
37 match provider_config.provider_type {
38 SearchProviderType::Brave => brave::search(provider_config, query, count).await,
39 SearchProviderType::Exa => exa::search(provider_config, query, count).await,
40 SearchProviderType::Serper => serper::search(provider_config, query, count).await,
41 SearchProviderType::SerpApi => serpapi::search(provider_config, query, count).await,
42 SearchProviderType::DuckDuckGo => {
43 duckduckgo::search(provider_config, query, count).await
44 }
45 SearchProviderType::Jina => jina::search(provider_config, query, count).await,
46 SearchProviderType::Tavily => tavily::search(provider_config, query, count).await,
47 }
48 }
49
50 pub fn format_results_json(&self, results: &SearchResults) -> Result<String> {
51 Ok(serde_json::to_string_pretty(results)?)
52 }
53
54 pub fn format_results_markdown(&self, results: &SearchResults) -> String {
55 let mut output = String::new();
56
57 output.push_str(&format!("# Search Results for: {}\n\n", results.query));
58 output.push_str(&format!(
59 "Provider: {} | Total Results: {}\n\n",
60 results.provider,
61 results.results.len()
62 ));
63
64 for (i, result) in results.results.iter().enumerate() {
65 output.push_str(&format!("## {}. {}\n", i + 1, result.title));
66 output.push_str(&format!("**URL:** {}\n\n", result.url));
67 output.push_str(&format!("{}\n\n", result.snippet));
68
69 if let Some(published) = &result.published_date {
70 output.push_str(&format!("*Published: {}*\n\n", published));
71 }
72
73 output.push_str("---\n\n");
74 }
75
76 output
77 }
78
79 pub fn extract_context_for_llm(&self, results: &SearchResults, max_results: usize) -> String {
80 let mut context = String::new();
81 context.push_str("Web search results:\n\n");
82
83 for (i, result) in results.results.iter().take(max_results).enumerate() {
84 context.push_str(&format!("{}. **{}**\n", i + 1, result.title));
85 context.push_str(&format!(" URL: {}\n", result.url));
86 context.push_str(&format!(" {}\n\n", result.snippet));
87 }
88
89 context
90 }
91}