reasoninglayer 0.1.2

Rust client SDK for the Reasoning Layer API
Documentation
//! End-to-end quickstart mirroring the TypeScript SDK README. Requires a live Reasoning Layer
//! backend at `REASONINGLAYER_URL` (defaults to `http://localhost:8083`) and a tenant UUID in
//! `REASONINGLAYER_TENANT_ID`.
//!
//! ```sh
//! REASONINGLAYER_TENANT_ID=00000000-0000-0000-0000-000000000001 cargo run --example quickstart
//! ```

use std::env;

use reasoninglayer::{
    constrained, guard, psi, var, AddRuleRequest, BackwardChainRequest, ClientConfig,
    CreateSortRequest, CreateTermRequest, GuardOp, ReasoningLayerClient, Value,
};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let base_url =
        env::var("REASONINGLAYER_URL").unwrap_or_else(|_| "http://localhost:8083".into());
    let tenant_id = env::var("REASONINGLAYER_TENANT_ID")
        .expect("REASONINGLAYER_TENANT_ID environment variable is required");

    let client = ReasoningLayerClient::new(ClientConfig::new(base_url, &tenant_id))?;

    // Sort hierarchy.
    let person = client
        .sorts()
        .create_sort(CreateSortRequest::with_name("person"), None)
        .await?;
    println!("created sort person: {}", person.id);

    let mut employee_req = CreateSortRequest::with_name("employee");
    employee_req.parents = vec![person.id.clone()];
    let employee = client.sorts().create_sort(employee_req, None).await?;
    println!("created sort employee: {}", employee.id);

    // Record a term (tagged `ValueDto` features).
    let mut features = std::collections::BTreeMap::new();
    features.insert("name".into(), Value::string("Alice"));
    features.insert("salary".into(), Value::real(95_000.0));
    let term = client
        .terms()
        .create_term(
            CreateTermRequest {
                sort_id: employee.id.clone(),
                owner_id: tenant_id.clone(),
                features,
            },
            None,
        )
        .await?;
    println!("created term {}", term.term.id);

    // Rule:   well_paid(X) :- employee(name=X, salary=S), S > 80_000.
    client
        .inference()
        .add_rule(
            AddRuleRequest {
                term: psi("well_paid", [("person", var("?X"))]),
                antecedents: vec![psi(
                    "employee",
                    [
                        ("name", var("?X")),
                        ("salary", constrained("?S", guard(GuardOp::Gt, 80_000_i64))),
                    ],
                )],
                certainty: None,
            },
            None,
        )
        .await?;
    println!("added rule");

    // Query.
    let req = BackwardChainRequest {
        goal: Some(psi("well_paid", [("person", var("?Who"))])),
        max_solutions: Some(10),
        ..Default::default()
    };
    let result = client.inference().backward_chain(req, None).await?;
    println!(
        "{} solutions in {}ms",
        result.solutions.len(),
        result.query_time_ms
    );
    for (i, solution) in result.solutions.iter().enumerate() {
        for binding in &solution.substitution.bindings {
            let name = binding.variable_name.as_deref().unwrap_or("?");
            println!("  [{i}] {name} = {}", binding.bound_to_display);
        }
    }

    Ok(())
}