reasongraph 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. reasongraph-0.2.0/.github/workflows/publish.yml +28 -0
  2. reasongraph-0.2.0/.github/workflows/test.yml +29 -0
  3. reasongraph-0.2.0/.gitignore +39 -0
  4. reasongraph-0.2.0/LICENSE +21 -0
  5. reasongraph-0.2.0/PKG-INFO +230 -0
  6. reasongraph-0.2.0/README.md +187 -0
  7. reasongraph-0.2.0/examples/financial_demo.py +75 -0
  8. reasongraph-0.2.0/pyproject.toml +73 -0
  9. reasongraph-0.2.0/src/reasongraph/__init__.py +21 -0
  10. reasongraph-0.2.0/src/reasongraph/_embeddings.py +67 -0
  11. reasongraph-0.2.0/src/reasongraph/_extraction.py +157 -0
  12. reasongraph-0.2.0/src/reasongraph/_types.py +24 -0
  13. reasongraph-0.2.0/src/reasongraph/backends/__init__.py +5 -0
  14. reasongraph-0.2.0/src/reasongraph/backends/_base.py +72 -0
  15. reasongraph-0.2.0/src/reasongraph/backends/_postgres.py +235 -0
  16. reasongraph-0.2.0/src/reasongraph/backends/_sqlite.py +352 -0
  17. reasongraph-0.2.0/src/reasongraph/datasets/__init__.py +3 -0
  18. reasongraph-0.2.0/src/reasongraph/datasets/_registry.py +28 -0
  19. reasongraph-0.2.0/src/reasongraph/datasets/analysis_patterns.json +203 -0
  20. reasongraph-0.2.0/src/reasongraph/datasets/causal.json +83 -0
  21. reasongraph-0.2.0/src/reasongraph/datasets/financial.json +120 -0
  22. reasongraph-0.2.0/src/reasongraph/datasets/medical.json +85 -0
  23. reasongraph-0.2.0/src/reasongraph/datasets/syllogisms.json +86 -0
  24. reasongraph-0.2.0/src/reasongraph/datasets/taxonomy.json +87 -0
  25. reasongraph-0.2.0/src/reasongraph/graph.py +366 -0
  26. reasongraph-0.2.0/src/reasongraph/py.typed +0 -0
  27. reasongraph-0.2.0/tests/eval_financial_reasoning.py +732 -0
  28. reasongraph-0.2.0/tests/test_datasets.py +47 -0
  29. reasongraph-0.2.0/tests/test_graph.py +428 -0
  30. reasongraph-0.2.0/tests/test_sqlite_backend.py +186 -0
@@ -0,0 +1,28 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ id-token: write
9
+
10
+ jobs:
11
+ publish:
12
+ runs-on: ubuntu-latest
13
+ environment: pypi
14
+
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - name: Install uv
19
+ uses: astral-sh/setup-uv@v4
20
+
21
+ - name: Set up Python
22
+ run: uv python install 3.12
23
+
24
+ - name: Build package
25
+ run: uv build
26
+
27
+ - name: Publish to PyPI
28
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,29 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.11", "3.12"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Install uv
20
+ uses: astral-sh/setup-uv@v4
21
+
22
+ - name: Set up Python ${{ matrix.python-version }}
23
+ run: uv python install ${{ matrix.python-version }}
24
+
25
+ - name: Install dependencies
26
+ run: uv sync --extra dev
27
+
28
+ - name: Run tests
29
+ run: uv run pytest -v
@@ -0,0 +1,39 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ *.egg
7
+ *.egg-info/
8
+ .eggs/
9
+
10
+ # Distribution
11
+ dist/
12
+ build/
13
+ *.whl
14
+
15
+ # Virtual environments
16
+ .venv/
17
+ venv/
18
+ env/
19
+
20
+ # IDE
21
+ .idea/
22
+ .vscode/
23
+ *.swp
24
+ *.swo
25
+
26
+ # Testing
27
+ .pytest_cache/
28
+ .coverage
29
+ htmlcov/
30
+
31
+ # OS
32
+ .DS_Store
33
+ Thumbs.db
34
+
35
+ # Locks
36
+ uv.lock
37
+
38
+ # Local scratch
39
+ tmp/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Berk
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,230 @@
1
+ Metadata-Version: 2.4
2
+ Name: reasongraph
3
+ Version: 0.2.0
4
+ Summary: A graph-based reasoning library with embedding search and multi-hop traversal
5
+ Project-URL: Homepage, https://github.com/bgokden/reasongraph
6
+ Project-URL: Repository, https://github.com/bgokden/reasongraph
7
+ Project-URL: Issues, https://github.com/bgokden/reasongraph/issues
8
+ Author: Berk
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: agent,causal-reasoning,embeddings,knowledge-graph,multi-hop,ner,nlp,rag,reasoning,semantic-search
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.11
20
+ Requires-Dist: aiosqlite>=0.19.0
21
+ Requires-Dist: sentence-transformers>=2.2.0
22
+ Requires-Dist: sqlite-vec>=0.1.6
23
+ Provides-Extra: all
24
+ Requires-Dist: gliner2>=1.2.0; extra == 'all'
25
+ Requires-Dist: pgvector>=0.2.0; extra == 'all'
26
+ Requires-Dist: psycopg-pool>=3.1.0; extra == 'all'
27
+ Requires-Dist: psycopg[binary]>=3.1.0; extra == 'all'
28
+ Requires-Dist: requests>=2.28.0; extra == 'all'
29
+ Requires-Dist: urllib3>=2.0.0; extra == 'all'
30
+ Provides-Extra: dev
31
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
32
+ Requires-Dist: pytest>=7.0.0; extra == 'dev'
33
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
34
+ Provides-Extra: gliner2
35
+ Requires-Dist: gliner2>=1.2.0; extra == 'gliner2'
36
+ Requires-Dist: requests>=2.28.0; extra == 'gliner2'
37
+ Requires-Dist: urllib3>=2.0.0; extra == 'gliner2'
38
+ Provides-Extra: postgres
39
+ Requires-Dist: pgvector>=0.2.0; extra == 'postgres'
40
+ Requires-Dist: psycopg-pool>=3.1.0; extra == 'postgres'
41
+ Requires-Dist: psycopg[binary]>=3.1.0; extra == 'postgres'
42
+ Description-Content-Type: text/markdown
43
+
44
+ # reasongraph
45
+
46
+ A graph-based reasoning library with embedding search, multi-hop traversal, and automatic entity/causal extraction.
47
+
48
+ [![PyPI version](https://img.shields.io/pypi/v/reasongraph)](https://pypi.org/project/reasongraph/)
49
+ [![Python 3.11+](https://img.shields.io/pypi/pyversions/reasongraph)](https://pypi.org/project/reasongraph/)
50
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
51
+
52
+ ## Installation
53
+
54
+ ```bash
55
+ pip install reasongraph[all] # everything included
56
+ ```
57
+
58
+ Or install only what you need:
59
+
60
+ ```bash
61
+ pip install reasongraph # core: SQLite backend, NER extraction, embeddings
62
+ pip install reasongraph[gliner2] # + GLiNER2 entity + causal extraction (recommended)
63
+ pip install reasongraph[postgres] # + PostgreSQL + pgvector backend
64
+ ```
65
+
66
+ ## Quick Start
67
+
68
+ ```python
69
+ from reasongraph import ReasonGraph
70
+
71
+ graph = ReasonGraph()
72
+ graph.initialize_sync()
73
+
74
+ # Add text with automatic entity + causal extraction
75
+ graph.add_text_sync("Lehman Brothers filed for bankruptcy in September 2008.")
76
+ graph.add_text_sync("The Federal Reserve cut interest rates to near zero.")
77
+
78
+ # Query with embedding search + multi-hop graph traversal
79
+ results = graph.query_sync("What caused the 2008 financial crisis?")
80
+ for text in results:
81
+ print(text)
82
+
83
+ graph.close_sync()
84
+ ```
85
+
86
+ Or use the async API with a context manager:
87
+
88
+ ```python
89
+ import asyncio
90
+ from reasongraph import ReasonGraph
91
+
92
+ async def main():
93
+ async with ReasonGraph() as graph:
94
+ await graph.load_dataset("financial")
95
+ results = await graph.query("What caused the 2008 crisis?")
96
+ for text in results:
97
+ print(text)
98
+
99
+ asyncio.run(main())
100
+ ```
101
+
102
+ ## Features
103
+
104
+ - **Automatic extraction** -- GLiNER2 extracts entities and causal relations in one pass (falls back to BERT NER when gliner2 is not installed)
105
+ - **Hybrid search** -- combine embedding similarity, keyword (trigram) matching, or both
106
+ - **Multi-hop traversal** -- follow graph edges to discover connected reasoning chains
107
+ - **Cross-encoder reranking** -- rerank results at each hop with `ms-marco-MiniLM-L-6-v2`
108
+ - **Built-in datasets** -- load curated reasoning graphs for immediate use
109
+ - **Async-first** -- native async API with sync convenience wrappers
110
+ - **Pluggable backends** -- SQLite (zero-config default) or PostgreSQL with pgvector
111
+
112
+ ## Built-in Datasets
113
+
114
+ | Dataset | Description |
115
+ |---------|-------------|
116
+ | `syllogisms` | Classical syllogistic reasoning chains |
117
+ | `causal` | Cause-effect reasoning with entity annotations |
118
+ | `taxonomy` | Hierarchical concept taxonomy |
119
+ | `financial` | Financial crisis causal chains (2008 crisis, dot-com, inflation, eurozone) |
120
+ | `medical` | Medical causal chains (heart disease, diabetes, infectious disease, cancer) |
121
+ | `analysis_patterns` | Data analysis reasoning: scenario detection, technique selection, implementation patterns |
122
+
123
+ ```python
124
+ graph.load_dataset_sync("financial")
125
+ ```
126
+
127
+ ## Search Modes
128
+
129
+ ```python
130
+ # Pure embedding similarity (default)
131
+ results = graph.query_sync("credit freeze", search_mode="embedding")
132
+
133
+ # Pure keyword/trigram matching
134
+ results = graph.query_sync("credit freeze", search_mode="keyword")
135
+
136
+ # Hybrid: Reciprocal Rank Fusion of embedding + trigram rankings
137
+ results = graph.query_sync("credit freeze", search_mode="hybrid")
138
+
139
+ # Tune the RRF smoothing constant (default 60, lower = more weight to top ranks)
140
+ results = graph.query_sync("credit freeze", search_mode="hybrid", rrf_k=30)
141
+ ```
142
+
143
+ ## Entity and Causal Extraction
144
+
145
+ When `gliner2` is installed, `add_text()` / `add_texts()` automatically use GLiNER2 for both entity extraction and causal relation detection. Without `gliner2`, it falls back to BERT NER (entities only).
146
+
147
+ ```python
148
+ from reasongraph import ReasonGraph, NERExtractor, GLiNER2Extractor
149
+
150
+ graph = ReasonGraph()
151
+ graph.initialize_sync()
152
+
153
+ # Default: GLiNER2 (entities + causal relations) if installed, else BERT NER
154
+ entities = graph.add_text_sync("Apple released the iPhone in 2007.")
155
+ print(entities) # ['Apple', 'iPhone']
156
+
157
+ # Explicit: force BERT NER even if GLiNER2 is installed
158
+ entities = graph.add_text_sync("Apple released the iPhone in 2007.", extractor=NERExtractor())
159
+
160
+ # Explicit: GLiNER2 with custom entity types
161
+ gliner = GLiNER2Extractor(entity_types=["company", "product", "date"])
162
+ entities = graph.add_text_sync("Apple released the iPhone in 2007.", extractor=gliner)
163
+
164
+ # Any callable works
165
+ entities = graph.add_text_sync("some text", extractor=lambda t: ["custom"])
166
+ ```
167
+
168
+ ## PostgreSQL Backend
169
+
170
+ ```python
171
+ from reasongraph import ReasonGraph
172
+ from reasongraph.backends import PostgresBackend
173
+
174
+ graph = ReasonGraph(backend=PostgresBackend(database_url="postgresql://user:pass@localhost/db"))
175
+ ```
176
+
177
+ Requires `pip install reasongraph[postgres]` and the `pgvector` + `pg_trgm` extensions enabled on your database.
178
+
179
+ ## Evaluation: Mixed-Domain Reasoning
180
+
181
+ We evaluate reasoning quality by loading all 6 built-in datasets into a single graph (~130 text nodes, ~104 entity nodes, ~280 edges) and testing whether the library can trace the correct causal chains, syllogistic proofs, taxonomic hierarchies, and data analysis patterns -- without being distracted by unrelated facts from other domains.
182
+
183
+ 32 test cases simulate agent-style queries like *"I need to understand what caused the 2008 financial crisis"*, *"How does insulin resistance lead to kidney failure?"*, or *"I have two numeric columns, check if related"* and check whether the returned reasoning chain matches the expected ground truth.
184
+
185
+ **Per-domain results (hybrid search, `top_k=5`, `hops=4`, `rerank_top_k=4`):**
186
+
187
+ | Domain | Cases | Chain Completeness | Recall@5 | Precision@5 | Domain Accuracy |
188
+ |--------|------:|--------------------|----------|-------------|-----------------|
189
+ | Causal | 5 | 100% | 100% | 92% | 100% |
190
+ | Financial | 6 | 100% | 82% | 60% | 100% |
191
+ | Medical | 5 | 100% | 92% | 76% | 92% |
192
+ | Syllogisms | 5 | 100% | 100% | 92% | 85% |
193
+ | Taxonomy | 3 | 100% | 83% | 53% | 92% |
194
+ | Analysis Patterns | 8 | 96% | 75% | 45% | 96% |
195
+ | **Overall** | **32** | **99%** | **88%** | **68%** | **95%** |
196
+
197
+ 32/32 cases pass (>= 50% chain completeness). Split reranking gives chain continuations (text-to-text edges) priority over bridge discoveries (entity-to-text edges), keeping traversal focused.
198
+
199
+ **Search mode comparison:**
200
+
201
+ | Mode | Chain Completeness | Recall@5 | Precision@5 | Domain Accuracy |
202
+ |------|-------------------|----------|-------------|-----------------|
203
+ | Embedding | 99% | 88% | 68% | 95% |
204
+ | Keyword | 0% | 0% | 0% | 0% |
205
+ | Hybrid | 99% | 88% | 68% | 95% |
206
+
207
+ Keyword-only mode scores 0% because the eval queries are natural language questions that don't substring-match the dataset's declarative statements. This is expected -- keyword search is designed for known-term lookups, not question answering.
208
+
209
+ Reproduce: `uv run python tests/eval_financial_reasoning.py`
210
+
211
+ ## API Reference
212
+
213
+ ### `ReasonGraph(backend=None, embed_model=None, rerank_model=None, forget_after=30)`
214
+
215
+ | Method | Description |
216
+ |--------|-------------|
217
+ | `add_nodes(nodes)` | Add `(content, type)` tuples to the graph |
218
+ | `add_edges(edges)` | Add `(from, to)` content edges |
219
+ | `add_text(text, extractor=None)` | Add text with automatic entity extraction |
220
+ | `add_texts(texts, extractor=None, causal_extractor=None)` | Batch add with entity + causal extraction (auto-enabled with GLiNER2) |
221
+ | `query(query, top_k=5, hops=4, rerank_top_k=4, search_mode="embedding", rrf_k=60)` | Search and traverse the graph |
222
+ | `load_dataset(name)` | Load a built-in dataset |
223
+ | `delete_stale()` | Remove nodes not accessed within `forget_after` days |
224
+ | `get_all_nodes()` / `get_all_edges()` | Inspect graph contents |
225
+
226
+ All methods are async. Sync variants are available with a `_sync` suffix (e.g. `query_sync`).
227
+
228
+ ## License
229
+
230
+ MIT
@@ -0,0 +1,187 @@
1
+ # reasongraph
2
+
3
+ A graph-based reasoning library with embedding search, multi-hop traversal, and automatic entity/causal extraction.
4
+
5
+ [![PyPI version](https://img.shields.io/pypi/v/reasongraph)](https://pypi.org/project/reasongraph/)
6
+ [![Python 3.11+](https://img.shields.io/pypi/pyversions/reasongraph)](https://pypi.org/project/reasongraph/)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ pip install reasongraph[all] # everything included
13
+ ```
14
+
15
+ Or install only what you need:
16
+
17
+ ```bash
18
+ pip install reasongraph # core: SQLite backend, NER extraction, embeddings
19
+ pip install reasongraph[gliner2] # + GLiNER2 entity + causal extraction (recommended)
20
+ pip install reasongraph[postgres] # + PostgreSQL + pgvector backend
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ```python
26
+ from reasongraph import ReasonGraph
27
+
28
+ graph = ReasonGraph()
29
+ graph.initialize_sync()
30
+
31
+ # Add text with automatic entity + causal extraction
32
+ graph.add_text_sync("Lehman Brothers filed for bankruptcy in September 2008.")
33
+ graph.add_text_sync("The Federal Reserve cut interest rates to near zero.")
34
+
35
+ # Query with embedding search + multi-hop graph traversal
36
+ results = graph.query_sync("What caused the 2008 financial crisis?")
37
+ for text in results:
38
+ print(text)
39
+
40
+ graph.close_sync()
41
+ ```
42
+
43
+ Or use the async API with a context manager:
44
+
45
+ ```python
46
+ import asyncio
47
+ from reasongraph import ReasonGraph
48
+
49
+ async def main():
50
+ async with ReasonGraph() as graph:
51
+ await graph.load_dataset("financial")
52
+ results = await graph.query("What caused the 2008 crisis?")
53
+ for text in results:
54
+ print(text)
55
+
56
+ asyncio.run(main())
57
+ ```
58
+
59
+ ## Features
60
+
61
+ - **Automatic extraction** -- GLiNER2 extracts entities and causal relations in one pass (falls back to BERT NER when gliner2 is not installed)
62
+ - **Hybrid search** -- combine embedding similarity, keyword (trigram) matching, or both
63
+ - **Multi-hop traversal** -- follow graph edges to discover connected reasoning chains
64
+ - **Cross-encoder reranking** -- rerank results at each hop with `ms-marco-MiniLM-L-6-v2`
65
+ - **Built-in datasets** -- load curated reasoning graphs for immediate use
66
+ - **Async-first** -- native async API with sync convenience wrappers
67
+ - **Pluggable backends** -- SQLite (zero-config default) or PostgreSQL with pgvector
68
+
69
+ ## Built-in Datasets
70
+
71
+ | Dataset | Description |
72
+ |---------|-------------|
73
+ | `syllogisms` | Classical syllogistic reasoning chains |
74
+ | `causal` | Cause-effect reasoning with entity annotations |
75
+ | `taxonomy` | Hierarchical concept taxonomy |
76
+ | `financial` | Financial crisis causal chains (2008 crisis, dot-com, inflation, eurozone) |
77
+ | `medical` | Medical causal chains (heart disease, diabetes, infectious disease, cancer) |
78
+ | `analysis_patterns` | Data analysis reasoning: scenario detection, technique selection, implementation patterns |
79
+
80
+ ```python
81
+ graph.load_dataset_sync("financial")
82
+ ```
83
+
84
+ ## Search Modes
85
+
86
+ ```python
87
+ # Pure embedding similarity (default)
88
+ results = graph.query_sync("credit freeze", search_mode="embedding")
89
+
90
+ # Pure keyword/trigram matching
91
+ results = graph.query_sync("credit freeze", search_mode="keyword")
92
+
93
+ # Hybrid: Reciprocal Rank Fusion of embedding + trigram rankings
94
+ results = graph.query_sync("credit freeze", search_mode="hybrid")
95
+
96
+ # Tune the RRF smoothing constant (default 60, lower = more weight to top ranks)
97
+ results = graph.query_sync("credit freeze", search_mode="hybrid", rrf_k=30)
98
+ ```
99
+
100
+ ## Entity and Causal Extraction
101
+
102
+ When `gliner2` is installed, `add_text()` / `add_texts()` automatically use GLiNER2 for both entity extraction and causal relation detection. Without `gliner2`, it falls back to BERT NER (entities only).
103
+
104
+ ```python
105
+ from reasongraph import ReasonGraph, NERExtractor, GLiNER2Extractor
106
+
107
+ graph = ReasonGraph()
108
+ graph.initialize_sync()
109
+
110
+ # Default: GLiNER2 (entities + causal relations) if installed, else BERT NER
111
+ entities = graph.add_text_sync("Apple released the iPhone in 2007.")
112
+ print(entities) # ['Apple', 'iPhone']
113
+
114
+ # Explicit: force BERT NER even if GLiNER2 is installed
115
+ entities = graph.add_text_sync("Apple released the iPhone in 2007.", extractor=NERExtractor())
116
+
117
+ # Explicit: GLiNER2 with custom entity types
118
+ gliner = GLiNER2Extractor(entity_types=["company", "product", "date"])
119
+ entities = graph.add_text_sync("Apple released the iPhone in 2007.", extractor=gliner)
120
+
121
+ # Any callable works
122
+ entities = graph.add_text_sync("some text", extractor=lambda t: ["custom"])
123
+ ```
124
+
125
+ ## PostgreSQL Backend
126
+
127
+ ```python
128
+ from reasongraph import ReasonGraph
129
+ from reasongraph.backends import PostgresBackend
130
+
131
+ graph = ReasonGraph(backend=PostgresBackend(database_url="postgresql://user:pass@localhost/db"))
132
+ ```
133
+
134
+ Requires `pip install reasongraph[postgres]` and the `pgvector` + `pg_trgm` extensions enabled on your database.
135
+
136
+ ## Evaluation: Mixed-Domain Reasoning
137
+
138
+ We evaluate reasoning quality by loading all 6 built-in datasets into a single graph (~130 text nodes, ~104 entity nodes, ~280 edges) and testing whether the library can trace the correct causal chains, syllogistic proofs, taxonomic hierarchies, and data analysis patterns -- without being distracted by unrelated facts from other domains.
139
+
140
+ 32 test cases simulate agent-style queries like *"I need to understand what caused the 2008 financial crisis"*, *"How does insulin resistance lead to kidney failure?"*, or *"I have two numeric columns, check if related"* and check whether the returned reasoning chain matches the expected ground truth.
141
+
142
+ **Per-domain results (hybrid search, `top_k=5`, `hops=4`, `rerank_top_k=4`):**
143
+
144
+ | Domain | Cases | Chain Completeness | Recall@5 | Precision@5 | Domain Accuracy |
145
+ |--------|------:|--------------------|----------|-------------|-----------------|
146
+ | Causal | 5 | 100% | 100% | 92% | 100% |
147
+ | Financial | 6 | 100% | 82% | 60% | 100% |
148
+ | Medical | 5 | 100% | 92% | 76% | 92% |
149
+ | Syllogisms | 5 | 100% | 100% | 92% | 85% |
150
+ | Taxonomy | 3 | 100% | 83% | 53% | 92% |
151
+ | Analysis Patterns | 8 | 96% | 75% | 45% | 96% |
152
+ | **Overall** | **32** | **99%** | **88%** | **68%** | **95%** |
153
+
154
+ 32/32 cases pass (>= 50% chain completeness). Split reranking gives chain continuations (text-to-text edges) priority over bridge discoveries (entity-to-text edges), keeping traversal focused.
155
+
156
+ **Search mode comparison:**
157
+
158
+ | Mode | Chain Completeness | Recall@5 | Precision@5 | Domain Accuracy |
159
+ |------|-------------------|----------|-------------|-----------------|
160
+ | Embedding | 99% | 88% | 68% | 95% |
161
+ | Keyword | 0% | 0% | 0% | 0% |
162
+ | Hybrid | 99% | 88% | 68% | 95% |
163
+
164
+ Keyword-only mode scores 0% because the eval queries are natural language questions that don't substring-match the dataset's declarative statements. This is expected -- keyword search is designed for known-term lookups, not question answering.
165
+
166
+ Reproduce: `uv run python tests/eval_financial_reasoning.py`
167
+
168
+ ## API Reference
169
+
170
+ ### `ReasonGraph(backend=None, embed_model=None, rerank_model=None, forget_after=30)`
171
+
172
+ | Method | Description |
173
+ |--------|-------------|
174
+ | `add_nodes(nodes)` | Add `(content, type)` tuples to the graph |
175
+ | `add_edges(edges)` | Add `(from, to)` content edges |
176
+ | `add_text(text, extractor=None)` | Add text with automatic entity extraction |
177
+ | `add_texts(texts, extractor=None, causal_extractor=None)` | Batch add with entity + causal extraction (auto-enabled with GLiNER2) |
178
+ | `query(query, top_k=5, hops=4, rerank_top_k=4, search_mode="embedding", rrf_k=60)` | Search and traverse the graph |
179
+ | `load_dataset(name)` | Load a built-in dataset |
180
+ | `delete_stale()` | Remove nodes not accessed within `forget_after` days |
181
+ | `get_all_nodes()` / `get_all_edges()` | Inspect graph contents |
182
+
183
+ All methods are async. Sync variants are available with a `_sync` suffix (e.g. `query_sync`).
184
+
185
+ ## License
186
+
187
+ MIT
@@ -0,0 +1,75 @@
1
+ """Financial crisis analysis with reasongraph.
2
+
3
+ This demo loads the built-in financial dataset and runs queries that
4
+ an analyst or agent might ask when researching economic crises.
5
+ """
6
+
7
+ import textwrap
8
+ from reasongraph import ReasonGraph
9
+
10
+
11
+ def print_results(question: str, results: list[str]) -> None:
12
+ print(f"\n{'=' * 70}")
13
+ print(f"Q: {question}")
14
+ print(f"{'=' * 70}")
15
+ for i, text in enumerate(results, 1):
16
+ wrapped = textwrap.fill(text, width=66, initial_indent=" ", subsequent_indent=" ")
17
+ print(f" [{i}] {text}")
18
+ print()
19
+
20
+
21
+ def main():
22
+ graph = ReasonGraph()
23
+ graph.initialize_sync()
24
+ graph.load_dataset_sync("financial")
25
+
26
+ # An analyst starts by asking about the 2008 crisis.
27
+ results = graph.query_sync(
28
+ "What caused the 2008 financial crisis?",
29
+ search_mode="hybrid",
30
+ top_k=5,
31
+ hops=2,
32
+ )
33
+ print_results("What caused the 2008 financial crisis?", results)
34
+
35
+ # They want to understand how inflation spiraled in 2021-2022.
36
+ results = graph.query_sync(
37
+ "How did inflation surge after the pandemic?",
38
+ search_mode="hybrid",
39
+ top_k=5,
40
+ hops=2,
41
+ )
42
+ print_results("How did inflation surge after the pandemic?", results)
43
+
44
+ # Next: how does the Fed respond to crises?
45
+ results = graph.query_sync(
46
+ "What did the Federal Reserve do about interest rates?",
47
+ search_mode="hybrid",
48
+ top_k=5,
49
+ hops=2,
50
+ )
51
+ print_results("What did the Federal Reserve do about interest rates?", results)
52
+
53
+ # The dot-com bubble -- is there a pattern?
54
+ results = graph.query_sync(
55
+ "What happened during the dot-com bubble?",
56
+ search_mode="hybrid",
57
+ top_k=5,
58
+ hops=2,
59
+ )
60
+ print_results("What happened during the dot-com bubble?", results)
61
+
62
+ # European sovereign debt -- contagion across borders.
63
+ results = graph.query_sync(
64
+ "How did the Greek debt crisis spread to other countries?",
65
+ search_mode="hybrid",
66
+ top_k=3,
67
+ hops=3,
68
+ )
69
+ print_results("How did the Greek debt crisis spread to other countries?", results)
70
+
71
+ graph.close_sync()
72
+
73
+
74
+ if __name__ == "__main__":
75
+ main()
@@ -0,0 +1,73 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "reasongraph"
7
+ version = "0.2.0"
8
+ description = "A graph-based reasoning library with embedding search and multi-hop traversal"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ authors = [
13
+ {name = "Berk"},
14
+ ]
15
+ keywords = [
16
+ "knowledge-graph", "reasoning", "rag", "embeddings", "nlp",
17
+ "semantic-search", "causal-reasoning", "multi-hop", "ner", "agent",
18
+ ]
19
+ classifiers = [
20
+ "License :: OSI Approved :: MIT License",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
26
+ "Typing :: Typed",
27
+ ]
28
+ dependencies = [
29
+ "sentence-transformers>=2.2.0",
30
+ "aiosqlite>=0.19.0",
31
+ "sqlite-vec>=0.1.6",
32
+ ]
33
+
34
+ [project.urls]
35
+ Homepage = "https://github.com/bgokden/reasongraph"
36
+ Repository = "https://github.com/bgokden/reasongraph"
37
+ Issues = "https://github.com/bgokden/reasongraph/issues"
38
+
39
+ [project.optional-dependencies]
40
+ gliner2 = [
41
+ "gliner2>=1.2.0",
42
+ "urllib3>=2.0.0",
43
+ "requests>=2.28.0",
44
+ ]
45
+ postgres = [
46
+ "psycopg[binary]>=3.1.0",
47
+ "psycopg-pool>=3.1.0",
48
+ "pgvector>=0.2.0",
49
+ ]
50
+ all = [
51
+ "gliner2>=1.2.0",
52
+ "urllib3>=2.0.0",
53
+ "requests>=2.28.0",
54
+ "psycopg[binary]>=3.1.0",
55
+ "psycopg-pool>=3.1.0",
56
+ "pgvector>=0.2.0",
57
+ ]
58
+ dev = [
59
+ "pytest>=7.0.0",
60
+ "pytest-asyncio>=0.21.0",
61
+ "ruff>=0.1.0",
62
+ ]
63
+
64
+ [tool.hatch.build.targets.wheel]
65
+ packages = ["src/reasongraph"]
66
+
67
+ [tool.pytest.ini_options]
68
+ asyncio_mode = "auto"
69
+ testpaths = ["tests"]
70
+
71
+ [tool.ruff]
72
+ target-version = "py311"
73
+ line-length = 100