reasoninglayer 0.1.1

Rust client SDK for the Reasoning Layer API
Documentation
//! Inference operations — rules, facts, backward/forward chaining, NAF, fuzzy prove, Bayesian.

use std::sync::Arc;

use crate::error::Error;
use crate::http::HttpClient;
use crate::types::common::RequestOptions;
use crate::types::homoiconic::PsiTermDto;
use crate::types::inference::{
    AddFactRequest, AddFactResponse, AddRuleRequest, AddRuleResponse, BackwardChainRequest,
    BackwardChainResponse, BayesianPredictRequest, BayesianPredictResponse, BulkAddFactsRequest,
    BulkAddFactsResponse, BulkAddRulesRequest, BulkAddRulesResponse, BulkFuzzyProveRequest,
    BulkFuzzyProveResponse, ClearFactsResponse, CreateGoalRequest, CreateGoalResponse,
    DeleteGoalResponse, ForwardChainRequest, ForwardChainResponse, FuzzyProveRequest,
    FuzzyProveResponse, GetFactsResponse, ListGoalsResponse, MetaSortsResponse, NafProveRequest,
    NafProveResponse, TaggedForwardChainRequest, TaggedForwardChainResponse,
};

/// Resource client for inference operations.
///
/// Requests use the untagged [`TermInputDto`](crate::types::homoiconic::TermInputDto) format. Use
/// the [`psi`](crate::psi), [`var`](crate::var), [`constrained`](crate::constrained),
/// and [`guard`](crate::guard) builders to construct request payloads.
#[derive(Debug, Clone)]
pub struct InferenceClient {
    http: HttpClient,
    tenant_id: Arc<String>,
}

impl InferenceClient {
    pub(crate) fn new(http: HttpClient) -> Self {
        let tenant_id = Arc::new(http.config.tenant_id.clone());
        Self { http, tenant_id }
    }

    /// Add an inference rule.
    pub async fn add_rule(
        &self,
        request: AddRuleRequest,
        options: Option<&RequestOptions>,
    ) -> Result<AddRuleResponse, Error> {
        self.http.post("/inference/rules", &request, options).await
    }

    /// Add a fact.
    pub async fn add_fact(
        &self,
        request: AddFactRequest,
        options: Option<&RequestOptions>,
    ) -> Result<AddFactResponse, Error> {
        self.http.post("/inference/facts", &request, options).await
    }

    /// Bulk-add rules.
    pub async fn bulk_add_rules(
        &self,
        request: BulkAddRulesRequest,
        options: Option<&RequestOptions>,
    ) -> Result<BulkAddRulesResponse, Error> {
        self.http
            .post("/inference/rules/bulk", &request, options)
            .await
    }

    /// Bulk-add facts.
    pub async fn bulk_add_facts(
        &self,
        request: BulkAddFactsRequest,
        options: Option<&RequestOptions>,
    ) -> Result<BulkAddFactsResponse, Error> {
        self.http
            .post("/inference/facts/bulk", &request, options)
            .await
    }

    /// Get all stored facts for the tenant.
    pub async fn get_facts(
        &self,
        options: Option<&RequestOptions>,
    ) -> Result<GetFactsResponse, Error> {
        let path = format!("/inference/facts/{}", encode(self.tenant_id.as_str()));
        self.http.get(&path, None, options).await
    }

    /// Clear all stored facts for the tenant.
    pub async fn clear_facts(
        &self,
        options: Option<&RequestOptions>,
    ) -> Result<ClearFactsResponse, Error> {
        let path = format!("/inference/facts/{}", encode(self.tenant_id.as_str()));
        self.http.delete(&path, None, options).await
    }

    /// Query for matching data by searching rules and facts backwards from a goal.
    pub async fn backward_chain(
        &self,
        request: BackwardChainRequest,
        options: Option<&RequestOptions>,
    ) -> Result<BackwardChainResponse, Error> {
        self.http
            .post("/inference/backward-chain", &request, options)
            .await
    }

    /// Derive new facts by applying rules forward from existing facts.
    pub async fn forward_chain(
        &self,
        request: ForwardChainRequest,
        options: Option<&RequestOptions>,
    ) -> Result<ForwardChainResponse, Error> {
        self.http
            .post("/inference/forward-chain", &request, options)
            .await
    }

    /// Forward chain with probabilistic provenance tags.
    pub async fn forward_chain_tagged(
        &self,
        request: TaggedForwardChainRequest,
        options: Option<&RequestOptions>,
    ) -> Result<TaggedForwardChainResponse, Error> {
        self.http
            .post("/inference/forward-chain-tagged", &request, options)
            .await
    }

    /// Fuzzy proof search.
    pub async fn fuzzy_prove(
        &self,
        request: FuzzyProveRequest,
        options: Option<&RequestOptions>,
    ) -> Result<FuzzyProveResponse, Error> {
        self.http
            .post("/inference/fuzzy-prove", &request, options)
            .await
    }

    /// Bulk fuzzy inference.
    pub async fn bulk_fuzzy_prove(
        &self,
        request: BulkFuzzyProveRequest,
        options: Option<&RequestOptions>,
    ) -> Result<BulkFuzzyProveResponse, Error> {
        self.http
            .post("/inference/fuzzy-prove/bulk", &request, options)
            .await
    }

    /// Bayesian prediction.
    pub async fn bayesian_predict(
        &self,
        request: BayesianPredictRequest,
        options: Option<&RequestOptions>,
    ) -> Result<BayesianPredictResponse, Error> {
        self.http
            .post("/inference/bayesian-predict", &request, options)
            .await
    }

    /// Negation-as-failure proof search.
    pub async fn naf_prove(
        &self,
        request: NafProveRequest,
        options: Option<&RequestOptions>,
    ) -> Result<NafProveResponse, Error> {
        self.http
            .post("/inference/naf-prove", &request, options)
            .await
    }

    /// Alias for [`InferenceClient::naf_prove`].
    pub async fn prove_with_negation(
        &self,
        request: NafProveRequest,
        options: Option<&RequestOptions>,
    ) -> Result<NafProveResponse, Error> {
        self.naf_prove(request, options).await
    }

    /// Create a saved goal for reuse.
    pub async fn create_goal(
        &self,
        request: CreateGoalRequest,
        options: Option<&RequestOptions>,
    ) -> Result<CreateGoalResponse, Error> {
        self.http.post("/inference/goals", &request, options).await
    }

    /// List all saved goals.
    pub async fn list_goals(
        &self,
        options: Option<&RequestOptions>,
    ) -> Result<ListGoalsResponse, Error> {
        self.http.get("/inference/goals", None, options).await
    }

    /// Get a saved goal by ID.
    pub async fn get_goal(
        &self,
        goal_id: &str,
        options: Option<&RequestOptions>,
    ) -> Result<PsiTermDto, Error> {
        let path = format!("/inference/goals/{}", encode(goal_id));
        self.http.get(&path, None, options).await
    }

    /// Delete a saved goal.
    pub async fn delete_goal(
        &self,
        goal_id: &str,
        options: Option<&RequestOptions>,
    ) -> Result<DeleteGoalResponse, Error> {
        let path = format!("/inference/goals/{}", encode(goal_id));
        self.http.delete(&path, None, options).await
    }

    /// Get meta-sorts used by the inference engine.
    pub async fn get_meta_sorts(
        &self,
        options: Option<&RequestOptions>,
    ) -> Result<MetaSortsResponse, Error> {
        self.http.get("/inference/meta-sorts", None, options).await
    }

    // ─── Friendly aliases ─────────────────────────────────────────────────

    /// Alias for [`InferenceClient::backward_chain`].
    pub async fn query(
        &self,
        request: BackwardChainRequest,
        options: Option<&RequestOptions>,
    ) -> Result<BackwardChainResponse, Error> {
        self.backward_chain(request, options).await
    }

    /// Alias for [`InferenceClient::forward_chain`].
    pub async fn derive(
        &self,
        request: ForwardChainRequest,
        options: Option<&RequestOptions>,
    ) -> Result<ForwardChainResponse, Error> {
        self.forward_chain(request, options).await
    }

    /// Alias for [`InferenceClient::add_fact`].
    pub async fn assert_fact(
        &self,
        request: AddFactRequest,
        options: Option<&RequestOptions>,
    ) -> Result<AddFactResponse, Error> {
        self.add_fact(request, options).await
    }
}

fn encode(segment: &str) -> String {
    url::form_urlencoded::byte_serialize(segment.as_bytes()).collect()
}