HumboldtJoker's picture
Upload folder using huggingface_hub
4554903 verified
Raw
History Blame Contribute Delete
5.55 kB
"""BDI coherence scoring."""
from dataclasses import dataclass, field
from typing import List, Tuple
from .models import BDIBelief, BDIDesire, BDIIntention, BDISnapshot
@dataclass(frozen=True)
class CoherenceScore:
belief_desire_alignment: float
desire_intention_alignment: float
belief_intention_alignment: float
overall: float
issues: Tuple[str, ...] = () # frozen dataclass needs immutable default
class CoherenceChecker:
"""Checks coherence across BDI layers."""
def __init__(self) -> None:
self._weights = {
"belief_desire": 0.35,
"desire_intention": 0.35,
"belief_intention": 0.30,
}
def check_coherence(self, snapshot: BDISnapshot) -> CoherenceScore:
bd_score, bd_issues = self._check_belief_desire_alignment(
snapshot.beliefs, snapshot.desires
)
di_score, di_issues = self._check_desire_intention_alignment(
snapshot.desires, snapshot.intentions
)
bi_score, bi_issues = self._check_belief_intention_alignment(
snapshot.beliefs, snapshot.intentions
)
overall = (
self._weights["belief_desire"] * bd_score
+ self._weights["desire_intention"] * di_score
+ self._weights["belief_intention"] * bi_score
)
all_issues = bd_issues + di_issues + bi_issues
return CoherenceScore(
belief_desire_alignment=round(bd_score, 4),
desire_intention_alignment=round(di_score, 4),
belief_intention_alignment=round(bi_score, 4),
overall=round(overall, 4),
issues=tuple(all_issues),
)
def _check_belief_desire_alignment(
self,
beliefs: List[BDIBelief],
desires: List[BDIDesire],
) -> Tuple[float, List[str]]:
"""Check tag overlap and content keyword matching between beliefs and desires."""
if not beliefs or not desires:
return (0.5, ["Insufficient beliefs or desires for alignment check."])
issues: List[str] = []
belief_tags: set = set()
belief_words: set = set()
for b in beliefs:
belief_tags.update(b.tags)
belief_words.update(w.lower() for w in b.content.split() if len(w) > 3)
scores: List[float] = []
for d in desires:
desire_tags = set(d.related_tags)
desire_words = set(w.lower() for w in d.content.split() if len(w) > 3)
tag_overlap = len(desire_tags & belief_tags) / max(len(desire_tags), 1)
word_overlap = len(desire_words & belief_words) / max(len(desire_words), 1)
score = 0.6 * tag_overlap + 0.4 * min(word_overlap, 1.0)
scores.append(score)
if score < 0.3:
issues.append(
f"Desire '{d.id}' has weak belief support (score={score:.2f})."
)
return (sum(scores) / len(scores), issues)
def _check_desire_intention_alignment(
self,
desires: List[BDIDesire],
intentions: List[BDIIntention],
) -> Tuple[float, List[str]]:
"""Check that intentions reference active desires via desire_ids."""
if not desires or not intentions:
return (0.5, ["Insufficient desires or intentions for alignment check."])
issues: List[str] = []
desire_ids = {d.id for d in desires}
active_desire_ids = {d.id for d in desires if d.status.value == "active"}
scores: List[float] = []
for intention in intentions:
linked = set(intention.desire_ids)
if not linked:
scores.append(0.0)
issues.append(
f"Intention '{intention.id}' is not linked to any desire."
)
continue
valid = linked & desire_ids
active = linked & active_desire_ids
score = (len(valid) / len(linked)) * 0.5 + (len(active) / len(linked)) * 0.5
scores.append(score)
if not active:
issues.append(
f"Intention '{intention.id}' links only to inactive desires."
)
return (sum(scores) / len(scores), issues)
def _check_belief_intention_alignment(
self,
beliefs: List[BDIBelief],
intentions: List[BDIIntention],
) -> Tuple[float, List[str]]:
"""Check that intentions reference active beliefs via belief_ids."""
if not beliefs or not intentions:
return (0.5, ["Insufficient beliefs or intentions for alignment check."])
issues: List[str] = []
belief_ids = {b.id for b in beliefs}
active_belief_ids = {b.id for b in beliefs if b.status.value == "active"}
scores: List[float] = []
for intention in intentions:
linked = set(intention.belief_ids)
if not linked:
scores.append(0.0)
issues.append(
f"Intention '{intention.id}' is not linked to any belief."
)
continue
valid = linked & belief_ids
active = linked & active_belief_ids
score = (len(valid) / len(linked)) * 0.5 + (len(active) / len(linked)) * 0.5
scores.append(score)
if not active:
issues.append(
f"Intention '{intention.id}' links only to inactive/stale beliefs."
)
return (sum(scores) / len(scores), issues)