lc/search/
mod.rs

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
18/// Main search interface
19pub 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}