Extract, enrich, cluster, and query decisions from unstructured conversations using LLMs.
Project description
py-context-graph
Extract, enrich, cluster, and query decisions from unstructured conversations using LLMs.
Quick Start
pip install py-context-graph[all]
import asyncio
from decision_graph import LiteLLMAdapter
from decision_graph.backends.memory import InMemoryBackend
from decision_graph.backends.memory.stores import InMemoryGraphStore, InMemoryVectorIndex
from decision_graph.decision_trace_pipeline import DecisionTracePipeline
backend = InMemoryBackend()
pipeline = DecisionTracePipeline(
backend=backend,
executor=LiteLLMAdapter(),
vector_index=InMemoryVectorIndex(),
graph_store=InMemoryGraphStore(),
)
async def main():
decisions = await pipeline.run_from_text(
conv_text="Alice: We decided to switch from REST to GraphQL for the new API...",
conv_id="standup-2024-01-15",
gid="engineering-team",
updated_at=1705334400.0,
summary_pid="summary_standup-2024-01-15",
query_gids=["engineering-team"],
)
print(f"Extracted {len(decisions)} decisions")
asyncio.run(main())
See it in Action
The examples/ directory includes sample conversations and an interactive viewer with dashboards, a cluster board, timeline, and force-directed graph.
cd examples
export OPENAI_API_KEY=sk-... # or any LiteLLM-supported provider
python run.py # opens browser with live pipeline progress
Options: --port 9000, --no-browser, --model anthropic/claude-3.5-sonnet, or pass your own files (python run.py my_notes.txt).
What is this?
py-context-graph turns messy conversation text (meeting notes, Slack threads, standups) into a structured decision graph. It uses LLMs to:
- Extract decision items from text (what was decided, by whom, about what)
- Deduplicate near-identical decisions across conversations
- Enrich each decision with structured metadata (topics, entities, constraints, key facts)
- Cluster related decisions across conversations into coherent themes
- Materialize the result into a queryable graph
Text → Extract (LLM) → Persist → Deduplicate → Enrich (LLM) → Cluster → Graph
Install
pip install py-context-graph
With optional backends:
pip install py-context-graph[all] # LiteLLM + Firestore + in-memory vector index
pip install py-context-graph[llm] # LiteLLM adapter only
pip install py-context-graph[firestore] # Google Cloud Firestore backend
pip install py-context-graph[memory] # In-memory TF-IDF vector index (pandas)
Usage
import asyncio
from decision_graph import DecisionGraph, LiteLLMAdapter
from decision_graph.backends.memory import InMemoryBackend
from decision_graph.backends.memory.stores import InMemoryGraphStore, InMemoryVectorIndex
from decision_graph.decision_trace_pipeline import DecisionTracePipeline
backend = InMemoryBackend()
pipeline = DecisionTracePipeline(
backend=backend,
executor=LiteLLMAdapter(),
vector_index=InMemoryVectorIndex(),
graph_store=InMemoryGraphStore(),
)
async def main():
# Process a conversation
decisions = await pipeline.run_from_text(
conv_text="Alice: We decided to switch from REST to GraphQL for the new API...",
conv_id="standup-2024-01-15",
gid="engineering-team",
updated_at=1705334400.0,
summary_pid="summary_standup-2024-01-15",
query_gids=["engineering-team"],
)
# Query the results
dg = DecisionGraph(backend=backend, executor=LiteLLMAdapter())
service = dg.graph_service()
result = await service.get_enrichments_and_projections_joined(
group_ids=["engineering-team"]
)
print(f"Found {result['total_joined']} enriched decisions")
asyncio.run(main())
Key concepts
The four protocols
py-context-graph is built around pluggable interfaces. You only implement what you need:
| Protocol | Purpose | Bundled implementations |
|---|---|---|
StorageBackend |
Groups 4 document stores (enrichments, projections, clusters, links) | InMemoryBackend, FirestoreBackend |
LLMAdapter |
Executes LLM calls for extraction and enrichment | LiteLLMAdapter (supports OpenAI, Anthropic, and any LiteLLM provider) |
VectorIndex |
Similarity search for cross-conversation clustering | InMemoryVectorIndex (TF-IDF + cosine) |
GraphStore |
Write-only sync of hydrated clusters to a graph DB | InMemoryGraphStore, NullGraphStore |
DecisionGraph facade
The main entry point. Wire a backend and LLM adapter, then access services:
from decision_graph import DecisionGraph
dg = DecisionGraph(backend=my_backend, executor=my_llm)
service = dg.graph_service() # query enrichments, projections, clusters
retrieval = dg.retrieval() # filtered queries over enrichments
clusterer = dg.cluster_service() # cluster management
DecisionTracePipeline
The end-to-end processing pipeline. Feed it text, get structured decisions:
from decision_graph.decision_trace_pipeline import DecisionTracePipeline
pipeline = DecisionTracePipeline(
backend=backend,
executor=llm_adapter,
vector_index=vector_index, # optional, enables cross-conversation clustering
graph_store=graph_store, # use NullGraphStore() to skip graph materialization
)
# From raw text
decisions = await pipeline.run_from_text(conv_text=text, conv_id="c1", gid="g1", ...)
# From pre-extracted decision items
decisions = await pipeline.run(decision_items=[...], conv_id="c1", gid="g1", ...)
Context Graph (query layer)
For querying the materialized graph (requires a GraphReader implementation, e.g. Neo4j):
from decision_graph.context_graph.service import ContextGraphService
ctx = ContextGraphService(reader=my_graph_reader)
result = await ctx.query(text="What decisions were made about the API?", mode="chat")
Bring your own backend
Implement StorageBackend to use any database:
from decision_graph.core.registry import StorageBackend
from decision_graph.core.interfaces import EnrichmentStore, ProjectionStore, ClusterStore, LinkStore
class PostgresBackend(StorageBackend):
def enrichment_store(self) -> EnrichmentStore: ...
def projection_store(self) -> ProjectionStore: ...
def cluster_store(self) -> ClusterStore: ...
def link_store(self) -> LinkStore: ...
Each store protocol is defined in decision_graph.core.interfaces with clear method signatures.
Bring your own LLM
Implement the LLMAdapter protocol:
from decision_graph.core.interfaces import LLMAdapter
class MyLLMAdapter(LLMAdapter):
async def execute_async(self, model_config, data, additional_data=None):
# Call your LLM, return parsed result
...
Project structure
src/decision_graph/
├── __init__.py # Public API: DecisionGraph, LLMAdapter, LLMConfig, LiteLLMAdapter
├── graph.py # DecisionGraph facade
├── decision_trace_pipeline.py # End-to-end pipeline
├── extraction_service.py # LLM-based decision extraction
├── enrichment_service.py # LLM-based decision enrichment
├── clustering_service.py # Decision clustering
├── retrieval.py # Query/filter over enrichments
├── context_retrieval.py # Vector-based context retrieval
├── services.py # DecisionGraphService (joins, hydration)
├── ingestion.py # Graph materialization helpers
├── visualization.py # vis.js graph builder
├── markdown_chunker.py # Split markdown by headings
├── core/
│ ├── interfaces.py # Protocol definitions
│ ├── registry.py # StorageBackend ABC
│ ├── domain.py # Pydantic models
│ ├── config.py # LLMConfig
│ └── matching.py # Dedup, scoring, similarity
├── llm/
│ └── litellm_adapter.py # LiteLLM-based LLMAdapter
├── backends/
│ ├── memory/ # In-memory stores + TF-IDF vector index
│ └── firestore/ # Google Cloud Firestore stores
├── context_graph/ # Graph query layer (planner, templates, post-processing)
└── prompts/ # LLM prompt templates
Development
A Makefile is included for common tasks. Run make to see all available targets:
make # show all targets
make install-dev # create venv + install with dev dependencies
make test # run tests
make test-verbose # run tests with verbose output
make test-cov # run tests with coverage (opens HTML report)
make build # build distribution packages
make clean # remove build artifacts and caches
make clean-all # remove build artifacts, caches, and venv
Contributing
Contributions are welcome. Please open an issue first to discuss what you'd like to change.
git clone https://github.com/ResearchifyLabs/py-context-graph.git
cd py-context-graph
make install-dev
make test
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file py_context_graph-0.1.2.tar.gz.
File metadata
- Download URL: py_context_graph-0.1.2.tar.gz
- Upload date:
- Size: 92.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dac693f530a6c4a78c00bb32d966f4338556a8ca747169d6b7df08b07670a7c8
|
|
| MD5 |
29a116da8f5173c5993e606fe8de465a
|
|
| BLAKE2b-256 |
2a347aea0082d4a6c32555d921422b37e6702dcdb26568efe0505fa2b80e283a
|
Provenance
The following attestation bundles were made for py_context_graph-0.1.2.tar.gz:
Publisher:
publish.yml on ResearchifyLabs/py-context-graph
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
py_context_graph-0.1.2.tar.gz -
Subject digest:
dac693f530a6c4a78c00bb32d966f4338556a8ca747169d6b7df08b07670a7c8 - Sigstore transparency entry: 1247039456
- Sigstore integration time:
-
Permalink:
ResearchifyLabs/py-context-graph@19a8b4face81e85f842e7c06a5af39a1b1238be9 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/ResearchifyLabs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@19a8b4face81e85f842e7c06a5af39a1b1238be9 -
Trigger Event:
push
-
Statement type:
File details
Details for the file py_context_graph-0.1.2-py3-none-any.whl.
File metadata
- Download URL: py_context_graph-0.1.2-py3-none-any.whl
- Upload date:
- Size: 57.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
76ed60c766a4eeba913471b81ad7ed74cbf23d1f773c78ffb81b02a1688023aa
|
|
| MD5 |
17beb9a1ecb287a1919a94738c08d2ce
|
|
| BLAKE2b-256 |
5325885e97b780f1c493ae37d98fab1276546406b6982b260d0ee3ad5f07a5f4
|
Provenance
The following attestation bundles were made for py_context_graph-0.1.2-py3-none-any.whl:
Publisher:
publish.yml on ResearchifyLabs/py-context-graph
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
py_context_graph-0.1.2-py3-none-any.whl -
Subject digest:
76ed60c766a4eeba913471b81ad7ed74cbf23d1f773c78ffb81b02a1688023aa - Sigstore transparency entry: 1247039464
- Sigstore integration time:
-
Permalink:
ResearchifyLabs/py-context-graph@19a8b4face81e85f842e7c06a5af39a1b1238be9 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/ResearchifyLabs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@19a8b4face81e85f842e7c06a5af39a1b1238be9 -
Trigger Event:
push
-
Statement type: