use pravah::flows::{Agent, AgentConfig, Flow, FlowError, FlowGraph, FlowRuntime, RunOut};
use pravah::{Context, FlowConf};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
struct ProjectRequest {
title: String,
description: String,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
struct ResearchRequest {
topic: String,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
struct DesignRequest {
feature: String,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
struct ResearchFindings {
summary: String,
references: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
struct DesignProposal {
approach: String,
trade_offs: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
struct FinalReport {
research: String,
design: String,
recommendation: String,
}
impl Agent for ResearchRequest {
type Output = ResearchFindings;
fn build() -> AgentConfig {
AgentConfig::new(
"You are a research analyst. Given a topic, produce a concise summary \
and a short list of relevant references.",
"gemini://gemini-2.5-flash-lite",
)
}
}
impl Agent for DesignRequest {
type Output = DesignProposal;
fn build() -> AgentConfig {
AgentConfig::new(
"You are a software architect. Given a feature description, propose an \
implementation approach and list the main trade-offs.",
"gemini://gemini-2.5-flash-lite",
)
}
}
fn split_project(
req: ProjectRequest,
_ctx: Context,
) -> Result<(ResearchRequest, DesignRequest), FlowError> {
Ok((
ResearchRequest { topic: req.title },
DesignRequest { feature: req.description },
))
}
fn merge_reports(
research: ResearchFindings,
design: DesignProposal,
_ctx: Context,
) -> Result<FinalReport, FlowError> {
Ok(FinalReport {
research: research.summary,
design: design.approach,
recommendation: format!(
"Proceed with design. Key trade-offs: {}",
design.trade_offs.join("; ")
),
})
}
impl Flow for ProjectRequest {
type Output = FinalReport;
fn build() -> Result<FlowGraph, FlowError> {
FlowGraph::builder()
.fork(split_project)
.agent::<ResearchRequest>()
.agent::<DesignRequest>()
.join(merge_reports)
.build()
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
dotenvy::dotenv().ok();
let ctx = Context::new(FlowConf::default());
let input = ProjectRequest {
title: "Distributed rate limiter".to_string(),
description: "Token-bucket algorithm backed by Redis, with per-user \
and per-route limits, suitable for a multi-region API."
.to_string(),
};
let mut runtime = FlowRuntime::new(input)?;
loop {
match runtime.next(ctx.clone()).await? {
RunOut::Continue => {}
RunOut::Done(report) => {
println!("## Research\n{}\n", report.research);
println!("## Design\n{}\n", report.design);
println!("## Recommendation\n{}", report.recommendation);
break;
}
RunOut::Suspend { value, tool_id } => {
eprintln!("Unexpected suspension at '{tool_id}': {value}");
break;
}
}
}
Ok(())
}