use async_trait::async_trait;
use saya_agent::CancellationToken;
mod prompts;
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct ResultShape {
pub row_count: usize,
pub columns: Vec<String>,
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Verdict {
pub agrees: bool,
pub reason: String,
}
pub(crate) struct RoundTrip {
pub restatement: Option<String>,
pub verdict: Option<Verdict>,
pub diverged: bool,
}
#[async_trait]
pub(crate) trait Restater: Sync {
async fn restate(&self, sql: &str, shape: &ResultShape) -> Option<String>;
async fn judge(&self, question: &str, restatement: &str) -> Option<Verdict>;
}
pub(crate) async fn round_trip(
question: &str,
sql: &str,
shape: &ResultShape,
restater: &dyn Restater,
cancellation: &CancellationToken,
) -> RoundTrip {
if cancellation.is_cancelled() {
return cancelled();
}
let Some(restatement) = restater.restate(sql, shape).await else {
return RoundTrip {
restatement: None,
verdict: None,
diverged: false,
};
};
if cancellation.is_cancelled() {
return cancelled();
}
let Some(verdict) = restater.judge(question, &restatement).await else {
return RoundTrip {
restatement: Some(restatement),
verdict: None,
diverged: false,
};
};
let diverged = !verdict.agrees;
RoundTrip {
restatement: Some(restatement),
verdict: Some(verdict),
diverged,
}
}
fn cancelled() -> RoundTrip {
RoundTrip {
restatement: None,
verdict: None,
diverged: false,
}
}
#[cfg(test)]
#[path = "../roundtrip_tests.rs"]
mod tests;