use crate::prepared::PendingWarnings;
use crate::{
DecisionCapability, DecisionProvider, DecisionRequest, DecisionResponse, DecisionValidation,
Degradations, Result, Subject, UnsupportedPolicy, Validation, ValidationCoverage,
};
#[must_use = "a prepared request reports its warnings through `finish` or `into_validation`"]
#[derive(Debug)]
pub struct Prepared<'a> {
request: &'a DecisionRequest,
pending: PendingWarnings,
}
pub fn prepare<'a, P: DecisionProvider + ?Sized>(
provider: &P,
request: &'a DecisionRequest,
) -> Result<Prepared<'a>> {
Prepared::new(provider, request, false)
}
pub fn prepare_dry_run<'a, P: DecisionProvider + ?Sized>(
provider: &P,
request: &'a DecisionRequest,
) -> Result<Prepared<'a>> {
Prepared::new(provider, request, true)
}
impl<'a> Prepared<'a> {
fn new<P: DecisionProvider + ?Sized>(
provider: &P,
request: &'a DecisionRequest,
dry_run: bool,
) -> Result<Self> {
request.validate(&provider.decision_limits(&request.model))?;
let mut degradations =
Degradations::new(provider.provider_name().to_owned(), UnsupportedPolicy::Warn);
for (name, question) in &request.questions {
let capability = DecisionCapability::from(question.kind());
let support = provider.decision_capability(&request.model, capability);
let subject = || Subject::Other {
name: format!("question {name:?}"),
};
if degradations.refuses(capability, support, subject) {
return Err(degradations.reject(
subject(),
format!(
"`{}` does not support {} questions",
provider.provider_name(),
question.kind()
),
));
}
}
if !request.images.is_empty() {
let capability = DecisionCapability::ImageInput;
let support = provider.decision_capability(&request.model, capability);
let subject = || Subject::Other {
name: "images".into(),
};
if degradations.refuses(capability, support, subject) {
return Err(degradations.reject(
subject(),
format!(
"`{}` does not take images in decision requests",
provider.provider_name()
),
));
}
}
Ok(Self {
request,
pending: PendingWarnings::new(request.model.clone(), degradations, dry_run),
})
}
#[must_use]
pub fn request(&self) -> &DecisionRequest {
self.request
}
pub fn degradations_mut(&mut self) -> &mut Degradations {
self.pending.degradations_mut()
}
pub fn parts_mut(&mut self) -> (&DecisionRequest, &mut Degradations) {
(self.request, self.pending.degradations_mut())
}
#[must_use]
pub fn is_dry_run(&self) -> bool {
self.pending.is_dry_run()
}
pub fn mark_sent(&mut self) {
self.pending.mark_sent();
}
pub fn finish(self, mut response: DecisionResponse) -> DecisionResponse {
let inner = std::mem::take(&mut response.warnings);
response.warnings = self.pending.take().merge(inner);
response
}
pub fn finish_validation(self, inner: DecisionValidation) -> DecisionValidation {
self.pending.take().merge_validation(inner)
}
pub fn into_validation(self, coverage: ValidationCoverage) -> DecisionValidation {
let (warnings, unknown) = self.pending.take().into_validation_parts();
Validation::from_parts(warnings, unknown, coverage)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::decision::Question;
use crate::decision::mock::MockDecisionProvider;
use crate::{CapabilitySupport, Error, Image};
fn request() -> DecisionRequest {
DecisionRequest::new("m", "x")
.image(Image::base64("image/png", "aGk="))
.question("q", Question::yes_no("?"))
}
#[test]
fn images_are_refused_where_unsupported() {
let provider = MockDecisionProvider::empty().with_decision_capability(
DecisionCapability::ImageInput,
CapabilitySupport::Unsupported,
);
let err = prepare(&provider, &request()).unwrap_err();
assert!(matches!(err, Error::Unsupported { .. }), "{err:?}");
assert!(err.message().contains("images"), "{err}");
let text_only = DecisionRequest::new("m", "x").question("q", Question::yes_no("?"));
let validation = prepare_dry_run(&provider, &text_only)
.unwrap()
.into_validation(ValidationCoverage::Capabilities);
assert!(
validation
.relied_on_unknown()
.iter()
.all(|(capability, _)| *capability != DecisionCapability::ImageInput),
"{validation:?}"
);
}
#[test]
fn images_rely_on_an_unknown_answer() {
let provider = MockDecisionProvider::empty();
let validation = prepare_dry_run(&provider, &request())
.unwrap()
.into_validation(ValidationCoverage::Capabilities);
assert!(
validation
.relied_on_unknown()
.iter()
.any(|(capability, _)| *capability == DecisionCapability::ImageInput),
"{validation:?}"
);
}
}