1use serde::{Deserialize, Serialize};
2
3use crate::client::Client;
4use crate::error::Result;
5
6#[derive(Debug, Clone, Serialize, Default)]
8pub struct RagSearchRequest {
9 pub query: String,
11
12 #[serde(skip_serializing_if = "Option::is_none")]
14 pub corpus: Option<String>,
15
16 #[serde(skip_serializing_if = "Option::is_none")]
18 pub top_k: Option<i32>,
19}
20
21#[derive(Debug, Clone, Deserialize)]
23pub struct RagSearchResponse {
24 pub results: Vec<RagResult>,
26
27 pub query: String,
29
30 #[serde(default)]
32 pub corpora: Option<Vec<String>>,
33
34 #[serde(default)]
36 pub cost_ticks: i64,
37
38 #[serde(default)]
40 pub request_id: String,
41}
42
43#[derive(Debug, Clone, Deserialize)]
45pub struct RagResult {
46 pub source_uri: String,
48
49 pub source_name: String,
51
52 pub text: String,
54
55 pub score: f64,
57
58 pub distance: f64,
60}
61
62#[derive(Debug, Clone, Deserialize)]
64pub struct RagCorpus {
65 pub name: String,
67
68 #[serde(rename = "displayName")]
70 pub display_name: String,
71
72 pub description: String,
74
75 pub state: String,
77}
78
79#[derive(Deserialize)]
80struct RagCorporaResponse {
81 corpora: Vec<RagCorpus>,
82}
83
84#[derive(Debug, Clone, Serialize, Default)]
86pub struct SurrealRagSearchRequest {
87 pub query: String,
89
90 #[serde(skip_serializing_if = "Option::is_none")]
92 pub provider: Option<String>,
93
94 #[serde(skip_serializing_if = "Option::is_none")]
96 pub limit: Option<i32>,
97}
98
99#[derive(Debug, Clone, Deserialize)]
101pub struct SurrealRagSearchResponse {
102 pub results: Vec<SurrealRagResult>,
104
105 pub query: String,
107
108 #[serde(default)]
110 pub provider: Option<String>,
111
112 #[serde(default)]
114 pub cost_ticks: i64,
115
116 #[serde(default)]
118 pub request_id: String,
119}
120
121#[derive(Debug, Clone, Deserialize)]
123pub struct SurrealRagResult {
124 pub provider: String,
126
127 pub title: String,
129
130 pub heading: String,
132
133 pub source_file: String,
135
136 pub content: String,
138
139 pub score: f64,
141}
142
143#[derive(Debug, Clone, Deserialize)]
145pub struct SurrealRagProviderInfo {
146 pub provider: String,
148
149 #[serde(default)]
151 pub chunk_count: Option<i64>,
152}
153
154pub type SurrealRagProvider = SurrealRagProviderInfo;
156
157#[derive(Debug, Clone, Deserialize)]
159pub struct SurrealRagProvidersResponse {
160 pub providers: Vec<SurrealRagProviderInfo>,
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize)]
167pub struct Collection {
168 pub id: String,
170
171 pub name: String,
173
174 #[serde(default)]
176 pub description: Option<String>,
177
178 #[serde(default)]
180 pub document_count: Option<u64>,
181
182 #[serde(default)]
184 pub owner: Option<String>,
185
186 #[serde(default)]
188 pub provider: Option<String>,
189
190 #[serde(default)]
192 pub created_at: Option<String>,
193}
194
195#[derive(Debug, Clone, Serialize)]
197pub struct CreateCollectionRequest {
198 pub name: String,
199}
200
201#[derive(Debug, Clone, Serialize, Deserialize)]
203pub struct CollectionDocument {
204 pub file_id: String,
205 pub name: String,
206 #[serde(default)]
207 pub size_bytes: Option<u64>,
208 #[serde(default)]
209 pub content_type: Option<String>,
210 #[serde(default)]
211 pub processing_status: Option<String>,
212 #[serde(default)]
213 pub document_status: Option<String>,
214 #[serde(default)]
215 pub indexed: Option<bool>,
216 #[serde(default)]
217 pub created_at: Option<String>,
218}
219
220#[derive(Debug, Clone, Deserialize)]
222pub struct CollectionSearchResult {
223 pub content: String,
224 #[serde(default)]
225 pub score: Option<f64>,
226 #[serde(default)]
227 pub file_id: Option<String>,
228 #[serde(default)]
229 pub collection_id: Option<String>,
230 #[serde(default)]
231 pub metadata: Option<serde_json::Value>,
232}
233
234#[derive(Debug, Clone, Serialize)]
236pub struct CollectionSearchRequest {
237 pub query: String,
238 pub collection_ids: Vec<String>,
239 #[serde(skip_serializing_if = "Option::is_none")]
240 pub mode: Option<String>,
241 #[serde(skip_serializing_if = "Option::is_none")]
242 pub max_results: Option<usize>,
243}
244
245#[derive(Debug, Clone, Deserialize)]
247pub struct CollectionUploadResult {
248 pub file_id: String,
249 pub filename: String,
250 #[serde(default)]
251 pub bytes: Option<u64>,
252}
253
254#[derive(Deserialize)]
257struct CollectionsListResponse {
258 collections: Vec<Collection>,
259}
260
261#[derive(Deserialize)]
262struct CollectionDocumentsResponse {
263 documents: Vec<CollectionDocument>,
264}
265
266#[derive(Deserialize)]
267struct CollectionSearchResponse {
268 results: Vec<CollectionSearchResult>,
269}
270
271#[derive(Deserialize)]
272struct DeleteCollectionResponse {
273 #[serde(default)]
274 message: String,
275}
276
277impl Client {
278 pub async fn rag_search(&self, req: &RagSearchRequest) -> Result<RagSearchResponse> {
280 let (mut resp, meta) = self
281 .post_json::<RagSearchRequest, RagSearchResponse>("/qai/v1/rag/search", req)
282 .await?;
283 if resp.cost_ticks == 0 {
284 resp.cost_ticks = meta.cost_ticks;
285 }
286 if resp.request_id.is_empty() {
287 resp.request_id = meta.request_id;
288 }
289 Ok(resp)
290 }
291
292 pub async fn rag_corpora(&self) -> Result<Vec<RagCorpus>> {
294 let (resp, _meta) = self
295 .get_json::<RagCorporaResponse>("/qai/v1/rag/corpora")
296 .await?;
297 Ok(resp.corpora)
298 }
299
300 pub async fn surreal_rag_search(
302 &self,
303 req: &SurrealRagSearchRequest,
304 ) -> Result<SurrealRagSearchResponse> {
305 let (mut resp, meta) = self
306 .post_json::<SurrealRagSearchRequest, SurrealRagSearchResponse>(
307 "/qai/v1/rag/surreal/search",
308 req,
309 )
310 .await?;
311 if resp.cost_ticks == 0 {
312 resp.cost_ticks = meta.cost_ticks;
313 }
314 if resp.request_id.is_empty() {
315 resp.request_id = meta.request_id;
316 }
317 Ok(resp)
318 }
319
320 pub async fn surreal_rag_providers(&self) -> Result<SurrealRagProvidersResponse> {
322 let (resp, _meta) = self
323 .get_json::<SurrealRagProvidersResponse>("/qai/v1/rag/surreal/providers")
324 .await?;
325 Ok(resp)
326 }
327
328 pub async fn collections_list(&self) -> Result<Vec<Collection>> {
332 let (resp, _meta) = self
333 .get_json::<CollectionsListResponse>("/qai/v1/rag/collections")
334 .await?;
335 Ok(resp.collections)
336 }
337
338 pub async fn collections_create(&self, name: &str) -> Result<Collection> {
340 let req = CreateCollectionRequest {
341 name: name.to_string(),
342 };
343 let (resp, _meta) = self
344 .post_json::<CreateCollectionRequest, Collection>("/qai/v1/rag/collections", &req)
345 .await?;
346 Ok(resp)
347 }
348
349 pub async fn collections_get(&self, id: &str) -> Result<Collection> {
351 let (resp, _meta) = self
352 .get_json::<Collection>(&format!("/qai/v1/rag/collections/{id}"))
353 .await?;
354 Ok(resp)
355 }
356
357 pub async fn collections_delete(&self, id: &str) -> Result<String> {
359 let (resp, _meta) = self
360 .delete_json::<DeleteCollectionResponse>(&format!("/qai/v1/rag/collections/{id}"))
361 .await?;
362 Ok(resp.message)
363 }
364
365 pub async fn collections_documents(&self, collection_id: &str) -> Result<Vec<CollectionDocument>> {
367 let (resp, _meta) = self
368 .get_json::<CollectionDocumentsResponse>(&format!(
369 "/qai/v1/rag/collections/{collection_id}/documents"
370 ))
371 .await?;
372 Ok(resp.documents)
373 }
374
375 pub async fn collections_upload(
378 &self,
379 collection_id: &str,
380 filename: &str,
381 content: Vec<u8>,
382 ) -> Result<CollectionUploadResult> {
383 let part = reqwest::multipart::Part::bytes(content)
384 .file_name(filename.to_string())
385 .mime_str("application/octet-stream")
386 .map_err(|e| crate::error::Error::Api(crate::error::ApiError {
387 status_code: 0,
388 code: "multipart_error".into(),
389 message: e.to_string(),
390 request_id: String::new(),
391 }))?;
392 let form = reqwest::multipart::Form::new().part("file", part);
393 let (resp, _meta) = self
394 .post_multipart::<CollectionUploadResult>(
395 &format!("/qai/v1/rag/collections/{collection_id}/upload"),
396 form,
397 )
398 .await?;
399 Ok(resp)
400 }
401
402 pub async fn collections_search(
404 &self,
405 req: &CollectionSearchRequest,
406 ) -> Result<Vec<CollectionSearchResult>> {
407 let (resp, _meta) = self
408 .post_json::<CollectionSearchRequest, CollectionSearchResponse>(
409 "/qai/v1/rag/search/collections",
410 req,
411 )
412 .await?;
413 Ok(resp.results)
414 }
415}