Skip to main content

rainy_sdk/endpoints/
search.rs

1//! Web Research endpoint
2//!
3//! This endpoint provides web research capabilities via the Rainy API v3 search API.
4
5use crate::{
6    error::{RainyError, Result},
7    search::{DeepResearchResponse, ResearchConfig},
8    RainyClient,
9};
10use serde::Deserialize;
11use serde_json::json;
12
13impl RainyClient {
14    /// Perform deep web research on a topic.
15    ///
16    /// This method leverages the Rainy Agent Network to perform comprehensive
17    /// web research using providers like Exa or Tavily.
18    ///
19    /// # Arguments
20    ///
21    /// * `topic` - The research topic or question.
22    /// * `config` - Research configuration (provider, depth, etc.)
23    ///
24    /// # Returns
25    ///
26    /// A `Result` containing `DeepResearchResponse` on success.
27    ///
28    /// # Example
29    ///
30    /// ```rust,no_run
31    /// # use rainy_sdk::{RainyClient, search::ResearchConfig, models::{ResearchProvider, ResearchDepth}};
32    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
33    /// let client = RainyClient::with_api_key("your-api-key")?;
34    ///
35    /// // Basic research
36    /// let response = client.research("Latest Rust features", None).await?;
37    /// if let Some(content) = response.result {
38    ///     println!("Report: {}", content);
39    /// }
40    ///
41    /// // Advanced deep research with Exa
42    /// let config = ResearchConfig::new()
43    ///     .with_provider(ResearchProvider::Exa)
44    ///     .with_depth(ResearchDepth::Advanced);
45    ///
46    /// let response = client.research("Quantum Computing advances", Some(config)).await?;
47    /// # Ok(())
48    /// # }
49    /// ```
50    pub async fn research(
51        &self,
52        topic: impl Into<String>,
53        config: Option<ResearchConfig>,
54    ) -> Result<DeepResearchResponse> {
55        #[derive(Deserialize)]
56        struct SearchResultItem {
57            title: Option<String>,
58            url: Option<String>,
59            content: Option<String>,
60            snippet: Option<String>,
61        }
62        #[derive(Deserialize)]
63        struct SearchData {
64            results: Vec<SearchResultItem>,
65        }
66        #[derive(Deserialize)]
67        struct SearchEnvelope {
68            success: bool,
69            data: SearchData,
70        }
71
72        let cfg = config.unwrap_or_default();
73        let topic = topic.into();
74        let url = self.api_v1_url("/search");
75        let search_depth = match cfg.depth {
76            crate::models::ResearchDepth::Advanced => "advanced",
77            _ => "basic",
78        };
79        let request = json!({
80            "query": topic,
81            "searchDepth": search_depth,
82            "maxResults": cfg.max_sources.min(20),
83        });
84
85        let response = self
86            .http_client()
87            .post(&url)
88            .json(&request)
89            .send()
90            .await
91            .map_err(|e| RainyError::Network {
92                message: e.to_string(),
93                retryable: true,
94                source_error: Some(e.to_string()),
95            })?;
96
97        let envelope: SearchEnvelope = self.handle_response(response).await?;
98
99        let results_json = envelope
100            .data
101            .results
102            .iter()
103            .map(|item| {
104                json!({
105                    "title": item.title,
106                    "url": item.url,
107                    "snippet": item.snippet.as_ref().or(item.content.as_ref()),
108                })
109            })
110            .collect::<Vec<_>>();
111
112        let synthesized_content = envelope
113            .data
114            .results
115            .iter()
116            .enumerate()
117            .map(|(idx, item)| {
118                let title = item.title.as_deref().unwrap_or("Untitled");
119                let url = item.url.as_deref().unwrap_or("");
120                let snippet = item
121                    .snippet
122                    .as_deref()
123                    .or(item.content.as_deref())
124                    .unwrap_or("");
125                format!("{}. {}\n{}\n{}", idx + 1, title, url, snippet)
126            })
127            .collect::<Vec<_>>()
128            .join("\n\n");
129
130        Ok(DeepResearchResponse {
131            success: envelope.success,
132            mode: "sync".to_string(),
133            result: Some(json!({
134                "content": synthesized_content,
135                "results": results_json,
136            })),
137            task_id: None,
138            generated_at: None,
139            provider: Some("tavily".to_string()),
140            message: None,
141        })
142    }
143}