Skip to main content

quantum_sdk/
search.rs

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