This repository has been archived on 2026-05-05. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
engram-retired/engrams/engram-server/src/routes/reasoning.rs
T
Will Anderson d1ec384b27 rename crates/ to engrams/, bindings/ to receptors/
- crates/ → engrams/ (Rust engrams live here)
- bindings/ → receptors/ (cross-language access points into the graph)
- Cargo.toml workspace paths updated
2026-04-29 03:27:33 -05:00

178 lines
5.5 KiB
Rust

/// Reasoning API routes — graph-native inference over the Engram knowledge graph.
///
/// POST /reason — evaluate a hypothesis
/// POST /reason/causal — find causal chains for a concept
/// POST /reason/contradictions — detect contradictions around a topic
use axum::{extract::State, http::StatusCode, Json};
use engram_reasoning::{
CausalDirection, Hypothesis, HypothesisType, ReasoningConfig, ReasoningEngine,
ReasoningResult, EvidenceChain, EvidenceNode,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use crate::state::AppState;
// ── POST /reason ──────────────────────────────────────────────────────────────
#[derive(Deserialize)]
pub struct ReasonRequest {
pub hypothesis: String,
pub hypothesis_type: HypothesisTypeParam,
pub embedding: Vec<f32>,
#[serde(default)]
pub config: ReasoningConfigParam,
}
/// JSON-friendly version of HypothesisType (mirrors the enum for serde)
#[derive(Deserialize)]
#[serde(rename_all = "PascalCase")]
pub enum HypothesisTypeParam {
IsTrue,
WhatCauses,
HowTo,
WhatIs,
Compare,
}
impl From<HypothesisTypeParam> for HypothesisType {
fn from(p: HypothesisTypeParam) -> Self {
match p {
HypothesisTypeParam::IsTrue => HypothesisType::IsTrue,
HypothesisTypeParam::WhatCauses => HypothesisType::WhatCauses,
HypothesisTypeParam::HowTo => HypothesisType::HowTo,
HypothesisTypeParam::WhatIs => HypothesisType::WhatIs,
HypothesisTypeParam::Compare => HypothesisType::Compare,
}
}
}
#[derive(Deserialize, Default)]
pub struct ReasoningConfigParam {
pub max_depth: Option<u32>,
pub min_confidence: Option<f32>,
pub max_evidence_nodes: Option<u32>,
pub contradiction_threshold: Option<f32>,
}
impl From<ReasoningConfigParam> for ReasoningConfig {
fn from(p: ReasoningConfigParam) -> Self {
let def = ReasoningConfig::default();
ReasoningConfig {
max_depth: p.max_depth.unwrap_or(def.max_depth),
min_confidence: p.min_confidence.unwrap_or(def.min_confidence),
max_evidence_nodes: p.max_evidence_nodes.unwrap_or(def.max_evidence_nodes),
contradiction_threshold: p
.contradiction_threshold
.unwrap_or(def.contradiction_threshold),
}
}
}
pub async fn reason(
State(state): State<Arc<AppState>>,
Json(req): Json<ReasonRequest>,
) -> Result<Json<ReasoningResult>, StatusCode> {
let config: ReasoningConfig = req.config.into();
let hypothesis = Hypothesis::new(req.hypothesis, req.embedding, req.hypothesis_type.into());
let db = state.db.clone();
let mut engine = ReasoningEngine::new(db, config);
let result = engine
.reason(&hypothesis)
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
Ok(Json(result))
}
// ── POST /reason/causal ───────────────────────────────────────────────────────
#[derive(Deserialize)]
pub struct CausalRequest {
pub concept_embedding: Vec<f32>,
#[serde(default = "default_causal_direction")]
pub direction: CausalDirectionParam,
}
fn default_causal_direction() -> CausalDirectionParam {
CausalDirectionParam::Forward
}
#[derive(Deserialize)]
#[serde(rename_all = "PascalCase")]
pub enum CausalDirectionParam {
Forward,
Backward,
Both,
}
impl From<CausalDirectionParam> for CausalDirection {
fn from(p: CausalDirectionParam) -> Self {
match p {
CausalDirectionParam::Forward => CausalDirection::Forward,
CausalDirectionParam::Backward => CausalDirection::Backward,
CausalDirectionParam::Both => CausalDirection::Both,
}
}
}
#[derive(Serialize)]
pub struct CausalResponse {
pub chains: Vec<EvidenceChain>,
}
pub async fn causal(
State(state): State<Arc<AppState>>,
Json(req): Json<CausalRequest>,
) -> Result<Json<CausalResponse>, StatusCode> {
let db = state.db.clone();
let mut engine = ReasoningEngine::with_default_config(db);
let chains = engine
.causal_chain(&req.concept_embedding, req.direction.into())
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
Ok(Json(CausalResponse { chains }))
}
// ── POST /reason/contradictions ───────────────────────────────────────────────
#[derive(Deserialize)]
pub struct ContradictionsRequest {
pub topic_embedding: Vec<f32>,
}
#[derive(Serialize)]
pub struct ContradictionPair {
pub supporting: EvidenceNode,
pub refuting: EvidenceNode,
}
#[derive(Serialize)]
pub struct ContradictionsResponse {
pub contradictions: Vec<ContradictionPair>,
}
pub async fn contradictions(
State(state): State<Arc<AppState>>,
Json(req): Json<ContradictionsRequest>,
) -> Result<Json<ContradictionsResponse>, StatusCode> {
let db = state.db.clone();
let mut engine = ReasoningEngine::with_default_config(db);
let pairs = engine
.find_contradictions(&req.topic_embedding)
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
Ok(Json(ContradictionsResponse {
contradictions: pairs
.into_iter()
.map(|(s, r)| ContradictionPair {
supporting: s,
refuting: r,
})
.collect(),
}))
}