Spaces:
Sleeping
Sleeping
File size: 12,935 Bytes
1605cbb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 | """
FALSIFY demo corpus — the "Company X recall" investigation.
This module builds the Session-1 belief graph that the demo revises in Session 2.
It is intentionally small and hand-authored so the belief-revision mechanics are
legible on screen in under 30 seconds, and so the cascade result is deterministic
(the winning demo must not depend on a flaky LLM).
The investigation
-----------------
Question Q: "Did Company X know about the defect before the recall?"
Competing hypotheses:
A — "X knew via the QA report dated March 2021" (supported by E_qa)
B — "X knew via a supplier email in January 2021" (supported by E_email)
C — "X did not know before the recall" (unsupported)
Evidence:
E_qa — the March-2021 QA report (supports A)
E_email — the January-2021 supplier email (supports B)
Conclusion:
K — "Company X knew about the defect by March 2021"
depends_on E_qa (critical=True) <-- the propagation rail
The Session-2 fact (NEW_FACT) is a forensic finding that the March QA report was
back-dated. It contradicts E_qa. Refuting E_qa must cascade:
E_qa -> refuted
-> K (depends_on E_qa, critical) -> invalidated
-> A (only supporter E_qa now dead) -> superseded ; B promoted (new frontier)
-> K orphaned (no alive consumer) -> forgotten (hard-deleted, graph + vector)
-> E_qa kept as refuted provenance (it is NEW_FACT's supersedes anchor)
The scoreboard then shows FALSIFY answering via B (Jan 2021) while a plain vector
(RAG) baseline still cites the refuted March QA report.
"""
from __future__ import annotations
import logging
from dataclasses import dataclass, field
from typing import Dict, List
# NOTE: importing falsify.models runs falsify/__init__, which sets the Cognee env
# defaults (access-control off, session cache on) before Cognee is imported.
from falsify import graph_ops
from falsify.edges import DEPENDS_ON, SUPPORTS
from falsify.models import (
Conclusion,
Evidence,
Hypothesis,
InvestigationQuestion,
)
logger = logging.getLogger("falsify.seed")
# The research question this whole graph hangs off of.
QUESTION_TEXT = "Did Company X know about the defect before the recall?"
# The Session-2 fact that triggers belief revision. It contradicts E_qa.
NEW_FACT = (
"A forensic audit found that the March 2021 QA report was back-dated: it was "
"actually created shortly after the product recall, not before it."
)
# The SECOND contradiction, used only by the diamond scenario. It contradicts
# E_email. Dropped after NEW_FACT to demonstrate iterative revision: once BOTH
# legs of a multi-source conclusion are refuted, the conclusion finally collapses.
NEW_FACT_2 = (
"A forensic analysis of email headers shows the January 2021 supplier email was "
"fabricated: the sender domain was not registered until April 2021, and the DKIM "
"signature is invalid."
)
# Human-readable stable keys -> used by --demo mode to pin the refutation target
# deterministically (so the cascade runs on real graph APIs even if the LLM judge
# is unavailable). These map to the ``key`` field on the seeded nodes below.
REFUTED_EVIDENCE_KEY = "E_qa"
REFUTED_EVIDENCE_KEY_2 = "E_email" # second-phase target for the diamond scenario
@dataclass
class SeededGraph:
"""Handles to the nodes created by :func:`build_investigation`.
``ids`` maps a stable human key (e.g. ``"E_qa"``, ``"A"``, ``"K"``) to the
string node id in the graph, so the demo/tests can reference specific nodes
without re-querying. ``labels`` maps the same keys to display strings.
"""
question_id: str
ids: Dict[str, str] = field(default_factory=dict)
labels: Dict[str, str] = field(default_factory=dict)
@property
def refuted_target_id(self) -> str:
"""Node id of the evidence the Session-2 fact contradicts (E_qa)."""
return self.ids[REFUTED_EVIDENCE_KEY]
async def build_investigation() -> SeededGraph:
"""Create and persist the Session-1 belief graph. Returns a :class:`SeededGraph`.
The caller is responsible for starting from a clean state (e.g. via
``cognee.forget(everything=True)`` and ``cognee.low_level.setup()``). This
function only builds; it does not prune.
Nodes are persisted with Cognee's ``add_data_points`` (writing both the graph
node and the vector row for each ``Embeddable`` field). Typed edges with
properties are then added explicitly through the graph engine so we control the
exact relationship names and edge properties (``critical`` / ``weight``).
"""
# Import here so the module import stays cheap and env defaults are already set.
from cognee.tasks.storage import add_data_points
# ------------------------------------------------------------------ nodes
question = InvestigationQuestion(question=QUESTION_TEXT, source_id="investigation")
hyp_a = Hypothesis(
statement="Company X knew via the QA report dated March 2021.",
question_id=str(question.id),
prior=0.5,
confidence=0.6,
source_id="analyst",
)
hyp_b = Hypothesis(
statement="Company X knew via a supplier email in January 2021.",
question_id=str(question.id),
prior=0.5,
confidence=0.55,
source_id="analyst",
)
hyp_c = Hypothesis(
statement="Company X did not know about the defect before the recall.",
question_id=str(question.id),
prior=0.5,
confidence=0.4,
source_id="analyst",
)
ev_qa = Evidence(
claim="A QA report dated March 2021 documented the defect internally.",
source_id="qa_report_2021_03",
quote="Internal QA report, dated 2021-03-15, flags the defect.",
stance="supports",
asserted_at="2021-03-15",
confidence=0.8,
)
ev_email = Evidence(
claim="A supplier email in January 2021 warned Company X about the defect.",
source_id="supplier_email_2021_01",
quote="Supplier email, 2021-01-20: 'we have observed the defect in test units.'",
stance="supports",
asserted_at="2021-01-20",
confidence=0.7,
)
conclusion_k = Conclusion(
statement="Company X knew about the defect by March 2021.",
confidence=0.8,
depends_on_ids=[str(ev_qa.id)],
source_id="analyst",
)
nodes: List = [question, hyp_a, hyp_b, hyp_c, ev_qa, ev_email, conclusion_k]
logger.info("Persisting %d belief nodes via add_data_points", len(nodes))
await add_data_points(nodes)
# ------------------------------------------------------------------ edges
# Evidence -> Hypothesis (supports, weighted)
await graph_ops.add_edge(str(ev_qa.id), str(hyp_a.id), SUPPORTS, {"weight": 0.8})
await graph_ops.add_edge(str(ev_email.id), str(hyp_b.id), SUPPORTS, {"weight": 0.7})
# Conclusion -> Evidence (depends_on, critical) — THE propagation rail
await graph_ops.add_edge(
str(conclusion_k.id), str(ev_qa.id), DEPENDS_ON, {"critical": True}
)
# Hypothesis -> Question (answers) — keeps the graph connected for visualization
for hyp in (hyp_a, hyp_b, hyp_c):
await graph_ops.add_edge(str(hyp.id), str(question.id), "answers", {})
seeded = SeededGraph(
question_id=str(question.id),
ids={
"Q": str(question.id),
"A": str(hyp_a.id),
"B": str(hyp_b.id),
"C": str(hyp_c.id),
"E_qa": str(ev_qa.id),
"E_email": str(ev_email.id),
"K": str(conclusion_k.id),
},
labels={
"Q": QUESTION_TEXT,
"A": hyp_a.statement,
"B": hyp_b.statement,
"C": hyp_c.statement,
"E_qa": ev_qa.claim,
"E_email": ev_email.claim,
"K": conclusion_k.statement,
},
)
logger.info("Seeded investigation graph: %s", seeded.ids)
return seeded
async def build_diamond_investigation() -> SeededGraph:
"""Build the Session-1 graph WITH a diamond dependency (Conclusion K2).
This is the same investigation as :func:`build_investigation`, plus one extra
Conclusion K2 that *critically depends on BOTH* E_qa and E_email. It exists to
demonstrate that FALSIFY's propagation is a grounded least-fixpoint, not a naive
cascade: a conclusion with two critical supports survives losing one of them, and
only collapses when the LAST support dies.
Two-phase story the demo drives on top of this graph:
Phase 1 — refute E_qa (NEW_FACT):
E_qa -> refuted ; K (single dep) -> invalidated -> forgotten
K2 -> STILL ALIVE (E_email keeps it grounded) ; A superseded, B promoted
E_qa -> retained (K2 alive still depends on it, and it anchors NEW_FACT)
Phase 2 — refute E_email (NEW_FACT_2):
E_email -> refuted ; K2 (last dep now dead) -> invalidated -> forgotten
B -> superseded ; only C ("did not know") may remain
The base nodes are re-created here (rather than shared with build_investigation)
so the proven single-contradiction demo path stays untouched.
"""
from cognee.tasks.storage import add_data_points
# ------------------------------------------------------------------ nodes
question = InvestigationQuestion(question=QUESTION_TEXT, source_id="investigation")
hyp_a = Hypothesis(
statement="Company X knew via the QA report dated March 2021.",
question_id=str(question.id), prior=0.5, confidence=0.6, source_id="analyst",
)
hyp_b = Hypothesis(
statement="Company X knew via a supplier email in January 2021.",
question_id=str(question.id), prior=0.5, confidence=0.55, source_id="analyst",
)
hyp_c = Hypothesis(
statement="Company X did not know about the defect before the recall.",
question_id=str(question.id), prior=0.5, confidence=0.4, source_id="analyst",
)
ev_qa = Evidence(
claim="A QA report dated March 2021 documented the defect internally.",
source_id="qa_report_2021_03",
quote="Internal QA report, dated 2021-03-15, flags the defect.",
stance="supports", asserted_at="2021-03-15", confidence=0.8,
)
ev_email = Evidence(
claim="A supplier email in January 2021 warned Company X about the defect.",
source_id="supplier_email_2021_01",
quote="Supplier email, 2021-01-20: 'we have observed the defect in test units.'",
stance="supports", asserted_at="2021-01-20", confidence=0.7,
)
conclusion_k = Conclusion(
statement="Company X knew about the defect by March 2021.",
confidence=0.8, depends_on_ids=[str(ev_qa.id)], source_id="analyst",
)
# THE DIAMOND: K2 rests on two independent critical supports.
conclusion_k2 = Conclusion(
statement="Multiple independent sources confirm Company X had pre-recall "
"knowledge of the defect.",
confidence=0.85,
depends_on_ids=[str(ev_qa.id), str(ev_email.id)],
source_id="analyst",
)
nodes: List = [question, hyp_a, hyp_b, hyp_c, ev_qa, ev_email, conclusion_k, conclusion_k2]
logger.info("Persisting %d belief nodes (diamond) via add_data_points", len(nodes))
await add_data_points(nodes)
# ------------------------------------------------------------------ edges
await graph_ops.add_edge(str(ev_qa.id), str(hyp_a.id), SUPPORTS, {"weight": 0.8})
await graph_ops.add_edge(str(ev_email.id), str(hyp_b.id), SUPPORTS, {"weight": 0.7})
# K depends on E_qa only (single-leg — dies in phase 1).
await graph_ops.add_edge(
str(conclusion_k.id), str(ev_qa.id), DEPENDS_ON, {"critical": True}
)
# K2's two critical legs — the diamond. Survives phase 1, collapses in phase 2.
await graph_ops.add_edge(
str(conclusion_k2.id), str(ev_qa.id), DEPENDS_ON, {"critical": True}
)
await graph_ops.add_edge(
str(conclusion_k2.id), str(ev_email.id), DEPENDS_ON, {"critical": True}
)
for hyp in (hyp_a, hyp_b, hyp_c):
await graph_ops.add_edge(str(hyp.id), str(question.id), "answers", {})
seeded = SeededGraph(
question_id=str(question.id),
ids={
"Q": str(question.id),
"A": str(hyp_a.id), "B": str(hyp_b.id), "C": str(hyp_c.id),
"E_qa": str(ev_qa.id), "E_email": str(ev_email.id),
"K": str(conclusion_k.id), "K2": str(conclusion_k2.id),
},
labels={
"Q": QUESTION_TEXT,
"A": hyp_a.statement, "B": hyp_b.statement, "C": hyp_c.statement,
"E_qa": ev_qa.claim, "E_email": ev_email.claim,
"K": conclusion_k.statement, "K2": conclusion_k2.statement,
},
)
logger.info("Seeded diamond investigation graph: %s", seeded.ids)
return seeded
|