weft-embed 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
weft_embed/__init__.py ADDED
@@ -0,0 +1,41 @@
1
+ """First-party embedding pack.
2
+
3
+ Publishes the `Embedder` contract, in `contract.py`, and registers
4
+ `HashEmbedder` (`hash_embedder.py`) — a deterministic local embedder that is
5
+ **not a quality component**, per `docs/06-phase-0-build.md` step 8. This
6
+ pack exists at all because of a forced conclusion, not a design choice: G4
7
+ forbids a `Store` from embedding, G2 has not placed the embed step, and a
8
+ walking skeleton must not need a model download or an API key to prove that
9
+ indexing produces stored nodes.
10
+
11
+ Registered through the same public `weft.packs` entry point any third-party
12
+ pack uses — fitness function 2 — with no shortcut for being first-party.
13
+ """
14
+
15
+ from pydantic import BaseModel, ConfigDict
16
+
17
+ from weft_embed.contract import EMBEDDER_CONTRACT_VERSION, Embedder
18
+ from weft_embed.hash_embedder import HashEmbedder, HashEmbedderConfig
19
+ from weft_kernel.discovery import PackRegistrar
20
+
21
+
22
+ class Settings(BaseModel):
23
+ """`weft-embed` takes no pack settings — an empty model is still the required shape."""
24
+
25
+ model_config = ConfigDict(frozen=True, extra="forbid")
26
+
27
+
28
+ def register(registrar: PackRegistrar, settings: Settings) -> None:
29
+ """Register `HashEmbedder` as `"hash"` for `Embedder`. The only plugin this pack ships."""
30
+ del settings
31
+ registrar.add(Embedder, "hash", HashEmbedder)
32
+
33
+
34
+ __all__ = [
35
+ "EMBEDDER_CONTRACT_VERSION",
36
+ "Embedder",
37
+ "HashEmbedder",
38
+ "HashEmbedderConfig",
39
+ "Settings",
40
+ "register",
41
+ ]
weft_embed/contract.py ADDED
@@ -0,0 +1,66 @@
1
+ """The `Embedder` contract — published here, never by the kernel.
2
+
3
+ Specified in `docs/06-phase-0-build.md` step 8. `weft-embed` is the fourth
4
+ pack Phase 0 needs and the plan did not originally anticipate — the reasoning
5
+ is forced, not a design flourish: G4 forbids a `Store` from embedding
6
+ (`docs/02-extension-model.md` → *The store contract family*, "stores never
7
+ embed"), G2 has not decided whether embedding is a pipeline stage or a step
8
+ inside another one, and a walking skeleton must not depend on a model
9
+ download or an API key. Phase 0 puts embedding in the pipeline list as its
10
+ own stage — `06`'s minimal, reversible choice for G2's first trap, not an
11
+ answer to G2 — which is exactly what forces this contract to exist somewhere
12
+ a `Store` cannot own it.
13
+
14
+ **`Embedder` needs no boundary type of its own**, for the same reason
15
+ `weft_chunk.contract.Chunker` does not: `docs/02-extension-model.md` →
16
+ *Composition is typed and checked at load* states the ingest path is
17
+ `Stage[Seq[Node], Seq[Node]]` throughout, and embedding a `Node` that already
18
+ has content but no vector yet is squarely inside that stretch — a `Node` in,
19
+ the same `Node` with `embedding` set (via `Node.with_embedding`) out.
20
+
21
+ **`Embedder` declares `Stage[Sequence[Node], Sequence[Node]]` as one of its
22
+ own bases**, per `weft_kernel.runner`'s documented convention — the linear
23
+ runner reads a pipeline stage's `In`/`Out` off the *contract* named in its
24
+ `StageSpec`, via `__orig_bases__`, never off the plugin implementing it.
25
+ `@runtime_checkable` makes capability checkable by `isinstance` rather than
26
+ by a declared flag, the same property every other Phase 0 contract shares.
27
+ `Embedder.__protocol_attrs__` is exactly `{'run'}` — `Stage` itself declares
28
+ nothing beyond `run` (see `weft_kernel.runner`'s module docstring), so a
29
+ plugin implementing only `run` satisfies `Embedder`, full stop.
30
+
31
+ `version` is readable off the class (`Embedder.version`) but is not part of
32
+ that `isinstance` membership — see `weft_extract.contract`'s module
33
+ docstring for the full reasoning behind the `if TYPE_CHECKING:` /
34
+ assign-after-the-class-body split, which applies here unchanged.
35
+ """
36
+
37
+ from collections.abc import Sequence
38
+ from typing import TYPE_CHECKING, ClassVar, Protocol, runtime_checkable
39
+
40
+ from weft_kernel.context import Context
41
+ from weft_kernel.payload import Node, Outcome
42
+ from weft_kernel.runner import Stage
43
+
44
+ #: Fitness function 6's subject for this contract — see the module docstring.
45
+ EMBEDDER_CONTRACT_VERSION = "1.0.0"
46
+
47
+
48
+ @runtime_checkable
49
+ class Embedder(Stage[Sequence[Node], Sequence[Node]], Protocol):
50
+ """Attaches an embedding to each `Node` it is handed.
51
+
52
+ One method, domain types on both sides, exactly `Chunker`'s shape one
53
+ stage later in the pipeline. An embedder that finds nothing to embed
54
+ (an empty batch) answers `NothingToProduce`, not an empty `Produced([])`
55
+ — the same donor-trap fix every other Phase 0 contract documents.
56
+ """
57
+
58
+ if TYPE_CHECKING:
59
+ #: See the module docstring — declared only for a type checker, assigned for real
60
+ #: after the class body, so it never joins `__protocol_attrs__`.
61
+ version: ClassVar[str]
62
+
63
+ async def run(self, payload: Sequence[Node], ctx: Context) -> Outcome[Sequence[Node]]: ...
64
+
65
+
66
+ Embedder.version = EMBEDDER_CONTRACT_VERSION
@@ -0,0 +1,100 @@
1
+ """`HashEmbedder` — a deterministic, content-hashed vector. **Not a quality embedder.**
2
+
3
+ Specified in `docs/06-phase-0-build.md` step 8: "Phase 0 ships a deterministic
4
+ local embedder (hashing to a fixed-dimension vector) whose only job is to be
5
+ a real vector produced by a real stage. It is not a quality component and
6
+ its docstring should say so." This is that docstring, said plainly: `HashEmbedder`
7
+ carries no semantic understanding of content whatsoever. Two documents about
8
+ unrelated topics that happen to hash to nearby components are not "similar"
9
+ in any sense a retrieval strategy should trust — this class exists only so
10
+ that Phase 0's ingest pipeline has a real `Vector`, produced by a real stage,
11
+ flowing into a real `NodeStore.search_vector` call, with no model download
12
+ and no API key standing between a clean checkout and a passing test. Replace
13
+ it with a real embedding model before trusting a single retrieval result
14
+ against it.
15
+
16
+ **Deterministic by construction, not by convention.** Every component is a
17
+ SHA-256 digest of the node's content concatenated with that component's own
18
+ index, so the same content always hashes to the same vector — required for
19
+ `weft index`'s re-index-is-idempotent property (`docs/02-extension-model.md`
20
+ → *Identity is a content-addressed digest*) to hold all the way through
21
+ storage, not just at the `Node` layer. It is not, and must never become,
22
+ a hash-based *approximation* of semantic similarity: nothing here reads
23
+ tokens, n-grams or any structure in `content` at all.
24
+
25
+ **Configuration is `dimension`, and nothing else.** No model name, no API
26
+ endpoint, no batching knob — there is no model to name, and pretending
27
+ otherwise (a `model: "text-embedding-3-small"` field that is silently
28
+ ignored) would be exactly the kind of plausible-but-false configuration
29
+ surface the project's catch-specific-exceptions rule already refuses at the
30
+ error-handling layer. `dimension` is real: it is the length of the vector
31
+ this stage actually produces.
32
+ """
33
+
34
+ import hashlib
35
+ from collections.abc import Sequence
36
+
37
+ from pydantic import BaseModel, ConfigDict, model_validator
38
+
39
+ from weft_kernel.context import Context
40
+ from weft_kernel.payload import Node, NothingToProduce, Outcome, Produced, Vector
41
+
42
+ #: Matches `weft-store`'s pgvector column, which is declared without a fixed
43
+ #: dimension — see `docs/06-phase-0-build.md` step 8 and `weft_store.pgvector_store`.
44
+ #: Any positive dimension works; this is only the default a `with:` block may omit.
45
+ _DEFAULT_DIMENSION = 64
46
+
47
+ #: The width of the slice of each SHA-256 digest this module turns into one component.
48
+ _UINT64_RANGE = 2**64
49
+
50
+
51
+ class HashEmbedderConfig(BaseModel):
52
+ """`HashEmbedder`'s `with:` config — one real field. See the module docstring for why."""
53
+
54
+ model_config = ConfigDict(frozen=True, extra="forbid")
55
+
56
+ dimension: int = _DEFAULT_DIMENSION
57
+
58
+ @model_validator(mode="after")
59
+ def _dimension_is_positive(self) -> "HashEmbedderConfig":
60
+ if self.dimension < 1:
61
+ raise ValueError(
62
+ f"dimension must be at least 1 — a Vector cannot be empty (got {self.dimension})"
63
+ )
64
+ return self
65
+
66
+
67
+ class HashEmbedder:
68
+ """Attaches a deterministic, content-hashed `Vector` to every node it is handed.
69
+
70
+ **Not a quality component** — see the module docstring. Satisfies
71
+ `weft_embed.contract.Embedder` structurally: this class never imports it,
72
+ the same path every third-party embedder pack is expected to take.
73
+ """
74
+
75
+ def __init__(self, config: HashEmbedderConfig | None = None) -> None:
76
+ self._config = config if config is not None else HashEmbedderConfig()
77
+
78
+ async def run(self, payload: Sequence[Node], ctx: Context) -> Outcome[Sequence[Node]]:
79
+ del ctx # no service or locale this stage needs
80
+ if not payload:
81
+ return NothingToProduce(reason="no nodes to embed")
82
+ embedded = [
83
+ node.with_embedding(_hash_vector(node.content, self._config.dimension))
84
+ for node in payload
85
+ ]
86
+ return Produced(value=embedded)
87
+
88
+
89
+ def _hash_vector(content: str, dimension: int) -> Vector:
90
+ """`dimension` components, each a deterministic function of `content` and its own index."""
91
+ encoded = content.encode("utf-8")
92
+ components = tuple(_component(encoded, index) for index in range(dimension))
93
+ return Vector(values=components)
94
+
95
+
96
+ def _component(encoded: bytes, index: int) -> float:
97
+ """One component: a SHA-256 digest of `encoded` and `index`, mapped onto `[-1.0, 1.0)`."""
98
+ digest = hashlib.sha256(encoded + index.to_bytes(4, byteorder="big")).digest()
99
+ raw = int.from_bytes(digest[:8], byteorder="big")
100
+ return (raw / _UINT64_RANGE) * 2.0 - 1.0
weft_embed/py.typed ADDED
File without changes
@@ -0,0 +1,9 @@
1
+ Metadata-Version: 2.5
2
+ Name: weft-embed
3
+ Version: 1.0.0
4
+ Summary: First-party embedding pack. Publishes the Embedder contract.
5
+ License-Expression: MIT
6
+ License-File: LICENSE
7
+ License-File: NOTICE
8
+ Requires-Python: >=3.12
9
+ Requires-Dist: weft-kernel<1.0.0,>=0.1.0
@@ -0,0 +1,10 @@
1
+ weft_embed/__init__.py,sha256=_DAwQxzEMoFmPJWqcJaU247-1wnQSWUGCNIKYm_aYzw,1455
2
+ weft_embed/contract.py,sha256=_ITHn2qVTItG5Bzm0D4BPqv-gqpoUvPjGqU6jF1owa8,3325
3
+ weft_embed/hash_embedder.py,sha256=hoMW094tM8DM2AbqliLwldaFIxl7JXJaPEgbSY9gZ_k,4710
4
+ weft_embed/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ weft_embed-1.0.0.dist-info/METADATA,sha256=5KuilRYOoR32nkNenOK0OfSDEZ_ixbjDvEla3k21bnw,256
6
+ weft_embed-1.0.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
7
+ weft_embed-1.0.0.dist-info/entry_points.txt,sha256=rRgQUgbTUZSt22hP2UH6AdmL9D_E5qMB3GwR5TxEmMw,41
8
+ weft_embed-1.0.0.dist-info/licenses/LICENSE,sha256=47pnkFo9fLIDIV2XXdn1IKAKte67X9buw7roSIO3kTA,1071
9
+ weft_embed-1.0.0.dist-info/licenses/NOTICE,sha256=zXu39bCB9SpYA26wsAsONwdVmv-M6azeqy69LbWFOdo,1182
10
+ weft_embed-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [weft.packs]
2
+ embed = weft_embed:register
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Adam Krysztopa
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,24 @@
1
+ Weft
2
+ Copyright (c) 2026 Adam Krysztopa
3
+
4
+ This product is licensed under the MIT License. See the LICENSE file at the root
5
+ of this repository.
6
+
7
+ --------------------------------------------------------------------------------
8
+ Original work
9
+ --------------------------------------------------------------------------------
10
+
11
+ **Weft contains no source text from any other codebase.** Every line here is
12
+ written for this project, against this project's contracts.
13
+
14
+ This is a rule, not a description of the current state: no file may be copied or
15
+ adapted from another project's source, and no third-party source text may be
16
+ pasted into this repository. Where a prior system informed a design, what was
17
+ carried across is understanding — an approach, an ordering, a measurement, a
18
+ reason a guard exists — restated in this project's own words and implemented
19
+ fresh. Copyright does not reach any of that, and nothing in this repository
20
+ depends on a licence granted by anyone else.
21
+
22
+ `docs/04-donor-inventory.md` records what was learned from prior work and what
23
+ was deliberately not taken. It is a design record. Nothing in it authorises a
24
+ copy, because copying is not permitted here at all.