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 #[serde(default)]
162 pub request_id: Option<String>,
163}
164
165#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct Collection {
170 pub id: String,
172
173 pub name: String,
175
176 #[serde(default)]
178 pub description: Option<String>,
179
180 #[serde(default)]
182 pub document_count: Option<u64>,
183
184 #[serde(default)]
186 pub owner: Option<String>,
187
188 #[serde(default)]
190 pub provider: Option<String>,
191
192 #[serde(default)]
194 pub created_at: Option<String>,
195}
196
197#[derive(Debug, Clone, Serialize)]
199pub struct CreateCollectionRequest {
200 pub name: String,
201}
202
203#[derive(Debug, Clone, Serialize, Deserialize)]
205pub struct CollectionDocument {
206 pub file_id: String,
207 pub name: String,
208 #[serde(default)]
209 pub size_bytes: Option<u64>,
210 #[serde(default)]
211 pub content_type: Option<String>,
212 #[serde(default)]
213 pub processing_status: Option<String>,
214 #[serde(default)]
215 pub document_status: Option<String>,
216 #[serde(default)]
217 pub indexed: Option<bool>,
218 #[serde(default)]
219 pub created_at: Option<String>,
220}
221
222#[derive(Debug, Clone, Deserialize)]
224pub struct CollectionSearchResult {
225 pub content: String,
226 #[serde(default)]
227 pub score: Option<f64>,
228 #[serde(default)]
229 pub file_id: Option<String>,
230 #[serde(default)]
231 pub collection_id: Option<String>,
232 #[serde(default)]
233 pub metadata: Option<serde_json::Value>,
234}
235
236#[derive(Debug, Clone, Serialize)]
238pub struct CollectionSearchRequest {
239 pub query: String,
240 pub collection_ids: Vec<String>,
241 #[serde(skip_serializing_if = "Option::is_none")]
242 pub mode: Option<String>,
243 #[serde(skip_serializing_if = "Option::is_none")]
244 pub max_results: Option<usize>,
245}
246
247#[derive(Debug, Clone, Deserialize)]
249pub struct CollectionUploadResult {
250 pub file_id: String,
251 pub filename: String,
252 #[serde(default)]
253 pub bytes: Option<u64>,
254}
255
256#[derive(Deserialize)]
259struct CollectionsListResponse {
260 collections: Vec<Collection>,
261}
262
263#[derive(Deserialize)]
264struct CollectionDocumentsResponse {
265 documents: Vec<CollectionDocument>,
266}
267
268#[derive(Deserialize)]
269struct CollectionSearchResponse {
270 results: Vec<CollectionSearchResult>,
271}
272
273#[derive(Deserialize)]
274struct DeleteCollectionResponse {
275 #[serde(default)]
276 message: String,
277}
278
279impl Client {
280 pub async fn rag_search(&self, req: &RagSearchRequest) -> Result<RagSearchResponse> {
282 let (mut resp, meta) = self
283 .post_json::<RagSearchRequest, RagSearchResponse>("/qai/v1/rag/search", req)
284 .await?;
285 if resp.cost_ticks == 0 {
286 resp.cost_ticks = meta.cost_ticks;
287 }
288 if resp.request_id.is_empty() {
289 resp.request_id = meta.request_id;
290 }
291 Ok(resp)
292 }
293
294 pub async fn rag_corpora(&self) -> Result<Vec<RagCorpus>> {
296 let (resp, _meta) = self
297 .get_json::<RagCorporaResponse>("/qai/v1/rag/corpora")
298 .await?;
299 Ok(resp.corpora)
300 }
301
302 pub async fn surreal_rag_search(
304 &self,
305 req: &SurrealRagSearchRequest,
306 ) -> Result<SurrealRagSearchResponse> {
307 let (mut resp, meta) = self
308 .post_json::<SurrealRagSearchRequest, SurrealRagSearchResponse>(
309 "/qai/v1/rag/surreal/search",
310 req,
311 )
312 .await?;
313 if resp.cost_ticks == 0 {
314 resp.cost_ticks = meta.cost_ticks;
315 }
316 if resp.request_id.is_empty() {
317 resp.request_id = meta.request_id;
318 }
319 Ok(resp)
320 }
321
322 pub async fn surreal_rag_providers(&self) -> Result<SurrealRagProvidersResponse> {
324 let (resp, _meta) = self
325 .get_json::<SurrealRagProvidersResponse>("/qai/v1/rag/surreal/providers")
326 .await?;
327 Ok(resp)
328 }
329
330 pub async fn collections_list(&self) -> Result<Vec<Collection>> {
334 let (resp, _meta) = self
335 .get_json::<CollectionsListResponse>("/qai/v1/rag/collections")
336 .await?;
337 Ok(resp.collections)
338 }
339
340 pub async fn collections_create(&self, name: &str) -> Result<Collection> {
342 let req = CreateCollectionRequest {
343 name: name.to_string(),
344 };
345 let (resp, _meta) = self
346 .post_json::<CreateCollectionRequest, Collection>("/qai/v1/rag/collections", &req)
347 .await?;
348 Ok(resp)
349 }
350
351 pub async fn collections_get(&self, id: &str) -> Result<Collection> {
353 let (resp, _meta) = self
354 .get_json::<Collection>(&format!("/qai/v1/rag/collections/{id}"))
355 .await?;
356 Ok(resp)
357 }
358
359 pub async fn collections_delete(&self, id: &str) -> Result<String> {
361 let (resp, _meta) = self
362 .delete_json::<DeleteCollectionResponse>(&format!("/qai/v1/rag/collections/{id}"))
363 .await?;
364 Ok(resp.message)
365 }
366
367 pub async fn collections_documents(&self, collection_id: &str) -> Result<Vec<CollectionDocument>> {
369 let (resp, _meta) = self
370 .get_json::<CollectionDocumentsResponse>(&format!(
371 "/qai/v1/rag/collections/{collection_id}/documents"
372 ))
373 .await?;
374 Ok(resp.documents)
375 }
376
377 pub async fn collections_upload(
380 &self,
381 collection_id: &str,
382 filename: &str,
383 content: Vec<u8>,
384 ) -> Result<CollectionUploadResult> {
385 let part = reqwest::multipart::Part::bytes(content)
386 .file_name(filename.to_string())
387 .mime_str("application/octet-stream")
388 .map_err(|e| crate::error::Error::Api(crate::error::ApiError {
389 status_code: 0,
390 code: "multipart_error".into(),
391 message: e.to_string(),
392 request_id: String::new(),
393 }))?;
394 let form = reqwest::multipart::Form::new().part("file", part);
395 let (resp, _meta) = self
396 .post_multipart::<CollectionUploadResult>(
397 &format!("/qai/v1/rag/collections/{collection_id}/upload"),
398 form,
399 )
400 .await?;
401 Ok(resp)
402 }
403
404 pub async fn collections_search(
406 &self,
407 req: &CollectionSearchRequest,
408 ) -> Result<Vec<CollectionSearchResult>> {
409 let (resp, _meta) = self
410 .post_json::<CollectionSearchRequest, CollectionSearchResponse>(
411 "/qai/v1/rag/search/collections",
412 req,
413 )
414 .await?;
415 Ok(resp.results)
416 }
417}