use std::path::Path;
use steeldb::agent::{run_agent, ProviderConfig};
use steeldb::discover::{profile_csv, propose_spec, ProjectionSpec, SpecProjector};
use steeldb::Corpus;
fn provider_config() -> ProviderConfig {
if std::env::var("STEELDB_LLM").as_deref() == Ok("bedrock") {
return ProviderConfig::from_env();
}
ProviderConfig::Paddock {
base_url: std::env::var("STEELDB_PADDOCK_URL").unwrap_or_else(|_| "http://localhost:11434/v1".to_string()),
model: std::env::var("STEELDB_PADDOCK_MODEL").unwrap_or_else(|_| "qwen3:1.7b".to_string()),
api_key: None,
}
}
#[tokio::main]
async fn main() {
let args: Vec<String> = std::env::args().collect();
let path = match args.get(1) {
Some(p) => p.clone(),
None => {
eprintln!("usage: discover <file.csv> [\"question\"]");
std::process::exit(2);
}
};
let profile = match profile_csv(Path::new(&path)) {
Ok(p) => p,
Err(e) => {
eprintln!("profile error: {e}");
std::process::exit(1);
}
};
eprintln!("profiled {} rows, {} columns:", profile.rows, profile.columns.len());
for c in &profile.columns {
let key = if c.candidate_key { " [key]" } else { "" };
eprintln!(" {:<20} {:<11} distinct={:<5} ratio={:.2}{key}", c.name, c.kind, c.distinct, c.distinct_ratio);
}
let spec: ProjectionSpec = match provider_config().build().await {
Ok(provider) => {
eprintln!("\nproposing projection with {} …", provider.name());
propose_spec(provider.as_ref(), &profile).await
}
Err(e) => {
eprintln!("\nno LLM ({e}); using deterministic projection");
ProjectionSpec::default_for(&profile)
}
};
eprintln!("\nprojection spec:");
eprintln!(" situation: {}", spec.situation);
eprintln!(" facets: {}", spec.facets.iter().map(|f| f.column.clone()).collect::<Vec<_>>().join(", "));
eprintln!(" measures: {}", spec.measures.iter().map(|m| format!("{}(×{})", m.column, m.bins)).collect::<Vec<_>>().join(", "));
if !spec.relations.is_empty() {
eprintln!(" relations: {}", spec.relations.iter().map(|r| format!("{}:{}→{}", r.name, r.head, r.tail)).collect::<Vec<_>>().join(", "));
}
if !spec.notes.is_empty() {
eprintln!(" notes: {}", spec.notes);
}
let projector = match SpecProjector::open(&path, spec) {
Ok(p) => p,
Err(e) => {
eprintln!("materialize error: {e}");
std::process::exit(1);
}
};
let corpus = match Corpus::from_projector(Box::new(projector)) {
Ok(c) => c,
Err(e) => {
eprintln!("ingest error: {e}");
std::process::exit(1);
}
};
let stats = corpus.stats();
eprintln!("\nbuilt hypergraph: {} situations, {} tokens", stats.situations, stats.vocab);
eprintln!("facets: {}", stats.facets.iter().map(|(f, n)| format!("{f}({n})")).collect::<Vec<_>>().join(" "));
if let Some(question) = args.get(2) {
let provider = match provider_config().build().await {
Ok(p) => p,
Err(e) => {
eprintln!("provider error: {e}");
std::process::exit(1);
}
};
eprintln!("\n── asking: {question} ──");
match run_agent(provider.as_ref(), &corpus, question, 14).await {
Ok(ans) => {
eprintln!("({} tool calls)", ans.trace.len());
println!("\n{}", ans.answer);
}
Err(e) => eprintln!("agent error: {e}"),
}
}
}