Archived
feat: engram-reasoning — graph-native inference engine, evidence chains, confidence propagation
This commit is contained in:
@@ -228,6 +228,12 @@ async fn main() -> anyhow::Result<()> {
|
||||
.route("/tx/history", get(routes::tx::tx_history))
|
||||
.route("/tx/chain/{command_id}", get(routes::tx::tx_causal_chain));
|
||||
|
||||
// Reasoning routes (no auth — graph-native inference)
|
||||
let reasoning_routes = Router::new()
|
||||
.route("/reason", post(routes::reasoning::reason))
|
||||
.route("/reason/causal", post(routes::reasoning::causal))
|
||||
.route("/reason/contradictions", post(routes::reasoning::contradictions));
|
||||
|
||||
let studio_routes = Router::new()
|
||||
.route("/", get(serve_studio_index))
|
||||
.route("/studio", get(serve_studio_index))
|
||||
@@ -239,6 +245,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
.merge(sync_routes)
|
||||
.merge(projection_routes)
|
||||
.merge(tx_routes)
|
||||
.merge(reasoning_routes)
|
||||
.layer(CorsLayer::permissive())
|
||||
.with_state(state);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pub mod core;
|
||||
pub mod projection;
|
||||
pub mod reasoning;
|
||||
pub mod sync;
|
||||
pub mod swarm;
|
||||
pub mod tx;
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
/// 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(),
|
||||
}))
|
||||
}
|
||||
Reference in New Issue
Block a user