Archived
d1ec384b27
- crates/ → engrams/ (Rust engrams live here) - bindings/ → receptors/ (cross-language access points into the graph) - Cargo.toml workspace paths updated
178 lines
5.5 KiB
Rust
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(),
|
|
}))
|
|
}
|