use crate::{Outcome, Payload, Run, SchemaVersion, Step, StepKind};
use serde_json::Map;
pub fn step(idx: usize, name: impl Into<String>) -> StepBuilder {
StepBuilder {
step: Step {
idx,
kind: StepKind::Tool,
name: name.into(),
inputs: None,
outputs: None,
attrs: Map::new(),
t_start: None,
t_end: None,
parent_idx: None,
},
}
}
pub fn run(id: impl Into<String>, steps: Vec<Step>) -> RunBuilder {
RunBuilder {
run: Run {
schema_version: SchemaVersion::current(),
id: id.into(),
task: None,
outcome: None,
steps,
edges: None,
},
}
}
#[derive(Debug, Clone)]
pub struct StepBuilder {
step: Step,
}
impl StepBuilder {
#[must_use]
pub fn kind(mut self, kind: StepKind) -> Self {
self.step.kind = kind;
self
}
#[must_use]
pub fn inputs(mut self, inputs: impl Into<Option<Payload>>) -> Self {
self.step.inputs = inputs.into();
self
}
#[must_use]
pub fn outputs(mut self, outputs: impl Into<Option<Payload>>) -> Self {
self.step.outputs = outputs.into();
self
}
#[must_use]
pub fn text_output(self, out: impl Into<String>) -> Self {
self.outputs(Payload::Text(out.into()))
}
#[must_use]
pub fn t_start(mut self, t: impl Into<String>) -> Self {
self.step.t_start = Some(t.into());
self
}
#[must_use]
pub fn t_end(mut self, t: impl Into<String>) -> Self {
self.step.t_end = Some(t.into());
self
}
#[must_use]
pub fn build(self) -> Step {
self.step
}
}
#[derive(Debug, Clone)]
pub struct RunBuilder {
run: Run,
}
impl RunBuilder {
#[must_use]
pub fn task(mut self, task: impl Into<String>) -> Self {
self.run.task = Some(task.into());
self
}
#[must_use]
pub fn outcome(mut self, outcome: Outcome) -> Self {
self.run.outcome = Some(outcome);
self
}
#[must_use]
pub fn build(self) -> Run {
self.run
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn minimal_step_is_a_bare_tool_step() {
let expected = Step {
idx: 3,
kind: StepKind::Tool,
name: "web.search".to_string(),
inputs: None,
outputs: None,
attrs: Map::new(),
t_start: None,
t_end: None,
parent_idx: None,
};
assert_eq!(step(3, "web.search").build(), expected);
}
#[test]
fn step_setters_reach_their_fields() {
let s = step(0, "planner")
.kind(StepKind::Llm)
.inputs(Payload::Text("question".to_string()))
.text_output("plan: search then verify")
.build();
assert_eq!(s.kind, StepKind::Llm);
assert_eq!(s.inputs, Some(Payload::Text("question".to_string())));
assert_eq!(
s.outputs,
Some(Payload::Text("plan: search then verify".to_string()))
);
}
#[test]
fn outputs_accepts_an_option_directly() {
let s = step(0, "fetch").outputs(None).build();
assert_eq!(s.outputs, None);
}
#[test]
fn minimal_run_is_current_schema_with_no_verdict() {
let steps = vec![step(0, "plan").build()];
let expected = Run {
schema_version: SchemaVersion::current(),
id: "r1".to_string(),
task: None,
outcome: None,
steps: steps.clone(),
edges: None,
};
assert_eq!(run("r1", steps).build(), expected);
}
#[test]
fn run_setters_reach_their_fields() {
let r = run("r2", Vec::new())
.task("find the census figure")
.outcome(Outcome::Fail)
.build();
assert_eq!(r.task.as_deref(), Some("find the census figure"));
assert_eq!(r.outcome, Some(Outcome::Fail));
}
}