hypersteeldb 0.3.1

A database that compiles questions instead of guessing answers: typed vocabulary discovered from your documents, queries type-checked before they run, roaring-bitmap set algebra over reified hyperedges, and Dempster-Shafer evidence with an explicit conflict guard.
Documentation
//! Agent CLI — answer a question over a corpus using the configured LLM provider.
//!
//! Usage:   agent <corpus.csv|.json|.jsonl> "your question"
//! Provider selection (env): STEELDB_LLM=bedrock|paddock (default bedrock).
//!   Bedrock:  STEELDB_BEDROCK_MODEL=<id>  AWS_REGION=<region>
//!   Paddock:  STEELDB_PADDOCK_URL=http://host:port/v1  STEELDB_PADDOCK_MODEL=Qwen/Qwen3.5-2B
//!
//! Build with a backend: `cargo run --features bedrock --bin agent -- data.csv "..."`.

use std::path::Path;
use steeldb::agent::{run_agent, ProviderConfig};
use steeldb::Corpus;

fn load_corpus(path: &str) -> Result<Corpus, String> {
    let p = Path::new(path);
    if p.is_dir() {
        let (corpus, report) = Corpus::from_folder(p).map_err(|e| e.to_string())?;
        eprintln!("folder: {} situations from {} files", report.situations, report.ingested.len());
        for (f, n) in &report.ingested {
            eprintln!("  + {f}  ({n})");
        }
        for (f, why) in &report.skipped {
            eprintln!("  - {f}  [{why}]");
        }
        return Ok(corpus);
    }
    let ext = p.extension().and_then(|e| e.to_str()).unwrap_or("").to_lowercase();
    #[cfg(feature = "docs")]
    if steeldb::docs::is_doc_ext(&ext) {
        return Corpus::from_document(p).map_err(|e| e.to_string());
    }
    match ext.as_str() {
        "csv" | "tsv" => Corpus::from_csv(p).map_err(|e| e.to_string()),
        "json" | "ndjson" => Corpus::from_json(p).map_err(|e| e.to_string()),
        "jsonl" => Corpus::from_jsonl(p, None).map_err(|e| e.to_string()),
        _ => Corpus::from_csv(p).map_err(|e| e.to_string()),
    }
}

#[tokio::main]
async fn main() {
    let args: Vec<String> = std::env::args().collect();
    if args.len() < 3 {
        eprintln!("usage: agent <corpus.csv|.json|.jsonl> \"question\"");
        std::process::exit(2);
    }
    let corpus = match load_corpus(&args[1]) {
        Ok(c) => c,
        Err(e) => {
            eprintln!("load error: {e}");
            std::process::exit(1);
        }
    };
    let s = corpus.stats();
    eprintln!("corpus: {} situations, {} tokens", s.situations, s.vocab);

    let cfg = ProviderConfig::from_env();
    eprintln!("provider config: {cfg:?}");
    let provider = match cfg.build().await {
        Ok(p) => p,
        Err(e) => {
            eprintln!("provider error: {e}");
            std::process::exit(1);
        }
    };
    eprintln!("using provider: {}\n", provider.name());

    match run_agent(provider.as_ref(), &corpus, &args[2], 12).await {
        Ok(ans) => {
            eprintln!("── tool trace ({} calls, {} steps) ──", ans.trace.len(), ans.steps);
            for (i, t) in ans.trace.iter().enumerate() {
                let flag = if t.is_error { " [error]" } else { "" };
                eprintln!("  {}. {}{}  {}", i + 1, t.name, flag, t.input);
            }
            println!("\n{}", ans.answer);
        }
        Err(e) => {
            eprintln!("agent error: {e}");
            std::process::exit(1);
        }
    }
}