The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

Advanced Features: Vector Search, Temporal Queries, and GraphRAG

This vignette covers the advanced capabilities of the AstraeaDB R package: vector similarity search, hybrid and semantic search, temporal (time-travel) queries, GraphRAG for LLM integration, Apache Arrow Flight transport, and authentication.

All examples assume you have a running AstraeaDB server and a connected client:

library(AstraeaDB)
client <- astraea_connect()

Temporal Queries (Time-Travel)

AstraeaDB edges can have temporal validity windows. When an edge has valid_from and/or valid_to set (milliseconds since the Unix epoch), it is only visible in temporal queries whose timestamp falls within the window.

Setting Up Temporal Edges

# Create a social network with temporal edges
alice <- client$create_node(c("Person"), list(name = "Alice"))
bob   <- client$create_node(c("Person"), list(name = "Bob"))
carol <- client$create_node(c("Person"), list(name = "Carol"))
dave  <- client$create_node(c("Person"), list(name = "Dave"))

# Helper: convert date string to epoch milliseconds
to_epoch_ms <- function(date_str) {
  as.numeric(as.POSIXct(date_str, tz = "UTC")) * 1000
}

# Alice knew Bob from 2020 to 2022
client$create_edge(
  source     = alice,
  target     = bob,
  edge_type  = "FRIENDS",
  valid_from = to_epoch_ms("2020-01-01"),
  valid_to   = to_epoch_ms("2022-01-01")
)

# Alice has known Carol since 2021 (no end date -- still active)
client$create_edge(
  source     = alice,
  target     = carol,
  edge_type  = "FRIENDS",
  valid_from = to_epoch_ms("2021-06-01")
)

# Alice has known Dave since 2023
client$create_edge(
  source     = alice,
  target     = dave,
  edge_type  = "FRIENDS",
  valid_from = to_epoch_ms("2023-01-01")
)

Querying at a Point in Time

neighbors_at

# Who was Alice friends with on July 1, 2021?
mid_2021 <- to_epoch_ms("2021-07-01")
friends_2021 <- client$neighbors_at(alice, "outgoing", mid_2021)

# Result includes Bob and Carol, but not Dave (not yet friends)
for (f in friends_2021) {
  node <- client$get_node(f$node_id)
  cat(node$properties$name, "\n")
}
#> Bob
#> Carol

bfs_at

# BFS at a point in time
bfs_2021 <- client$bfs_at(alice, max_depth = 2L, timestamp = mid_2021)

for (entry in bfs_2021) {
  cat(sprintf("Node %d at depth %d\n", entry$node_id, entry$depth))
}

shortest_path_at

# Shortest path at a specific time
early_2024 <- to_epoch_ms("2024-01-15")

sp <- client$shortest_path_at(
  from_node = alice,
  to_node   = dave,
  timestamp = early_2024,
  weighted  = FALSE
)

cat("Path:", paste(sp$path, collapse = " -> "), "\n")
cat("Hops:", sp$length, "\n")

Use Case: Evolving Relationships

Temporal queries allow you to answer questions such as:

By sweeping a timestamp across a range, you can reconstruct the evolution of a graph.

GraphRAG

GraphRAG integrates graph data with large language models. AstraeaDB provides two methods:

  1. extract_subgraph() – Extracts a subgraph centered on a node and linearizes it to text.
  2. graph_rag() – Full pipeline that extracts a subgraph, linearizes it, and sends the context and a question to a language model.

Subgraph Extraction

# Extract a 2-hop subgraph around Alice, linearized as structured text
sg <- client$extract_subgraph(
  center    = alice,
  hops      = 2L,
  max_nodes = 20L,
  format    = "structured"
)

cat("Nodes:", sg$node_count, "\n")
cat("Edges:", sg$edge_count, "\n")
cat("\n", sg$text, "\n")

The format parameter controls how the subgraph is serialized:

Format Description
"structured" Indented, human-readable summary of nodes and edges.
"prose" Natural-language narrative describing the subgraph.
"triples" List of (subject, predicate, object) triples.
"json" Machine-readable JSON representation.

Full GraphRAG Pipeline

graph_rag() sends the extracted subgraph context along with your question to the language model configured on the server:

answer <- client$graph_rag(
  question  = "Who are Alice's current friends and what do they work on?",
  anchor    = alice,
  hops      = 2L,
  max_nodes = 30L,
  format    = "prose"
)

cat(answer$answer, "\n")

You can also provide a question_embedding instead of (or in addition to) an anchor node. This lets the server find the most relevant anchor automatically via vector search:

answer <- client$graph_rag(
  question            = "What research topics are related to graph databases?",
  question_embedding  = c(0.9, 0.1, 0.2, 0.05),
  hops                = 3L,
  max_nodes           = 50L,
  format              = "structured"
)

cat(answer$answer, "\n")

Graph Algorithms

AstraeaDB computes classic graph-analytics algorithms server-side and returns the results over the wire. Pass nodes = to any of them to restrict the computation to a subset of node IDs; the default is the whole graph.

Centrality and Ranking

# PageRank
scores <- client$run_pagerank(damping = 0.85, max_iterations = 100L)

# Degree and betweenness centrality
deg <- client$run_degree_centrality(direction = "both")
btw <- client$run_betweenness_centrality()

Each returns a named list mapping the node ID (as a character key) to its score.

Community Detection and Components

# Louvain community detection
louvain <- client$run_louvain()
cat("Communities found:", louvain$num_communities, "\n")

# Connected components (weakly connected by default; strong = TRUE for SCCs)
cc <- client$run_connected_components(strong = FALSE)
cat("Number of components:", cc$count, "\n")

Traversal, Lookups, and Statistics

Finding and Bulk-Deleting by Label

# All node IDs carrying a label
people <- client$find_by_label("Person")

# All edges of a given type, each as {edge_id, source, target}
knows_edges <- client$find_edge_by_type("KNOWS")

# Bulk-delete every node with a label (and its edges); returns the count
removed <- client$delete_by_label("Temporary")

Subgraph Export and Statistics

# Raw subgraph (nodes + edges) around a center node, for visualization
sg <- client$get_subgraph(alice, hops = 2L, max_nodes = 100L)

# Graph-wide statistics
stats <- client$graph_stats()
cat("Nodes:", stats$total_nodes, " Edges:", stats$total_edges, "\n")

Arrow Flight Transport

For large result sets and analytical workloads, the Arrow Flight transport provides significantly better performance than JSON/TCP. Arrow Flight uses Apache Arrow’s columnar format for zero-copy data exchange between the server and R.

When to Use Arrow Flight

For small CRUD operations (create a node, get an edge), JSON/TCP is perfectly adequate and has no extra dependencies.

ArrowClient (Direct Use)

The ArrowClient class communicates directly with the Arrow Flight endpoint (default port 7689). It requires the arrow package.

# install.packages("arrow")  # if not already installed
library(AstraeaDB)

ac <- ArrowClient$new("grpc://localhost:7689")
ac$connect()

# Execute a GQL query -- returns an Arrow Table
table <- ac$query("MATCH (p:Person) RETURN p.name, p.age")

# Convert to data.frame
df <- as.data.frame(table)

# Or use the convenience method
df <- ac$query_df("MATCH (p:Person) RETURN p.name, p.age")

For very large result sets, stream the results in batches to control memory usage:

ac$query_batches(
  "MATCH (n) RETURN n",
  callback = function(batch) {
    cat("Received batch with", nrow(batch), "rows\n")
    # Process each batch incrementally
  }
)

ac$disconnect()

You can also use the convenience wrapper:

ac <- astraea_arrow_connect("grpc://localhost:7689")
# ... work ...
ac$disconnect()

UnifiedClient (Automatic Transport Selection)

The UnifiedClient is the recommended choice when you want the best of both worlds. It delegates CRUD operations to the JSON/TCP client and routes GQL queries through Arrow Flight when the arrow package is installed:

uc <- UnifiedClient$new(
  host       = "127.0.0.1",
  port       = 7687L,
  flight_uri = "grpc://localhost:7689"
)
uc$connect()

# Check which transports are active
uc$is_arrow_enabled()
#> [1] TRUE

# CRUD operations go through JSON/TCP
node_id <- uc$create_node(c("Person"), list(name = "Grace", age = 42))

# Queries go through Arrow Flight (or fall back to JSON/TCP)
df <- uc$query_df("MATCH (p:Person) RETURN p.name, p.age")

# All other operations are available as usual
uc$neighbors(node_id, direction = "outgoing")
uc$vector_search(c(0.5, 0.5, 0.5, 0.5), k = 3L)

uc$disconnect()

If the arrow package is not installed, or if the Arrow Flight connection fails, the UnifiedClient silently falls back to JSON/TCP for everything. A message is printed when fallback occurs.

Authentication

AstraeaDB supports token-based authentication with server-side role-based access control (RBAC). Three roles are available:

Role Permissions
Reader Read-only: get nodes/edges, traversals, queries.
Writer Read and write: CRUD, batch, import/export.
Admin Full access: all operations plus server management.

Using an Auth Token

Pass the auth_token parameter when creating any client. The token is automatically attached to every request:

# AstraeaClient with authentication
client <- AstraeaClient$new(
  host       = "127.0.0.1",
  port       = 7687L,
  auth_token = "my-secret-token"
)
client$connect()

# All operations now carry the token
client$ping()
client$create_node(c("Person"), list(name = "Secured"))
client$disconnect()

The convenience function also accepts a token:

client <- astraea_connect(auth_token = "my-secret-token")
# ... work ...
client$disconnect()

The UnifiedClient passes the token to the JSON/TCP client:

uc <- UnifiedClient$new(
  host       = "127.0.0.1",
  port       = 7687L,
  auth_token = "my-secret-token"
)
uc$connect()
# ... work ...
uc$disconnect()

Handling Authentication Errors

If the token is missing, invalid, or the user’s role lacks the required permissions, the server returns an error. Handle it with tryCatch():

tryCatch(
  {
    client <- astraea_connect(auth_token = "wrong-token")
    client$create_node(c("Test"), list(x = 1))
  },
  error = function(e) {
    message("Auth error: ", conditionMessage(e))
  }
)

Putting It All Together

The following example builds a small knowledge graph with embeddings and temporal edges, then demonstrates vector search, hybrid search, temporal queries, and GraphRAG in a single workflow:

library(AstraeaDB)
client <- astraea_connect()
on.exit(client$disconnect(), add = TRUE)

# --- Build the graph ---
ml   <- client$create_node(c("Topic"), list(name = "Machine Learning"),
                            embedding = c(0.1, 0.8, 0.9, 0.7))
nlp  <- client$create_node(c("Topic"), list(name = "NLP"),
                            embedding = c(0.2, 0.9, 0.7, 0.6))
kg   <- client$create_node(c("Topic"), list(name = "Knowledge Graphs"),
                            embedding = c(0.85, 0.2, 0.25, 0.1))
rag  <- client$create_node(c("Topic"), list(name = "RAG"),
                            embedding = c(0.6, 0.7, 0.5, 0.4))

to_ms <- function(d) as.numeric(as.POSIXct(d, tz = "UTC")) * 1000

client$create_edge(ml,  nlp, "RELATED_TO", weight = 0.9,
                   valid_from = to_ms("2018-01-01"))
client$create_edge(nlp, rag, "ENABLES",    weight = 0.8,
                   valid_from = to_ms("2022-01-01"))
client$create_edge(kg,  rag, "ENABLES",    weight = 0.85,
                   valid_from = to_ms("2020-01-01"))
client$create_edge(ml,  kg,  "RELATED_TO", weight = 0.7,
                   valid_from = to_ms("2015-01-01"))

# --- Vector search ---
cat("== Vector Search ==\n")
vs <- client$vector_search(c(0.15, 0.85, 0.8, 0.65), k = 2L)
for (r in vs) {
  cat(sprintf("  Node %d (dist %.4f)\n", r$node_id, r$distance))
}

# --- Hybrid search ---
cat("\n== Hybrid Search ==\n")
hs <- client$hybrid_search(
  anchor = ml, query_vector = c(0.6, 0.7, 0.5, 0.4),
  max_hops = 2L, k = 3L, alpha = 0.5
)
for (r in hs) {
  cat(sprintf("  Node %d\n", r$node_id))
}

# --- Temporal query ---
cat("\n== Temporal Query (2019) ==\n")
nbrs_2019 <- client$neighbors_at(ml, "outgoing", to_ms("2019-06-01"))
for (n in nbrs_2019) {
  node <- client$get_node(n$node_id)
  cat(sprintf("  %s\n", node$properties$name))
}
# Only "NLP" and "Knowledge Graphs" -- RAG edge did not exist in 2019

# --- GraphRAG ---
cat("\n== GraphRAG ==\n")
answer <- client$graph_rag(
  question  = "How are ML and RAG connected?",
  anchor    = ml,
  hops      = 2L,
  max_nodes = 20L,
  format    = "prose"
)
cat(answer$answer, "\n")

Summary

This vignette demonstrated the advanced features of the AstraeaDB R package:

For basic CRUD operations, traversals, GQL queries, and data-frame helpers, see vignette("getting-started"). For an overview of the package and data model, see vignette("introduction").

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.