gragdb 0.1.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.
- gragdb-0.1.0/.gitignore +13 -0
- gragdb-0.1.0/LICENSE +21 -0
- gragdb-0.1.0/PKG-INFO +210 -0
- gragdb-0.1.0/README.md +187 -0
- gragdb-0.1.0/pyproject.toml +60 -0
- gragdb-0.1.0/src/grag/__init__.py +8 -0
- gragdb-0.1.0/src/grag/api/__init__.py +1 -0
- gragdb-0.1.0/src/grag/api/main.py +204 -0
- gragdb-0.1.0/src/grag/api/static/assets/index-C2RYf8Fw.js +94 -0
- gragdb-0.1.0/src/grag/api/static/assets/index-Cqmq96EV.css +1 -0
- gragdb-0.1.0/src/grag/api/static/index.html +13 -0
- gragdb-0.1.0/src/grag/cli.py +88 -0
- gragdb-0.1.0/src/grag/config.py +58 -0
- gragdb-0.1.0/src/grag/core/__init__.py +1 -0
- gragdb-0.1.0/src/grag/core/engine.py +344 -0
- gragdb-0.1.0/src/grag/core/errors.py +39 -0
- gragdb-0.1.0/src/grag/core/mutate.py +609 -0
- gragdb-0.1.0/src/grag/core/schema.py +223 -0
- gragdb-0.1.0/src/grag/core/serialize.py +126 -0
- gragdb-0.1.0/src/grag/core/types.py +318 -0
- gragdb-0.1.0/src/grag/ingest/__init__.py +1 -0
- gragdb-0.1.0/src/grag/ingest/loaders.py +292 -0
- gragdb-0.1.0/src/grag/mcp_server/__init__.py +1 -0
- gragdb-0.1.0/src/grag/mcp_server/server.py +447 -0
- gragdb-0.1.0/src/grag/registry.py +105 -0
- gragdb-0.1.0/src/grag/retrieval/__init__.py +1 -0
- gragdb-0.1.0/src/grag/retrieval/bench.py +267 -0
- gragdb-0.1.0/src/grag/retrieval/context.py +82 -0
- gragdb-0.1.0/src/grag/retrieval/polar.py +422 -0
- gragdb-0.1.0/src/grag/retrieval/search.py +259 -0
- gragdb-0.1.0/src/grag/retrieval/vectors.py +736 -0
- gragdb-0.1.0/src/grag/service.py +186 -0
- gragdb-0.1.0/ui/README.md +4 -0
- gragdb-0.1.0/ui/index.html +12 -0
- gragdb-0.1.0/ui/package-lock.json +2390 -0
- gragdb-0.1.0/ui/package.json +27 -0
- gragdb-0.1.0/ui/src/App.tsx +253 -0
- gragdb-0.1.0/ui/src/api.ts +94 -0
- gragdb-0.1.0/ui/src/components/Console.tsx +209 -0
- gragdb-0.1.0/ui/src/components/GraphCanvas.tsx +260 -0
- gragdb-0.1.0/ui/src/components/Inspector.tsx +72 -0
- gragdb-0.1.0/ui/src/components/SchemaPanel.tsx +98 -0
- gragdb-0.1.0/ui/src/components/SearchBar.tsx +87 -0
- gragdb-0.1.0/ui/src/graph-utils.ts +220 -0
- gragdb-0.1.0/ui/src/main.tsx +10 -0
- gragdb-0.1.0/ui/src/styles.css +813 -0
- gragdb-0.1.0/ui/src/types.ts +95 -0
- gragdb-0.1.0/ui/tsconfig.json +18 -0
- gragdb-0.1.0/ui/vite.config.ts +20 -0
gragdb-0.1.0/.gitignore
ADDED
gragdb-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Adam
|
|
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.
|
gragdb-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gragdb
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: LLM-first graph knowledgebase: embedded Cypher engine, MCP tool contract, GraphRAG retrieval
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: <3.15,>=3.10
|
|
8
|
+
Requires-Dist: fastapi>=0.110
|
|
9
|
+
Requires-Dist: ladybug>=0.19
|
|
10
|
+
Requires-Dist: mcp>=2.0
|
|
11
|
+
Requires-Dist: numpy>=1.26
|
|
12
|
+
Requires-Dist: pydantic>=2.6
|
|
13
|
+
Requires-Dist: uvicorn>=0.29
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Requires-Dist: httpx>=0.27; extra == 'dev'
|
|
16
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
17
|
+
Requires-Dist: pyyaml>=6.0; extra == 'dev'
|
|
18
|
+
Provides-Extra: embed-local
|
|
19
|
+
Requires-Dist: fastembed>=0.4; extra == 'embed-local'
|
|
20
|
+
Provides-Extra: embed-remote
|
|
21
|
+
Requires-Dist: httpx>=0.27; extra == 'embed-remote'
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# grag
|
|
25
|
+
|
|
26
|
+
**LLM-first graph knowledgebase.** One embedded Cypher engine ([LadybugDB](https://ladybugdb.com), the Kuzu successor), one file per database, zero daemons — wrapped in the tool contract LLMs actually need: schema introspection that anchors text-to-Cypher, idempotent upserts with provenance, hybrid FTS/vector search, and token-budgeted subgraph context for grounded, low-hallucination answers.
|
|
27
|
+
|
|
28
|
+
*(**G**(raph)**RAG** — retrieval-augmented generation grounded in a graph.)*
|
|
29
|
+
|
|
30
|
+
Not an enterprise platform. `pip install`, point an MCP client at it, done.
|
|
31
|
+
|
|
32
|
+
## Why
|
|
33
|
+
|
|
34
|
+
LLM answers hallucinate when retrieval returns isolated chunks. grag stores knowledge as a **graph** — entities, documents, and their relationships — so retrieval returns a connected, cited subgraph an LLM can reason over. The LLM can also *build* the graph: `define_schema` + `upsert_nodes/edges` are first-class tools, so "turn these docs into a knowledge graph" is a normal conversation, not a pipeline project.
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
**From PyPI** (ships the web UI):
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install gragdb
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Python 3.10–3.14; **3.13 recommended** (faster interpreter for the Python-side
|
|
45
|
+
packing/serialization paths, and 3.10 reaches end-of-life in October 2026).
|
|
46
|
+
|
|
47
|
+
**From source** (for development). Build the UI **first** — `pip install` needs the
|
|
48
|
+
built bundle at `src/grag/api/static` (the wheel's force-include; see `pyproject.toml`):
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
cd ui && npm ci && npm run build && cd .. # builds the UI into src/grag/api/static/
|
|
52
|
+
pip install -e . # core: engine, REST, MCP, FTS — no torch, no GPU stack
|
|
53
|
+
pip install -e ".[dev]" # tests
|
|
54
|
+
pip install -e ".[embed-local]" # optional: local embeddings (fastembed/ONNX, still no torch)
|
|
55
|
+
pip install -e ".[embed-remote]" # optional: OpenAI-compatible remote embeddings
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Without an embedder, everything works FTS-only (BM25 is native to the engine).
|
|
59
|
+
|
|
60
|
+
**Enabling semantic search:** install `embed-local`, then set `GRAG_EMBED_PROVIDER=fastembed`
|
|
61
|
+
when serving. This uses ONNX Runtime — **no PyTorch** — so grag stays light (~50-100MB,
|
|
62
|
+
model downloads once then works offline). Nodes are (re)embedded lazily on the next
|
|
63
|
+
search whenever their embedding is NULL. First query downloads the model + embeds all
|
|
64
|
+
nodes (seconds); steady state is ~300ms/query on CPU.
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install -e ".[embed-local]"
|
|
68
|
+
GRAG_EMBED_PROVIDER=fastembed grag --db knowledge.lbdb serve
|
|
69
|
+
# optional: GRAG_EMBED_MODEL=BAAI/bge-base-en-v1.5 GRAG_EMBED_DIM=768
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Quickstart
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# build the demo knowledgebase (fictional company handbook, entities + relations)
|
|
76
|
+
python examples/build_example.py
|
|
77
|
+
|
|
78
|
+
# serve REST + the graph UI at http://127.0.0.1:8471
|
|
79
|
+
# (note: start it from a normal terminal — servers launched inside an agent
|
|
80
|
+
# sandbox get torn down and can't be reached from your browser)
|
|
81
|
+
grag --db examples/knowledge.lbdb serve
|
|
82
|
+
|
|
83
|
+
# or answer 3 demo questions end-to-end in the terminal
|
|
84
|
+
python examples/demo_e2e.py
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The UI: force-graph explorer (click = inspect, double-click = expand neighbors), Cypher console (Ctrl+Enter, graph/table results), schema sidebar, and a search bar that shows the exact grounding text an LLM would receive.
|
|
88
|
+
|
|
89
|
+
## Use from an LLM harness (MCP)
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
grag --db knowledge.lbdb mcp
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Cursor / `.cursor/mcp.json`:
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"mcpServers": {
|
|
100
|
+
"grag": {
|
|
101
|
+
"command": "grag",
|
|
102
|
+
"args": ["--db", "/absolute/path/knowledge.lbdb", "mcp"]
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Any MCP client gets these 7 tools:
|
|
109
|
+
|
|
110
|
+
| tool | purpose |
|
|
111
|
+
|---|---|
|
|
112
|
+
| `describe_schema` | prompt-shaped schema: tables, properties, row counts, sample keys. Call before writing Cypher — kills hallucinated labels. |
|
|
113
|
+
| `define_schema` | create node/rel tables (LLM designs the graph for a domain) |
|
|
114
|
+
| `upsert_nodes` / `upsert_edges` | idempotent MERGE writes; `_source` provenance automatic |
|
|
115
|
+
| `cypher_query` | read-only Cypher; errors come back with correction hints |
|
|
116
|
+
| `search_knowledge` | hybrid BM25 + vector seeds → k-hop expansion → cited, token-budgeted context |
|
|
117
|
+
| `get_context` | re-pack chosen node ids into a token budget |
|
|
118
|
+
|
|
119
|
+
Errors are returned as `ERROR: ... HINT: ...` tool output so the model self-corrects in-loop.
|
|
120
|
+
|
|
121
|
+
## Multiple projects / shared server
|
|
122
|
+
|
|
123
|
+
One `.lbdb` = one isolated universe — no shared entities, no cross-db queries. Per-project DBs is the default pattern; multi-db serving is opt-in via `--db-dir` (single-db is unchanged).
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
grag --db-dir ~/kb serve # one process serves every .lbdb in ~/kb
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Every `/api/*` endpoint accepts `?db=<name>` or an `x-grag-db: <name>` header (query param wins). `GET /api/dbs` returns `{"dbs": ["alpha","beta"], "default": "alpha"}` (`{"dbs": [], "default": null}` in single-db mode). Without a selector the server prefers the file matching `db_path`'s name, else a lone `.lbdb`, else 400 with a hint; unknown name → 404 listing available DBs.
|
|
130
|
+
|
|
131
|
+
For MCP, several IDE windows on one DB collide: stdio spawns a `grag mcp` process per client and LadybugDB allows only ONE process to write a given `.lbdb` ("Could not set lock"). One shared HTTP server avoids it — each window sends its project name via `x-grag-db`:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
grag --db-dir ~/kb mcp --transport streamable-http --host 127.0.0.1 --port 8472
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Cursor / `.cursor/mcp.json` (per window, one header per project):
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"mcpServers": {
|
|
142
|
+
"grag": {
|
|
143
|
+
"url": "http://127.0.0.1:8472/mcp",
|
|
144
|
+
"headers": { "x-grag-db": "project-a" }
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
The server is localhost-only by default, and db names are routing hints, not auth — resolution rejects absolute paths and `..`. Single-db stdio (`grag --db knowledge.lbdb mcp`) remains the simple default.
|
|
151
|
+
|
|
152
|
+
## Python API
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
from grag import GragConfig
|
|
156
|
+
from grag.service import GragService
|
|
157
|
+
from grag.core.types import SearchRequest
|
|
158
|
+
|
|
159
|
+
svc = GragService(GragConfig(db_path="knowledge.lbdb"))
|
|
160
|
+
res = svc.search_knowledge(SearchRequest(query="who owns the ingestion gateway?", hops=1))
|
|
161
|
+
print(res.context) # cited subgraph text, ready for a prompt
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Everything is also mirrored over REST: `POST /api/{query,search,context,ingest}`, `GET /api/{schema,graph/sample,health}`, `POST /api/{schema/define,nodes/upsert,edges/upsert}`.
|
|
165
|
+
|
|
166
|
+
## Retrieval: hybrid + polar-split vectors
|
|
167
|
+
|
|
168
|
+
1. Text properties get a native BM25 FTS index per searchable table.
|
|
169
|
+
2. With an embedder configured, embeddings are written with a **polar decomposition**: magnitude `r` in one float property, direction `u` quantized by a swappable codec. Codes only generate candidates; final scores are exact fp32 rescore + graph rerank, so recall loss is bounded and measurable.
|
|
170
|
+
3. Seeds (RRF-fused FTS+vector) expand k hops through the graph — structure compensates for aggressive quantization.
|
|
171
|
+
|
|
172
|
+
Codec ladder (`grag bench` reproduces these numbers on a synthetic 1500-doc corpus):
|
|
173
|
+
|
|
174
|
+
| codec | bytes/vec (dim 64) | recall@10 | note |
|
|
175
|
+
|---|---|---|---|
|
|
176
|
+
| `fp32` | 256 | 0.998 | baseline; native HNSW index |
|
|
177
|
+
| `int8` | 68 | 0.998 | 4x smaller, near-zero loss |
|
|
178
|
+
| `binary` | 8 | 0.476 | 32x, hamming scan + rescore |
|
|
179
|
+
| `polar` | 14 | 0.766 | experimental PolarQuant-style angular codes (sine-power-law bit allocation, training-free) |
|
|
180
|
+
|
|
181
|
+
Select with `GRAG_VECTOR_CODEC` / `GragConfig.vector_codec`. `polar` is opt-in; `int8` is the sweet spot today.
|
|
182
|
+
|
|
183
|
+
## Configuration
|
|
184
|
+
|
|
185
|
+
Env vars: `GRAG_DB_PATH`, `GRAG_BUFFER_POOL_MB` (default 256), `GRAG_VECTOR_CODEC`, `GRAG_TOKEN_BUDGET`, `GRAG_EMBED_PROVIDER` (`fastembed`|`remote`), `GRAG_EMBED_MODEL`, `GRAG_EMBED_DIM`, `GRAG_EMBED_BASE_URL`, `GRAG_EMBED_API_KEY_ENV`.
|
|
186
|
+
|
|
187
|
+
## Performance budget
|
|
188
|
+
|
|
189
|
+
Measured, not assumed — `tests/test_perf.py` guards cold start (< 2s), search latency, and RSS; `grag bench` reports recall + p50/p95 + RSS per codec. Design rules: no heavy deps in the default install, one process for API+UI, lazy embedder loading, default `LIMIT`s, hop caps, statement timeouts, token budgets everywhere.
|
|
190
|
+
|
|
191
|
+
## Storage conventions
|
|
192
|
+
|
|
193
|
+
- One `.lbdb` file per database. Properties starting with `_` are grag-internal.
|
|
194
|
+
- Provenance: `_source`, `_created_at` on every table created via `define_schema`.
|
|
195
|
+
- Vector columns (`embedding`, `_emb_r`, `_emb_code`, `_emb_model`) are added lazily by the retrieval layer.
|
|
196
|
+
- `_grag_tables` registry powers introspection and canonical `Label:key` node ids.
|
|
197
|
+
|
|
198
|
+
## Develop
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
python -m pytest tests/ # 160+ tests, ~10s
|
|
202
|
+
grag bench # codec recall/latency/RSS table
|
|
203
|
+
cd ui && npm run build # rebuilds the UI into src/grag/api/static/
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
See **[CONTRIBUTING.md](CONTRIBUTING.md)** for the branching model (Gitflow-lite:
|
|
207
|
+
`main` + `develop` + `feature`/`release`/`hotfix`), PR rules, and how releases are
|
|
208
|
+
cut and published to PyPI.
|
|
209
|
+
|
|
210
|
+
Known limits: embedded engine = single-writer; LadybugDB reserves a large *virtual* address space per open database (actual RSS stays within the buffer pool) — close `Engine`s you create; polar codec encode is Python-speed (fine at query time, slower at write time).
|
gragdb-0.1.0/README.md
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# grag
|
|
2
|
+
|
|
3
|
+
**LLM-first graph knowledgebase.** One embedded Cypher engine ([LadybugDB](https://ladybugdb.com), the Kuzu successor), one file per database, zero daemons — wrapped in the tool contract LLMs actually need: schema introspection that anchors text-to-Cypher, idempotent upserts with provenance, hybrid FTS/vector search, and token-budgeted subgraph context for grounded, low-hallucination answers.
|
|
4
|
+
|
|
5
|
+
*(**G**(raph)**RAG** — retrieval-augmented generation grounded in a graph.)*
|
|
6
|
+
|
|
7
|
+
Not an enterprise platform. `pip install`, point an MCP client at it, done.
|
|
8
|
+
|
|
9
|
+
## Why
|
|
10
|
+
|
|
11
|
+
LLM answers hallucinate when retrieval returns isolated chunks. grag stores knowledge as a **graph** — entities, documents, and their relationships — so retrieval returns a connected, cited subgraph an LLM can reason over. The LLM can also *build* the graph: `define_schema` + `upsert_nodes/edges` are first-class tools, so "turn these docs into a knowledge graph" is a normal conversation, not a pipeline project.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
**From PyPI** (ships the web UI):
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install gragdb
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Python 3.10–3.14; **3.13 recommended** (faster interpreter for the Python-side
|
|
22
|
+
packing/serialization paths, and 3.10 reaches end-of-life in October 2026).
|
|
23
|
+
|
|
24
|
+
**From source** (for development). Build the UI **first** — `pip install` needs the
|
|
25
|
+
built bundle at `src/grag/api/static` (the wheel's force-include; see `pyproject.toml`):
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
cd ui && npm ci && npm run build && cd .. # builds the UI into src/grag/api/static/
|
|
29
|
+
pip install -e . # core: engine, REST, MCP, FTS — no torch, no GPU stack
|
|
30
|
+
pip install -e ".[dev]" # tests
|
|
31
|
+
pip install -e ".[embed-local]" # optional: local embeddings (fastembed/ONNX, still no torch)
|
|
32
|
+
pip install -e ".[embed-remote]" # optional: OpenAI-compatible remote embeddings
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Without an embedder, everything works FTS-only (BM25 is native to the engine).
|
|
36
|
+
|
|
37
|
+
**Enabling semantic search:** install `embed-local`, then set `GRAG_EMBED_PROVIDER=fastembed`
|
|
38
|
+
when serving. This uses ONNX Runtime — **no PyTorch** — so grag stays light (~50-100MB,
|
|
39
|
+
model downloads once then works offline). Nodes are (re)embedded lazily on the next
|
|
40
|
+
search whenever their embedding is NULL. First query downloads the model + embeds all
|
|
41
|
+
nodes (seconds); steady state is ~300ms/query on CPU.
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install -e ".[embed-local]"
|
|
45
|
+
GRAG_EMBED_PROVIDER=fastembed grag --db knowledge.lbdb serve
|
|
46
|
+
# optional: GRAG_EMBED_MODEL=BAAI/bge-base-en-v1.5 GRAG_EMBED_DIM=768
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Quickstart
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# build the demo knowledgebase (fictional company handbook, entities + relations)
|
|
53
|
+
python examples/build_example.py
|
|
54
|
+
|
|
55
|
+
# serve REST + the graph UI at http://127.0.0.1:8471
|
|
56
|
+
# (note: start it from a normal terminal — servers launched inside an agent
|
|
57
|
+
# sandbox get torn down and can't be reached from your browser)
|
|
58
|
+
grag --db examples/knowledge.lbdb serve
|
|
59
|
+
|
|
60
|
+
# or answer 3 demo questions end-to-end in the terminal
|
|
61
|
+
python examples/demo_e2e.py
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The UI: force-graph explorer (click = inspect, double-click = expand neighbors), Cypher console (Ctrl+Enter, graph/table results), schema sidebar, and a search bar that shows the exact grounding text an LLM would receive.
|
|
65
|
+
|
|
66
|
+
## Use from an LLM harness (MCP)
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
grag --db knowledge.lbdb mcp
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Cursor / `.cursor/mcp.json`:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"mcpServers": {
|
|
77
|
+
"grag": {
|
|
78
|
+
"command": "grag",
|
|
79
|
+
"args": ["--db", "/absolute/path/knowledge.lbdb", "mcp"]
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Any MCP client gets these 7 tools:
|
|
86
|
+
|
|
87
|
+
| tool | purpose |
|
|
88
|
+
|---|---|
|
|
89
|
+
| `describe_schema` | prompt-shaped schema: tables, properties, row counts, sample keys. Call before writing Cypher — kills hallucinated labels. |
|
|
90
|
+
| `define_schema` | create node/rel tables (LLM designs the graph for a domain) |
|
|
91
|
+
| `upsert_nodes` / `upsert_edges` | idempotent MERGE writes; `_source` provenance automatic |
|
|
92
|
+
| `cypher_query` | read-only Cypher; errors come back with correction hints |
|
|
93
|
+
| `search_knowledge` | hybrid BM25 + vector seeds → k-hop expansion → cited, token-budgeted context |
|
|
94
|
+
| `get_context` | re-pack chosen node ids into a token budget |
|
|
95
|
+
|
|
96
|
+
Errors are returned as `ERROR: ... HINT: ...` tool output so the model self-corrects in-loop.
|
|
97
|
+
|
|
98
|
+
## Multiple projects / shared server
|
|
99
|
+
|
|
100
|
+
One `.lbdb` = one isolated universe — no shared entities, no cross-db queries. Per-project DBs is the default pattern; multi-db serving is opt-in via `--db-dir` (single-db is unchanged).
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
grag --db-dir ~/kb serve # one process serves every .lbdb in ~/kb
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Every `/api/*` endpoint accepts `?db=<name>` or an `x-grag-db: <name>` header (query param wins). `GET /api/dbs` returns `{"dbs": ["alpha","beta"], "default": "alpha"}` (`{"dbs": [], "default": null}` in single-db mode). Without a selector the server prefers the file matching `db_path`'s name, else a lone `.lbdb`, else 400 with a hint; unknown name → 404 listing available DBs.
|
|
107
|
+
|
|
108
|
+
For MCP, several IDE windows on one DB collide: stdio spawns a `grag mcp` process per client and LadybugDB allows only ONE process to write a given `.lbdb` ("Could not set lock"). One shared HTTP server avoids it — each window sends its project name via `x-grag-db`:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
grag --db-dir ~/kb mcp --transport streamable-http --host 127.0.0.1 --port 8472
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Cursor / `.cursor/mcp.json` (per window, one header per project):
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"mcpServers": {
|
|
119
|
+
"grag": {
|
|
120
|
+
"url": "http://127.0.0.1:8472/mcp",
|
|
121
|
+
"headers": { "x-grag-db": "project-a" }
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The server is localhost-only by default, and db names are routing hints, not auth — resolution rejects absolute paths and `..`. Single-db stdio (`grag --db knowledge.lbdb mcp`) remains the simple default.
|
|
128
|
+
|
|
129
|
+
## Python API
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from grag import GragConfig
|
|
133
|
+
from grag.service import GragService
|
|
134
|
+
from grag.core.types import SearchRequest
|
|
135
|
+
|
|
136
|
+
svc = GragService(GragConfig(db_path="knowledge.lbdb"))
|
|
137
|
+
res = svc.search_knowledge(SearchRequest(query="who owns the ingestion gateway?", hops=1))
|
|
138
|
+
print(res.context) # cited subgraph text, ready for a prompt
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Everything is also mirrored over REST: `POST /api/{query,search,context,ingest}`, `GET /api/{schema,graph/sample,health}`, `POST /api/{schema/define,nodes/upsert,edges/upsert}`.
|
|
142
|
+
|
|
143
|
+
## Retrieval: hybrid + polar-split vectors
|
|
144
|
+
|
|
145
|
+
1. Text properties get a native BM25 FTS index per searchable table.
|
|
146
|
+
2. With an embedder configured, embeddings are written with a **polar decomposition**: magnitude `r` in one float property, direction `u` quantized by a swappable codec. Codes only generate candidates; final scores are exact fp32 rescore + graph rerank, so recall loss is bounded and measurable.
|
|
147
|
+
3. Seeds (RRF-fused FTS+vector) expand k hops through the graph — structure compensates for aggressive quantization.
|
|
148
|
+
|
|
149
|
+
Codec ladder (`grag bench` reproduces these numbers on a synthetic 1500-doc corpus):
|
|
150
|
+
|
|
151
|
+
| codec | bytes/vec (dim 64) | recall@10 | note |
|
|
152
|
+
|---|---|---|---|
|
|
153
|
+
| `fp32` | 256 | 0.998 | baseline; native HNSW index |
|
|
154
|
+
| `int8` | 68 | 0.998 | 4x smaller, near-zero loss |
|
|
155
|
+
| `binary` | 8 | 0.476 | 32x, hamming scan + rescore |
|
|
156
|
+
| `polar` | 14 | 0.766 | experimental PolarQuant-style angular codes (sine-power-law bit allocation, training-free) |
|
|
157
|
+
|
|
158
|
+
Select with `GRAG_VECTOR_CODEC` / `GragConfig.vector_codec`. `polar` is opt-in; `int8` is the sweet spot today.
|
|
159
|
+
|
|
160
|
+
## Configuration
|
|
161
|
+
|
|
162
|
+
Env vars: `GRAG_DB_PATH`, `GRAG_BUFFER_POOL_MB` (default 256), `GRAG_VECTOR_CODEC`, `GRAG_TOKEN_BUDGET`, `GRAG_EMBED_PROVIDER` (`fastembed`|`remote`), `GRAG_EMBED_MODEL`, `GRAG_EMBED_DIM`, `GRAG_EMBED_BASE_URL`, `GRAG_EMBED_API_KEY_ENV`.
|
|
163
|
+
|
|
164
|
+
## Performance budget
|
|
165
|
+
|
|
166
|
+
Measured, not assumed — `tests/test_perf.py` guards cold start (< 2s), search latency, and RSS; `grag bench` reports recall + p50/p95 + RSS per codec. Design rules: no heavy deps in the default install, one process for API+UI, lazy embedder loading, default `LIMIT`s, hop caps, statement timeouts, token budgets everywhere.
|
|
167
|
+
|
|
168
|
+
## Storage conventions
|
|
169
|
+
|
|
170
|
+
- One `.lbdb` file per database. Properties starting with `_` are grag-internal.
|
|
171
|
+
- Provenance: `_source`, `_created_at` on every table created via `define_schema`.
|
|
172
|
+
- Vector columns (`embedding`, `_emb_r`, `_emb_code`, `_emb_model`) are added lazily by the retrieval layer.
|
|
173
|
+
- `_grag_tables` registry powers introspection and canonical `Label:key` node ids.
|
|
174
|
+
|
|
175
|
+
## Develop
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
python -m pytest tests/ # 160+ tests, ~10s
|
|
179
|
+
grag bench # codec recall/latency/RSS table
|
|
180
|
+
cd ui && npm run build # rebuilds the UI into src/grag/api/static/
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
See **[CONTRIBUTING.md](CONTRIBUTING.md)** for the branching model (Gitflow-lite:
|
|
184
|
+
`main` + `develop` + `feature`/`release`/`hotfix`), PR rules, and how releases are
|
|
185
|
+
cut and published to PyPI.
|
|
186
|
+
|
|
187
|
+
Known limits: embedded engine = single-writer; LadybugDB reserves a large *virtual* address space per open database (actual RSS stays within the buffer pool) — close `Engine`s you create; polar codec encode is Python-speed (fine at query time, slower at write time).
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "gragdb"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "LLM-first graph knowledgebase: embedded Cypher engine, MCP tool contract, GraphRAG retrieval"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10,<3.15"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
dependencies = [
|
|
13
|
+
"ladybug>=0.19",
|
|
14
|
+
"fastapi>=0.110",
|
|
15
|
+
"uvicorn>=0.29",
|
|
16
|
+
"mcp>=2.0",
|
|
17
|
+
"pydantic>=2.6",
|
|
18
|
+
"numpy>=1.26",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.optional-dependencies]
|
|
22
|
+
embed-local = ["fastembed>=0.4"]
|
|
23
|
+
embed-remote = ["httpx>=0.27"]
|
|
24
|
+
dev = ["pytest>=8.0", "httpx>=0.27", "pyyaml>=6.0"]
|
|
25
|
+
|
|
26
|
+
[project.scripts]
|
|
27
|
+
grag = "grag.cli:main"
|
|
28
|
+
|
|
29
|
+
[tool.hatch.build.targets.wheel]
|
|
30
|
+
packages = ["src/grag"]
|
|
31
|
+
|
|
32
|
+
# The built web UI lives in src/grag/api/static but is gitignored (it's a build
|
|
33
|
+
# artifact of ui/), so the default wheel omits it. Force-include it so a
|
|
34
|
+
# `pip install gragdb` ships the working UI. NOTE: force-include makes the dir
|
|
35
|
+
# REQUIRED at build time — so build the UI first (cd ui && npm ci && npm run
|
|
36
|
+
# build) before `pip install .` / `pip install -e .` / `python -m build`. CI and
|
|
37
|
+
# the publish workflow both do this; an editable install only *serves* the UI if
|
|
38
|
+
# it's been built.
|
|
39
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
40
|
+
"src/grag/api/static" = "grag/api/static"
|
|
41
|
+
|
|
42
|
+
[tool.hatch.build.targets.sdist]
|
|
43
|
+
# Don't let .gitignore exclude files from the sdist: src/grag/api/static is a
|
|
44
|
+
# gitignored *build artifact* that we DO ship when present. include (below)
|
|
45
|
+
# governs instead. The sdist lists it WITHOUT a trailing slash so it's an
|
|
46
|
+
# optional prefix (tolerated when absent); the wheel's force-include above is
|
|
47
|
+
# what *requires* it for distribution builds.
|
|
48
|
+
ignore-vcs = true
|
|
49
|
+
include = [
|
|
50
|
+
"src/grag",
|
|
51
|
+
"src/grag/api/static",
|
|
52
|
+
"ui",
|
|
53
|
+
"README.md",
|
|
54
|
+
"LICENSE",
|
|
55
|
+
"pyproject.toml",
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
[tool.pytest.ini_options]
|
|
59
|
+
testpaths = ["tests"]
|
|
60
|
+
addopts = "-q"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""grag REST API (FastAPI)."""
|