use crate::client::{AuraClient, RequestBody};
use crate::error::AuraError;
use crate::types::{
AuraResponse, ChatMessage, ChatResponse, EmbedRequest, EmbedResponse, RagIngestRequest,
RagIngestResponse, RagRequest, RagResponse, SemanticSearchResult,
};
use serde::{Deserialize, Serialize};
pub struct AiService {
client: AuraClient,
}
#[derive(Debug, Clone, Default)]
pub struct ChatOptions {
pub model: Option<String>,
pub temperature: Option<f64>,
pub max_tokens: Option<u32>,
}
#[derive(Debug, Clone, Default)]
pub struct Nl2SqlOptions {
pub dialect: Option<String>,
pub examples: Option<Vec<Nl2SqlExample>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Nl2SqlExample {
pub question: String,
pub sql: String,
}
impl AiService {
pub fn new(client: AuraClient) -> Self {
Self { client }
}
fn prefix(&self) -> String {
"/v1/ai".to_string()
}
pub async fn chat(
&self,
messages: Vec<ChatMessage>,
options: Option<ChatOptions>,
) -> Result<AuraResponse<ChatResponse>, AuraError> {
let opts = options.unwrap_or_default();
let mut body = serde_json::json!({
"messages": messages,
});
if let Some(m) = opts.model {
body["model"] = serde_json::Value::String(m);
}
if let Some(t) = opts.temperature {
body["temperature"] = serde_json::json!(t);
}
if let Some(mt) = opts.max_tokens {
body["max_tokens"] = serde_json::json!(mt);
}
self.client
.request(
reqwest::Method::POST,
&format!("{}/chat", self.prefix()),
RequestBody::Json(body),
)
.await
}
pub async fn embed(&self, req: EmbedRequest) -> Result<AuraResponse<EmbedResponse>, AuraError> {
self.client
.request(
reqwest::Method::POST,
&format!("{}/embed", self.prefix()),
RequestBody::Json(serde_json::to_value(&req).unwrap_or_default()),
)
.await
}
pub async fn rag(&self, req: RagRequest) -> Result<AuraResponse<RagResponse>, AuraError> {
self.client
.request(
reqwest::Method::POST,
&format!("{}/rag/query", self.prefix()),
RequestBody::Json(serde_json::to_value(&req).unwrap_or_default()),
)
.await
}
pub async fn rag_ingest(
&self,
req: RagIngestRequest,
) -> Result<AuraResponse<RagIngestResponse>, AuraError> {
self.client
.request(
reqwest::Method::POST,
&format!("{}/rag/ingest", self.prefix()),
RequestBody::Json(serde_json::to_value(&req).unwrap_or_default()),
)
.await
}
pub async fn semantic_search(
&self,
query: &str,
namespace: &str,
top_k: Option<u32>,
threshold: Option<f64>,
) -> Result<AuraResponse<Vec<SemanticSearchResult>>, AuraError> {
let mut params = Vec::new();
params.push(format!("query={}", urlencoding::encode(query)));
params.push(format!("namespace={}", urlencoding::encode(namespace)));
if let Some(k) = top_k {
params.push(format!("top_k={}", k));
}
if let Some(t) = threshold {
params.push(format!("threshold={}", t));
}
self.client
.request(
reqwest::Method::GET,
&format!("{}/search?{}", self.prefix(), params.join("&")),
RequestBody::None,
)
.await
}
pub async fn nl2sql(
&self,
question: &str,
options: Option<Nl2SqlOptions>,
) -> Result<AuraResponse<serde_json::Value>, AuraError> {
let opts = options.unwrap_or_default();
let body = serde_json::json!({
"question": question,
"dialect": opts.dialect,
"examples": opts.examples,
});
self.client
.request(
reqwest::Method::POST,
&format!("{}/nl2sql", self.prefix()),
RequestBody::Json(body),
)
.await
}
}