This project was designed and directed by a human author. Code, documentation, and the research paper were substantially drafted with the assistance of Claude Sonnet 4.6 (Anthropic). Architecture decisions, domain framing, and editorial judgement remain the author's own. Disclosed using the AI Attribution Toolkit.
Propagation & Retrieval via Informed Semantic Mapping
Epistemic Graph RAG with Spreading Activation
PRISM is a retrieval library that layers a typed epistemic knowledge graph over your existing vector store, then uses spreading activation to surface knowledge structured by how it relates — not just how similar it is.
Standard RAG returns a flat ranked list. Every chunk is treated the same — a similarity score and nothing else.
query → embed → similarity → [chunk, chunk, chunk, ...] ← no structure
This loses the relational fabric of your knowledge. Chunk B may refute Chunk A. Chunk C may specialize a principle in Chunk D. An older document may have been superseded by a newer one. Standard RAG can't express any of this — and neither can the LLM reasoning over it.
PRISM builds a graph where edges carry epistemic type:
Doc A ──[supports]──▶ Doc B
Doc C ──[refutes]───▶ Doc D
Doc E ──[supersedes]▶ Doc F
Retrieval then uses spreading activation: a query fires seed nodes via vector search, activation propagates through typed edges, and nodes reached by multiple independent paths (convergence) rank highest.
The result is a structured epistemic answer — not a ranked list:
| Bucket | Contents |
|---|---|
| PRIMARY | Core relevant chunks, highest convergence |
| SUPPORTING | Chunks that reinforce or extend the primary answer |
| CONTRASTING | Chunks that challenge or take a different position |
| QUALIFYING | Chunks that add conditions, exceptions, nuances |
| SUPERSEDED | Historically relevant context now replaced by newer work |
Three stages:
- Seed — embed the query, vector-search your corpus, get top-K scored chunks as activation seeds
- Activate — propagate activation through the epistemic graph; track which seeds independently reach each node (convergence)
- Structure — bucket results by epistemic role based on dominant edge valence
| System | Vector Search | Knowledge Graph | Epistemic Edge Typing | Spreading Activation |
|---|---|---|---|---|
| Standard RAG | ✅ | ❌ | ❌ | ❌ |
| GraphRAG (Microsoft, 2024) | ✅ | ✅ | ❌ | ❌ |
| SYNAPSE (2026) | ✅ | ✅ | ❌ | ✅ |
| PRISM | ✅ | ✅ | ✅ | ✅ |
To our knowledge, no open-source retrieval library combines all four of these signals. If you know of one, please open an issue — we'd genuinely like to know.
supports — A provides evidence reinforcing B
refutes — A directly contradicts B
supersedes — A replaces or updates B (A is newer / more authoritative)
derives_from — A is logically or conceptually derived from B
specializes — A is a specific instance of the general principle in B
contrasts_with — A and B take different but non-exclusive positions
implements — A is a concrete method that puts the abstract concept of B into practice
generalizes — A is a broader abstraction of which B is a specific case
exemplifies — A is a concrete example illustrating the concept in B
qualifies — A adds conditions, exceptions, or nuances to B
Each edge has:
- A propagation weight — how strongly it carries activation (0.40–0.90)
- A valence — determines which result bucket the target lands in (positive / qualifying / dialectical / temporal)
# Core — bring your own vector store adapter
pip install prism-rag
# With a built-in vector store adapter:
pip install prism-rag[lancedb] # LanceDB
pip install prism-rag[chroma] # ChromaDB
pip install prism-rag[qdrant] # Qdrant
pip install prism-rag[weaviate] # Weaviate (v4 client)
pip install prism-rag[pgvector] # PostgreSQL + pgvectorFrom source:
git clone https://github.com/MadMando/prism
cd prism
pip install -e .[lancedb] # or [chroma], [qdrant], etc.Requirements: Python 3.11+, an embedding provider (Ollama local or any OpenAI-compatible API), and a vector store.
from prism import PRISM
# Using Ollama for embeddings (local)
p = PRISM(
lancedb_path = "/path/to/your/lancedb",
graph_path = "/path/to/prism_graph.json.gz",
ollama_url = "http://localhost:11434",
embed_model = "nomic-embed-text",
llm_base_url = "https://api.openai.com",
llm_model = "gpt-4o-mini",
llm_api_key = "sk-...",
)
# Using an API for embeddings instead
p = PRISM(
lancedb_path = "/path/to/your/lancedb",
graph_path = "/path/to/prism_graph.json.gz",
embed_api_url = "https://api.openai.com/v1/embeddings",
embed_api_key = "sk-...",
embed_model = "text-embedding-3-small",
llm_base_url = "https://api.openai.com",
llm_model = "gpt-4o-mini",
llm_api_key = "sk-...",
)
p.build(
k_neighbors = 8, # semantic neighbours per chunk to examine
cross_source_only = False, # False recommended — see note below
max_pairs = 50_000 # cap for testing; omit for full build
)Or via CLI:
prism-build \
--lancedb-path /path/to/lancedb \
--graph-path /path/to/prism_graph.json.gz \
--llm-api-key $OPENAI_API_KEY
cross_source_only— default isFalse(include all sources)PRISM defaults to extracting edges from all chunk pairs — same-source and cross-source alike. This produces richer graphs. On a 30k-chunk governance corpus:
Setting Edges Avg deg Connected chunks cross_source_only=True3,571 0.23 ~49% cross_source_only=False(default)9,989 0.64 ~82% Pass
--cross-source-onlyto restrict to inter-source pairs only. The old--all-sourcesflag is deprecated (it was the opt-in to current default behaviour).
p.load_graph()
result = p.retrieve("your question here", top_k=5)
print(result.format_for_llm())Output:
PRISM retrieval for: "your question here"
────────────────────────────────────────────────────────────
## PRIMARY
[1] source-a p.14 § 2.1 (score: 0.923)
The core relevant passage from your corpus...
[2] source-b p.67 § 5.0 (score: 0.891)
Another highly activated chunk...
## SUPPORTING EVIDENCE
[1] source-c p.201 § 8.2 (score: 0.841 [via: specializes])
A passage that specializes or extends the primary answer...
## QUALIFICATIONS & NUANCES
[1] source-d p.38 § 3.1 (score: 0.712 [via: qualifies])
A passage adding conditions or exceptions...
─ 2 primary · 1 supporting · 0 contrasting · 1 qualifying · 0 superseded ─
# Access buckets directly
for chunk in result.primary:
print(chunk.source, chunk.page, chunk.final_score)
print(chunk.text)
for chunk in result.contrasting:
print("Contrasting view:", chunk.text[:200])
# Feed into your LLM
context = result.format_for_llm()
# ... pass `context` to your LLM system prompt
# Stats
print(f"Seeds: {result.n_seeds}")
print(f"Graph nodes reached: {result.n_graph_nodes}")
print(f"Graph used: {result.graph_was_used}")PRISM supports two embedding modes — use whichever matches how your corpus was built.
PRISM(
ollama_url = "http://localhost:11434", # your Ollama instance
embed_model = "nomic-embed-text", # any model loaded in Ollama
...
)Popular Ollama embedding models: nomic-embed-text, mxbai-embed-large, all-minilm
PRISM(
embed_api_url = "https://api.openai.com/v1/embeddings", # or any compatible endpoint
embed_api_key = "sk-...",
embed_model = "text-embedding-3-small",
...
)Works with OpenAI, Azure OpenAI, Together AI, Jina, Cohere, Mistral, or any endpoint that accepts {"model": ..., "input": ...} and returns {"data": [{"embedding": [...]}]}.
Important: The embedding model at retrieval time must match the model used when your LanceDB corpus was originally indexed. Dimensions must be identical.
PRISM works on top of your existing vector store. If you already have a LanceDB corpus with embeddings, you do not need to re-index anything.
- Existing vectors → used as-is for seed activation
- Epistemic graph → built from text via LLM, stored separately as a
.json.gzfile - Build is a one-time offline step
The build phase uses a two-stage pipeline to extract epistemic relationships efficiently:
Stage 1 — fast pre-filter (binary yes/no)
A fast model screens every candidate pair with a binary question: does any epistemic relationship exist here at all? About half of all candidate pairs are topically similar but not epistemically related — Stage 1 discards these before any Stage 2 API call is made. Stage 1 supports any OpenAI-compatible endpoint (Ollama local, DeepSeek, OpenAI, etc.).
Stage 2 — Async LLM classification
Surviving pairs are sent to an OpenAI-compatible LLM in batches of 20, with 20 concurrent requests running in parallel. For each pair that passes Stage 1, the LLM determines the relationship type, direction, and confidence score. The async architecture means 20 batches complete in the time v1 took to complete one.
Stage 3 — Graph construction
Confirmed relationships (above confidence threshold) become typed, weighted edges saved to prism_graph.json.gz.
Build time comparison (30k-chunk corpus, ~50k candidate pairs):
| Pipeline | Batches | Wall Time |
|---|---|---|
| v1 — sync, batch=5 | ~10,000 | ~40 hours |
| v2 — async, batch=20, no filter | ~2,500 | ~30 minutes |
| v2 — async + stage-1 filter (fast model) | ~1,250 | ~15–20 minutes |
Cost estimate (30k-chunk corpus, v2 pipeline):
| Item | Estimate |
|---|---|
| Stage 1 filter calls | ~5,000 |
| Stage 2 LLM calls after filter (batch=20) | ~1,250 |
| Input tokens | ~12M–18M |
| Output tokens | ~2M–4M |
Cost with deepseek-chat |
~$2–4 |
Cost with gpt-4o-mini |
~$1–2 |
| Total wall time | ~15–20 min |
Checkpoint & resume — if the build is interrupted, PRISM saves a .partial.json.gz checkpoint and resumes automatically from where it left off.
Stage 1 API — Stage 1 uses any OpenAI-compatible endpoint. Point --filter-base-url at Ollama (default), DeepSeek, or any compatible provider. When using a paid API for both stages, the filter only saves money if it reduces Stage 2 calls enough to offset Stage 1 cost — for local Ollama it is effectively free.
Stage 1 only pays off if the filter is fast. The goal is a binary yes/no decision per pair in well under a second. Stage 1 accepts any OpenAI-compatible endpoint via --filter-base-url / filter_base_url=.
Using local Ollama (free): use a model under ~8 GB — small models complete each binary call in under a second. Models above ~10 GB can take 2–4 seconds per call and negate the benefit of filtering.
| Model | Size | Notes |
|---|---|---|
llama3.1:8b |
4.9 GB | Recommended default — fast, strong instruction following |
llama3.2:3b |
2.0 GB | Fastest; good for binary yes/no |
gemma3:4b |
3.3 GB | Lightweight, accurate |
Using a remote Ollama server:
p = PRISM(
ollama_url = "http://your-ollama-host:11434",
embed_model = "qwen3-embedding:4b",
filter_model = "llama3.1:8b",
...
)Using a paid API (e.g. DeepSeek) for Stage 1:
p = PRISM(
filter_base_url = "https://api.deepseek.com/v1",
filter_model = "deepseek-chat",
filter_api_key = "sk-...",
...
)Note: when both stages use a paid API, Stage 1 adds cost — it only saves money if it reduces Stage 2 calls more than it costs. For most corpora, using a fast local model for Stage 1 and a paid API for Stage 2 is the most cost-effective combination.
Skip Stage 1 entirely:
prism-build --no-filter ... # async Stage 2 only, ~30 min for 50k pairsIf no graph file exists (or graph loading fails), PRISM automatically falls back to pure vector search and still returns a valid EpistemicResult — just without epistemic bucketing. All chunks land in primary.
This means PRISM is a safe drop-in replacement for any standard vector retriever from day one.
PRISM(
# ── Storage ───────────────────────────────────────────────────
graph_path = "/path/to/prism_graph.json.gz",
lancedb_path = "/path/to/lancedb", # OR use adapter= below
table_name = "knowledge", # LanceDB table name
# ── Custom adapter (alternative to lancedb_path) ─────────────
adapter = None, # pass a VectorAdapter instance
# ── Embedding: Ollama (default) ───────────────────────────────
ollama_url = "http://localhost:11434",
embed_model = "nomic-embed-text",
# ── Embedding: API (set embed_api_key to activate) ────────────
embed_api_url = "https://api.openai.com/v1/embeddings",
embed_api_key = None, # set to switch from Ollama
# ── LLM for graph building ────────────────────────────────────
llm_base_url = "https://api.openai.com",
llm_model = "gpt-4o-mini",
llm_api_key = "sk-...",
min_confidence = 0.65, # edge confidence threshold
batch_size = 20, # pairs per LLM call (v2 default)
max_concurrent = 20, # concurrent LLM requests
max_retries = 3, # retries per failed batch (exp backoff)
failure_log_path = None, # path to write failed-batch JSON log
# ── Stage 1 filter ────────────────────────────────────────────
filter_model = "llama3.1:8b",
filter_batch_size = 10,
filter_max_concurrent = 5,
# ── Retrieval tuning ──────────────────────────────────────────
hops = 3, # spreading activation depth
decay = 0.7, # per-hop decay factor
seed_top_k = 20, # vector search seed count
convergence_weight = 0.4, # bonus weight for convergence
reranker = None, # optional Reranker callable
)prism/
├── prism/
│ ├── __init__.py public API (PRISM, VectorAdapter, Embedder, Reranker, ...)
│ ├── prism.py PRISM — main entry point + add_documents()
│ ├── edges.py epistemic edge taxonomy + propagation weights
│ ├── graph.py EpistemicGraph (networkx MultiDiGraph + JSON serialisation)
│ ├── extractor.py LLM-based triple extraction (async, retries, failure log)
│ ├── filter.py Stage 1 Ollama pre-filter
│ ├── activation.py SpreadingActivation engine + convergence scoring
│ ├── retriever.py PRISMRetriever — 5-step pipeline + reranker hook
│ ├── result.py EpistemicResult + EpistemicChunk dataclasses
│ ├── cli.py prism-build CLI
│ ├── inspect_cli.py prism-stats + prism-inspect diagnostic CLIs
│ ├── viz_cli.py prism-viz — export to Gephi GEXF, D3 JSON, or self-contained HTML
│ └── adapters/
│ ├── base.py VectorAdapter Protocol (@runtime_checkable)
│ ├── embedder.py Embedder — reusable Ollama + OpenAI-compatible embedding helper
│ ├── lancedb.py LanceDB adapter (requires prism-rag[lancedb])
│ ├── chroma.py ChromaDB adapter (requires prism-rag[chroma])
│ ├── qdrant.py Qdrant adapter (requires prism-rag[qdrant])
│ ├── weaviate.py Weaviate v4 adapter (requires prism-rag[weaviate])
│ ├── pgvector.py pgvector/PostgreSQL adapter (requires prism-rag[pgvector])
│ └── template.py copy-paste skeleton for building a custom adapter
├── scripts/
│ └── build_graph.py standalone build script
├── examples/
│ └── governance_search.py
├── docs/
│ └── architecture.svg
└── pyproject.toml
After your graph is built, add new documents without a full rebuild:
# 1. Ingest new docs into your vector store first (LanceDB, etc.)
# 2. Collect the new chunk IDs
new_ids = ["chunk_abc", "chunk_def", "chunk_ghi"]
# 3. Update the graph — finds neighbours of new chunks, runs pipeline, saves
p.load_graph()
n_new_edges = p.add_documents(new_ids)
print(f"Added {n_new_edges} new edges")add_documents() runs the same two-stage pipeline but only over candidate pairs involving the new chunks. The existing graph is updated in-place and re-saved.
PRISM ships adapters for four popular vector stores in addition to LanceDB. All share the same interface and support both Ollama and OpenAI-compatible embedding providers.
from prism import PRISM
from prism.adapters.chroma import ChromaAdapter
adapter = ChromaAdapter(
collection_name = "knowledge",
host = "localhost",
port = 8000,
embed_model = "nomic-embed-text",
)
p = PRISM(graph_path="prism_graph.json.gz", adapter=adapter, ...)Your collection should be configured with
hnsw:space="cosine"for correct similarity scores.
from prism.adapters.qdrant import QdrantAdapter
adapter = QdrantAdapter(
collection_name = "knowledge",
url = "http://localhost:6333",
embed_model = "nomic-embed-text",
)Chunk IDs are read from the
"id"key in the point payload (configurable viaid_payload_key).
from prism.adapters.weaviate import WeaviateAdapter
adapter = WeaviateAdapter(
collection_name = "Knowledge",
host = "localhost",
port = 8080,
id_property = "chunk_id", # property holding the PRISM chunk ID
embed_model = "nomic-embed-text",
)Uses the Weaviate v4 Python client. Objects must have a
chunk_idproperty (or whateverid_propertyyou configure) to be retrievable by ID.
from prism.adapters.pgvector import PgvectorAdapter
adapter = PgvectorAdapter(
dsn = "postgresql://user:pass@localhost:5432/mydb",
table = "chunks", # table with id, source, page, section, text, embedding columns
embed_model = "nomic-embed-text",
)Requires a table with a
vector(N)column (pgvector extension). Column names are configurable.
Implement the VectorAdapter Protocol to connect PRISM to any store not listed above.
Quickest path: copy prism/adapters/template.py from the repo — it's a fully-commented skeleton with all 7 methods stubbed and the Embedder helper already wired in.
from prism import PRISM, VectorAdapter
from prism.adapters.embedder import Embedder
class MyAdapter:
def __init__(self, ...):
self._embedder = Embedder(model="nomic-embed-text")
def seed_scores(self, query, top_k=20, source_filter=None) -> dict[str, float]: ...
def get_chunks(self, node_ids) -> dict[str, dict]: ...
def connect(self) -> None: ...
def populate_graph_nodes(self, graph) -> int: ...
def candidate_pairs(self, k_neighbors=8, cross_source_only=False, max_pairs=None): ...
def candidate_pairs_for(self, node_ids, k_neighbors=8, cross_source_only=False): ...
def stats(self) -> dict: ...
assert isinstance(MyAdapter(), VectorAdapter) # Protocol is @runtime_checkable
p = PRISM(graph_path="prism_graph.json.gz", adapter=MyAdapter(), ...)Plug in any reranker (cross-encoder, LLM-based, Cohere Rerank, etc.) to post-process retrieval results:
from prism import PRISM, Reranker, EpistemicChunk
def my_reranker(query: str, chunks: list[EpistemicChunk]) -> list[EpistemicChunk]:
# Call your reranker API, return chunks in new order
scores = cohere_client.rerank(query=query, documents=[c.text for c in chunks])
return [chunks[r.index] for r in scores.results]
p = PRISM(
lancedb_path = "/path/to/lancedb",
graph_path = "/path/to/prism_graph.json.gz",
reranker = my_reranker,
...
)
result = p.retrieve("your question")
# Chunks in result.primary are in reranker order, with final_score updated to reflect rankThe reranker runs after epistemic bucketing as a final pass over all retrieved chunks. If the reranker raises an exception, PRISM falls back to the original spreading-activation order.
Export the epistemic graph for visual exploration:
# Self-contained interactive HTML — share via GitHub or any browser (no server needed)
prism-viz prism_graph.json.gz --format html --output graph.html
# Cap node count for large graphs (keeps top-N by degree)
prism-viz prism_graph.json.gz --format html --max-nodes 1000 --output graph.html
# D3 JSON (force-directed graph) — load with d3.json()
prism-viz prism_graph.json.gz --output graph.json
# Gephi GEXF — open directly in Gephi for layout and clustering
prism-viz prism_graph.json.gz --format gexf --output graph.gexf
# Filter: only high-confidence supporting/refuting edges
prism-viz prism_graph.json.gz --format d3 --edge-types supports,refutes --min-confidence 0.8
# Filter: only chunks from one source
prism-viz prism_graph.json.gz --format d3 --source-filter "dmbok"HTML format — generates a fully self-contained interactive viewer with D3 v7 and graph data embedded. Three-panel layout: filters (left), force-directed graph (centre), node detail on click (right). Viewable offline or hosted on htmlpreview.github.io.
D3 JSON format — nodes have id, source, group, and degree; links have source, target, type, weight, and confidence. Load directly with d3.json() and d3.forceSimulation.
GEXF format — standard Gephi format. Edge type, weight, confidence, and rationale are exported as edge attributes. Open in Gephi: File → Open → graph.gexf.
Two diagnostic commands are included after pip install prism-rag:
# Graph and store statistics
prism-stats /path/to/prism_graph.json.gz
prism-stats /path/to/prism_graph.json.gz --lancedb-path /path/to/lancedb # include store stats
prism-stats /path/to/prism_graph.json.gz --json # machine-readable output
# Inspect a specific node — its edges, neighbours, rationales
prism-inspect /path/to/prism_graph.json.gz --node chunk_abc123
prism-inspect /path/to/prism_graph.json.gz --node chunk_abc123 --jsonExample prism-stats output:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PRISM Graph Statistics
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
File : prism_graph.json.gz
Nodes : 15,426
Edges : 9,989
Avg deg : 0.64 edges/node
Edge type distribution:
exemplifies 4,183 41.9% ████████████
supports 2,710 27.1% ████████
specializes 1,289 12.9% ████
...
PRISM is alpha software. These are known constraints you should understand before using it in production:
Graph quality depends on your corpus and LLM.
The epistemic graph is built by asking an LLM to classify relationships between chunk pairs. The LLM can misclassify — a chunk may be tagged supports when it actually only tangentially relates, or refutes when it merely offers a different framing. Graph quality is correlated with chunk quality, chunking strategy, and the capability of the extraction model. Review extracted edges before trusting them.
Confidence scores are not ground truth.
The confidence values returned by the LLM are self-reported estimates, not calibrated probabilities. They are used as a filter (default threshold: 0.65) and as edge weights, but should not be treated as precise measures of relationship strength.
Retrieval quality can degrade with noisy edges. If the graph contains many false-positive edges, spreading activation will propagate to irrelevant nodes. This produces worse results than pure vector search. Monitor your graph's edge yield rate and edge type distribution after building.
cross_source_only=True produces sparse graphs on multi-book corpora.
Setting cross_source_only=True skips all chunk pairs from the same source document. This seems conservative and clean, but in practice it misses the majority of valuable epistemic relationships. A large book like DMBOK (5,940 chunks) contains hundreds of intra-chapter supports, derives_from, and specializes relationships that are never extracted when same-source pairs are excluded.
In a real-world test on a 30k-chunk governance corpus (DMBOK + 2 governance books + erwin docs):
cross_source_only=True, k=8: 3,571 edges, ~51% of chunks unconnected — graph rarely fires on queriescross_source_only=False, k=8: 9,989 edges, same corpus — graph activates supporting/qualifying buckets on most queries
Recommendation: use cross_source_only=False for most corpora. Only use True if your sources are genuinely independent and you specifically want to suppress intra-document structure.
Build is slow and LLM-dependent.
The one-time graph build requires thousands of LLM API calls and takes hours for large corpora. Incremental updates via add_documents() are supported for adding new chunks without a full rebuild.
No evaluation benchmark yet.
We do not currently publish retrieval quality metrics comparing PRISM against standard RAG on standardised QA datasets. The benchmarks/ directory contains a structural demonstration only.
- Async LLM extraction (parallel API calls → 80× faster build)
- Two-stage pipeline: local pre-filter + async classification
- Checkpoint / resume for interrupted builds
- Incremental graph updates via
add_documents() - Pluggable
VectorAdapterProtocol for custom vector stores - Reranker hook for cross-encoder / LLM-based reranking
-
prism-statsandprism-inspectdiagnostic CLIs - Additional vector store adapters (Chroma, Qdrant, Weaviate, pgvector)
- Graph visualisation (
prism-vizCLI — exports to Gephi GEXF / D3 JSON / self-contained HTML) - Adapter bug fixes + unit test coverage + CI matrix across all extras (0.2.5)
- Export to Neo4j / NetworkX formats (
prism-exportCLI + Python API) - Retrieval quality benchmarks on standard QA datasets
- Propagation weights — Table 1 corrected to match
edges.pyground truth:derives_from0.85,specializes0.80,implements/exemplifies0.75,generalizes0.70,qualifies0.65,contrasts_with0.55,refutes0.50 - Sum-pool propagation — §3.3 corrected from max-pool to sum-pool (matching
activation.py); nodes corroborated by multiple paths now accurately described as accumulating activation - Multiplicative scoring — final score formula corrected from convex combination
(1−λ)·a + λ·convto multiplicative bonusa·(1 + λ·conv)(matchingresult.py) - Reverse edge propagation — §3.3 now documents bidirectional traversal with 0.6 reverse-edge penalty (was undocumented despite being enabled by default)
- LightRAG citation — corrected fabricated author list to actual 5 authors: Guo, Xia, Yu, Ao, Huang (arXiv:2410.05779)
- Brachman & Levesque (1985) — corrected from "authors" to "(Eds.)" — Readings in Knowledge Representation is an edited volume
- HTML export fixed —
prism-viz --format htmlnow inlines D3 v7 at generation time instead of loading from CDN; the exported file is fully self-contained and works in htmlpreview.github.io, offline, and sandboxed iframes - Three-panel layout — exported HTML now matches the live
prism-explorelayout: left panel (filters), centre (graph), right panel (node detail on click — source, page, preview, all edges with type/direction/confidence) - Stage 1 filter — now accepts any OpenAI-compatible API endpoint via
filter_base_url/filter_api_key(previously Ollama-native only); backward-compatible — Ollama still works as before - Docs — Stage 1 section updated to reflect paid-API option;
--filter-max-concurrentflag name corrected in CLI reference
prism-viz --format html— new output mode generates a fully self-contained interactive HTML viewer with the graph data embedded; no server required; viewable via htmlpreview.github.io or any browser- Thicker edge rendering — stroke-width formula updated from
Math.max(1.2, w*3)toMath.max(2.5, w*5)in both the live explorer and the exported HTML; edges are now clearly visible on dark backgrounds - Updated demo — README badge links to a fresh 1,000-node / 891-edge Quinn graph sample
- Extraction prompt rewrite — each of the 10 edge types now has a one-line definition; types are grouped by valence (reinforcing / modifying / dialectical / temporal); directionality is clarified with concrete examples; "most specific type wins" rule added to reduce
supports/specializesambiguity scripts/build_graph.py— new flags:--no-filter(skip Stage 1 pre-filter),--filter-model(Ollama model for Stage 1, defaultllama3.1:8b),--filter-batch-size,--filter-max-concurrent; default LLM updated to localphi4:latestvia Ollama; default batch size increased to 20pandasadded as core dependency (required by LanceDB adapter'spopulate_graph_nodes)- FEVER benchmark (
benchmarks/fever/) — build corpus, run retrieval scoring, standalone scorer for Retrieval% and Bucket% per FEVER label
- README_PYPI.md rewritten — full CLI reference for all 6 commands (
prism-build,prism-stats,prism-inspect,prism-explore,prism-viz,prism-export) with option tables and quickstart - AI Attribution — both READMEs and the research paper now carry an AI Attribution Toolkit statement (
AIA HAb SeCeNc Hin R Claude Sonnet 4.6 v1.0)
EpistemicGraph.to_networkx()— returns anx.MultiDiGraphcopy; run PageRank, community detection, shortest paths, or any NetworkX algorithm directlyEpistemicGraph.to_cypher(path)— writes batched CypherCREATEstatements; load into Neo4j withcypher-shellor BrowserEpistemicGraph.to_neo4j(uri, user, password)— pushes nodes and typed relationships directly into a running Neo4j instance via Boltprism-exportCLI —--format cypheror--format neo4jfrom the command line- Install Bolt driver:
pip install prism-rag[neo4j]
prism-exploreCLI — launches a local FastAPI + D3.js explorer onhttp://localhost:7860- Interactive force-directed graph — nodes sized by degree, colored by source, edges colored by epistemic type with directional arrows
- Query mode — submit a question and watch spreading activation highlight nodes by epistemic bucket
- Filters — edge type toggles, source substring filter, min-confidence slider
- Export HTML — save the current layout as a fully self-contained interactive file
- Install:
pip install prism-rag[lancedb,explorer]
- LanceDB:
get_chunksno longer silently drops node IDs past the first 100 — queries are batched. - ChromaDB: removed the invalid
{"source": {"$contains": ...}}wherefilter; source filtering now applies client-side with no silent fallback. - Weaviate:
candidate_pairs_fornow reuses vectors from the initial scan instead of onefetch_objectscall per chunk ID (avoids N+1). Also drops a deadinfovariable instats(). - pgvector:
candidate_pairs_foruses one cursor for the target-row fetch and a second cursor for neighbour queries — avoids psycopg2 buffer invalidation. - Silent
except Exception: passblocks in adapter code now emit stderr warnings. - Tests: new unit suites for Chroma, Qdrant, Weaviate, pgvector, and
prism-viz. 158 tests total. - CI: matrix job per adapter extra (
lancedb,chroma,qdrant,weaviate,pgvector) so missing optional deps are caught at PR time. New coverage job (pytest-cov) with a 50 % floor.
- Collins, A.M. & Loftus, E.F. (1975). A spreading-activation theory of semantic processing. Psychological Review, 82(6), 407–428.
- Edge, D. et al. (2024). From Local to Global: A Graph RAG Approach to Query-Focused Summarization. Microsoft Research.
MIT

