Skip to content

Neo4j

rig::neo4j (feature neo4j) runs Rig’s vector search on Neo4j’s native vector indexes. Use it when your embeddings live on the nodes of a graph and you want similarity search without moving vectors into a separate store.

  • The Neo4j GenAI plugin. Neo4j Aura enables it by default; self-managed instances need it installed.
  • A vector index over the nodes you search. Create it with Cypher (CREATE VECTOR INDEX ...) or with Neo4jClient::create_vector_index, shown below. Neo4j builds new indexes in the background, so one may not be queryable immediately.
[dependencies]
rig = { version = "0.44.0", features = ["neo4j"] }
tokio = { version = "1", features = ["full"] }

Add neo4rs as well if you want to run your own Cypher through client.graph or build a connection with Neo4jClient::from_config.

Connect, create an index, insert embedded documents, and search:

use rig::neo4j::{Neo4jClient, vector_index::IndexConfig};
use rig::prelude::*;
use serde::{Deserialize, Serialize};
use rig::providers::openai::{self, OpenAI};
use rig::vector_store::InsertDocuments;
#[derive(Embed, Clone, Serialize, Deserialize, Debug)]
struct Word {
id: String,
#[embed]
definition: String,
}
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let model = OpenAI::from_env()?.embedding(openai::TEXT_EMBEDDING_3_SMALL, None);
let client = Neo4jClient::connect("neo4j://localhost:7687", "neo4j", "password").await?;
// A vector index named "words" over the `embedding` property of `Word` nodes,
// as wide as the model's vectors.
client
.create_vector_index(IndexConfig::new("words"), "Word", model.capabilities().ndims)
.await?;
// Reuse the model that produced (or will produce) the stored embeddings.
let index = client.get_index(model.clone(), "words").await?;
let embeddings = EmbeddingsBuilder::new(model)
.document(Word { id: "doc0".into(), definition: "A flurbo is a green alien.".into() })?
.document(Word { id: "doc1".into(), definition: "A glarb-glarb is an ancient farming tool.".into() })?
.build()
.await?;
index.insert_documents(embeddings).await?;
let req = VectorSearchRequest::builder()
.query("What is a glarb-glarb?")
.samples(1)
.build();
for result in index.top_n::<Word>(req).await? {
println!("{:.3} {} {}", result.score, result.id, result.document.definition);
}
Ok(())
}
  • Neo4jClient::connect(uri, user, password) opens a connection. Neo4jClient::from_config(config) takes a neo4rs::Config for other settings (database name, TLS, pool size), and client.graph is the underlying neo4rs::Graph for your own Cypher.
  • create_vector_index(config, node_label, ndims) creates the index if it doesn’t exist and waits (best effort) for it to come online. IndexConfig::new(name) defaults to the embedding property and cosine similarity; change them with .embedding_property(..) and .similarity_function(..). ndims must be the width of the model you query with: model.capabilities().ndims.
  • get_index(model, index_name) returns a Neo4jVectorIndex for an existing index. It reads the node label and embedding property from the index, and errors if the index doesn’t exist.
  • insert_documents creates one node per embedding under the index’s label, with the document’s fields flattened onto the node, the vector in the embedding property, and the embedded text in embedded_text.
  • top_n::<T>(req) queries the index for the samples nearest nodes and deserializes each node (without its embedding) into T. result.id is Neo4j’s internal node id. top_n_ids returns only ids and scores.

Because the vectors are ordinary node properties, you can combine similarity search with graph traversals in your own Cypher. The rig-neo4j examples include a movie-recommendation graph that adds embeddings to existing nodes and queries them.