Initial commit: add .gitignore and README
This commit is contained in:
85
fusionagi/reasoning/meta_reasoning.py
Normal file
85
fusionagi/reasoning/meta_reasoning.py
Normal file
@@ -0,0 +1,85 @@
|
||||
"""Meta-reasoning: challenge assumptions, detect contradictions, revisit nodes."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from fusionagi.schemas.atomic import AtomicSemanticUnit, AtomicUnitType
|
||||
from fusionagi.reasoning.tot import ThoughtNode, expand_node
|
||||
from fusionagi._logger import logger
|
||||
|
||||
|
||||
def challenge_assumptions(
|
||||
units: list[AtomicSemanticUnit],
|
||||
current_conclusion: str,
|
||||
) -> list[str]:
|
||||
"""
|
||||
Identify and flag assumptions in units that support the conclusion.
|
||||
"""
|
||||
flagged: list[str] = []
|
||||
conclusion_lower = current_conclusion.lower()
|
||||
|
||||
for u in units:
|
||||
if u.type == AtomicUnitType.ASSUMPTION:
|
||||
flagged.append(u.content)
|
||||
elif "assume" in u.content.lower() or "assumption" in u.content.lower():
|
||||
flagged.append(u.content)
|
||||
elif u.type == AtomicUnitType.CONSTRAINT:
|
||||
if any(w in conclusion_lower for w in ["must", "should", "require"]):
|
||||
flagged.append(f"Constraint may be assumed: {u.content[:100]}")
|
||||
|
||||
logger.debug("Assumptions flagged", extra={"count": len(flagged)})
|
||||
return flagged
|
||||
|
||||
|
||||
def detect_contradictions(
|
||||
units: list[AtomicSemanticUnit],
|
||||
) -> list[tuple[str, str]]:
|
||||
"""
|
||||
Find conflicting units (heuristic: negation mismatch, same subject).
|
||||
"""
|
||||
neg_words = {"not", "no", "never", "none", "cannot", "shouldn't", "won't", "don't", "doesn't"}
|
||||
pairs: list[tuple[str, str]] = []
|
||||
|
||||
for i, a in enumerate(units):
|
||||
wa = set(a.content.lower().split())
|
||||
for b in units[i + 1:]:
|
||||
wb = set(b.content.lower().split())
|
||||
a_neg = bool(wa & neg_words)
|
||||
b_neg = bool(wb & neg_words)
|
||||
if a_neg != b_neg:
|
||||
overlap = len(wa & wb) / max(len(wa), 1)
|
||||
if overlap > 0.2:
|
||||
pairs.append((a.unit_id, b.unit_id))
|
||||
|
||||
logger.debug("Contradictions detected", extra={"count": len(pairs)})
|
||||
return pairs
|
||||
|
||||
|
||||
def revisit_node(
|
||||
tree: ThoughtNode | None,
|
||||
node_id: str,
|
||||
new_evidence: str,
|
||||
) -> ThoughtNode | None:
|
||||
"""
|
||||
Re-expand a node when new evidence arrives.
|
||||
Creates a new child with the new evidence.
|
||||
"""
|
||||
if tree is None:
|
||||
return None
|
||||
|
||||
def find_node(n: ThoughtNode) -> ThoughtNode | None:
|
||||
if n.node_id == node_id:
|
||||
return n
|
||||
for c in n.children:
|
||||
found = find_node(c)
|
||||
if found:
|
||||
return found
|
||||
return None
|
||||
|
||||
node = find_node(tree)
|
||||
if not node:
|
||||
return tree
|
||||
child = expand_node(node, new_evidence)
|
||||
child.metadata["revisit_evidence"] = new_evidence[:200]
|
||||
return tree
|
||||
Reference in New Issue
Block a user