Skip to main content

quantum_sdk/
search.rs

1use serde::{Deserialize, Serialize};
2
3use crate::client::Client;
4use crate::error::Result;
5
6// ---------------------------------------------------------------------------
7// Web Search
8// ---------------------------------------------------------------------------
9
10/// Request body for Brave web search.
11#[derive(Debug, Clone, Serialize, Default)]
12pub struct WebSearchRequest {
13    /// Search query string.
14    pub query: String,
15
16    /// Number of results to return.
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub count: Option<i32>,
19
20    /// Pagination offset.
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub offset: Option<i32>,
23
24    /// Country code filter (e.g. "US", "GB").
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub country: Option<String>,
27
28    /// Language code filter (e.g. "en", "fr").
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub language: Option<String>,
31
32    /// Freshness filter (e.g. "pd" for past day, "pw" for past week).
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub freshness: Option<String>,
35
36    /// Safe search level (e.g. "off", "moderate", "strict").
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub safesearch: Option<String>,
39}
40
41/// A single web search result.
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct WebResult {
44    /// Page title.
45    pub title: String,
46
47    /// Page URL.
48    pub url: String,
49
50    /// Result description / snippet.
51    #[serde(default)]
52    pub description: String,
53
54    /// Age of the result (e.g. "2 hours ago").
55    #[serde(default)]
56    pub age: Option<String>,
57
58    /// Favicon URL.
59    #[serde(default)]
60    pub favicon: Option<String>,
61}
62
63/// A news search result.
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct NewsResult {
66    /// Article title.
67    pub title: String,
68
69    /// Article URL.
70    pub url: String,
71
72    /// Short description.
73    #[serde(default)]
74    pub description: String,
75
76    /// Age of the article.
77    #[serde(default)]
78    pub age: Option<String>,
79
80    /// Publisher name.
81    #[serde(default)]
82    pub source: Option<String>,
83}
84
85/// A video search result.
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct VideoResult {
88    /// Video title.
89    pub title: String,
90
91    /// Video page URL.
92    pub url: String,
93
94    /// Short description.
95    #[serde(default)]
96    pub description: String,
97
98    /// Thumbnail URL.
99    #[serde(default)]
100    pub thumbnail: Option<String>,
101
102    /// Age of the video.
103    #[serde(default)]
104    pub age: Option<String>,
105}
106
107/// An infobox (knowledge panel) result.
108#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct InfoboxResult {
110    /// Infobox title.
111    pub title: String,
112
113    /// Long description.
114    #[serde(default)]
115    pub description: String,
116
117    /// Source URL.
118    #[serde(default)]
119    pub url: Option<String>,
120}
121
122/// A discussion / forum result.
123#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct DiscussionResult {
125    /// Discussion title.
126    pub title: String,
127
128    /// Discussion URL.
129    pub url: String,
130
131    /// Short description.
132    #[serde(default)]
133    pub description: String,
134
135    /// Age of the discussion.
136    #[serde(default)]
137    pub age: Option<String>,
138
139    /// Forum name.
140    #[serde(default)]
141    pub forum: Option<String>,
142}
143
144/// Response from the web search endpoint.
145#[derive(Debug, Clone, Deserialize)]
146pub struct WebSearchResponse {
147    /// Original query.
148    pub query: String,
149
150    /// Web search results.
151    #[serde(default)]
152    pub web: Vec<WebResult>,
153
154    /// News results.
155    #[serde(default)]
156    pub news: Vec<NewsResult>,
157
158    /// Video results.
159    #[serde(default)]
160    pub videos: Vec<VideoResult>,
161
162    /// Infobox / knowledge panel entries.
163    #[serde(default)]
164    pub infobox: Vec<InfoboxResult>,
165
166    /// Discussion / forum results.
167    #[serde(default)]
168    pub discussions: Vec<DiscussionResult>,
169}
170
171// ---------------------------------------------------------------------------
172// Search Context
173// ---------------------------------------------------------------------------
174
175/// Request body for search context (returns chunked page content).
176#[derive(Debug, Clone, Serialize, Default)]
177pub struct SearchContextRequest {
178    /// Search query string.
179    pub query: String,
180
181    /// Number of results to fetch context from.
182    #[serde(skip_serializing_if = "Option::is_none")]
183    pub count: Option<i32>,
184
185    /// Country code filter.
186    #[serde(skip_serializing_if = "Option::is_none")]
187    pub country: Option<String>,
188
189    /// Language code filter.
190    #[serde(skip_serializing_if = "Option::is_none")]
191    pub language: Option<String>,
192
193    /// Freshness filter.
194    #[serde(skip_serializing_if = "Option::is_none")]
195    pub freshness: Option<String>,
196}
197
198/// A content chunk from search context.
199#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct SearchContextChunk {
201    /// Extracted page content.
202    pub content: String,
203
204    /// Source URL.
205    pub url: String,
206
207    /// Page title.
208    #[serde(default)]
209    pub title: String,
210
211    /// Relevance score.
212    #[serde(default)]
213    pub score: f64,
214
215    /// Content type (e.g. "text/html").
216    #[serde(default)]
217    pub content_type: Option<String>,
218}
219
220/// A source reference from search context.
221#[derive(Debug, Clone, Serialize, Deserialize)]
222pub struct SearchContextSource {
223    /// Source URL.
224    pub url: String,
225
226    /// Source title.
227    #[serde(default)]
228    pub title: String,
229}
230
231/// Response from the search context endpoint.
232#[derive(Debug, Clone, Deserialize)]
233pub struct SearchContextResponse {
234    /// Content chunks extracted from search results.
235    pub chunks: Vec<SearchContextChunk>,
236
237    /// Source references.
238    #[serde(default)]
239    pub sources: Vec<SearchContextSource>,
240
241    /// Original query.
242    pub query: String,
243}
244
245// ---------------------------------------------------------------------------
246// Search Answer (AI-generated answer with citations)
247// ---------------------------------------------------------------------------
248
249/// A chat message for the search answer endpoint.
250#[derive(Debug, Clone, Serialize, Deserialize)]
251pub struct SearchAnswerMessage {
252    /// Message role ("user", "assistant", "system").
253    pub role: String,
254
255    /// Message text content.
256    pub content: String,
257}
258
259/// Request body for search answer (AI-generated answer grounded in search).
260#[derive(Debug, Clone, Serialize, Default)]
261pub struct SearchAnswerRequest {
262    /// Conversation messages.
263    pub messages: Vec<SearchAnswerMessage>,
264
265    /// Model to use for answer generation.
266    #[serde(skip_serializing_if = "Option::is_none")]
267    pub model: Option<String>,
268}
269
270/// A citation reference in a search answer.
271#[derive(Debug, Clone, Serialize, Deserialize)]
272pub struct SearchAnswerCitation {
273    /// Source URL.
274    pub url: String,
275
276    /// Source title.
277    #[serde(default)]
278    pub title: String,
279
280    /// Snippet from the source.
281    #[serde(default)]
282    pub snippet: Option<String>,
283}
284
285/// A choice in the search answer response.
286#[derive(Debug, Clone, Deserialize)]
287pub struct SearchAnswerChoice {
288    /// Choice index.
289    pub index: i32,
290
291    /// The generated message.
292    pub message: SearchAnswerMessage,
293
294    /// Finish reason (e.g. "stop").
295    #[serde(default)]
296    pub finish_reason: Option<String>,
297}
298
299/// Response from the search answer endpoint.
300#[derive(Debug, Clone, Deserialize)]
301pub struct SearchAnswerResponse {
302    /// Generated answer choices.
303    pub choices: Vec<SearchAnswerChoice>,
304
305    /// Model that produced the answer.
306    #[serde(default)]
307    pub model: String,
308
309    /// Unique response identifier.
310    #[serde(default)]
311    pub id: String,
312
313    /// Citations used in the answer.
314    #[serde(default)]
315    pub citations: Vec<SearchAnswerCitation>,
316}
317
318// ---------------------------------------------------------------------------
319// Client methods
320// ---------------------------------------------------------------------------
321
322impl Client {
323    /// Performs a Brave web search, returning structured results across web, news,
324    /// videos, discussions, and infoboxes.
325    pub async fn web_search(&self, req: &WebSearchRequest) -> Result<WebSearchResponse> {
326        let (resp, _meta) = self
327            .post_json::<WebSearchRequest, WebSearchResponse>("/qai/v1/search/web", req)
328            .await?;
329        Ok(resp)
330    }
331
332    /// Searches the web and returns chunked page content suitable for RAG or
333    /// context injection into LLM prompts.
334    pub async fn search_context(
335        &self,
336        req: &SearchContextRequest,
337    ) -> Result<SearchContextResponse> {
338        let (resp, _meta) = self
339            .post_json::<SearchContextRequest, SearchContextResponse>(
340                "/qai/v1/search/context",
341                req,
342            )
343            .await?;
344        Ok(resp)
345    }
346
347    /// Generates an AI-powered answer grounded in live web search results,
348    /// with citations.
349    pub async fn search_answer(&self, req: &SearchAnswerRequest) -> Result<SearchAnswerResponse> {
350        let (resp, _meta) = self
351            .post_json::<SearchAnswerRequest, SearchAnswerResponse>(
352                "/qai/v1/search/answer",
353                req,
354            )
355            .await?;
356        Ok(resp)
357    }
358}