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;
10
11use anyhow::Result;
12
13pub use config::SearchConfig;
14pub use providers::{SearchProviderConfig, SearchProviderType};
15pub use search_result::{SearchResult, SearchResults};
16
17/// Main search interface
18pub 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}