Falsify / falsify /models.py
Aaryan Kumar
deploy to hugging face
1605cbb
Raw
History Blame Contribute Delete
7.89 kB
"""
FALSIFY belief-graph data models.
Every node is a Cognee ``DataPoint`` subclass, so the same object serializes into
the graph DB (Ladybug) and, via its ``Embeddable`` field, into the vector DB
(LanceDB). The vector collection auto-created for a class is ``"{ClassName}_{field}"``
(e.g. ``Evidence_claim`` — the collection the contradiction prefilter searches).
Lifecycle / truth-state note
----------------------------
The *authoritative, persistent* belief state of a node lives on the graph node as
``truth_alignment`` (a list) + ``truth_epoch`` (int), written through the graph
engine (``set_node_truth_state``), not as pydantic model fields. Those props are
what ``recall()`` filters on and what survives a process restart.
The ``truth_state`` model field below is a convenience mirror of the node's initial
state at ingest time (defaults to ``ALIVE``). Do not treat it as the source of
truth after a memify run — always read back with ``get_node_truth_state``.
Dedup across sessions
---------------------
``Hypothesis``, ``Evidence`` and ``Assertion`` mark identity fields with ``Dedup()``.
DataPoint derives a stable UUID5 id from those fields (``DataPoint.id_for``), so
re-adding the same belief in a later session updates the existing node instead of
creating a duplicate — essential for a Session-2 contradiction to land on the exact
node built in Session 1.
"""
from datetime import datetime, timezone
from enum import Enum
from typing import Annotated, List, Optional
from pydantic import Field
from cognee.infrastructure.engine import DataPoint, Dedup, Embeddable, LLMContext
def _now_iso() -> str:
"""Return the current UTC time as an ISO-8601 string (used for node timestamps)."""
return datetime.now(timezone.utc).isoformat()
class TruthState(str, Enum):
"""Canonical belief-lifecycle states for a FALSIFY graph node.
The values are the exact strings written into the node's ``truth_alignment``
list, so a state can be compared directly against what ``get_node_truth_state``
returns (e.g. ``get_node_truth_state([n])[n]["truth_alignment"] == [TruthState.REFUTED]``).
States:
- ``ALIVE``: default; the node participates in recall context.
- ``REFUTED``: Evidence directly contradicted by a newer fact — the entry point
of a forward cascade.
- ``SUPERSEDED``: a node replaced/demoted by a newer competing node; retained as
provenance, excluded from recall.
- ``INVALIDATED``: a Conclusion whose critical supporting Evidence chain was
refuted (forward-cascade victim).
- ``FORGOTTEN``: an orphaned dead-end scheduled for surgical delete (transient;
the node is then hard-removed from both graph and vector stores).
Note: the FALSIFY spec's minimal enum names ALIVE/REFUTED/SUPERSEDED/FORGOTTEN.
``INVALIDATED`` is added here because the forward-propagation algorithm
(REQUIREMENTS §1.2/§1.4) needs a distinct state for cascade-victim Conclusions
versus directly-refuted Evidence.
"""
ALIVE = "alive"
REFUTED = "refuted"
SUPERSEDED = "superseded"
INVALIDATED = "invalidated"
FORGOTTEN = "forgotten"
# --------------------------------------------------------------------------- #
# Edge relationship-name constants (passed as ``relationship_name`` to add_edge)
# --------------------------------------------------------------------------- #
# Conclusion -> Evidence. THE forward-propagation rail. edge_properties={"critical": bool}
DEPENDS_ON = "depends_on"
# Evidence -> Hypothesis. Evidence corroborates a hypothesis. edge_properties={"weight": float}
SUPPORTS = "supports"
# Evidence -> Hypothesis. Evidence contradicts a hypothesis. edge_properties={"weight": float}
# (REQUIREMENTS §1.1 names this edge "refutes"; ``REFUTES`` is provided as an alias.)
CONTRADICTS = "refutes"
REFUTES = CONTRADICTS
# Evidence(new) -> Evidence(old). New fact overrides an old evidence node.
# edge_properties={"confidence": float}
SUPERSEDES = "supersedes"
class InvestigationQuestion(DataPoint):
"""Root node of a belief graph: the research question under investigation.
Example: "Did Company X know about the defect before the recall?" Everything
else (hypotheses, evidence, conclusions) hangs off this question via
``question_id``.
"""
question: Annotated[str, Embeddable(), LLMContext()]
truth_state: TruthState = TruthState.ALIVE
confidence: float = 1.0
timestamp: str = Field(default_factory=_now_iso)
source_id: Optional[str] = None
metadata: dict = {
"index_fields": ["question"],
"identity_fields": ["question"],
}
class Hypothesis(DataPoint):
"""A candidate explanation competing to answer the InvestigationQuestion.
Hypotheses gain/lose standing through ``supports``/``refutes`` Evidence edges.
When a hypothesis' only supporting Evidence is refuted, it is demoted to
``SUPERSEDED`` and the rival with the strongest surviving support is promoted.
"""
statement: Annotated[str, Embeddable(), Dedup(), LLMContext()]
question_id: str
status: str = "alive"
prior: float = 0.5
truth_state: TruthState = TruthState.ALIVE
confidence: float = 0.5
timestamp: str = Field(default_factory=_now_iso)
source_id: Optional[str] = None
metadata: dict = {
"index_fields": ["statement"],
"identity_fields": ["question_id", "statement"],
}
class Evidence(DataPoint):
"""A factual claim bearing on one or more hypotheses.
Evidence is the contradiction entry point: the ``Evidence_claim`` vector
collection is what the two-gate detector prefilters, and a ``REFUTED`` Evidence
node is the seed of every forward cascade. ``asserted_at`` is used as the
tie-break when deciding which of two competing claims supersedes the other
(newer wins).
"""
claim: Annotated[str, Embeddable(), Dedup(), LLMContext()]
source_id: str
quote: str = ""
stance: str = "supports" # "supports" | "refutes"
asserted_at: str = Field(default_factory=_now_iso)
truth_state: TruthState = TruthState.ALIVE
confidence: float = 0.5
timestamp: str = Field(default_factory=_now_iso)
metadata: dict = {
"index_fields": ["claim"],
"identity_fields": ["source_id", "claim"],
}
class Conclusion(DataPoint):
"""A derived finding that rests on one or more Evidence nodes.
A Conclusion ``depends_on`` the Evidence it is built from (edge carries
``critical: bool``). When a *critical* dependency is refuted and no alive
critical alternative remains, the Conclusion is ``INVALIDATED`` by the forward
cascade; if it then has no surviving consumer it is ``FORGOTTEN`` (hard-deleted).
"""
statement: Annotated[str, Embeddable(), LLMContext()]
confidence: float = 0.5
depends_on_ids: List[str] = Field(default_factory=list)
truth_state: TruthState = TruthState.ALIVE
timestamp: str = Field(default_factory=_now_iso)
source_id: Optional[str] = None
metadata: dict = {
"index_fields": ["statement"],
"identity_fields": ["statement"],
}
class Assertion(DataPoint):
"""A raw, unclassified incoming claim — e.g. the new fact pasted in Session 2.
An Assertion is the pre-belief form of an incoming statement before the detector
decides whether it contradicts/supersedes existing Evidence and materializes a
proper ``Evidence`` node. It carries the same lifecycle scaffolding as the other
nodes so it can be reasoned over uniformly.
"""
text: Annotated[str, Embeddable(), Dedup(), LLMContext()]
truth_state: TruthState = TruthState.ALIVE
confidence: float = 0.5
timestamp: str = Field(default_factory=_now_iso)
source_id: Optional[str] = None
metadata: dict = {
"index_fields": ["text"],
"identity_fields": ["text"],
}