ef.config¶
The config layer — declarative, content-addressable pipeline specs.
A pipeline in ef is not an object graph you wire by hand — it is data.
A PipelineSpec declares the segment→embed chain; each step is a
TransformSpec (an op key, an op version, and the keyword params that
feed the content hash). Because a spec is fully serializable and content-hashed
(config_id()), two configs that share a step share that step’s artifacts
in the ArtifactGraph for free — config branching
costs nothing beyond the divergent cone (design notes §3.1).
This module is the bridge between an ordinary Python call and a content-
addressed graph node. For content addressing to be correct, two
semantically-equal calls must produce identical params — so op(text) and
op(text, size=512) (512 being the default) must hash the same. The
canonical i2 idiom does exactly that normalization:
full_kwargs()— the raw recipe (design notes §3.8): a call(args, kwargs)→ a fully-named, defaults-filled kwargs dict.step_params()— its spec-building application: the canonical keyword params of an op, with the leading positional input slots (the upstream artifact ids) dropped, since those becomeProducerSpec.inputs, not params.
Phase 4’s ArtifactGraph takes ProducerSpecs as given; this module (and
ef.ops / ef.source_manager) is where ordinary calls become
those specs.
Example — two pipelines, equal segment step, divergent embed step:
>>> seg = TransformSpec(op='segment:recursive', op_version='1', params={})
>>> embed_a = TransformSpec(op='embed:m-a', op_version='1', params={})
>>> embed_b = TransformSpec(op='embed:m-b', op_version='1', params={})
>>> pa = PipelineSpec(segment=seg, embed=embed_a)
>>> pb = PipelineSpec(segment=seg, embed=embed_b)
>>> config_id(pa) == config_id(pb) # different embedders → different config
False
>>> config_id(pa) == config_id(PipelineSpec.from_dict(pa.as_dict()))
True
Module Attributes
A content-addressed pipeline identifier — the |
Functions
|
Normalize a call into a canonical, fully-named, defaults-filled kwargs dict. |
|
The canonical keyword params of an op step — its inputs dropped. |
|
Content-addressed id of a |
Classes
A content-addressed pipeline identifier — the |
|
|
One step of a pipeline — a serializable recipe for an op application. |
|
A whole indexing pipeline as data — the segment→embed chain. |
- ef.config.ConfigId¶
A content-addressed pipeline identifier — the
sha256hex digest of aPipelineSpec(config_id()). It is whatefwrites intovdindex metadata asconfig_hashso a later phase can tell which pipeline produced a vector.
- class ef.config.PipelineSpec(segment, embed)[source]¶
Bases:
objectA whole indexing pipeline as data — the segment→embed chain.
A
PipelineSpecis the declarative description of one config: aTransformSpecfor the segmenter and one for the embedder. It is fully serializable (as_dict()/from_dict()) and content-hashed byconfig_id(). Registering a secondPipelineSpecthat differs only in its embed step shares the segment artifacts in theArtifactGraph— that is config branching, and the graph gives it for free.The
segment``→``embedordering is the input wiring: the embed step consumes the segment step’s output. (Phase 5 fixes this two-step shape; a later phase may generalize it to an arbitrary step list.)>>> seg = TransformSpec(op='segment:r', op_version='1', params={}) >>> emb = TransformSpec(op='embed:m@8', op_version='1', params={'input_type': 'document'}) >>> spec = PipelineSpec(segment=seg, embed=emb) >>> spec.embed.params['input_type'] 'document' >>> PipelineSpec.from_dict(spec.as_dict()) == spec True- as_dict()[source]¶
Return a plain-
dict(JSON-serializable) form of the pipeline.>>> seg = TransformSpec(op='segment:r', op_version='1', params={}) >>> emb = TransformSpec(op='embed:m', op_version='1', params={}) >>> PipelineSpec(segment=seg, embed=emb).as_dict()['segment']['op'] 'segment:r'
- classmethod from_dict(d)[source]¶
Rebuild a
PipelineSpecfrom itsas_dict()form.- Return type:
- class ef.config.TransformSpec(op, op_version, params=<factory>)[source]¶
Bases:
objectOne step of a pipeline — a serializable recipe for an op application.
A
TransformSpecnames which op (op, a key resolved through theArtifactGraph’sopsregistry), *at which version* (op_version— it participates in every downstream hash), and with which keyword params (params). It does not record the step’s inputs: those are wired structurally by thePipelineSpec(the embed step consumes the segment step’s output). It is the per-step counterpart of aProducerSpec.The op key carries the component identity — e.g.
"embed:openai:text- embedding-3-large@1024"or"segment:<segmenter-identity>"(seeef.ops) — so two configs with different components get disjoint artifact cones automatically.>>> ts = TransformSpec(op='embed:m@8', op_version='2', params={'input_type': 'document'}) >>> ts.op, ts.op_version ('embed:m@8', '2') >>> TransformSpec.from_dict(ts.as_dict()) == ts True- as_dict()[source]¶
Return a plain-
dict(JSON-serializable) form of the spec.>>> TransformSpec(op='segment:r', op_version='1', params={}).as_dict() {'op': 'segment:r', 'op_version': '1', 'params': {}}
- classmethod from_dict(d)[source]¶
Rebuild a
TransformSpecfrom itsas_dict()form.- Return type:
- ef.config.config_id(spec)[source]¶
Content-addressed id of a
PipelineSpec—sha256over its steps.The id is
sha256of the canonical (sorted-key) JSON ofPipelineSpec.as_dict(). Two pipelines that describe the same computation hash identically; any change to a step’s op, version or params yields a different id.efwrites this digest intovdindex metadata asconfig_hash.- Return type:
>>> seg = TransformSpec(op='segment:r', op_version='1', params={}) >>> emb = TransformSpec(op='embed:m@8', op_version='1', params={}) >>> cid = config_id(PipelineSpec(segment=seg, embed=emb)) >>> len(cid) 64 >>> cid == config_id(PipelineSpec(segment=seg, embed=emb)) True
- ef.config.full_kwargs(op, args=(), kwargs=None)[source]¶
Normalize a call into a canonical, fully-named, defaults-filled kwargs dict.
The
i2.Sigrecipe of design notes §3.8. A call expressed as positionalargs+ keywordkwargsis mapped ontoop’s signature: every parameter is named, and any parameter the call left implicit is filled with its default. The result is therefore stable —op(x)andop(x, size=512)(512the default) yield the same dict, which is what makes a content hash built from it correct.- Parameters:
op (
Callable[...,Any]) – the callable whose signature defines the parameter names/defaults.args (
tuple[Any,...]) – the call’s positional arguments — for anef.opsop these are the upstream artifact ids;step_params()drops them again.kwargs (
Mapping[str,Any] |None) – the call’s keyword arguments.
- Return type:
>>> def embed(segments, *, input_type='document', dim=None): ... >>> full_kwargs(embed, ('seg-1',), {'input_type': 'query'}) {'segments': 'seg-1', 'input_type': 'query', 'dim': None} >>> full_kwargs(embed, ('seg-1',)) # defaults filled in {'segments': 'seg-1', 'input_type': 'document', 'dim': None}
- ef.config.step_params(op, raw_params=None, *, n_inputs=1)[source]¶
The canonical keyword params of an op step — its inputs dropped.
An
ef.opsop is calledop(*input_values, **params): the firstn_inputspositional parameters are upstream artifacts (they belong inProducerSpec.inputs, not in the params), and every remaining parameter is a hashable step param. This runsfull_kwargs()with placeholder inputs, then drops those input slots — leaving exactly the params, defaults filled, ready for aTransformSpec.- Parameters:
- Return type:
>>> def embed(segments, *, input_type='document'): ... >>> step_params(embed, {'input_type': 'query'}) {'input_type': 'query'} >>> step_params(embed) # default filled in {'input_type': 'document'} >>> def segment(source): ... >>> step_params(segment) # no params at all {}