#![forbid(unsafe_code)]
mod check;
mod eval;
pub mod program;
use rustev_contract::definition::{Determinism, Operation};
use rustev_contract::descriptor::{
BackendDescriptor, InputExcess, InputLimit, OperationSupport, OutputKind,
};
use rustev_contract::ids::ArtifactId;
use rustev_contract::run::{Charge, CostBound, CostModel};
use rustev_contract::schema;
use rustev_core::seams::{
AdapterFailure, AttemptCall, AttemptReport, BoxFuture, CancelAck, CancelSignal,
DecisionBackend, RemoteEnd,
};
pub use check::{PlanMismatch, PlanMismatchKind};
pub use eval::Evaluated;
pub use program::{ProgramError, ProgramErrorKind, RulesProgram};
use program::{CTask, ProgramErrorKind as K};
#[derive(Debug, Clone)]
pub struct RulesBackend {
program: RulesProgram,
tasks: Vec<CTask>,
descriptor: BackendDescriptor,
}
impl RulesBackend {
pub fn new(program: RulesProgram) -> Result<Self, ProgramError> {
let tasks = program::check(&program)?;
let artifact = program.artifact_id().map_err(|e| ProgramError {
at: String::new(),
kind: K::Canonical(e),
})?;
let descriptor = derive_descriptor(&program, &tasks, artifact);
Ok(Self {
program,
tasks,
descriptor,
})
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, ProgramError> {
use rustev_contract::Document;
let program = RulesProgram::parse(bytes).map_err(|e| ProgramError {
at: String::new(),
kind: K::Document(e),
})?;
Self::new(program)
}
pub fn program(&self) -> &RulesProgram {
&self.program
}
pub fn artifact(&self) -> &ArtifactId {
&self.descriptor.artifact
}
pub fn evaluate(&self, projection: &[u8], cancel: &CancelSignal) -> Evaluated {
self.evaluate_observed(projection, &mut |_| cancel.is_raised())
}
pub(crate) fn evaluate_observed(
&self,
projection: &[u8],
cancelled: &mut dyn FnMut(eval::Point) -> bool,
) -> Evaluated {
eval::evaluate(
&self.tasks,
self.program.limits.max_projection_bytes,
projection,
cancelled,
)
}
fn charge(&self) -> Charge {
Charge::Observed {
units: self.program.cost.units_per_call,
}
}
}
fn derive_descriptor(
program: &RulesProgram,
tasks: &[CTask],
artifact: ArtifactId,
) -> BackendDescriptor {
let operations = [
Operation::Classify,
Operation::Proposition,
Operation::Rubric,
]
.into_iter()
.filter_map(|op| {
tasks
.iter()
.filter(|t| t.operation == op)
.map(|t| t.options.len() as u64)
.max()
.map(|max_options| OperationSupport {
isolation: None,
screen: None,
operation: op,
output: OutputKind::Logits,
max_options,
})
})
.collect();
BackendDescriptor {
ranking: tasks
.iter()
.any(|t| t.operation == Operation::Rank)
.then_some(rustev_contract::descriptor::Ranking {
modes: vec![rustev_contract::descriptor::RankingMode::Pointwise],
max_candidates: 1,
listwise_scores: rustev_contract::descriptor::ListwiseScores::Never,
listwise_partial: rustev_contract::descriptor::ListwisePartial::Never,
}),
schema: schema::BACKEND.into(),
backend_id: program.backend_id.clone(),
artifact,
operations,
input_limit: InputLimit {
max_bytes: program.limits.max_projection_bytes,
on_excess: InputExcess::Refuse,
},
determinism: Determinism::Bitwise,
}
}
impl DecisionBackend for RulesBackend {
fn descriptor(&self) -> &BackendDescriptor {
&self.descriptor
}
fn cost_model(&self) -> CostModel {
CostModel::Bounded
}
fn cost_bound(&self, _projection: &[u8]) -> CostBound {
CostBound::Bounded {
max_units: self.program.cost.units_per_call,
}
}
fn infer<'a>(&'a self, call: AttemptCall<'a>) -> BoxFuture<'a, AttemptReport> {
Box::pin(async move {
let charge = self.charge();
match self.evaluate(call.projection, &call.cancel) {
Evaluated::Output(output) => AttemptReport {
result: Ok(output),
charge,
cancel: CancelAck::NotRequested,
remote: RemoteEnd::Finished,
},
Evaluated::Failed(detail) => AttemptReport {
result: Err((AdapterFailure::Permanent, detail)),
charge,
cancel: CancelAck::NotRequested,
remote: RemoteEnd::Finished,
},
Evaluated::Cancelled(point) => AttemptReport {
result: Err((AdapterFailure::Cancelled, format!("cancelled {point}"))),
charge,
cancel: CancelAck::Stopped,
remote: RemoteEnd::Finished,
},
}
})
}
}
#[cfg(test)]
mod tests;