Combine links with row features
The language tabs use earlier client interfaces. Check SDK versions and connection setup before running them.
GraphSAGE learns embeddings from relationships and numeric row features. Each row needs a feature array of the declared length.
Before you run it: max_pool and lstm are preview paths whose HTTP training does not train their weights. Missing or wrongly sized features fail validation.
First load the five-person social graph. It creates the exact social.users table and follows relation used below.
Use the sample dataset's feat array and feature_dim: 4. Features are numeric measurements you choose; all rows must use the same meaning and order for each position. The tiny sample dataset checks the training-to-query flow, not prediction quality.
- Load links and four featuresEvery node needs its numeric features in the same order.
- Train with neighboring featuresThe mean aggregator incorporates nearby nodes.
- Persist the embeddingsSave the learned vectors for later queries.
- Query similar nodesInspect the returned node IDs before using the matches.
Follow the example
- Prepare complete features and the relationship.
- Choose mean for the fully trained aggregator path.
- Train with persist: true.
- Query a node and inspect its matches.
Set up the relationship
Beyond the [[relations]] block, GraphSAGE needs a per-row feature vector: a column holding an array of numbers, the same width on every row. It can be a declared column or an undeclared JSON array field on the row. The width you pass as feature_dim must match that column exactly - a mismatch is a 400, never a silent pad or truncate. See schemas/reference#relations.
Train and save the embeddings
rel and feature_col are required. feature_dim must match the array width. num_layers is capped at 4 and neighbor_sample_size at 50. The default mean aggregator is fully trained. max_pool and lstm remain preview paths: HTTP training learns the node embeddings but leaves their aggregator weights at their initial values.
curl -X POST "https://$OC_HOST/v1/tenants/$OC_TENANT/graph/social.users/graphsage" \
-H "Authorization: Bearer $OC_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"rel": "follows",
"feature_col": "feat",
"feature_dim": 4,
"embedding_dim": 64,
"hidden_dim": 64,
"num_layers": 2,
"neighbor_sample_size": 10,
"aggregator": "mean",
"epochs": 5,
"seed": 42,
"persist": true
}'Check training results
{
"embeddings": {
"alice": [0.0311, -0.0142, 0.0207],
"bob": [0.0288, -0.0166, 0.0193]
},
"vocab_size": 11,
"training_pairs": 3960,
"final_loss": 0.5842,
"embedding_dim": 64,
"feature_dim": 4,
"persisted": true
}The same envelope node2vec returns, with two differences: the width field is called embedding_dim rather than dim, and feature_dim echoes the input width back so you can confirm the engine read the column you meant. Vectors are shortened above - each one is embedding_dim floats long.
Find similar nodes
Identical grammar to the node2vec query: the relation is a path segment, query names the node and k the number of neighbours, and metric accepts cosine, dot, l2 or manhattan, defaulting to cosine. The two persisted sets are stored separately, so a relation can carry both a node2vec and a GraphSAGE index at once.
curl -G "https://$OC_HOST/v1/tenants/$OC_TENANT/graph/social.users/graphsage/follows/topk" \
--data-urlencode "query=alice" \
--data-urlencode "k=5" \
--data-urlencode "metric=cosine" \
-H "Authorization: Bearer $OC_TOKEN"Read the matched nodes
[
{ "pk": "bob", "score": 0.97 },
{ "pk": "dave", "score": 0.88 },
{ "pk": "carol", "score": 0.81 }
]A flat array of { pk, score }, best first, with the query node excluded from its own results. Until a training call has run with persist: true this route answers 503 with a message pointing at the persistence step - a missing prerequisite rather than a failure.
Understand the behavior
- Each layer samples up to
neighbor_sample_sizeneighbours and averages their representations with the node's own. That averaging is themeanaggregator, andnum_layersdecides how far out the neighbourhood reaches. - Features are read per row from the feature column. Both a declared column and an undeclared JSON array field on the row work.
- The same seed, graph, features and configuration give byte-identical vectors.
persist: truewrites one blob per schema and relation, stored alongside but separately from the node2vec blob, so both indexes can exist for the same relation.- Response size is capped exactly as node2vec is:
vocab_sizetimesembedding_dimabove one million floats is a 400. - A persisted set that has gone bad can be inspected with a
healthcall on the same path and retrained in place with arebuildcall, which retrains from the rows and edges already in the store. No re-ingest is needed, and only the derived embedding blob is rewritten - no row is touched.
Important limits and checks
- Match feature_dim to the numeric array length. Missing or incorrectly sized features return 400.
- Choose mean for the fully trained path; max_pool and lstm do not train their own aggregator weights over HTTP.
- Train with persist: true before querying; otherwise the query route returns 503.
- Keep num_layers at or below 4 and neighbor_sample_size at or below 50; larger values return 400.
Expected result
Queries search the persisted embeddings from training.