Recommendation Engine (Rust)¶
This notebook demonstrates two approaches to recommendation: Collaborative Filtering (Graph) and Vector Similarity (Semantic) using Uni's native Rust API.
In [ ]:
Copied!
:dep uni-db = { path = "../../../crates/uni" }
:dep tokio = { version = "1", features = ["full"] }
:dep serde_json = "1"
:dep uni-db = { path = "../../../crates/uni" }
:dep tokio = { version = "1", features = ["full"] }
:dep serde_json = "1"
In [ ]:
Copied!
use uni::{Uni, DataType, IndexType, ScalarType, VectorMetric, VectorAlgo, VectorIndexCfg};
use std::collections::HashMap;
use serde_json::json;
// Helper macro to run async code in evcxr
macro_rules! run {
($e:expr) => {
tokio::runtime::Runtime::new().unwrap().block_on($e)
};
}
use uni::{Uni, DataType, IndexType, ScalarType, VectorMetric, VectorAlgo, VectorIndexCfg};
use std::collections::HashMap;
use serde_json::json;
// Helper macro to run async code in evcxr
macro_rules! run {
($e:expr) => {
tokio::runtime::Runtime::new().unwrap().block_on($e)
};
}
In [ ]:
Copied!
let db_path = "./recommendation_db";
// Clean up any existing database
if std::path::Path::new(db_path).exists() {
std::fs::remove_dir_all(db_path).unwrap();
}
let db = run!(Uni::open(db_path).build()).unwrap();
println!("Opened database at {}", db_path);
let db_path = "./recommendation_db";
// Clean up any existing database
if std::path::Path::new(db_path).exists() {
std::fs::remove_dir_all(db_path).unwrap();
}
let db = run!(Uni::open(db_path).build()).unwrap();
println!("Opened database at {}", db_path);
1. Schema¶
Users view and purchase products. Products have vector embeddings.
In [ ]:
Copied!
run!(async {
db.schema()
.label("User")
.property("name", DataType::String)
.label("Product")
.property("name", DataType::String)
.property("price", DataType::Float64)
.property("embedding", DataType::Vector { dimensions: 4 })
.index("embedding", IndexType::Vector(VectorIndexCfg {
algorithm: VectorAlgo::Flat,
metric: VectorMetric::Cosine,
}))
.edge_type("VIEWED", &["User"], &["Product"])
.edge_type("PURCHASED", &["User"], &["Product"])
.apply()
.await
}).unwrap();
println!("Schema created");
run!(async {
db.schema()
.label("User")
.property("name", DataType::String)
.label("Product")
.property("name", DataType::String)
.property("price", DataType::Float64)
.property("embedding", DataType::Vector { dimensions: 4 })
.index("embedding", IndexType::Vector(VectorIndexCfg {
algorithm: VectorAlgo::Flat,
metric: VectorMetric::Cosine,
}))
.edge_type("VIEWED", &["User"], &["Product"])
.edge_type("PURCHASED", &["User"], &["Product"])
.apply()
.await
}).unwrap();
println!("Schema created");
2. Ingest Data¶
In [ ]:
Copied!
// Product embeddings (simplified 4D vectors)
let p1_vec = vec![1.0, 0.0, 0.0, 0.0]; // Running Shoes
let p2_vec = vec![0.9, 0.1, 0.0, 0.0]; // Socks (similar to shoes)
let p3_vec = vec![0.0, 1.0, 0.0, 0.0]; // Shampoo (different)
let products = vec![
HashMap::from([
("name".to_string(), json!("Running Shoes")),
("price".to_string(), json!(100.0)),
("embedding".to_string(), json!(p1_vec)),
]),
HashMap::from([
("name".to_string(), json!("Socks")),
("price".to_string(), json!(10.0)),
("embedding".to_string(), json!(p2_vec)),
]),
HashMap::from([
("name".to_string(), json!("Shampoo")),
("price".to_string(), json!(5.0)),
("embedding".to_string(), json!(p3_vec)),
]),
];
let prod_vids = run!(db.bulk_insert_vertices("Product", products)).unwrap();
let (p1, p2, p3) = (prod_vids[0], prod_vids[1], prod_vids[2]);
// Users
let users = vec![
HashMap::from([("name".to_string(), json!("Alice"))]),
HashMap::from([("name".to_string(), json!("Bob"))]),
HashMap::from([("name".to_string(), json!("Charlie"))]),
];
let user_vids = run!(db.bulk_insert_vertices("User", users)).unwrap();
let (u1, u2, u3) = (user_vids[0], user_vids[1], user_vids[2]);
// Purchase history: Alice, Bob, Charlie all bought Shoes
run!(db.bulk_insert_edges("PURCHASED", vec![
(u1, p1, HashMap::new()),
(u2, p1, HashMap::new()),
(u3, p1, HashMap::new()),
])).unwrap();
// View history: Alice viewed Socks and Shampoo
run!(db.bulk_insert_edges("VIEWED", vec![
(u1, p2, HashMap::new()),
(u1, p3, HashMap::new()),
])).unwrap();
run!(db.flush()).unwrap();
println!("Data ingested");
// Product embeddings (simplified 4D vectors)
let p1_vec = vec![1.0, 0.0, 0.0, 0.0]; // Running Shoes
let p2_vec = vec![0.9, 0.1, 0.0, 0.0]; // Socks (similar to shoes)
let p3_vec = vec![0.0, 1.0, 0.0, 0.0]; // Shampoo (different)
let products = vec![
HashMap::from([
("name".to_string(), json!("Running Shoes")),
("price".to_string(), json!(100.0)),
("embedding".to_string(), json!(p1_vec)),
]),
HashMap::from([
("name".to_string(), json!("Socks")),
("price".to_string(), json!(10.0)),
("embedding".to_string(), json!(p2_vec)),
]),
HashMap::from([
("name".to_string(), json!("Shampoo")),
("price".to_string(), json!(5.0)),
("embedding".to_string(), json!(p3_vec)),
]),
];
let prod_vids = run!(db.bulk_insert_vertices("Product", products)).unwrap();
let (p1, p2, p3) = (prod_vids[0], prod_vids[1], prod_vids[2]);
// Users
let users = vec![
HashMap::from([("name".to_string(), json!("Alice"))]),
HashMap::from([("name".to_string(), json!("Bob"))]),
HashMap::from([("name".to_string(), json!("Charlie"))]),
];
let user_vids = run!(db.bulk_insert_vertices("User", users)).unwrap();
let (u1, u2, u3) = (user_vids[0], user_vids[1], user_vids[2]);
// Purchase history: Alice, Bob, Charlie all bought Shoes
run!(db.bulk_insert_edges("PURCHASED", vec![
(u1, p1, HashMap::new()),
(u2, p1, HashMap::new()),
(u3, p1, HashMap::new()),
])).unwrap();
// View history: Alice viewed Socks and Shampoo
run!(db.bulk_insert_edges("VIEWED", vec![
(u1, p2, HashMap::new()),
(u1, p3, HashMap::new()),
])).unwrap();
run!(db.flush()).unwrap();
println!("Data ingested");
3. Collaborative Filtering¶
Who else bought what Alice bought?
In [ ]:
Copied!
let query = r#"
MATCH (u1:User {name: 'Alice'})-[:PURCHASED]->(p:Product)<-[:PURCHASED]-(other:User)
WHERE other._vid <> u1._vid
RETURN count(DISTINCT other) as count
"#;
let results = run!(db.query(query)).unwrap();
println!("Users with similar purchase history: {:?}", results.rows[0]);
let query = r#"
MATCH (u1:User {name: 'Alice'})-[:PURCHASED]->(p:Product)<-[:PURCHASED]-(other:User)
WHERE other._vid <> u1._vid
RETURN count(DISTINCT other) as count
"#;
let results = run!(db.query(query)).unwrap();
println!("Users with similar purchase history: {:?}", results.rows[0]);
4. Vector-Based Recommendation¶
Find products semantically similar to what Alice viewed.
In [ ]:
Copied!
// Get embeddings of products Alice viewed
let query = r#"
MATCH (u:User {name: 'Alice'})-[:VIEWED]->(p:Product)
RETURN p.embedding as emb, p.name as name
"#;
let results = run!(db.query(query)).unwrap();
for row in &results.rows {
let viewed_name = &row.values[1]; // name column
println!("Finding products similar to {:?}...", viewed_name);
// Note: In a full implementation, you would use vector_similarity function
// For now, we show the structure
}
// Get embeddings of products Alice viewed
let query = r#"
MATCH (u:User {name: 'Alice'})-[:VIEWED]->(p:Product)
RETURN p.embedding as emb, p.name as name
"#;
let results = run!(db.query(query)).unwrap();
for row in &results.rows {
let viewed_name = &row.values[1]; // name column
println!("Finding products similar to {:?}...", viewed_name);
// Note: In a full implementation, you would use vector_similarity function
// For now, we show the structure
}