Writing your own plug-in¶
Built and verified
Two ways to make a plug-in available are built and tested: binding by
class path (through the Container) and registering a named kind through the
coniti.plugins entry point (see “Register a named kind” below). The
Embedder, Structure, Strategy and MemoryBackend base classes are all
built; for memory backends see memory-backends.
A plug-in lives in your own package. You do not edit coniti. Either bind it by class path, or register a short name for it.
1. Write the class¶
Subclass the ABC and give it a params model. Params models are frozen and reject unknown fields.
# my_pkg/embedders.py
from coniti import Embedder, EmbedOutput, Params
from coniti.embedding import EmbedInput
class TinyParams(Params):
dim: int = 16
class TinyEmbedder(Embedder):
params_model = TinyParams
def __init__(self, params: TinyParams | None = None, **deps) -> None:
super().__init__(params, **deps)
self._dim = (params or TinyParams()).dim
@property
def dim(self) -> int:
return self._dim
def _embed(self, inp: EmbedInput) -> EmbedOutput:
... # return EmbedOutput(vector=<unit vector of length dim>)
The public embed checks your input and output contract and the vector length.
2. Bind it by class path¶
or in code:
from coniti import Container, ContinuityEngine, Embedder, use
c = Container()
with use(c), c.override(Embedder, {"kind": "my_pkg.embedders:TinyEmbedder", "params": {"dim": 64}}):
engine = ContinuityEngine()
or pass an instance directly, which wins over every configured layer:
from my_pkg.embedders import TinyEmbedder, TinyParams
engine = ContinuityEngine(embedder=TinyEmbedder(TinyParams(dim=64)))
The class is imported, checked to subclass the interface, and its params validated.
Register a named kind (entry point)¶
To let people write kind = "tiny" instead of a class path, register the class
and point the coniti.plugins entry-point group at the module that does it:
# my_pkg/coniti_plugins.py
from coniti import Params, register
from my_pkg.backend import MyBackend
register("memory", "my_store", Params)(MyBackend) # or your own Params subclass
# your package's pyproject.toml
[project.entry-points."coniti.plugins"]
my_plugins = "my_pkg.coniti_plugins"
The default registry loads every entry point in that group the first time an
unknown kind is looked up or kinds are listed. coniti’s own implementations are
registered the same way (builtin = "coniti.impl"), so the framework never imports
them directly and your package is treated exactly like coniti’s. A plug-in that
fails to import does not break other lookups: the failure is recorded in
default_registry.plugin_errors and mentioned in the unknown-kind error.
Needing another part¶
Declare it with Depends, so you never construct it yourself:
from coniti import Depends, MemoryBackend, Strategy, inject
class MyStrategy(Strategy):
@inject
def __init__(self, params=None, memory: MemoryBackend = Depends(MemoryBackend)):
...
Extending a contract¶
To return more than the base output, subclass the output model and set it on your class (see contracts). Added input fields need defaults.
Trusted code only
Class paths execute code on import. Only bind classes you trust.