c889a57b6b
Test Suites / Build CI Environment (push) Has been cancelled
Test Suites / Basic Tests (push) Has been cancelled
Test Suites / End-to-End Tests (push) Has been cancelled
Test Suites / CLI Tests (push) Has been cancelled
Test Suites / Slow End-to-End Tests (push) Has been cancelled
Test Suites / Graph Database Tests (push) Has been cancelled
Test Suites / Vector DB Tests (push) Has been cancelled
Test Suites / Temporal Graph Test (push) Has been cancelled
Test Suites / Search Test on Different DBs (push) Has been cancelled
Test Suites / Example Tests (push) Has been cancelled
Test Suites / Notebook Tests (push) Has been cancelled
Test Suites / OS and Python Tests Ubuntu (push) Has been cancelled
Test Suites / OS and Python Tests Extended (push) Has been cancelled
Test Suites / LLM Test Suite (push) Has been cancelled
Test Suites / S3 File Storage Test (push) Has been cancelled
Test Suites / Run Integration Tests (push) Has been cancelled
Test Suites / MCP Tests (push) Has been cancelled
Test Suites / Docker Compose Test (push) Has been cancelled
Test Suites / Docker CI test (push) Has been cancelled
Test Suites / Relational DB Migration Tests (push) Has been cancelled
Test Suites / Distributed Cognee Test (push) Has been cancelled
Test Suites / DB Examples Tests (push) Has been cancelled
Test Suites / Test Completion Status (push) Has been cancelled
Test Suites / Claude Code Review (push) Has been cancelled
Test Suites / basic checks (push) Has been cancelled
build | Build and Push Cognee MCP Docker Image to dockerhub / docker-build-and-push (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
build | Build and Push Docker Image to dockerhub / docker-build-and-push (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges Core Functionality (3.11) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges Core Functionality (3.12) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges with Different Graph Databases (kuzu, kuzu) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges with Different Graph Databases (neo4j, neo4j) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges Examples (push) Has been cancelled
Weighted Edges Tests / Code Quality for Weighted Edges (push) Has been cancelled
178 lines
7.5 KiB
Python
178 lines
7.5 KiB
Python
"""Truth-Subspace Reranking — runnable demo.
|
|
|
|
What it shows
|
|
-------------
|
|
A finished "session" teaches the system a preference (here: the user cares about
|
|
*coffee*, not tea). We distill that into deterministic centroid slots, project
|
|
every corpus chunk onto those slots, and store the per-chunk coordinates on the
|
|
graph node. At query time the HYBRID retriever reads current-epoch coordinates
|
|
and nudges ranking toward the learned preference.
|
|
|
|
The demo retrieves the SAME ambiguous query twice — once with truth weighting OFF
|
|
(exact baseline) and once ON — and prints a side-by-side rank diff so you can see
|
|
the coffee chunks rise.
|
|
|
|
Requirements
|
|
------------
|
|
- An LLM + embeddings provider configured (e.g. LLM_API_KEY in your .env). cognify
|
|
and retrieval call the embedding/LLM APIs.
|
|
|
|
Run it
|
|
------
|
|
python examples/python/truth_subspace_reranking_demo.py
|
|
|
|
It uses a dedicated dataset ("truth_subspace_demo") and does NOT prune, so it will
|
|
not touch your other cognee data. Re-running is safe (adds are content-addressed,
|
|
the subspace build is a full recompute).
|
|
"""
|
|
|
|
import asyncio
|
|
import os
|
|
import sys
|
|
|
|
# Run against THIS checkout of cognee even if a different copy is pip-installed,
|
|
# so the truth-subspace code in this working tree is the one that executes.
|
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")))
|
|
|
|
import cognee
|
|
from cognee.context_global_variables import set_database_global_context_variables
|
|
from cognee.modules.data.methods import get_authorized_existing_datasets
|
|
from cognee.modules.retrieval.hybrid.results import payload, result_id
|
|
from cognee.modules.retrieval.hybrid_retriever import HybridRetriever
|
|
from cognee.modules.truth_subspace.build import build_truth_subspace
|
|
from cognee.modules.users.methods import get_default_user
|
|
|
|
DATASET = "truth_subspace_demo"
|
|
CORPUS_NODE_SET = ["beverages"]
|
|
LESSONS_NODE_SET = ["session_learnings"] # the node set build_truth_subspace reads learnings from
|
|
|
|
# A small two-theme corpus. Each short doc becomes its own chunk.
|
|
CORPUS = [
|
|
"Espresso is brewed by forcing hot water through finely ground coffee under high pressure.",
|
|
"A pour-over coffee drips a slow stream of hot water over a paper filter of ground coffee.",
|
|
"Cold brew coffee steeps coarse coffee grounds in cold water for twelve hours or more.",
|
|
"A French press steeps coffee grounds in hot water, then a metal plunger separates them.",
|
|
"Green tea is brewed with water below boiling to avoid a bitter, astringent flavor.",
|
|
"Black tea is steeped in fully boiling water for three to five minutes before serving.",
|
|
"Herbal tisanes are caffeine-free infusions of dried herbs, flowers, and dried fruit.",
|
|
"Matcha is a powdered green tea whisked into hot water with a bamboo whisk until frothy.",
|
|
]
|
|
|
|
# What a finished session "learned" about the user. These fill the truth centroid
|
|
# slots. They are about coffee, so coffee chunks align more strongly.
|
|
LESSONS = [
|
|
"The user is a dedicated coffee drinker who cares about espresso extraction and pour-over technique.",
|
|
"We learned the user wants coffee brewing recommendations specifically, and is not interested in tea.",
|
|
"For this user, coffee details — grind size, water temperature, and bloom time — matter most.",
|
|
]
|
|
|
|
QUERY = "How should I prepare my morning drink at home?"
|
|
|
|
# Number of chunks to rank.
|
|
TOP_K = len(CORPUS)
|
|
|
|
|
|
def _theme(text: str) -> str:
|
|
coffee = ("coffee", "espresso", "pour-over", "french press", "cold brew")
|
|
return "☕ coffee" if any(w in text.lower() for w in coffee) else "🍵 tea "
|
|
|
|
|
|
def _snippet(text: str, width: int = 62) -> str:
|
|
text = " ".join(text.split())
|
|
return text if len(text) <= width else text[: width - 1] + "…"
|
|
|
|
|
|
async def ranked_chunks(dataset_obj, query: str, use_truth_weight: bool):
|
|
"""Return the hybrid retriever's ranked chunk dicts, within the dataset's DB context."""
|
|
async with set_database_global_context_variables(dataset_obj.id, dataset_obj.owner_id):
|
|
retriever = HybridRetriever(
|
|
chunks_top_k=TOP_K,
|
|
entities_top_k=0, # focus the demo on chunk-lane reranking
|
|
facts_top_k=0,
|
|
node_name=CORPUS_NODE_SET, # rank only the corpus, not the lesson chunks
|
|
use_truth_weight=use_truth_weight,
|
|
)
|
|
objects = await retriever.get_retrieved_objects(query=query)
|
|
return objects.get("chunks", [])
|
|
|
|
|
|
def _text(chunk) -> str:
|
|
return payload(chunk).get("text", "")
|
|
|
|
|
|
def print_ranking(title: str, chunks: list):
|
|
print(f"\n{title}")
|
|
print(" " + "-" * 74)
|
|
for i, chunk in enumerate(chunks, 1):
|
|
text = _text(chunk)
|
|
print(f" {i:>2}. {_theme(text)} {_snippet(text)}")
|
|
|
|
|
|
def print_diff(baseline: list, truthful: list):
|
|
base_rank = {result_id(c): i for i, c in enumerate(baseline, 1)}
|
|
print("\nRANK CHANGE WITH TRUTH WEIGHTING ON (vs baseline)")
|
|
print(" " + "-" * 74)
|
|
for new_rank, chunk in enumerate(truthful, 1):
|
|
cid = result_id(chunk)
|
|
old = base_rank.get(cid)
|
|
if old is None:
|
|
delta = " new"
|
|
elif old > new_rank:
|
|
delta = f" ↑{old - new_rank}"
|
|
elif old < new_rank:
|
|
delta = f" ↓{new_rank - old}"
|
|
else:
|
|
delta = " ="
|
|
text = _text(chunk)
|
|
print(f" {new_rank:>2}. {_theme(text)} {_snippet(text, 52)}{delta}")
|
|
|
|
|
|
async def main():
|
|
print("=" * 78)
|
|
print("Truth-Subspace Reranking demo")
|
|
print("=" * 78)
|
|
|
|
# 1) Ingest the corpus and build the knowledge graph.
|
|
print(f"\n[1/5] Adding {len(CORPUS)} corpus docs and cognifying (dataset='{DATASET}')…")
|
|
await cognee.add(CORPUS, dataset_name=DATASET, node_set=CORPUS_NODE_SET)
|
|
await cognee.cognify(datasets=[DATASET])
|
|
|
|
user = await get_default_user()
|
|
datasets = await get_authorized_existing_datasets([DATASET], "write", user)
|
|
dataset_obj = datasets[0]
|
|
|
|
# 2) Baseline ranking — truth weighting OFF (exact current behavior).
|
|
print(f"\n[2/5] Baseline retrieval (truth weighting OFF) for:\n “{QUERY}”")
|
|
baseline = await ranked_chunks(dataset_obj, QUERY, use_truth_weight=False)
|
|
print_ranking("BASELINE RANKING", baseline)
|
|
|
|
# 3) A finished session's learnings → seed the session_learnings node set.
|
|
print(f"\n[3/5] Recording {len(LESSONS)} session learnings (favoring coffee)…")
|
|
await cognee.add(LESSONS, dataset_name=DATASET, node_set=LESSONS_NODE_SET)
|
|
await cognee.cognify(datasets=[DATASET])
|
|
|
|
# 4) Build the truth subspace: lesson centroids -> coords on every corpus chunk.
|
|
print("\n[4/5] Building the truth subspace (build_truth_subspace)…")
|
|
result = await build_truth_subspace(dataset=DATASET, session_ids=None, user=user)
|
|
print(f" centroid_slots={result['anchors']} nodes_scored={result['nodes_scored']}")
|
|
if result["anchors"] == 0 or result["nodes_scored"] == 0:
|
|
print(" ⚠ No centroid slots or no scored nodes — truth weighting will be a no-op.")
|
|
|
|
# 5) Truth-weighted ranking — same query, truth weighting ON.
|
|
print("\n[5/5] Retrieval with truth weighting ON for the same query…")
|
|
truthful = await ranked_chunks(dataset_obj, QUERY, use_truth_weight=True)
|
|
print_ranking("TRUTH-WEIGHTED RANKING", truthful)
|
|
|
|
print_diff(baseline, truthful)
|
|
|
|
base_top = _theme(_text(baseline[0])) if baseline else "?"
|
|
truth_top = _theme(_text(truthful[0])) if truthful else "?"
|
|
print("\n" + "=" * 78)
|
|
print(f"Top result moved from {base_top.strip()} → {truth_top.strip()}")
|
|
print("The learned coffee preference reshaped retrieval ordering. ✔")
|
|
print("=" * 78)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|