Jadpo is experimental and not ready for production. These docs follow the compiler's main branch as of 30 Oct 2026. What's built · Install 0.1.0
jadpo
Reference: AI

AI

The vector index

On this page

You don't write an index for a vector search. The compiler derives every index your queries need (How the derived indexes are chosen), and a vector is no different. A query with nearest on a field gets an HNSW index on it, with the operator class of the type's metric, in its migration and the schema report. HNSW is the kind of approximate index pgvector uses to find near vectors fast. The query's equality tests get a B-tree beside it, as any query's do:

sql
-- store: mainCREATE INDEX "help_article_body_meaning_idx" ON "help_article" USING hnsw ("body_meaning" vector_cosine_ops);CREATE INDEX "help_article_shown_publicly_idx" ON "help_article" ("shown_publicly");
  • HNSW only, with pgvector's defaults (m 16, ef_construction 64). pgvector's other kind, IVFFlat, isn't offered: it needs the rows to exist before it is built, and a migration builds it on an empty table.
  • Beside the B-tree, so the database can choose an exact scan when a filter matches few rows. An entity with a lifecycle gets a partial HNSW index, as its other indexes are (Lifecycle).

Tuning the index

Most apps never tune it. On a large table that is searched often, an indexes: entry with kind: IndexKind.hnsw declares the index on one vector field, with either or both of:

  • m:, the links each vector keeps, 2 to 100;
  • ef_construction:, the candidates weighed as each vector is linked, 4 to 1,000, and at least twice m.

More of either finds the nearest more reliably on a large table, but builds slower and takes more space. A query searching the field uses the declared index in place of the one it would derive:

example
use support/types { ArticleMeaning }/// An article on the help site, with its meaning once it is embedded.entity HelpArticle {    id: Uuid    body_meaning: ArticleMeaning?    indexes: {        by_meaning: {            fields: [body_meaning]            kind: IndexKind.hnsw            m: 24  // a million articles, searched often            ef_construction: 128        }    }}
sql
-- store: mainCREATE INDEX "help_article_by_meaning_idx" ON "help_article" USING hnsw ("body_meaning" vector_cosine_ops) WITH (m = 24, ef_construction = 128);
  • A declared HNSW index is on exactly one vector field of fixed length and at most 4,000 dimensions, with the operator class its type's metric gives (type.setting_value otherwise; over 4,000 its error names query.nearest_exact). It may stand without a query, as any declared index may (Indexes). A query searching the same field uses it, tuned or not, rather than deriving a second index on the column.
  • The bounds are pgvector's: m 2 to 100, ef_construction 4 to 1,000, and ef_construction at least twice m, counting pgvector's default for the one not written, since pgvector refuses to build the index otherwise. A value that isn't a whole number, or is out of bounds, is type.setting_value; m: or ef_construction: on an index without kind: IndexKind.hnsw is type.unknown_setting; either twice is data.duplicate_rule. Only what the entry writes goes in the migration's WITH (...), in the order written; changing it drops and rebuilds the index, as any index change does.

Vectors of more than 2,000 dimensions

pgvector indexes a vector of up to 2,000 dimensions, and a half-precision halfvec of up to 4,000. Where the provider allows it, you can stay under 2,000 by asking for fewer dimensions with embedding_dimensions:.

  • 2,001 to 4,000 dimensions, such as the 3,072 of OpenAI's large embedding model. Each item must be within a half-precision float's ±65,504, so the index takes every vector the type holds. An item past that breaks max (or min), at its place in JSON, and a list written out with items in range passes. (pgvector refuses to index a larger item, rounding it up to infinity from 65,520, so without the rule a write would fail on the index, a defect no caller could map.) The vector is stored at full precision as vector(n) and indexed through a half-precision expression, with the metric's halfvec_ operator class. Its query orders by the same expression, so PostgreSQL matches the index to it. The distance it reads back and its where: thresholds stay at full precision, as ai.distance gives them. The parameter is cast to vector(n) first, so the server infers vector for it, which is the binary format the runtime sends:

    sql
    CREATE INDEX "help_article_body_meaning_idx" ON "help_article" USING hnsw (("body_meaning"::halfvec(3072)) halfvec_cosine_ops);
    sql
      SELECT "id", "title", "body_meaning" <=> $1 AS "jadpo_distance"  FROM "help_article"  WHERE "body_meaning" IS NOT NULL  ORDER BY "body_meaning"::halfvec(3072) <=> $1::vector(3072)::halfvec(3072)  LIMIT 5
  • Over 4,000 dimensions, no index can hold the vector, so a nearest query on it reads every record. That is the error query.nearest_exact, which you can decline (Declining a judgement call). A type with fewer dimensions fixes it. A decline above the query's order:, inside the query, accepts the cost, for a table small enough to read in full. Declined, the query measures every record, orders by the distance and id in one statement, and limits, so ties at the limit are broken by id as everywhere else. It sets no search settings and derives no index, so it doesn't need pgvector 0.8.0.

Markdown for agents: /docs/vector-index.md.Built from Jadpo main at a707efb.