Specs and ids¶
Specs and registry: built and verified. Implementations: built.
Specs and the registry are built and tested. The spec classes, Spec,
Params and register are exported from coniti; the rest (load_spec,
spec_class_for, create, build, the registry itself) live in the
submodules coniti.specs and coniti.registry. The base
classes that registered implementations must subclass (Embedder,
Structure, Strategy, MemoryBackend), the Container and the built-in
kinds (in_memory, local_hash, random_projection, flat,
segment_tree, default) are built. Examples below register their own kinds.
A spec is metadata that selects and configures an implementation. Four
exist: EmbeddingSpec, StructureSpec, StrategySpec and MemorySpec, all
subclasses of Spec in coniti.specs. Each is frozen and has only two fields:
| Field | Meaning |
|---|---|
kind |
A registered name, or a class path "package.module:ClassName" |
params |
Validated against the params model for that kind; defaults filled in; unknown fields rejected |
spec_class_for(category) returns the spec class for "embedding",
"structure", "strategy" or "memory".
Kinds: registered names and class paths¶
A kind is one of:
- A registered name. Registered with a params model; names may not contain
:. - A class path such as
"my_pkg.my_module:MyClass". The class is imported on demand, with no registration. It must subclass the category’s base class, and itsparams_modelattribute (defaultParams) validates the params.
Class paths run code when imported, so treat configuration files that contain them as trusted input.
Params¶
Params are normalised against the implementation’s params model (a subclass of
coniti.registry.Params, which is frozen and forbids unknown fields). Defaults
are filled in, and unknown fields are rejected. After construction params are
deep-frozen: mappings are read-only and lists become tuples, so a spec cannot
change after its id is computed. NaN and infinity anywhere in params are
rejected, because they cannot be represented faithfully in JSON.
Stable ids¶
A spec’s id (for example emb-1a2b3c4d5e6f) is <prefix>-<12 hex digits>,
a hash of its canonical content: category, kind and normalised params. The
prefixes are emb, stc, stg and mem. Two specs with the same content
have the same id, regardless of key order or whether defaults are written out.
Results from experiments can therefore be keyed by id.
from coniti import Embedder, EmbeddingSpec, EmbedOutput, Params, register
from coniti.embedding import EmbedInput
class HashParams(Params):
dim: int = 64
seed: int = 0
@register("embedding", "demo_hash", HashParams)
class DemoHash(Embedder):
params_model = HashParams
@property
def dim(self) -> int:
return 64
def _embed(self, inp: EmbedInput) -> EmbedOutput:
raise NotImplementedError # a spec-id demo; see the plugins guide for a real one
a = EmbeddingSpec(kind="demo_hash", params={"dim": 64})
b = EmbeddingSpec(kind="demo_hash", params={"seed": 0, "dim": 64}) # default written out
assert a.id == b.id # same content, same id
assert a == b
c = a.with_params(dim=128) # a new variant spec; a is unchanged
assert c.id != a.id
a.to_dict() # {'kind': 'demo_hash', 'params': {'dim': 64, 'seed': 0}}
An invalid spec fails at construction with a pydantic ValidationError
(unknown param, non-finite number). An unknown kind fails with
UnknownKindError.
Registry¶
coniti.registry binds each category to its base class
(define_category(category, base)) and registers kinds with
@register(category, kind, params_model). Registering a class that does not
subclass the category base fails, as does registering the same kind twice
unless replace=True.
Every implementation, registered or class-path, is built as
factory(params, **deps). The result must be an instance of the category base,
otherwise ContractError is raised. create(category, kind, params, **deps)
builds from parts and build(spec, **deps) builds from a spec.
| Error | When |
|---|---|
UnknownKindError |
A kind is not registered. The message lists the registered kinds. |
ClassPathError |
A class path is malformed, cannot be imported (including a module that raises while importing), names no class, or names a class that does not subclass the category base. A subclass of UnknownKindError. |
ContractError |
A factory built an object that is not an instance of the category base. |
Both lookup errors are LookupErrors, deliberately not ValueErrors: pydantic
would re-wrap a ValueError raised during validation into a generic
ValidationError and hide the type.
Loading specs¶
load_spec(source, spec_cls) builds a spec from:
- a mapping,
- JSON text, or YAML text (YAML needs the
yamlextra), - a
pathlib.Pathto a.toml,.json,.yamlor.ymlfile, chosen by suffix.
A plain str is always treated as text, never as a path; pass a Path to read
a file. load_mapping(source) does the same for a bare mapping.