OriginChainDB
Graph

Combine links with row features

← Graph examples

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.

Combine node features with neighborhood structureUse the documented mean aggregator and consistent feature order for this example.
  1. Load links and four featuresEvery node needs its numeric features in the same order.
  2. Train with neighboring featuresThe mean aggregator incorporates nearby nodes.
  3. Persist the embeddingsSave the learned vectors for later queries.
  4. Query similar nodesInspect the returned node IDs before using the matches.

Follow the example

  1. Prepare complete features and the relationship.
  2. Choose mean for the fully trained aggregator path.
  3. Train with persist: true.
  4. 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_size neighbours and averages their representations with the node's own. That averaging is the mean aggregator, and num_layers decides 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: true writes 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_size times embedding_dim above one million floats is a 400.
  • A persisted set that has gone bad can be inspected with a health call on the same path and retrained in place with a rebuild call, 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.

Continue

Continue to find nodes with similar graph roles.

© 2026 Silicoyn Technologies Pvt Ltd

On this page