## ----setup, include = FALSE--------------------------------------------------- knitr::opts_chunk$set( collapse = TRUE, comment = "#>", eval = FALSE ) ## ----connect------------------------------------------------------------------ # library(AstraeaDB) # client <- astraea_connect() ## ----create-embeddings-------------------------------------------------------- # # Create nodes representing documents, each with a 4-dimensional embedding # doc1 <- client$create_node( # labels = c("Document"), # properties = list(title = "Graph Databases", topic = "databases"), # embedding = c(0.9, 0.1, 0.2, 0.05) # ) # # doc2 <- client$create_node( # labels = c("Document"), # properties = list(title = "Vector Search", topic = "search"), # embedding = c(0.1, 0.85, 0.3, 0.1) # ) # # doc3 <- client$create_node( # labels = c("Document"), # properties = list(title = "Neural Networks", topic = "ML"), # embedding = c(0.15, 0.8, 0.9, 0.7) # ) # # doc4 <- client$create_node( # labels = c("Document"), # properties = list(title = "Knowledge Graphs", topic = "databases"), # embedding = c(0.85, 0.2, 0.25, 0.1) # ) ## ----vector-search------------------------------------------------------------ # results <- client$vector_search( # query_vector = c(0.88, 0.15, 0.2, 0.08), # k = 3L # ) # # for (r in results) { # cat(sprintf("Node %d -- distance: %.4f\n", r$node_id, r$distance)) # } # #> Node 1 -- distance: 0.0048 # #> Node 4 -- distance: 0.0129 # #> Node 2 -- distance: 0.5877 ## ----hybrid-search------------------------------------------------------------ # # Find nodes that are both near doc1 in the graph AND similar to a query vector # results <- client$hybrid_search( # anchor = doc1, # query_vector = c(0.8, 0.3, 0.25, 0.1), # max_hops = 3L, # k = 5L, # alpha = 0.7 # ) # # for (r in results) { # cat(sprintf("Node %d -- combined score: %.4f\n", r$node_id, r$score)) # } ## ----semantic-neighbors------------------------------------------------------- # # First, connect the documents with edges # client$create_edge(doc1, doc2, "RELATED_TO") # client$create_edge(doc1, doc3, "RELATED_TO") # client$create_edge(doc1, doc4, "RELATED_TO") # # # Rank doc1's neighbors by similarity to a "search-oriented" concept # ranked <- client$semantic_neighbors( # node_id = doc1, # concept = c(0.1, 0.9, 0.3, 0.1), # direction = "outgoing", # k = 5L # ) # # for (r in ranked) { # cat(sprintf("Neighbor %d -- distance: %.4f\n", r$node_id, r$distance)) # } ## ----semantic-walk------------------------------------------------------------ # # Walk the graph following the "ML" concept # path <- client$semantic_walk( # start = doc1, # concept = c(0.1, 0.8, 0.9, 0.7), # max_hops = 4L # ) # # cat("Walk path:", paste(path, collapse = " -> "), "\n") ## ----temporal-setup----------------------------------------------------------- # # 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") # ) ## ----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") ## ----extract-subgraph--------------------------------------------------------- # # 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") ## ----graph-rag---------------------------------------------------------------- # 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") ## ----graph-rag-embedding------------------------------------------------------ # 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") ## ----algorithms-centrality---------------------------------------------------- # # 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() ## ----algorithms-community----------------------------------------------------- # # 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-dfs------------------------------------------------------------ # # Depth-first traversal (a companion to bfs()) # visited <- client$dfs(alice, max_depth = 3L) # # # ...as of a point in time # visited_past <- client$dfs_at(alice, max_depth = 3L, timestamp = 1672531200000) ## ----lookups------------------------------------------------------------------ # # 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-stats----------------------------------------------------------- # # 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-client------------------------------------------------------------- # # 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") ## ----arrow-batches------------------------------------------------------------ # ac$query_batches( # "MATCH (n) RETURN n", # callback = function(batch) { # cat("Received batch with", nrow(batch), "rows\n") # # Process each batch incrementally # } # ) # # ac$disconnect() ## ----arrow-convenience-------------------------------------------------------- # ac <- astraea_arrow_connect("grpc://localhost:7689") # # ... work ... # ac$disconnect() ## ----unified-client----------------------------------------------------------- # 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() ## ----auth-token--------------------------------------------------------------- # # 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() ## ----auth-connect------------------------------------------------------------- # client <- astraea_connect(auth_token = "my-secret-token") # # ... work ... # client$disconnect() ## ----auth-unified------------------------------------------------------------- # uc <- UnifiedClient$new( # host = "127.0.0.1", # port = 7687L, # auth_token = "my-secret-token" # ) # uc$connect() # # ... work ... # uc$disconnect() ## ----auth-error--------------------------------------------------------------- # tryCatch( # { # client <- astraea_connect(auth_token = "wrong-token") # client$create_node(c("Test"), list(x = 1)) # }, # error = function(e) { # message("Auth error: ", conditionMessage(e)) # } # ) ## ----full-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")