domain

package
v0.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 14, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

Documentation

Index

Constants

View Source
const (
	DefaultChunkSize    = 512
	DefaultChunkOverlap = 76
)

Default chunking parameters: ~512 tokens, ~15% overlap. For the fixed strategy a "token" is a whitespace word; the structure-aware strategies measure real tokens via an injected counter.

View Source
const FixedChunkerVersion = 1

FixedChunkerVersion is the algorithm version of the fixed (word-window) strategy, recorded in a Collection's ChunkerSpec. Bump it if the fixed chunker's output could change for the same input.

View Source
const StructureChunkerVersion = 1

StructureChunkerVersion is the algorithm version of the structure-aware strategy (markdown sections + token sizing, plus the plain-text strategy that shares its sizing), recorded in a ChunkerSpec. Bump it when any structure chunker's output could change for the same input.

Variables

View Source
var (
	// ErrInvalidArgument marks construction/validation failures of domain values.
	ErrInvalidArgument = errors.New("invalid argument")

	// ErrSpaceMismatch marks a space-coherence violation:
	// an attempt to mix vectors from a different EmbeddingSpace into a Collection.
	ErrSpaceMismatch = errors.New("embedding space mismatch")

	// ErrChunkerMismatch marks an attempt to (re-)ingest into a Collection with a
	// chunker different from the one it was pinned to (or any chunker, for a
	// collection that predates pinning). Like a space mismatch it is an invariant
	// violation; the CLI maps it to exit 4.
	ErrChunkerMismatch = errors.New("chunker mismatch")
)

Sentinel errors for domain invariant violations. Infrastructure-level errors (ErrNotFound etc.) live in internal/app, next to the ports.

Functions

func SameSpace

func SameSpace(colls []*Collection) error

SameSpace enforces space coherence across a set of collections: their vectors are directly comparable only if every collection shares one EmbeddingSpace. It is the precondition for any cross-collection retrieval — feeding one collection's vectors into another (query --from-collection) or merging hits from several (multi-collection ask/query) — and returns ErrSpaceMismatch naming the first offending collection (and both spaces) on a divergence. Zero or one collection trivially shares a space.

func ValidateCollectionName

func ValidateCollectionName(name string) error

ValidateCollectionName reports whether name is non-empty, filesystem- and shell-safe, and at most 64 chars. It is the shared rule behind NewCollection and the import path, which reconstructs a (possibly renamed) collection from an artifact and must validate the target name without building a full collection.

Types

type Attachment

type Attachment struct {
	MediaType string // e.g. "image/png", "application/pdf"
	Name      string // original filename, for display/citation; optional
	Data      []byte
}

Attachment is raw content handed to a Generator alongside retrieved chunks — an image or document the model reads directly. Unlike ingested content it is ephemeral: never chunked, embedded, or stored. How it is encoded for a provider, and whether the provider accepts it, are the Generator adapter's concern.

func NewAttachment

func NewAttachment(mediaType, name string, data []byte) (Attachment, error)

NewAttachment validates and constructs an Attachment. Name may be empty.

type Chunk

type Chunk struct {
	ID         ChunkID
	DocumentID DocumentID
	Seq        int
	Text       string
	// HeadingPath is the document section a chunk came from, joined with " > "
	// (e.g. "Auth > Keys > Rotation"). Set by structure-aware chunkers; empty for
	// formats or strategies without headings. It is provenance for display and
	// inspection, not part of the chunk's identity.
	HeadingPath string
}

Chunk is the unit of retrieval. It belongs to exactly one Document (deleting the Document deletes its Chunks).

func NewChunk

func NewChunk(docID DocumentID, seq int, text string) (Chunk, error)

NewChunk constructs a Chunk with its deterministic ID.

type ChunkHit

type ChunkHit struct {
	Chunk  Chunk
	Score  float64
	Source string // source URI of the chunk's document
	// Collection names the collection the chunk came from. It is empty for
	// single-collection retrieval (where it is implied by the request) and set
	// only for cross-collection results (multi-collection query/ask), so the
	// merged hits stay attributable to their origin.
	Collection  string
	RerankScore *float64
}

ChunkHit is a hydrated retrieval result: the chunk itself, its similarity score, and the URI of the source document it came from (provenance for display and citation). RerankScore is set only after a cross-encoder rerank — nil means the hit was never reranked, so it serializes additively (rerank_score omitted) and the original Score is always preserved alongside it.

type ChunkID

type ChunkID string

ChunkID is deterministic, derived from (DocumentID, Seq), so re-chunking an unchanged document yields identical identities, keeping ingestion idempotent.

func DeriveChunkID

func DeriveChunkID(docID DocumentID, seq int) ChunkID

DeriveChunkID computes the deterministic identity of a chunk.

func (ChunkID) Valid

func (id ChunkID) Valid() bool

Valid reports whether id has the canonical shape produced by DeriveChunkID: a 64-character lowercase-hex document hash, a colon, and a non-negative sequence number (e.g. "3f2a…9c:5"). It is a format check, not an existence check.

type ChunkResult

type ChunkResult struct {
	Text string
	// EmbedText is the text to embed when it should differ from the stored Text —
	// e.g. a heading-path context prefix prepended so the embedding captures the
	// chunk's place in the document. Empty means embed Text as-is; either way the
	// stored Text (what citations and inspection show) is the original, never the
	// prefixed form.
	EmbedText string
	// HeadingPath is the document section the chunk came from (e.g.
	// "Auth > Keys > Rotation"), set by structure-aware chunkers; empty otherwise.
	HeadingPath string
}

ChunkResult is one chunk a Chunker produced: the text to store and cite, plus any section metadata. Identity (ChunkID, Seq) is assigned by the ingestor, not the chunker, since only the ingestor knows the owning Document.

func (ChunkResult) TextToEmbed

func (r ChunkResult) TextToEmbed() string

TextToEmbed returns the text that should be embedded for this chunk: the EmbedText override when set, otherwise the stored Text.

type Chunker

type Chunker interface {
	Chunk(doc ParsedDoc) ([]ChunkResult, error)
}

Chunker splits a parsed document into ordered chunks. Implementations are deterministic and pure: same input → identical output, no I/O, no map-iteration-order dependence. A token-aware strategy receives a token-count func at construction rather than importing a tokenizer, so stdlib-only domain stays clean.

type ChunkerSpec

type ChunkerSpec struct {
	Strategy      string // "structure" | "fixed"
	Version       int    // strategy algorithm version; bump when output could change for the same input
	Size          int    // target chunk size (tokens for structure, words for fixed)
	Overlap       int    // overlap between adjacent chunks, same unit as Size
	Tokenizer     string // sizing tokenizer identity, e.g. "o200k_base" (structure) or "words" (fixed)
	ContextPrefix bool   // whether a heading-path context line is prepended to the embedded text
}

ChunkerSpec identifies the chunking configuration a Collection is pinned to: the strategy, its algorithm version, the size/overlap parameters, the tokenizer used for sizing, and whether a heading-path context prefix is embedded. Any of these changing alters chunk output (boundaries, count, or embedded text) for the same input, so a Collection records its spec at creation and refuses re-ingestion under a different one — the chunker analogue of space coherence. It is comparable with ==.

func NewChunkerSpec

func NewChunkerSpec(strategy string, version, size, overlap int, tokenizer string, contextPrefix bool) (ChunkerSpec, error)

NewChunkerSpec validates and constructs a ChunkerSpec.

func (ChunkerSpec) IsZero

func (s ChunkerSpec) IsZero() bool

IsZero reports whether the spec is uninitialized — the state of a Collection created before chunker pinning existed.

func (ChunkerSpec) String

func (s ChunkerSpec) String() string

String renders the spec for human and error output, e.g. "structure/v1 size=512 overlap=64 tok=o200k_base prefix=on".

func (ChunkerSpec) Validate

func (s ChunkerSpec) Validate() error

Validate reports whether the spec is well-formed.

type Citation

type Citation struct {
	ChunkID ChunkID
	Source  string // source URI of the chunk's document
	Seq     int    // the chunk's position within that document
	// Collection names the chunk's collection, set only for cross-collection
	// answers (multi-collection ask) so a citation stays attributable to its
	// origin; empty for single-collection answers.
	Collection string
}

Citation references a chunk an answer was grounded in, carrying the provenance needed to display it: the source document's URI and the chunk's position within it.

type Collection

type Collection struct {
	Name      string
	Space     EmbeddingSpace
	Chunker   ChunkerSpec
	CreatedAt time.Time
	// Sources are the paths ingested into this collection, recorded by add and
	// sync so `lore sync` can replay them without a path argument.
	Sources []string
}

Collection is the aggregate root: a named corpus bound to exactly one EmbeddingSpace, and pinned to exactly one ChunkerSpec, for its entire lifetime.

func NewCollection

func NewCollection(name string, space EmbeddingSpace, chunker ChunkerSpec, now time.Time) (*Collection, error)

NewCollection validates identity, space, and chunker spec, and constructs a Collection. The clock is injected so the domain stays deterministic and testable. A new collection must be pinned to a valid ChunkerSpec; collections loaded from storage that predate chunker pinning carry a zero spec (built as a literal by the repository, not through this constructor).

func (*Collection) AcceptsChunker

func (c *Collection) AcceptsChunker(spec ChunkerSpec) error

AcceptsChunker enforces chunker coherence: a collection may be (re-)ingested only by the chunker it was pinned to. Mixing chunker configurations within a collection would silently leave it holding chunks of two incompatible layouts (unchanged documents fast-skip re-chunking), so a mismatch is refused — the collection must be rebuilt under the new chunker. A collection that predates chunker pinning (zero spec) is read-only: queryable, but not re-ingestable.

func (*Collection) AcceptsSpace

func (c *Collection) AcceptsSpace(s EmbeddingSpace) error

AcceptsSpace enforces space coherence: vectors may enter the collection only if they were produced in the collection's own space.

type ContentHash

type ContentHash string

ContentHash is the SHA-256 of document content, hex-encoded. Hash equality is what makes ingestion idempotent.

func HashContent

func HashContent(content []byte) ContentHash

HashContent computes the ContentHash of raw content.

type Document

type Document struct {
	ID         DocumentID
	Collection string
	SourceURI  string
	Hash       ContentHash
	IngestedAt time.Time
	// Fingerprint is a cheap source-side signature (e.g. size + sampled-content
	// hash) used to skip re-reading unchanged files before extraction. Unlike
	// Hash it is a heuristic, not an identity; empty means "unknown".
	Fingerprint string
}

Document is source content with identity. Whether its content *changed* is decided by Hash (hash equality => ingestion is a no-op).

func NewDocument

func NewDocument(collection, sourceURI string, hash ContentHash, now time.Time) (*Document, error)

NewDocument constructs a Document with its deterministic ID.

func (*Document) Unchanged

func (d *Document) Unchanged(hash ContentHash) bool

Unchanged reports whether content with the given hash would be a no-op re-ingestion of this document.

type DocumentID

type DocumentID string

DocumentID is deterministic, derived from (collection, source URI): re-ingesting the same source addresses the same identity, which is what makes `lore add` an upsert rather than an append.

func DeriveDocumentID

func DeriveDocumentID(collection, sourceURI string) DocumentID

DeriveDocumentID computes the deterministic identity of a source within a collection. The NUL separator prevents ambiguity between (a,bc) and (ab,c).

type EmbeddingSpace

type EmbeddingSpace struct {
	Model      string
	Dimensions int
}

EmbeddingSpace identifies the vector space a Collection is bound to: the embedding model plus its output dimensionality. Two vectors are comparable only if they come from the same space.

func NewEmbeddingSpace

func NewEmbeddingSpace(model string, dimensions int) (EmbeddingSpace, error)

NewEmbeddingSpace validates and constructs an EmbeddingSpace.

func (EmbeddingSpace) Equal

func (s EmbeddingSpace) Equal(other EmbeddingSpace) bool

Equal reports whether two spaces are the same (and therefore whether their vectors are comparable).

func (EmbeddingSpace) IsZero

func (s EmbeddingSpace) IsZero() bool

IsZero reports whether the space is uninitialized.

func (EmbeddingSpace) String

func (s EmbeddingSpace) String() string

type FixedChunker

type FixedChunker struct {
	// contains filtered or unexported fields
}

FixedChunker emits fixed-size, overlapping windows measured in whitespace words — the legacy chunking behavior, preserved verbatim as the `fixed` strategy and the default fallback. Word sizing is dependency-free and deterministic; the structure-aware strategies use real token counts instead. Chunk text is whitespace-normalized as a side effect.

Construct with NewFixedChunker; the zero value is not usable.

func NewFixedChunker

func NewFixedChunker(size, overlap int) (FixedChunker, error)

NewFixedChunker returns a FixedChunker that emits chunks of at most size words, with consecutive chunks sharing overlap words. It requires size > 0 and 0 <= overlap < size, so every window advances.

func (FixedChunker) Chunk

func (c FixedChunker) Chunk(doc ParsedDoc) ([]ChunkResult, error)

Chunk implements Chunker by windowing the document text. ContentType is ignored — fixed sizing is format-agnostic.

func (FixedChunker) Split

func (c FixedChunker) Split(text string) []string

Split divides text into chunks in document order. Consecutive chunks share up to overlap words. Empty or whitespace-only input yields no chunks, and no chunk is ever empty.

type MarkdownChunker

type MarkdownChunker struct {
	// contains filtered or unexported fields
}

MarkdownChunker splits markdown into token-sized chunks that respect the document's heading hierarchy: each chunk is a section (the content under a heading), carrying the full heading path as metadata. It never breaks inside a fenced code block; oversized sections split at paragraph (then sentence, then word) boundaries, and undersized adjacent sections merge up to the target size. Deterministic and pure.

Construct with NewMarkdownChunker; the zero value is not usable.

func NewMarkdownChunker

func NewMarkdownChunker(size, overlap int, contextPrefix bool, countTokens func(string) int) (MarkdownChunker, error)

NewMarkdownChunker returns a MarkdownChunker targeting size tokens per chunk with overlap tokens shared between size-driven sub-splits. When contextPrefix is true, each chunk's embedded text is prefixed with its heading path so the embedding captures the chunk's place in the document (the stored text is unchanged). It requires size > 0, 0 <= overlap < size, and a non-nil counter.

func (MarkdownChunker) Chunk

func (c MarkdownChunker) Chunk(doc ParsedDoc) ([]ChunkResult, error)

Chunk splits the document into heading-aware, token-sized chunks.

type ParsedDoc

type ParsedDoc struct {
	Text        string
	ContentType string
	SourceURI   string
}

ParsedDoc is the input to a Chunker: the extracted text of one document plus the context a strategy may need. ContentType lets the Registry dispatch to a per-format strategy; SourceURI is carried for diagnostics.

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry selects a Chunker by content type, falling back to a default. It is how per-format strategies (markdown, plain text, ...) plug in without the ingestor knowing which is which; a future code-aware strategy is just another registered entry (though a tree-sitter strategy could not be pure-domain). It also carries the ChunkerSpec the active configuration corresponds to, so the use cases can pin it on new collections and refuse re-ingest under a different one. Construct with NewRegistry; the zero value is not usable.

func NewRegistry

func NewRegistry(spec ChunkerSpec, def Chunker, byType map[string]Chunker) (Registry, error)

NewRegistry builds a Registry routing each content type in byType to its Chunker and everything else to def, identified by spec. It requires a valid spec and a non-nil default, and rejects nil entries; content-type keys are normalized so dispatch is case- and parameter-insensitive.

func (Registry) Chunk

func (r Registry) Chunk(doc ParsedDoc) ([]ChunkResult, error)

Chunk dispatches to the chunker registered for the document's content type, or the default if none matches.

func (Registry) Spec

func (r Registry) Spec() ChunkerSpec

Spec returns the ChunkerSpec the registry's active configuration corresponds to — what new collections are pinned to and re-ingest is checked against.

type TextChunker

type TextChunker struct {
	// contains filtered or unexported fields
}

TextChunker splits plain text into token-sized chunks at paragraph (then sentence, then word) boundaries, never mid-sentence where avoidable. It has no heading awareness: it is the structure strategy's chunker for plain text and the default fallback for formats without their own structure-aware chunker (docx paragraphs; best-effort pdf/xlsx text). Deterministic and pure.

Construct with NewTextChunker; the zero value is not usable.

func NewTextChunker

func NewTextChunker(size, overlap int, countTokens func(string) int) (TextChunker, error)

NewTextChunker returns a TextChunker targeting size tokens per chunk with overlap tokens shared between size-driven splits. It requires size > 0, 0 <= overlap < size, and a non-nil token counter.

func (TextChunker) Chunk

func (c TextChunker) Chunk(doc ParsedDoc) ([]ChunkResult, error)

Chunk packs the document text into token-sized chunks. There is no heading path; ContentType is ignored.

type VectorMatch

type VectorMatch struct {
	ChunkID ChunkID
	Score   float64
}

VectorMatch is what a VectorIndex returns: an identity and a similarity score (higher is more similar), not yet hydrated with chunk content.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL