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.
Prerequisites
Section titled “Prerequisites”- 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 withNeo4jClient::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.
Example
Section titled “Example”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(())}How it works
Section titled “How it works”Neo4jClient::connect(uri, user, password)opens a connection.Neo4jClient::from_config(config)takes aneo4rs::Configfor other settings (database name, TLS, pool size), andclient.graphis the underlyingneo4rs::Graphfor 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 theembeddingproperty and cosine similarity; change them with.embedding_property(..)and.similarity_function(..).ndimsmust be the width of the model you query with:model.capabilities().ndims.get_index(model, index_name)returns aNeo4jVectorIndexfor an existing index. It reads the node label and embedding property from the index, and errors if the index doesn’t exist.insert_documentscreates 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 inembedded_text.top_n::<T>(req)queries the index for thesamplesnearest nodes and deserializes each node (without its embedding) intoT.result.idis Neo4j’s internal node id.top_n_idsreturns 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.
