OriginChainDB
Vector

Combine dense and sparse matches

← Vector examples

The language tabs use earlier client interfaces. Check SDK versions and connection setup before running them.

Dense embeddings capture similarity. Sparse vectors hold selected features and weights. Hybrid search combines their ranked matches for the same record IDs.

Before you run it: Sparse indices and values must have equal lengths. The endpoint is refused for row-restricted users; review the access requirements below.

First write the dense embedding for sku-9281 in shop.products. The sparse write below uses the same ID. Dense means a full numeric array; sparse means only the non-zero positions and their weights. Use a consistent sparse vocabulary for documents and queries—the example positions do not generate themselves from text.

Combine dense and sparse evidence for one itemThe two vector entries need the same ID and a consistent sparse feature vocabulary.
  1. Store the dense embeddingWrite the full numeric vector under sku-9281.
  2. Store its sparse featuresUse the same ID with matching feature positions and weights.
  3. Search both representationsRequest candidates from the dense and sparse sides.
  4. Read the fused rankingScores combine ranks; they are not ordinary cosine scores.

Follow the example

  1. Write dense vectors.
  2. Write sparse vectors with the same IDs.
  3. Choose candidate counts and fusion settings.
  4. Run the hybrid query.

Write sparse vectors

curl -X POST "https://$OC_HOST/v1/tenants/$OC_TENANT/vector/shop.products/put_sparse" \
  -H "Authorization: Bearer $OC_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id":      "sku-9281",
    "indices": [17, 402, 5561],
    "values":  [0.82, 1.40, 0.31],
    "dim":     30000
  }'

Check the write result

201 Created with an empty body, the same as the dense put. The id is what ties the two sides together: write the sparse vector under the same id as the dense one and the fusion can see both.

Run the combined search

curl -X POST "https://$OC_HOST/v1/tenants/$OC_TENANT/vector/shop.products/topk_hybrid" \
  -H "Authorization: Bearer $OC_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "dense_query":          [0.0124, -0.0883, /* ... 768 floats ... */],
    "dense_dim":            768,
    "dense_metric":         "cosine",
    "sparse_query_indices": [17, 402],
    "sparse_query_values":  [0.9, 1.2],
    "sparse_dim":           30000,
    "k":                    10,
    "rrf_k":                60
  }'

Check the result

The result uses a fused ranking score, not a cosine distance.

[
  { "id": "sku-9281", "score": 0.0325 },
  { "id": "sku-1144", "score": 0.0161 },
  { "id": "sku-5520", "score": 0.0156 }
  /* ... up to k entries ... */
]

// A bare array - the same hit shape the single-mode topk routes return.

score here is the fused rank score, not a distance or a similarity. It is always positive and it compares two hits within one hybrid query - it does not compare to the cosine or L2 scores of an ordinary top-k.

Request options

FieldRequiredNotes
dense_queryyesThe dense embedding. Its length must equal dense_dim.
dense_dimyesDense vector width. Must match the table's dimension.
dense_metricno"cosine" (default), "dot", "l2" or "manhattan".
dense_modeno"fast" or "high_recall", tuning the dense leg only.
sparse_query_indicesyesNon-zero component positions. Need not be sorted - the server sorts, and sums the values of any duplicate index.
sparse_query_valuesyesThe values at those positions. Same length as the indices.
sparse_dimyesFull sparse width. Every index must be strictly below it.
kyesHow many fused hits to return.
rrf_knoFusion constant, default 60. Raising it flattens the contribution any single list makes; lowering it sharpens it.
candidatesnoHow many candidates to pull from each list before fusing. Defaults to the larger of 4k and 40, and is clamped to at least k. Larger means better recall and more work per query.
filternoMetadata equality filter, applied to both legs before fusion. Same shape as the filter on the single-mode routes.

Understand the behavior

Each leg produces a ranked list of candidates hits. Fusion then scores every id by summing 1 / (rrf_k + rank) over the lists it appears in, and returns the top k. Because the sum is over ranks and not over scores, the two legs need no calibration against each other - which is the reason to fuse this way rather than by adding raw similarities.

The consequence worth designing around: a document that places second in both lists beats a document that places first in one and is absent from the other. Appearing in both is the signal.

Sparse vectors go into their own inverted index, which walks only the posting lists for the terms your query actually names. A document with no term in common with the query has a zero dot product and no defined rank, so it never surfaces from the sparse leg - it can still reach the result through the dense one.

Important limits and checks

  • Comparing a hybrid score to a top-k score. They are different quantities. A hybrid score near 0.03 is not "worse" than a cosine score of 0.94; it is a rank sum. Compare hybrid scores only to other hybrid scores from the same rrf_k.
  • Writing the sparse and dense sides under different ids. The id is the join. Two ids means two documents, and fusion has nothing to combine.
  • Sending an index equal to dim. Indices are zero-based and must be strictly below the width - an out-of-bounds component is a 400, on the write and on the query.
  • Sending NaN or an infinity in values. Refused at the boundary with 400 rather than allowed to poison a score.
  • Mismatched array lengths. indices and values must be the same length, on both the write and the query side.
  • Calling it as a row-restricted user. The fused score encodes ranks assigned in lists that include rows a row-level policy would hide, so the route refuses for such a caller rather than serving a leak that looks fused.

Continue

Continue to find similar vector directions.

© 2026 Silicoyn Technologies Pvt Ltd

On this page