---
title: "Nearest vectors, filtered, then walked: one record, one call"
slug: hybrid-records-vector-filter-walk
date: 2026-10-12
author: Semurg
section: how-to
tags: [vector, graph, hybrid, search, how-to]
summary: One record in Semurg can be a vector, a few numeric attributes, a text and links to other records at once. One call finds the nearest vectors that pass your filters and walks out from them; another reads any node's links.
status: published
covers: [route:POST /v1/query, op:hybrid_ingest, op:hybrid_query, op:vector_top_k]
---
Retrieval rarely stops at "the nearest vectors". You want the nearest ones from the right year, that mention the right word, and then
what they are connected to. In most stacks that is a vector database, a filter in application code and a graph query, three systems
and two round trips. In Semurg it is one record and one call.

> Diagram hybrid-record: One hybrid record carries an id with a vector, up to seven numeric attribute slots, a text and links to other records, each link with a relation number.

This guide uses vectors you supply yourself, so it needs no embedding model on the node.

## A record with everything on it

A hybrid record has an id, a vector, up to seven numeric attribute slots, a text and links to other records, each link with a relation
number. Write a few at once:

```bash runnable kind=shell-node needs=token
export SEMURG_API_TOKEN=<your token>
curl -sS http://localhost:4000/v1/query \
  -H "Authorization: Bearer $SEMURG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"op":"hybrid_ingest","d":4,"records":[
        {"id": 2097153, "vector": [1.0, 0.0, 0.0, 0.0],
         "attrs": [{"slot": 0, "f16": 2016, "f24": 3}],
         "text": "deep learning for graphs", "links": [{"dst": 2097154, "rel": 1}]},
        {"id": 2097154, "vector": [0.9, 0.1, 0.0, 0.0],
         "attrs": [{"slot": 0, "f16": 2018, "f24": 1}],
         "text": "graphs on disk", "links": [{"dst": 2097155, "rel": 1}]},
        {"id": 2097155, "vector": [0.0, 1.0, 0.0, 0.0],
         "attrs": [{"slot": 0, "f16": 2012, "f24": 2}],
         "text": "a history of databases", "links": []}]}'
```

```expect
expect: three records written, with how many vector, attribute and link containers that took
assert: json:.records==3
```

## Nearest, filtered, then walked

Ask for the nearest records to a vector, keep only those whose text holds a word, and walk the links out from the hits:

> Diagram one-hybrid-call: One hybrid query takes a vector, finds the nearest records, keeps those that hold the word and pass the attribute range, and walks the links out from the hits, answering the hits, the seeds and what the walk touched.

```bash runnable kind=shell-node needs=token
export SEMURG_API_TOKEN=<your token>
curl -sS http://localhost:4000/v1/query \
  -H "Authorization: Bearer $SEMURG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"op":"hybrid_query","vector":[1.0,0.0,0.0,0.0],"k":2,"hops":1,"filter":[{"has_word":{"word":"graphs"}}]}'
```

```expect
expect: the nearest records whose text holds "graphs", each as a distance and an id, plus the nodes the walk visited
assert: json:(.hits|length)>=1 and ([.hits[][1]]|index(2097155))==null
```

The answer lists the hits as distance and id pairs, the seeds the walk started from, and how many nodes and links the walk touched.
The filters combine: an attribute range on a slot, for example `{"attr_range":{"slot":0,"field":16,"lo":2015,"hi":2019}}` for the
values written as `f16` above, a word in the text, or the presence of a link from a given record. A filter that cannot be completed is
refused with `409` rather than answered short, and a word filter over records that carry no text is refused rather than answered
empty.

## Read any node's links

The links a record carries are ordinary edges, and any node's edges can be read directly with `neighbour_ids`, out, in or both.

Known issue on the published release: `neighbour_ids` answers `absent` for the id of a hybrid record, because a hybrid record keeps its
parts at ids of their own and has no node at its own id, and the reader looked for one. The links are there: the walk in the query above
follows them. The fix is in the release train; until then, walk a hybrid record's links with `hybrid_query` and `hops`, as above.

## Plain vector search

When all you want is the nearest vectors, `vector_top_k` takes several query vectors at once as a base64 block of little-endian
single-precision floats, row by row, with the number of queries `q`, the width `d` and `k`. It is exact by default; an `ef` asks for the
indexed, approximate-reach answer instead, where the store has an index.

## What it does not do today

- These are your vectors. Turning text into vectors on the node needs its embedding model; a node that agrees to downloads installs it
  once the signed catalogue lists it, and this guide does not need it.
- Reading the links in toward a node (`direction` in) depends on the store having the reverse read built; where it does not, the
  answer says `could_not_judge` and names why, rather than returning a short list.

## When something is refused

- `422`: a malformed body, and the message names the field.
- `409`: a filter that could not be completed, or a word filter over records without text.
- `413`: too many ids or queries in one call.
- `401`: the token is missing or wrong.

## Next

- [Semurg as a vector database](/blog/semurg-as-a-vector-database): exact and indexed search on their own.
- [Connecting over REST](/blog/connect-over-rest): the token and every route.
