Skip to content

Decouple index record models from LanceModel / Vector #476

Closed

Description

@gloryfromca

The seven derived-index record models (everos.infra.persistence.index tables) subclass lancedb.pydantic.LanceModel and type their vector columns with lancedb.pydantic.Vector(dim). Milvus consumes them only through the backend-neutral IndexSchema (schema_for), so the dependency is one-directional, but the models are still named and typed by one backend's library.

Scope: replace LanceModel with a plain pydantic base and Vector(dim) with a neutral annotation; the LanceDB adapter then owns the pydantic→Arrow mapping (pydantic_to_schema special-cases lancedb's FixedSizeListMixin, so a custom mapper is required).

Deliberately left out of the (app_id, project_id, id) primary-key work: it touches all seven tables, six test files and the Arrow conversion, with no behaviour change. Tracked so it is not forgotten.

Activity

  1. gloryfromca commented on Oct 20, 2026

    @gloryfromca
    MemberAuthor

    #510 made the cost of this coupling concrete, so recording it here while it is fresh.

    It is not a storage-engine constraint. Both backends take the vector width as an ordinary runtime argument when the table is created — Milvus passes dim=field.dimension while building the collection schema, and a LanceDB table is created from an Arrow schema whose fixed_size_list size is just a number. Nothing underneath requires the width to be known early.

    What requires it is that the schema is declared as a Python class. vector: Vector(_DIM) is a type annotation, so it is evaluated when the class body runs — at import time, before anything has read a config file or parsed a command line. The width is written into the class's definition rather than handed over when a table is built.

    Making dim configurable therefore had to drag configuration-reading back to import time, which is where these came from:

    Added by #510 Exists only because the width is frozen at class creation
    _bootstrap_root_from_argv() in entrypoints/cli/main.py typer parses --root long after the record classes exist, so argv has to be pre-scanned
    infra/persistence/records/_dim.py a full Settings cannot be built at import time — a malformed everos.toml would break everos config show, the command used to repair it
    assert_record_dim_matches_settings() in infra/persistence/index/__init__.py two independent readers of the same value now exist and have to be cross-checked

    A review of #510 also found a defect that lives entirely in the first of those: with both a group-level and a command-level --root, the prescan and typer picked different ones. Fixed there, but the whole class of bug disappears with this issue.

    Verification for whoever picks this up: the change is supposed to be invisible, which is hard to demonstrate by testing behaviour. Dump every business table's to_arrow_schema() before the change and compare field by field afterwards — name, type and nullability for all seven tables. That comparison is the acceptance criterion; a behavioural test suite passing proves much less here.

    One thing worth folding in while the mapping is being rewritten: lancedb's _pydantic_to_field(name, field) returns pa.field(name, dt, nullable) with no metadata parameter, so a table created from a LanceModel cannot carry Arrow field metadata. That is why cascade rebuild has to write the embedding-model record explicitly after rebuilding, instead of the table carrying it from birth. Owning the mapping removes that special case too.

    Priority: low. No behaviour changes, nothing is broken today, and the workarounds above are small and tested. This is cleanup that pays off the next time something needs to be known before the record classes are created.

  2. added a commit that references this issue on Oct 30, 2026
    962c63e
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions