use anyhow::{Context, Result};
use candle_core::{DType, Device};
use clap::Parser;
use laya::{Agent, Options, Question};
use serde_json::Value;
use std::collections::BTreeMap;
use std::path::PathBuf;
#[derive(Parser)]
#[command(
about = "Typed decisions from the Laya model, in one forward pass",
version
)]
struct Args {
#[arg(long, default_value = "models/laya-base")]
model: PathBuf,
#[arg(long, conflicts_with = "state_file")]
state: Option<String>,
#[arg(long)]
state_file: Option<PathBuf>,
#[arg(long)]
questions: PathBuf,
#[arg(long)]
device: Option<String>,
#[arg(long)]
dtype: Option<String>,
}
fn main() -> Result<()> {
let args = Args::parse();
let state: Value = match (&args.state, &args.state_file) {
(Some(s), _) => Value::String(s.clone()),
(None, Some(p)) => {
let raw = std::fs::read_to_string(p)
.with_context(|| format!("reading state {}", p.display()))?;
serde_json::from_str(&raw).unwrap_or(Value::String(raw))
}
(None, None) => anyhow::bail!("pass --state or --state-file"),
};
let raw = std::fs::read_to_string(&args.questions)
.with_context(|| format!("reading questions {}", args.questions.display()))?;
let questions: BTreeMap<String, Question> =
serde_json::from_str(&raw).context("parsing questions JSON")?;
let questions: Vec<(String, Question)> = questions.into_iter().collect();
let opts = Options {
device: match args.device.as_deref() {
None => None,
Some("cpu") => Some(Device::Cpu),
Some("metal") => Some(Device::new_metal(0).context("metal device unavailable")?),
Some("cuda") => Some(Device::new_cuda(0).context("cuda device unavailable")?),
Some(other) => anyhow::bail!("unknown device {other:?}"),
},
dtype: match args.dtype.as_deref() {
None => None,
Some("f16") => Some(DType::F16),
Some("f32") => Some(DType::F32),
Some(other) => anyhow::bail!("unknown dtype {other:?}"),
},
};
let agent = Agent::from_dir(&args.model, opts)?;
let response = agent.system_one(&state, &questions)?;
println!("{}", serde_json::to_string_pretty(&response)?);
Ok(())
}