RAG and GraphRAG on LlamaIndex with one database — SynapCores
We just published two LlamaIndex integration packages that turn SynapCores into a one-binary backend for both standard RAG and GraphRAG.
pip install llama-index llama-index-vector-stores-synapcores
pip install llama-index llama-index-graph-stores-synapcores
from llama_index.core import VectorStoreIndex
from llama_index.vector_stores.synapcores import SynapCoresVectorStore
vector_store = SynapCoresVectorStore(
uri="http://localhost:8080",
table_name="my_docs",
embedding_dim=1536,
)
index = VectorStoreIndex.from_documents(docs, storage_context=...)
response = index.as_query_engine().query("What's in this document?")
If you've shipped an agent on LlamaIndex you already know the math. The integration directory lists Pinecone, Weaviate, Qdrant, Milvus, pgvector, and a dozen more for embeddings. It lists Neo4j, NebulaGraph, FalkorDB, Neptune, and a few more for property graphs. The two lists don't overlap. To build a GraphRAG pipeline you stand up two clusters.
That's what we wanted to collapse.
What's in each package
llama-index-vector-stores-synapcores
SynapCoresVectorStore(BasePydanticVectorStore). Standard LlamaIndex vector store integration shape — add(nodes), delete(ref_doc_id), query(VectorStoreQuery), plus the optional delete_nodes / clear / get_nodes extensions. async_add / aquery / adelete wrappers via asyncio.to_thread so it works inside an event loop without blocking.
- HNSW vector index on a
VECTOR(N)column. Cosine, Euclidean, or dot-product distance. - Full LlamaIndex
MetadataFiltersgrammar —EQ,NE,GT/GTE/LT/LTE,IN,NIN,TEXT_MATCH,TEXT_MATCH_INSENSITIVE,CONTAINS,IS_EMPTY, plusAND/OR/NOTwith nested groups. - Upsert on the primary key — re-running
from_documents()doesn't duplicate. VectorStoreIndex.from_vector_store(vs)for picking up existing tables.
llama-index-graph-stores-synapcores
SynapCoresPropertyGraphStore(PropertyGraphStore). Implements all 8 required abstract methods plus the async surface:
upsert_nodes(LabelledNode...)— bothEntityNode(withnameandlabel) andChunkNode(withtextand embedding). Mapped to:Entityand:ChunkCypher labels with agraph_nameproperty tag so multiple logical graphs share one engine.upsert_relations(Relation...)— typed edges with property maps.get(properties, ids)andget_triplets(entity_names, relation_names, properties, ids)for filtered fetches.get_rel_map(graph_nodes, depth, limit, ignore_rels)— depth-bounded BFS expansion, the load-bearing primitive forPropertyGraphIndexretrieval.structured_query(cypher, param_map)— pass-through Cypher with named-param substitution.vector_query(VectorStoreQuery)— cosine overChunkNodeembeddings.
Both supports_structured_queries and supports_vector_queries are True. We declare a real Cypher surface, not just node CRUD.
Why one database matters here
The pitch you hear when someone is debating Pinecone vs. Weaviate is "we're faster on recall@10 at QPS X." That's not the conversation that matters when you start gluing vectors to a graph.
The conversation that matters is operational. You picked Pinecone because the LlamaIndex integration was first-class. You added Neo4j because the LlamaIndex Neo4jPropertyGraphStore integration was clean. Now you have:
- Two clusters to provision.
- Two SDKs to keep in lockstep.
- Two networks of secrets.
- Two backup playbooks.
- Two outage modes.
- Two sets of cloud bills.
- And a synchronization pipeline between them, because the chunks you embed are the same chunks that mention the entities in your graph.
SynapCores collapses all of that. One binary. One config file. One persistence directory. Embeddings live next to graph nodes — literally the same RocksDB column family — so the synchronization pipeline is "issue both writes in the same connection."
That's the actual delta. Not benchmarks. Not API ergonomics. Operational surface area.
What about SynapCores' own models?
A common question worth answering up front: do I need to install any models in SynapCores for this to work?
No. The integration writes embeddings that LlamaIndex computes — via OpenAI, HuggingFace, Cohere, or whatever you've configured in Settings.embed_model — into a VECTOR(N) column. The engine just stores them. There's no engine-side embedding model to download. No GGUF, no Ollama, no API key on the engine side, nothing.
We verified this end-to-end in our test pass: 48/48 tests against a stock synapcores/community:latest container with an empty models/ directory and zero pulls. If you want the engine's in-DB AI surface (SELECT GENERATE(), SELECT EMBED(), SELECT AGENT_RUN()) for other purposes — that needs a model warmed via synapcores pull or an [query.ai_service] block. But for the LlamaIndex integration alone, you can docker run and pip install and you're done.
A 5-minute end-to-end
Assuming you've pip install-ed both packages and have OpenAI keys exported:
from llama_index.core import (
Document,
StorageContext,
PropertyGraphIndex,
VectorStoreIndex,
)
from llama_index.vector_stores.synapcores import SynapCoresVectorStore
from llama_index.graph_stores.synapcores import SynapCoresPropertyGraphStore
docs = [
Document(text="Alice Smith founded Acme Corp in 2024 in San Francisco. "
"She previously worked at Globex as a senior engineer."),
Document(text="Bob Jones joined Acme Corp as CTO in 2025. Bob and Alice "
"collaborated on an open-source library called GraphLib in 2021."),
]
# Same engine, same config. Two LlamaIndex integrations on top.
vector_store = SynapCoresVectorStore(
uri="http://localhost:8080",
table_name="acme_chunks",
embedding_dim=1536,
)
graph_store = SynapCoresPropertyGraphStore(
uri="http://localhost:8080",
graph_name="acme_kb",
embedding_dim=1536,
)
# RAG index over text chunks
rag_index = VectorStoreIndex.from_documents(
docs,
storage_context=StorageContext.from_defaults(vector_store=vector_store),
)
print(rag_index.as_query_engine().query("When was Acme founded?"))
# GraphRAG index over extracted entities + relationships
graph_index = PropertyGraphIndex.from_documents(
docs,
property_graph_store=graph_store,
)
for node in graph_index.as_retriever().retrieve("Who founded Acme?"):
print(node.text)
Same http://localhost:8080. Same binary. Different LlamaIndex abstractions on top.
What's actually inside
The vector store uses an HNSW vector index, the LlamaIndex canonical metadata_dict_to_node round-trip for node reconstruction, and engine-native INSERT OR REPLACE for upsert semantics. The metadata filter translator handles the full operator + condition grammar — recursive, all 12 operators, all 3 conditions.
The graph store maps EntityNode → :Entity {id, name, label, properties_json, embedding?} and ChunkNode → :Chunk {id, text, label, properties_json, embedding}. Relations are typed by Relation.label and stored as [:RELATION {label, properties_json}]. The get_rel_map(depth=2) expansion is a small Python BFS using single-hop Cypher per level — portable across engine versions and lets us apply ignore_rels + limit deterministically. structured_query() is a pass-through with $name → positional-bind translation.
Async is asyncio.to_thread over the sync SDK for v0.1.0. Native httpx lands in v0.2.0.
What's next
This is v0.1.0. The roadmap from here:
- v0.2.0 — hybrid retriever (
SynapCoresHybridRetriever) that runs vector search, expands graph neighbours, re-ranks, and returns grounded chunks plus the path that justified them. This is the package that shows off the unified engine the most clearly. - v0.3.0 — native
httpxasync path. - v0.4.0 — submit the package as an official LlamaIndex integration listing (PR to their docs site).
Try it
docker run -p 8080:8080 -e AIDB_ACCEPT_LICENSE=1 synapcores/community:latest
pip install llama-index llama-index-vector-stores-synapcores llama-index-graph-stores-synapcores
Source, examples, and tests on GitHub: SynapCores/synapcores-llamaindex.
Bugs, GraphRAG patterns you want to see, or "what about my framework" requests: github.com/SynapCores/synapcores-llamaindex/issues.
— The SynapCores team