quantum-sdk 0.7.0

Rust client SDK for the Quantum AI API
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
use serde::{Deserialize, Serialize};

use crate::client::Client;
use crate::error::Result;

// ---------------------------------------------------------------------------
// Search Options
// ---------------------------------------------------------------------------

/// Options for configuring web search requests.
#[derive(Debug, Clone, Serialize, Default)]
pub struct SearchOptions {
    /// Number of results to return.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub count: Option<i32>,

    /// Zero-based result offset for pagination.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub offset: Option<i32>,

    /// Country code filter (e.g. "US", "GB").
    #[serde(skip_serializing_if = "Option::is_none")]
    pub country: Option<String>,

    /// Language code filter (e.g. "en", "fr").
    #[serde(skip_serializing_if = "Option::is_none")]
    pub language: Option<String>,

    /// Time range filter (e.g. "24h", "7d", "30d").
    #[serde(skip_serializing_if = "Option::is_none")]
    pub freshness: Option<String>,

    /// Adult content filtering ("off", "moderate", "strict").
    #[serde(skip_serializing_if = "Option::is_none")]
    pub safe_search: Option<String>,
}

/// Options for configuring LLM context search requests.
#[derive(Debug, Clone, Serialize, Default)]
pub struct ContextOptions {
    /// Number of context chunks to return.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub count: Option<i32>,

    /// Country code filter.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub country: Option<String>,

    /// Language code filter.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub language: Option<String>,

    /// Time range filter.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub freshness: Option<String>,
}

/// A message in a search-answer conversation.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchMessage {
    /// Message role ("user" or "assistant").
    pub role: String,

    /// Message text content.
    pub content: String,
}

// ---------------------------------------------------------------------------
// Web Search
// ---------------------------------------------------------------------------

/// Request body for Brave web search.
#[derive(Debug, Clone, Serialize, Default)]
pub struct WebSearchRequest {
    /// Search query string.
    pub query: String,

    /// Number of results to return.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub count: Option<i32>,

    /// Pagination offset.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub offset: Option<i32>,

    /// Country code filter (e.g. "US", "GB").
    #[serde(skip_serializing_if = "Option::is_none")]
    pub country: Option<String>,

    /// Language code filter (e.g. "en", "fr").
    #[serde(skip_serializing_if = "Option::is_none")]
    pub language: Option<String>,

    /// Freshness filter (e.g. "pd" for past day, "pw" for past week).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub freshness: Option<String>,

    /// Safe search level (e.g. "off", "moderate", "strict").
    #[serde(skip_serializing_if = "Option::is_none")]
    pub safesearch: Option<String>,
}

/// A single web search result.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebResult {
    /// Page title.
    pub title: String,

    /// Page URL.
    pub url: String,

    /// Result description / snippet.
    #[serde(default)]
    pub description: String,

    /// Age of the result (e.g. "2 hours ago").
    #[serde(default)]
    pub age: Option<String>,

    /// Favicon URL.
    #[serde(default)]
    pub favicon: Option<String>,
}

/// A news search result.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NewsResult {
    /// Article title.
    pub title: String,

    /// Article URL.
    pub url: String,

    /// Short description.
    #[serde(default)]
    pub description: String,

    /// Age of the article.
    #[serde(default)]
    pub age: Option<String>,

    /// Publisher name.
    #[serde(default)]
    pub source: Option<String>,
}

/// A video search result.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VideoResult {
    /// Video title.
    pub title: String,

    /// Video page URL.
    pub url: String,

    /// Short description.
    #[serde(default)]
    pub description: String,

    /// Thumbnail URL.
    #[serde(default)]
    pub thumbnail: Option<String>,

    /// Age of the video.
    #[serde(default)]
    pub age: Option<String>,
}

/// An infobox (knowledge panel) result.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Infobox {
    /// Infobox title.
    pub title: String,

    /// Long description.
    #[serde(default)]
    pub description: String,

    /// Source URL.
    #[serde(default)]
    pub url: Option<String>,
}

/// Backwards-compatible alias.
pub type InfoboxResult = Infobox;

/// A discussion / forum result.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Discussion {
    /// Discussion title.
    pub title: String,

    /// Discussion URL.
    pub url: String,

    /// Short description.
    #[serde(default)]
    pub description: String,

    /// Age of the discussion.
    #[serde(default)]
    pub age: Option<String>,

    /// Forum name.
    #[serde(default)]
    pub forum: Option<String>,
}

/// Backwards-compatible alias.
pub type DiscussionResult = Discussion;

/// Response from the web search endpoint.
#[derive(Debug, Clone, Deserialize)]
pub struct WebSearchResponse {
    /// Original query.
    pub query: String,

    /// Web search results.
    #[serde(default)]
    pub web: Vec<WebResult>,

    /// News results.
    #[serde(default)]
    pub news: Vec<NewsResult>,

    /// Video results.
    #[serde(default)]
    pub videos: Vec<VideoResult>,

    /// Infobox / knowledge panel entries.
    #[serde(default)]
    pub infobox: Vec<Infobox>,

    /// Discussion / forum results.
    #[serde(default)]
    pub discussions: Vec<Discussion>,
}

// ---------------------------------------------------------------------------
// Search Context
// ---------------------------------------------------------------------------

/// Request body for search context (returns chunked page content).
#[derive(Debug, Clone, Serialize, Default)]
pub struct SearchContextRequest {
    /// Search query string.
    pub query: String,

    /// Number of results to fetch context from.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub count: Option<i32>,

    /// Country code filter.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub country: Option<String>,

    /// Language code filter.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub language: Option<String>,

    /// Freshness filter.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub freshness: Option<String>,
}

/// A content chunk from search context.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchContextChunk {
    /// Extracted page content.
    pub content: String,

    /// Source URL.
    pub url: String,

    /// Page title.
    #[serde(default)]
    pub title: String,

    /// Relevance score.
    #[serde(default)]
    pub score: f64,

    /// Content type (e.g. "text/html").
    #[serde(default)]
    pub content_type: Option<String>,
}

/// A source reference from search context.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchContextSource {
    /// Source URL.
    pub url: String,

    /// Source title.
    #[serde(default)]
    pub title: String,
}

/// Response from the search context endpoint.
#[derive(Debug, Clone, Deserialize)]
pub struct SearchContextResponse {
    /// Content chunks extracted from search results.
    pub chunks: Vec<SearchContextChunk>,

    /// Source references.
    #[serde(default)]
    pub sources: Vec<SearchContextSource>,

    /// Original query.
    pub query: String,
}

/// LLM-optimised context response from web search.
///
/// Unlike [`SearchContextResponse`], this returns simple string sources
/// and is the type returned by the Go SDK's `SearchContext` method.
#[derive(Debug, Clone, Deserialize)]
pub struct LLMContextResponse {
    /// Original search query.
    pub query: String,

    /// Content chunks suitable for LLM consumption.
    #[serde(default)]
    pub chunks: Vec<ContextChunk>,

    /// Source URLs used.
    #[serde(default)]
    pub sources: Vec<String>,
}

/// A single chunk of context from a web page (simple variant).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContextChunk {
    /// Extracted page content.
    pub content: String,

    /// Source URL.
    pub url: String,

    /// Page title.
    #[serde(default)]
    pub title: String,

    /// Relevance score.
    #[serde(default)]
    pub score: f64,

    /// Content type (e.g. "text/html").
    #[serde(default)]
    pub content_type: Option<String>,
}

// ---------------------------------------------------------------------------
// Search Answer (AI-generated answer with citations)
// ---------------------------------------------------------------------------

/// A chat message for the search answer endpoint.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchAnswerMessage {
    /// Message role ("user", "assistant", "system").
    pub role: String,

    /// Message text content.
    pub content: String,
}

/// Request body for search answer (AI-generated answer grounded in search).
#[derive(Debug, Clone, Serialize, Default)]
pub struct SearchAnswerRequest {
    /// Conversation messages.
    pub messages: Vec<SearchAnswerMessage>,

    /// Model to use for answer generation.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub model: Option<String>,
}

/// A citation reference in a search answer.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchAnswerCitation {
    /// Source URL.
    pub url: String,

    /// Source title.
    #[serde(default)]
    pub title: String,

    /// Snippet from the source.
    #[serde(default)]
    pub snippet: Option<String>,
}

/// A choice in the search answer response.
#[derive(Debug, Clone, Deserialize)]
pub struct SearchAnswerChoice {
    /// Choice index.
    pub index: i32,

    /// The generated message.
    pub message: SearchAnswerMessage,

    /// Finish reason (e.g. "stop").
    #[serde(default)]
    pub finish_reason: Option<String>,
}

/// Response from the search answer endpoint.
#[derive(Debug, Clone, Deserialize)]
pub struct SearchAnswerResponse {
    /// Generated answer choices.
    pub choices: Vec<SearchAnswerChoice>,

    /// Model that produced the answer.
    #[serde(default)]
    pub model: String,

    /// Unique response identifier.
    #[serde(default)]
    pub id: String,

    /// Citations used in the answer.
    #[serde(default)]
    pub citations: Vec<SearchAnswerCitation>,
}

// ---------------------------------------------------------------------------
// Google Grounded Search — Gemini Flash + google_search tool
// ---------------------------------------------------------------------------

/// Request body for Google grounded search via Gemini.
///
/// This is the *premium* search backend — quality is significantly higher
/// than Brave for technical/news queries because it taps into Google's
/// index, but billing is per-executed-query at $0.035 each. The model
/// decides how many queries to run for a single user prompt; check
/// `web_search_queries.len()` on the response to see the count.
#[derive(Debug, Clone, Serialize, Default)]
pub struct GoogleSearchRequest {
    /// Search query string. Free-form natural language; the model will
    /// translate this into one or more concrete Google searches.
    pub query: String,
}

/// A web source returned by Google grounding.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GoogleSearchCitation {
    /// Source URL (may be a Google redirect link the user can follow).
    pub url: String,

    /// Source title from the search result.
    #[serde(default)]
    pub title: String,
}

/// Links a span of the answer text to one or more citation indices,
/// enabling inline-citation rendering on the frontend.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GoogleSearchSupport {
    /// Byte offset where this span starts in the answer text.
    pub start_index: i32,

    /// Byte offset where this span ends (exclusive).
    pub end_index: i32,

    /// The actual text span — included for resilience when the answer
    /// has been streamed/transformed and indices no longer line up.
    #[serde(default)]
    pub text: String,

    /// Indices into `citations` for the sources backing this span.
    #[serde(default)]
    pub grounding_chunk_indices: Vec<i32>,
}

/// Response from the Google grounded search endpoint.
#[derive(Debug, Clone, Deserialize)]
pub struct GoogleSearchResponse {
    /// The grounded answer text Gemini produced. May be empty if the
    /// model decided no answer was warranted.
    #[serde(default)]
    pub answer: String,

    /// Web sources Gemini grounded its answer on.
    #[serde(default)]
    pub citations: Vec<GoogleSearchCitation>,

    /// **ToS-required** HTML/CSS widget showing search-suggestion chips.
    /// Google's grounding terms require this to be rendered alongside
    /// any grounded response. Pass it through verbatim — do not modify.
    #[serde(default)]
    pub search_entry_point: String,

    /// The actual queries Gemini executed against Google Search.
    /// Non-empty length is the BILLING UNIT on the backend.
    #[serde(default)]
    pub web_search_queries: Vec<String>,

    /// Inline-citation spans linking text segments to citations.
    #[serde(default)]
    pub supports: Vec<GoogleSearchSupport>,
}

// ---------------------------------------------------------------------------
// Client methods
// ---------------------------------------------------------------------------

impl Client {
    /// Performs a Brave web search, returning structured results across web, news,
    /// videos, discussions, and infoboxes.
    pub async fn web_search(&self, req: &WebSearchRequest) -> Result<WebSearchResponse> {
        let (resp, _meta) = self
            .post_json::<WebSearchRequest, WebSearchResponse>("/qai/v1/search/web", req)
            .await?;
        Ok(resp)
    }

    /// Searches the web and returns chunked page content suitable for RAG or
    /// context injection into LLM prompts.
    pub async fn search_context(
        &self,
        req: &SearchContextRequest,
    ) -> Result<SearchContextResponse> {
        let (resp, _meta) = self
            .post_json::<SearchContextRequest, SearchContextResponse>(
                "/qai/v1/search/context",
                req,
            )
            .await?;
        Ok(resp)
    }

    /// Generates an AI-powered answer grounded in live web search results,
    /// with citations.
    pub async fn search_answer(&self, req: &SearchAnswerRequest) -> Result<SearchAnswerResponse> {
        let (resp, _meta) = self
            .post_json::<SearchAnswerRequest, SearchAnswerResponse>(
                "/qai/v1/search/answer",
                req,
            )
            .await?;
        Ok(resp)
    }

    /// Performs a Google grounded search via Gemini Flash + the
    /// google_search built-in tool. Returns a grounded answer plus
    /// citations, the ToS-required search-entry-point widget, and the
    /// list of queries Gemini actually executed.
    ///
    /// Premium pricing — caller's wallet is debited per executed query
    /// ($0.035 each). Use `search_answer` (Brave-backed) for cheap
    /// high-volume search; reach for this when answer quality matters
    /// more than per-call cost.
    pub async fn google_search(
        &self,
        req: &GoogleSearchRequest,
    ) -> Result<GoogleSearchResponse> {
        let (resp, _meta) = self
            .post_json::<GoogleSearchRequest, GoogleSearchResponse>(
                "/qai/v1/search/google",
                req,
            )
            .await?;
        Ok(resp)
    }
}