Metadata-Version: 2.5
Name: llama-index-graph-stores-rushdb
Version: 0.1.0
Summary: LlamaIndex property graph store on RushDB: typed entity labels, relations, vector search and a schema derived from the data, on RushDB Cloud or self-hosted
Project-URL: Homepage, https://github.com/rush-db/llama-index-rushdb
Project-URL: Repository, https://github.com/rush-db/llama-index-rushdb
Project-URL: Issues, https://github.com/rush-db/llama-index-rushdb/issues
Project-URL: Documentation, https://github.com/rush-db/llama-index-rushdb#readme
Author-email: RushDB Team <hi@rushdb.com>
License: Apache-2.0
License-File: LICENSE
Keywords: graph store,graphrag,knowledge graph,llama-index,llamaindex,property graph,rushdb,self-hosted,vector search
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Database
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.10
Requires-Dist: llama-index-core<0.15,>=0.13
Requires-Dist: rushdb<4,>=3.4.0
Description-Content-Type: text/markdown

# llama-index-graph-stores-rushdb

A [LlamaIndex](https://www.llamaindex.ai/) property graph store on [RushDB](https://rushdb.com):
build a `PropertyGraphIndex` from your documents and keep the knowledge graph in RushDB, self-hosted
or on RushDB Cloud.

- **Real labels, no schema to declare.** Every extracted entity is stored under its own label
  (`PERSON`, `COMPANY`, …) and every relation as a typed relationship. Nothing is collapsed into a
  generic `Entity` node, and no table or label has to be created first.
- **Vectors in the same database.** Entity and chunk embeddings go to RushDB vector indexes, so
  `VectorContextRetriever` searches and traverses in one place, with metadata filters.
- **A schema derived from the data.** `get_schema_str()` returns what is actually stored: labels with
  counts, typed properties with value ranges, and relationship patterns such as
  `(PERSON)-[:WORKS_AT]->(COMPANY)`. That is what text-to-Cypher prompts are given.

## Install

```bash
pip install llama-index-graph-stores-rushdb
```

Requires Python 3.10+, `llama-index-core` 0.13 or 0.14, and RushDB 3.x: a self-hosted engine 3.0.10 or
later, or RushDB Cloud.

## Connect

Start a local engine ([other ways to run it](https://github.com/rush-db/rushdb#quick-start)):

```bash
docker run -p 3000:3000 -e RUSHDB_TOKEN=change-me rushdb/rushdb:3 --languages sql,cypher
```

Then point the store at it, or at RushDB Cloud, through the environment:

```bash
# Self-hosted engine
export RUSHDB_ENGINE_URL=http://127.0.0.1:3000 RUSHDB_ENGINE_TOKEN=change-me
# and RUSHDB_SCOPE=<tenant> on a multitenant engine

# or RushDB Cloud
export RUSHDB_API_KEY=...
```

or pass a client yourself:

```python
from rushdb import RushDB
from llama_index.graph_stores.rushdb import RushDBPropertyGraphStore

graph_store = RushDBPropertyGraphStore(
    RushDB("change-me", direct=True, engine_url="http://127.0.0.1:3000")
)
```

## Quick start

```python
from llama_index.core import PropertyGraphIndex, SimpleDirectoryReader
from llama_index.core.indices.property_graph import SchemaLLMPathExtractor
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.llms.openai import OpenAI
from llama_index.graph_stores.rushdb import RushDBPropertyGraphStore

graph_store = RushDBPropertyGraphStore()  # RUSHDB_* from the environment
llm = OpenAI(model="gpt-4o-mini")

index = PropertyGraphIndex.from_documents(
    SimpleDirectoryReader("./data").load_data(),
    property_graph_store=graph_store,
    kg_extractors=[
        SchemaLLMPathExtractor(
            llm=llm,
            possible_entities=["PERSON", "COMPANY", "PRODUCT"],
            possible_relations=["WORKS_AT", "FOUNDED", "MAKES"],
        )
    ],
    embed_model=OpenAIEmbedding(model_name="text-embedding-3-small"),
)

print(index.as_query_engine(llm=llm).query("Who founded the companies that make our products?"))
```

[`examples/quickstart.py`](examples/quickstart.py) is the same flow as a script.

## How the graph is stored

| LlamaIndex | RushDB |
|---|---|
| `EntityNode(name, label, properties)` | A record under `label`, with `id`, `name` and the properties |
| `ChunkNode` (a text chunk) | A record under `chunk_label` (`Chunk`), with `id`, `text` and the chunk's metadata |
| `Relation(label, source_id, target_id, properties)` | A relationship of type `label`, with the properties on the edge |
| An entity extracted from a chunk | A `MENTIONS` relationship from the chunk to the entity |
| An embedding | An external vector index per label: on `name` for entities, on `text` for chunks |

Every record the store writes carries `_kg_kind` (`entity` or `chunk`), so the graph can share a
RushDB project with other data. Nodes are matched by `id`: upserting a node updates it in place, and a
node that first appeared only as a relation endpoint (label `entity`) takes its real label when the
node itself arrives.

## Retrievers

| Retriever | Works | Notes |
|---|---|---|
| `VectorContextRetriever` | yes | Vector search over every entity label, then the paths around the hits |
| `LLMSynonymRetriever` | yes | Looks entities up by id, then the paths around them |
| `TextToCypherRetriever`, `CypherTemplateRetriever` | with `enable_cypher=True` | Needs Cypher on the engine (`--languages ...,cypher`); RushDB's Cypher support is a release candidate |

```python
from llama_index.core.indices.property_graph import VectorContextRetriever

retriever = index.as_retriever(
    sub_retrievers=[VectorContextRetriever(graph_store, embed_model=embed_model, path_depth=2)]
)
```

`vector_query` supports metadata filters with `==`, `!=`, `>`, `>=`, `<`, `<=`, `in`, `nin`,
`contains` (an element of a list), `text_match` (substring) and `is_empty`, combined with `and`, `or`
and `not`.

## Notes

- A node has one label. Upserting a node under a different label relabels it.
- A relation is created once per (source, type, target); upserting it again keeps the first
  properties, as the Neo4j store does.
- Objects in properties, and lists of objects, are stored as JSON text (RushDB would otherwise ingest a
  nested object as a related record). ISO 8601 date strings are stored as datetimes.
- Deleting a node removes its relationships; its neighbours stay.
- `persist()` does nothing: the graph is already in RushDB.

## Development

```bash
uv sync
scripts/start-engines.sh   # two local engines in Docker: single-tenant and multitenant
export RUSHDB_TEST_ENGINE_URL=http://127.0.0.1:3917 RUSHDB_TEST_ENGINE_TOKEN=test-token
export RUSHDB_TEST_MT_ENGINE_URL=http://127.0.0.1:3918 RUSHDB_TEST_MT_ENGINE_TOKEN=test-token
uv run pytest
uv run ruff check . && uv run ruff format --check . && uv run mypy llama_index
```

The integration tests compare the store with LlamaIndex's in-memory `SimplePropertyGraphStore` where
the two define the same behaviour, and run a `PropertyGraphIndex` end to end with a deterministic
extractor. CI runs them on both self-hosted engines, on Python 3.10 and 3.13 and on the lowest
supported dependency versions. Set `RUSHDB_TEST_API_KEY` to a RushDB Cloud project used only for
testing to run them there too: the tests delete the store's records in it.

## License

Apache-2.0
