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