use serde::{Deserialize, Serialize};
use crate::client::Client;
use crate::error::Result;
#[derive(Debug, Clone, Serialize, Default)]
pub struct RagSearchRequest {
pub query: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub corpus: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_k: Option<i32>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct RagSearchResponse {
pub results: Vec<RagResult>,
pub query: String,
#[serde(default)]
pub corpora: Option<Vec<String>>,
#[serde(default)]
pub cost_ticks: i64,
#[serde(default)]
pub request_id: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct RagResult {
pub source_uri: String,
pub source_name: String,
pub text: String,
pub score: f64,
pub distance: f64,
}
#[derive(Debug, Clone, Deserialize)]
pub struct RagCorpus {
pub name: String,
#[serde(rename = "displayName")]
pub display_name: String,
pub description: String,
pub state: String,
}
#[derive(Deserialize)]
struct RagCorporaResponse {
corpora: Vec<RagCorpus>,
}
#[derive(Debug, Clone, Serialize, Default)]
pub struct SurrealRagSearchRequest {
pub query: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub provider: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<i32>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct SurrealRagSearchResponse {
pub results: Vec<SurrealRagResult>,
pub query: String,
#[serde(default)]
pub provider: Option<String>,
#[serde(default)]
pub cost_ticks: i64,
#[serde(default)]
pub request_id: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct SurrealRagResult {
pub provider: String,
pub title: String,
pub heading: String,
pub source_file: String,
pub content: String,
pub score: f64,
}
#[derive(Debug, Clone, Deserialize)]
pub struct SurrealRagProviderInfo {
pub provider: String,
#[serde(default)]
pub chunk_count: Option<i64>,
}
pub type SurrealRagProvider = SurrealRagProviderInfo;
#[derive(Debug, Clone, Deserialize)]
pub struct SurrealRagProvidersResponse {
pub providers: Vec<SurrealRagProviderInfo>,
#[serde(default)]
pub request_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Collection {
pub id: String,
pub name: String,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub document_count: Option<u64>,
#[serde(default)]
pub owner: Option<String>,
#[serde(default)]
pub provider: Option<String>,
#[serde(default)]
pub created_at: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct CreateCollectionRequest {
pub name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CollectionDocument {
pub file_id: String,
pub name: String,
#[serde(default)]
pub size_bytes: Option<u64>,
#[serde(default)]
pub content_type: Option<String>,
#[serde(default)]
pub processing_status: Option<String>,
#[serde(default)]
pub document_status: Option<String>,
#[serde(default)]
pub indexed: Option<bool>,
#[serde(default)]
pub created_at: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CollectionSearchResult {
pub content: String,
#[serde(default)]
pub score: Option<f64>,
#[serde(default)]
pub file_id: Option<String>,
#[serde(default)]
pub collection_id: Option<String>,
#[serde(default)]
pub metadata: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize)]
pub struct CollectionSearchRequest {
pub query: String,
pub collection_ids: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mode: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_results: Option<usize>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CollectionUploadResult {
pub file_id: String,
pub filename: String,
#[serde(default)]
pub bytes: Option<u64>,
}
#[derive(Deserialize)]
struct CollectionsListResponse {
collections: Vec<Collection>,
}
#[derive(Deserialize)]
struct CollectionDocumentsResponse {
documents: Vec<CollectionDocument>,
}
#[derive(Deserialize)]
struct CollectionSearchResponse {
results: Vec<CollectionSearchResult>,
}
#[derive(Deserialize)]
struct DeleteCollectionResponse {
#[serde(default)]
message: String,
}
impl Client {
pub async fn rag_search(&self, req: &RagSearchRequest) -> Result<RagSearchResponse> {
let (mut resp, meta) = self
.post_json::<RagSearchRequest, RagSearchResponse>("/qai/v1/rag/search", req)
.await?;
if resp.cost_ticks == 0 {
resp.cost_ticks = meta.cost_ticks;
}
if resp.request_id.is_empty() {
resp.request_id = meta.request_id;
}
Ok(resp)
}
pub async fn rag_corpora(&self) -> Result<Vec<RagCorpus>> {
let (resp, _meta) = self
.get_json::<RagCorporaResponse>("/qai/v1/rag/corpora")
.await?;
Ok(resp.corpora)
}
pub async fn surreal_rag_search(
&self,
req: &SurrealRagSearchRequest,
) -> Result<SurrealRagSearchResponse> {
let (mut resp, meta) = self
.post_json::<SurrealRagSearchRequest, SurrealRagSearchResponse>(
"/qai/v1/rag/surreal/search",
req,
)
.await?;
if resp.cost_ticks == 0 {
resp.cost_ticks = meta.cost_ticks;
}
if resp.request_id.is_empty() {
resp.request_id = meta.request_id;
}
Ok(resp)
}
pub async fn surreal_rag_providers(&self) -> Result<SurrealRagProvidersResponse> {
let (resp, _meta) = self
.get_json::<SurrealRagProvidersResponse>("/qai/v1/rag/surreal/providers")
.await?;
Ok(resp)
}
pub async fn collections_list(&self) -> Result<Vec<Collection>> {
let (resp, _meta) = self
.get_json::<CollectionsListResponse>("/qai/v1/rag/collections")
.await?;
Ok(resp.collections)
}
pub async fn collections_create(&self, name: &str) -> Result<Collection> {
let req = CreateCollectionRequest {
name: name.to_string(),
};
let (resp, _meta) = self
.post_json::<CreateCollectionRequest, Collection>("/qai/v1/rag/collections", &req)
.await?;
Ok(resp)
}
pub async fn collections_get(&self, id: &str) -> Result<Collection> {
let (resp, _meta) = self
.get_json::<Collection>(&format!("/qai/v1/rag/collections/{id}"))
.await?;
Ok(resp)
}
pub async fn collections_delete(&self, id: &str) -> Result<String> {
let (resp, _meta) = self
.delete_json::<DeleteCollectionResponse>(&format!("/qai/v1/rag/collections/{id}"))
.await?;
Ok(resp.message)
}
pub async fn collections_documents(&self, collection_id: &str) -> Result<Vec<CollectionDocument>> {
let (resp, _meta) = self
.get_json::<CollectionDocumentsResponse>(&format!(
"/qai/v1/rag/collections/{collection_id}/documents"
))
.await?;
Ok(resp.documents)
}
pub async fn collections_upload(
&self,
collection_id: &str,
filename: &str,
content: Vec<u8>,
) -> Result<CollectionUploadResult> {
let part = reqwest::multipart::Part::bytes(content)
.file_name(filename.to_string())
.mime_str("application/octet-stream")
.map_err(|e| crate::error::Error::Api(crate::error::ApiError {
status_code: 0,
code: "multipart_error".into(),
message: e.to_string(),
request_id: String::new(),
}))?;
let form = reqwest::multipart::Form::new().part("file", part);
let (resp, _meta) = self
.post_multipart::<CollectionUploadResult>(
&format!("/qai/v1/rag/collections/{collection_id}/upload"),
form,
)
.await?;
Ok(resp)
}
pub async fn collections_search(
&self,
req: &CollectionSearchRequest,
) -> Result<Vec<CollectionSearchResult>> {
let (resp, _meta) = self
.post_json::<CollectionSearchRequest, CollectionSearchResponse>(
"/qai/v1/rag/search/collections",
req,
)
.await?;
Ok(resp.results)
}
}