Skip to content

Base classes and contracts

Built and verified

The contract machinery (Contract, Vector, Contracted, ContractError) and serialisation are built and tested. They live in coniti.contracts; Contract, ContractError and Vector are also exported from coniti. The four abstract base classes below (Embedder, Structure, Strategy, MemoryBackend) are built too.

Four abstract base classes make up the pluggable surface.

ABC Public operations
Embedder embed, embed_many, dim
Structure insert, query, reset, neighbors (optional), stats, rebuild
Strategy admit (default: admit everything), retrieve
MemoryBackend put, get, delete, scan, count, get_meta, set_meta, clear

Instantiating a subclass that leaves an abstract method unimplemented fails.

Input and output contracts

Each operation takes an input base model and returns an output base model, all deriving from a common Contract: for example EmbedInput/EmbedOutput, InsertInput/InsertOutput, QueryInput/QueryOutput.

An implementation may extend a contract with a subclass, never replace it:

  • A declared model must be the base or a subclass. An unrelated type fails at class definition with ContractError.
  • A returned value that is not an instance of the declared model fails the call with ContractError.
  • The engine reads base fields only, and keeps the full output so other plug-ins can read the extras.
  • Fields added to an input model need defaults, because the engine builds inputs from base fields alone.

What is built

All names live in coniti.contracts.

Name Behaviour
Contract Pydantic base model for every input and output. Extra (undeclared) fields are forbidden.
Vector Field type for vectors, see below.
as_vector(value) The function behind Vector: returns a validated float64 array.
Contracted Mixin for base classes. A base sets _contracts = {"slot": BaseModel}; each subclass’s slot attribute must be that model or a subclass, checked at class definition. Contracted._check(slot, value) raises ContractError if value is not an instance of the slot’s model.
ContractError A TypeError raised on any contract violation.

Vectors

A Vector accepts a non-empty, finite, 1-D sequence or array of ints or floats. It rejects strings, booleans, complex numbers, arbitrary objects, NaN or infinity, empty input and multi-dimensional input. The value is stored as a float64 numpy array that is a copy of the input, so changing the original afterwards does not change the model. In JSON it is serialised as a list of floats.

import numpy as np
from coniti import Contract, ContractError, Vector
from coniti.contracts import Contracted

class Point(Contract):
    vector: Vector

p = Point(vector=[1, 2, 3])
p.vector                       # array([1., 2., 3.]), float64
p.model_dump(mode="json")      # {'vector': [1.0, 2.0, 3.0]}

# Point(vector=[])             # rejected: empty
# Point(vector=[1.0, np.nan])  # rejected: not finite
# Point(vector=[[1, 2]])       # rejected: not 1-D
# Point(vector="abc")          # rejected: not numeric

class Extended(Point):         # extending a contract is allowed
    note: str = ""

class Base(Contracted):
    _contracts = {"output_model": Point}
    output_model = Point

class Good(Base):
    output_model = Extended    # a subclass of Point: accepted

# class Bad(Base):
#     output_model = int       # raises ContractError at class definition

Good._check("output_model", Extended(vector=[1.0]))   # passes
# Good._check("output_model", Point(vector=[1.0]))    # ContractError: not an Extended

The built Embedder uses the same mechanism. A subclass of DenseEmbedding (the default output) with a second vector is accepted and its extras are kept (a sketch; _embed omitted):

from coniti import DenseEmbedding, Embedder, Vector


class ProjectedOutput(DenseEmbedding):
    source_vector: Vector          # a second, higher-dimensional embedding

class MyEmbedder(Embedder):
    output_model = ProjectedOutput # a subclass of the default output: accepted

No experiment knobs in the base

Thresholds, decay, temperature and similar factors never appear on a base class, base spec or base contract. Each plug-in declares its own in its params model.

Serialisation

Built and verified

Requirement 15, task 4D. Available on every contract model and spec.

Every contract model and every spec provides these methods, defined once on the shared bases (Contract, Spec), so every subclass, including plug-in extensions, inherits them:

Method Behaviour
to_dict() A plain dict of JSON-compatible values (vectors become lists of floats).
Model.from_dict(data) Builds and validates a model, exactly like construction.
to_json(indent=None) A JSON string.
Model.from_json(text) Parses a str or bytes and validates.
Model.json_schema() The JSON Schema of the model.

The serialised form follows common standards so other tools can read it without coniti:

  • JSON as defined by RFC 8259, encoded as UTF-8; NaN and infinity are never written or accepted.
  • Schemas as JSON Schema draft 2020-12 (produced by pydantic; the $schema key says so).
  • Vectors as arrays of numbers.
  • Timestamps stay numeric (seconds since the Unix epoch), so they round-trip exactly. Where a human-readable form is offered (the created_at of the memory file) it is RFC 3339 with an explicit UTC offset.
  • from_x(to_x(m)) gives a model equal to m; invalid input raises pydantic’s ValidationError (a ValueError).
from coniti import EmbeddingSpec, Note

note = Note(id="n1", text="planning the october product launch", timestamp=100.0)
text = note.to_json()
# '{"id": "n1", "text": "planning the october product launch", "timestamp": 100.0, "metadata": {}}'
Note.from_json(text) == note                  # True
Note.from_dict(note.to_dict()) == note        # True
Note.json_schema()["$schema"]                 # 'https://json-schema.org/draft/2020-12/schema'

spec = EmbeddingSpec(kind="local_hash", params={"dim": 64})
spec.to_json()                                # '{"kind": "local_hash", "params": {"dim": 64, "seed": 0}}'
EmbeddingSpec.from_json(spec.to_json()) == spec   # True, and the spec id is the same

Embeddings serialise as well. embedder.embed(text).to_dict() includes "space": {"embedding_id": ..., "dim": ...} next to the vector, and DenseEmbedding.from_json(e.to_json()) == e. The abstract EmbedOutput cannot be built from data (TypeError); use a concrete kind.

Runtime-only fields. A field that holds live objects can be declared runtime-only (excluded from dumps and hidden from the schema): it is left out of to_dict / to_json and of the JSON Schema, and is None after deserialising. The context of the strategy inputs is the example, see strategy. Code that needs such a field raises ContractError if it is missing.

Coverage. A JSON Schema can be produced for every contract model in coniti; a test walks all of them to prove it. These methods are on the base contract layer only and add no runtime dependency. The portable memory file built on them is described in memory.