use alloc::{
string::{String, ToString},
vec::Vec,
};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::{queue::QueueProducer, utils};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TaskBuilder {
pub id: u128,
pub model: Option<String>,
pub max_tokens: Option<u64>,
pub payload: Option<Value>,
pub system: Option<String>,
pub history: Option<Vec<Interaction>>,
pub schema: Option<Value>,
pub prompt: Option<String>,
}
impl Default for TaskBuilder {
fn default() -> Self {
Self::new()
}
}
impl TaskBuilder {
pub fn new() -> Self {
Self {
id: utils::id(),
model: None,
max_tokens: None,
payload: None,
system: None,
history: None,
schema: None,
prompt: None,
}
}
pub fn with_model<M: ToString>(mut self, model: M) -> Self {
self.model.replace(model.to_string());
self
}
pub fn with_max_tokens(mut self, max_tokens: u64) -> Self {
self.max_tokens.replace(max_tokens);
self
}
pub fn with_payload<P: Serialize>(mut self, payload: P) -> Self {
let payload = serde_json::to_value(&payload).expect("infallible serialization");
self.payload.replace(payload);
self
}
pub fn with_system<S: ToString>(mut self, system: S) -> Self {
self.system.replace(system.to_string());
self
}
pub fn with_history<H: IntoIterator<Item = Interaction>>(mut self, history: H) -> Self {
self.history.replace(history.into_iter().collect());
self
}
pub fn with_schema<S: Serialize>(mut self, schema: S) -> Self {
let schema = serde_json::to_value(&schema).expect("infallible serialization");
self.schema.replace(schema);
self
}
pub fn with_prompt<P: ToString>(mut self, prompt: P) -> Self {
self.prompt.replace(prompt.to_string());
self
}
pub fn build_chat(self) -> anyhow::Result<Task> {
let Self {
id,
model,
max_tokens,
payload,
system,
history,
schema,
prompt,
} = self;
let prompt =
prompt.ok_or_else(|| anyhow::anyhow!("the prompt is mandatory for a chat task."))?;
let contents = serde_json::to_value(TaskChat {
system,
history,
schema,
prompt,
})
.expect("infallible serialization");
Ok(Task {
id,
task_type: TaskType::Chat,
contents,
model,
max_tokens,
payload,
})
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Task {
pub id: u128,
pub task_type: TaskType,
pub contents: Value,
pub model: Option<String>,
pub max_tokens: Option<u64>,
pub payload: Option<Value>,
}
impl Task {
pub async fn send_and_wait<Q: QueueProducer>(
self,
queue: &Q,
timeout_secs: Option<u64>,
) -> anyhow::Result<Response> {
match timeout_secs {
#[cfg(feature = "std")]
Some(t) => {
let timeout = core::time::Duration::from_secs(t);
let message = queue.send_task(self).await?;
let response = queue.receive_response(message);
let response = tokio::time::timeout(timeout, response).await??;
response.ok_or_else(|| anyhow::anyhow!("task response timeout"))
}
_ => {
anyhow::ensure!(timeout_secs.is_none(), "timeout not implemented on runtime");
let message = queue.send_task(self).await?;
let response = queue.receive_response(message).await?;
response.ok_or_else(|| anyhow::anyhow!("task response not available"))
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Response {
pub task: Task,
pub success: bool,
pub tokens: u64,
pub model: String,
pub contents: String,
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum TaskType {
#[default]
Chat,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TaskChat {
pub system: Option<String>,
pub history: Option<Vec<Interaction>>,
pub schema: Option<Value>,
pub prompt: String,
}
impl TaskChat {
pub fn new<P: ToString>(prompt: P) -> Self {
Self {
prompt: prompt.to_string(),
system: None,
history: None,
schema: None,
}
}
pub fn with_system<S: ToString>(mut self, system: S) -> Self {
self.system.replace(system.to_string());
self
}
pub fn with_schema<S: Serialize>(mut self, schema: S) -> Self {
let schema = serde_json::to_value(&schema).expect("failed to serialize schema");
self.schema.replace(schema);
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Interaction {
Assistant(String),
System(String),
User(String),
}
impl Task {
pub fn with_id(mut self, id: u128) -> Self {
self.id = id;
self
}
pub fn with_max_tokens(mut self, max_tokens: u64) -> Self {
self.max_tokens.replace(max_tokens);
self
}
pub fn with_payload<P: Serialize>(mut self, payload: P) -> Self {
let payload = serde_json::to_value(&payload).expect("failed to serialize payload");
self.payload.replace(payload);
self
}
pub fn with_model<M: ToString>(mut self, model: M) -> Self {
self.model.replace(model.to_string());
self
}
pub fn try_to_chat(&self) -> anyhow::Result<TaskChat> {
Ok(serde_json::from_value(self.contents.clone())?)
}
#[cfg(feature = "std")]
pub(crate) fn model(
&self,
default_model: &std::sync::Arc<Option<String>>,
) -> anyhow::Result<String> {
self.model
.clone()
.or_else(|| default_model.as_ref().clone())
.ok_or_else(|| anyhow::anyhow!("no model provided"))
}
}
impl Response {
pub fn success<M: ToString, C: ToString>(
task: Task,
tokens: u64,
model: M,
contents: C,
) -> Self {
Self {
task,
success: true,
tokens,
model: model.to_string(),
contents: contents.to_string(),
}
}
pub fn error<M: ToString, C: ToString>(task: Task, tokens: u64, model: M, error: C) -> Self {
Self {
task,
success: false,
tokens,
model: model.to_string(),
contents: error.to_string(),
}
}
pub fn with_history(mut self, history: Vec<Interaction>) -> Self {
self.task.contents["history"] =
serde_json::to_value(history).expect("infallible serialization");
self
}
pub fn chat_history(&self) -> anyhow::Result<Vec<Interaction>> {
let history = self
.task
.contents
.get("history")
.ok_or_else(|| anyhow::anyhow!("no history available"))?;
Ok(serde_json::from_value(history.clone())?)
}
pub fn usage(&self) -> f64 {
self.task
.max_tokens
.map(|t| (self.tokens as f64 / t.max(1) as f64).clamp(0.0, 1.0))
.unwrap_or(0.0)
}
}
impl Interaction {
pub fn assistant<A: ToString>(prompt: A) -> Self {
Self::Assistant(prompt.to_string())
}
pub fn user<U: ToString>(prompt: U) -> Self {
Self::User(prompt.to_string())
}
}