knowledge

package
v0.3.17 Latest Latest
Warning

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

Go to latest
Published: May 17, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package knowledge implements the layered knowledge base: document storage, chunking, tokenization, and BM25 / vector / hybrid retrieval over three context layers (L0 abstract, L1 overview, L2 chunk detail).

Architecture

  • Service (this package): orchestrates DocumentRepo / ChunkRepo / LayerRepo, normalises Query and stamps DerivedSig so callers see a single coherent contract.
  • factory (sdk/knowledge/factory): wires Service against a retrieval.Index (factory.NewRetrieval). The legacy filesystem wiring factory.NewLocal is deprecated as of v0.4 and slated for removal in v0.5.0; see #134 / docs/migrations/v0.5.0.md.
  • SearchEngine (this package): runs Retrievers in parallel, fuses with a Ranker (RRF by default).
  • EventReloader (this package): debounces ChangeEvents and triggers Service.Rebuild with the smallest possible scope.

L0/L1 derivation (GenerateDocumentContext / GenerateDatasetContext) is kept external to Service so callers own scheduling, retry and persistence policy.

Index

Constants

View Source
const (
	// AbstractPrompt produces a single-sentence L0 summary (~100 tokens).
	AbstractPrompt = `` /* 155-byte string literal not displayed */

	// OverviewPrompt produces a structured L1 overview (~1000 tokens).
	OverviewPrompt = `` /* 187-byte string literal not displayed */

	// DatasetOverviewPrompt produces a dataset-level L1 from per-document
	// abstracts ("- name: abstract" lines).
	DatasetOverviewPrompt = `` /* 196-byte string literal not displayed */

)

Prompt templates used to derive layered context from raw documents. Exported so callers can override or compose their own pipelines while reusing the SDK's defaults for the common case.

View Source
const DefaultPromptInputLimit = 8000

DefaultPromptInputLimit is the maximum number of characters of document content fed into a prompt. Content beyond this is truncated to keep prompts within typical context windows.

View Source
const DefaultRRFK = 60

DefaultRRFK is the conventional fusion constant for reciprocal-rank fusion. Smaller K weights the head of each ranked list more heavily.

View Source
const DefaultThreshold = 0.1

DefaultThreshold is the BM25-scale relevance floor used by legacy search paths. Service-driven Search uses Query.Threshold instead.

Variables

View Source
var (
	DetectTokenizer = textsearch.DetectTokenizer
	NewCorpusStats  = textsearch.NewCorpusStats
	ExtractKeywords = textsearch.ExtractKeywords
	ScoreText       = textsearch.ScoreText
)

Functions

func ChunkConfigSig added in v0.2.1

func ChunkConfigSig(c ChunkConfig) string

ChunkConfigSig returns a stable signature for a chunker configuration. It is the ChunkerSig embedded in DerivedSig so freshness checks can detect a configuration change.

func CosineSimilarity added in v0.2.1

func CosineSimilarity(a, b []float32) float64

CosineSimilarity returns the cosine similarity of two equal-length vectors. Returns 0 when lengths differ, either vector is empty, or either vector has zero norm. Exported so backends can share one canonical implementation.

func IsValidLayer added in v0.2.1

func IsValidLayer(l Layer) bool

IsValidLayer reports whether l is a recognised layer.

(Method set on a type alias must live on the underlying type; provided as a free function to avoid colliding with future ContextLayer methods.)

func IsValidMode added in v0.2.1

func IsValidMode(m Mode) bool

IsValidMode reports whether m is a recognised mode.

The empty string is accepted for backwards compatibility (legacy callers used "" to mean BM25); ResolveMode normalises it to ModeBM25.

Types

type BM25Retriever added in v0.2.1

type BM25Retriever struct {
	Chunks ChunkRepo
}

BM25Retriever queries ChunkRepo with Mode=ModeBM25. It is layered: queries whose Layer is not LayerDetail short-circuit to nil.

func NewBM25Retriever added in v0.2.1

func NewBM25Retriever(c ChunkRepo) *BM25Retriever

NewBM25Retriever constructs a BM25Retriever bound to a ChunkRepo.

func (*BM25Retriever) Name added in v0.2.1

func (r *BM25Retriever) Name() string

Name implements Retriever.

func (*BM25Retriever) Recall added in v0.2.1

func (r *BM25Retriever) Recall(ctx context.Context, q Query) ([]Candidate, error)

Recall implements Retriever. ModeVector queries are skipped because BM25 cannot satisfy them; ModeHybrid issues both lanes and the Ranker fuses the result.

type CJKTokenizer

type CJKTokenizer = textsearch.CJKTokenizer

Type and function aliases re-exported from sdk/textsearch for backward compatibility. Internal code and tests can use these without changing import paths.

type Candidate added in v0.2.1

type Candidate struct {
	Hit    Hit
	Source string
}

Candidate is the per-item recall result returned by ChunkRepo / LayerRepo. Source identifies the producing retriever ("bm25" / "vector" / "layer") and is consumed by the Ranker for fusion.

type ChangeEvent added in v0.2.1

type ChangeEvent struct {
	DatasetID string
	DocName   string
	Kind      EventKind
}

ChangeEvent carries enough granularity for targeted rebuilds. DocName == "" denotes a dataset-level event.

type ChunkConfig

type ChunkConfig struct {
	ChunkSize    int `json:"chunk_size,omitempty"`
	ChunkOverlap int `json:"chunk_overlap,omitempty"`
}

ChunkConfig controls document chunking.

func DefaultChunkConfig

func DefaultChunkConfig() ChunkConfig

DefaultChunkConfig returns the default chunking configuration.

type ChunkQuery added in v0.2.1

type ChunkQuery struct {
	DatasetIDs []string
	Text       string
	Vector     []float32
	Mode       Mode
	TopK       int
}

ChunkQuery is the recall input passed by Retrievers to ChunkRepo.Search.

Empty DatasetIDs means "every dataset" (cross-dataset search). When Mode is ModeVector or ModeHybrid, Vector should be supplied; backends that cannot satisfy a mode return an empty result without error.

type ChunkRepo added in v0.2.1

type ChunkRepo interface {
	Replace(ctx context.Context, datasetID, docName string, chunks []DerivedChunk) error
	DeleteByDoc(ctx context.Context, datasetID, docName string) error
	DeleteByDataset(ctx context.Context, datasetID string) error
	Search(ctx context.Context, q ChunkQuery) ([]Candidate, error)
}

ChunkRepo persists DerivedChunks and supports recall.

Replace MUST be atomic: callers rely on it to eliminate stale chunks when a SourceDocument is updated (contract guarantee #5).

type ChunkSigReader added in v0.3.16

type ChunkSigReader interface {
	GetDocSig(ctx context.Context, datasetID, docName string) (DerivedSig, bool, error)
}

ChunkSigReader is an OPTIONAL extension that ChunkRepo implementations may also satisfy. When supported, Service.Rebuild can compare the on-disk DerivedSig of an existing doc against the current (SourceVer, ChunkerSig, EmbedSig) and skip the chunk + embed work when the doc is already fresh — honouring the DerivedSig.IsStale contract documented on Rebuild.

Return (DerivedSig{}, false, nil) when the doc has no chunks yet (first ingest). Return an error only for backend faults; missing docs are NOT an error.

Backends that do not implement ChunkSigReader force Rebuild onto the unconditional re-chunk + re-embed path. Fix landed in #152.

type ChunkSpec added in v0.2.1

type ChunkSpec struct {
	Index   int
	Offset  int
	Content string
}

ChunkSpec is the chunker output: a positionally-tagged content slice without dataset/doc identity (the Service fills those in).

type Chunker added in v0.2.1

type Chunker interface {
	Split(content string) []ChunkSpec
	Sig() string
}

Chunker turns raw content into ordered ChunkSpecs.

Implementations MUST be deterministic (same input -> same output) and MUST return a stable Sig() so derived data freshness can be checked.

func NewDefaultChunker added in v0.2.1

func NewDefaultChunker(cfg ChunkConfig) Chunker

NewDefaultChunker returns the built-in paragraph/sentence-boundary chunker. Its output is UTF-8 safe; chunks never split inside a multi-byte rune.

Sizes are measured in runes (not bytes), so multi-byte UTF-8 content (CJK, emoji, etc.) is sliced safely on rune boundaries.

type ContextLayer

type ContextLayer = Layer

ContextLayer is the pre-v0.3.0 name for Layer. Retained as a type alias so external code that referenced knowledge.ContextLayer keeps compiling. The constant set (LayerAbstract / LayerOverview / LayerDetail) lives in types.go.

type CorpusStats

type CorpusStats = textsearch.CorpusStats

Type and function aliases re-exported from sdk/textsearch for backward compatibility. Internal code and tests can use these without changing import paths.

type DatasetContext

type DatasetContext struct {
	Abstract string // dataset-level L0
	Overview string // dataset-level L1
}

DatasetContext groups the layered context for an entire dataset.

func GenerateDatasetContext

func GenerateDatasetContext(ctx context.Context, l llm.LLM, summaries []DocumentSummary) (DatasetContext, error)

GenerateDatasetContext derives dataset-level L0 + L1 from per-document abstracts. The L1 overview is generated first, then distilled into L0. Returns an empty context with no error when summaries is empty.

type DerivedChunk added in v0.2.1

type DerivedChunk struct {
	DatasetID string
	DocName   string
	Index     int
	Offset    int
	Content   string
	Vector    []float32
	Sig       DerivedSig
}

DerivedChunk is one retrieval unit derived from a SourceDocument.

func ChunkText added in v0.2.1

func ChunkText(docName, content string, cfg ChunkConfig) []DerivedChunk

ChunkText splits content into UTF-8-safe overlapping chunks.

Behavior:

  • Slicing is performed on rune indices, never byte indices.
  • End offsets are reported in bytes for compatibility with retrieval filters that key on byte position.
  • Boundary preference: paragraph (\n\n) > sentence (". ") > line (\n).
  • The result is deterministic for a given (content, cfg) pair.

type DerivedLayer added in v0.2.1

type DerivedLayer struct {
	DatasetID string
	DocName   string
	Layer     Layer
	Content   string
	Vector    []float32
	Sig       DerivedSig
}

DerivedLayer is an LLM-produced summary of a document or dataset. DocName == "" denotes a dataset-level layer.

type DerivedSig added in v0.2.1

type DerivedSig struct {
	SourceVer  uint64
	ChunkerSig string
	PromptSig  string
	EmbedSig   string
}

DerivedSig binds a derived artifact to the source revision and to the configuration that produced it. Required on every derived object.

  • SourceVer is the SourceDocument.Version that produced this artifact.
  • ChunkerSig is non-empty for chunk artifacts and empty for layers.
  • PromptSig is non-empty for layer artifacts and empty for chunks.
  • EmbedSig identifies the embedder, "" when no vector is attached.

func (DerivedSig) IsStale added in v0.2.1

func (sig DerivedSig) IsStale(want DerivedSig) bool

IsStale returns true when sig was produced for an earlier source version or with a different chunker / prompt / embed configuration than want.

A zero EmbedSig in want is treated as "don't care".

type DocLevelSearcher added in v0.3.10

type DocLevelSearcher interface {
	SearchDocs(ctx context.Context, q ChunkQuery) ([]Candidate, error)
}

DocLevelSearcher is an OPTIONAL extension that ChunkRepo implementations may also satisfy. When supported, the backend can answer searches at doc-level granularity (one Candidate per docName, scored over the whole document rather than per chunk), avoiding the chunks→docID collapse that callers (e.g. eval/beir) would otherwise have to implement themselves.

SCOPE: BM25 only. Doc-level vector / hybrid retrieval is a separate capability — see DocVectorSearcher. Service.SearchDocuments routes q.Mode == ModeBM25 here and ModeVector / ModeHybrid to DocVectorSearcher; backends that do not implement the requested capability surface a clear error rather than silently degrading. See #126 for the BM25 rationale, #145 for the vector rationale.

type DocVectorSearcher added in v0.3.16

type DocVectorSearcher interface {
	SearchDocsByVector(ctx context.Context, q ChunkQuery) ([]Candidate, error)
}

DocVectorSearcher is an OPTIONAL extension declaring doc-level vector (and hybrid) retrieval as an explicit capability (issue #145).

Pre-fix, ChunkRepo.SearchDocs accepted q.Mode and returned errdefs.NotAvailable mid-call when the requested mode was anything other than ModeBM25. That mixed two unrelated concerns — "what granularity is supported" (DocLevelSearcher) and "what scoring modes are supported" — into a runtime error that callers had to pattern-match on. This interface separates them: the capability is declared at compile time and probed via type assertion in Service.SearchDocuments. Backends that do not implement it surface a clear NotAvailable up front, not a buried mid-search error.

Hybrid handling is the implementer's responsibility (BM25 + cosine fusion, late-chunking, etc.). The interface accepts the same ChunkQuery as DocLevelSearcher and is expected to honour q.Mode == ModeVector or ModeHybrid.

No in-tree implementation yet — both [FSChunkRepo] and [RetrievalChunkRepo] hold per-chunk vectors but no per-doc representation. Mean-pool / late-chunking will land via a follow-up that adds SearchDocsByVector to one or both repos.

type DocumentContext

type DocumentContext struct {
	Abstract string // L0
	Overview string // L1
}

DocumentContext groups the layered context for a single document.

func GenerateDocumentContext

func GenerateDocumentContext(ctx context.Context, l llm.LLM, content string) (DocumentContext, error)

GenerateDocumentContext synthesizes L0 (abstract) and L1 (overview) for a document by issuing two LLM calls. Pure function: no I/O, no caching, no retries; callers own scheduling and persistence.

Returns a partial result on error: if abstract generation fails the zero-value context is returned with the error; if overview fails the already-generated abstract is preserved so callers can choose to persist it.

type DocumentRepo added in v0.2.1

type DocumentRepo interface {
	Put(ctx context.Context, doc SourceDocument) error
	Get(ctx context.Context, datasetID, name string) (*SourceDocument, error)
	Delete(ctx context.Context, datasetID, name string) error
	List(ctx context.Context, datasetID string) ([]SourceDocument, error)
	ListDatasets(ctx context.Context) ([]string, error)
}

DocumentRepo persists SourceDocuments. Implementations MUST guarantee:

  • Put atomically increments SourceDocument.Version.
  • Get returns the most recent Put with Content losslessly preserved (contract guarantee #4).
  • Delete is idempotent.

Implementations live in sdk/knowledge/backend/*.

type DocumentSummary

type DocumentSummary struct {
	Name     string
	Abstract string
}

DocumentSummary pairs a document name with its L0 abstract, used as input to GenerateDatasetContext.

type Embedder

type Embedder = embedding.Embedder

Embedder is an alias for the SDK embedding.Embedder interface. It supports both single-text and batch embeddings.

type EventKind added in v0.2.1

type EventKind int

EventKind classifies a ChangeEvent emitted by an EventNotifier implementation. Used by EventReloader to decide whether to perform a targeted or dataset-wide rebuild.

const (
	// EventPut signals that a single document was created or updated.
	EventPut EventKind = iota
	// EventDelete signals that a single document was removed.
	EventDelete
	// EventBulk signals a dataset-level mass change (e.g. snapshot replaced).
	EventBulk
)

type EventNotifier added in v0.2.1

type EventNotifier interface {
	Events() <-chan ChangeEvent
	Close() error
}

EventNotifier is the producer side of the reload pipeline. Events carry dataset/doc granularity so the consumer can issue targeted Rebuilds instead of a global one.

Implementations live in adapter packages so the sdk core stays dependency-free. Implementations MUST close the Events channel when Close() is called.

type EventReloader added in v0.2.1

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

EventReloader debounces ChangeEvents and triggers Rebuild on the trailing edge. Rebuilds are serialised: a new rebuild waits for the previous one to finish.

Targeted vs global rebuilds: when the debounce window contains events for a single (dataset, doc) pair, the rebuild is scoped to that pair; when it touches multiple datasets or any EventBulk event, a dataset-wide rebuild is issued instead. Mixed datasets in one window collapse to a global RebuildScope{} (every dataset).

func NewEventReloader added in v0.2.1

func NewEventReloader(target Rebuilder, notifier EventNotifier, opts ReloaderOptions) *EventReloader

NewEventReloader wires a Rebuilder to an EventNotifier.

opts.Debounce defaults to 500ms; opts.RebuildTimeout defaults to 30s.

When target or notifier is nil, Run becomes a no-op (returns immediately) and Close is also a no-op for the background loop, so callers can wire up unconditionally and the call order does not matter.

In the normal (non-nil) configuration the contract is:

  • Run MUST be invoked at most once before Close;
  • Close blocks until Run has actually exited AND the notifier has been closed, so callers observing a successful Close are guaranteed no further Rebuild / timer callback can fire.

To uphold the second guarantee even when Close races with the goroutine that is about to call Run, wg.Add(1) is performed here (synchronously, before the constructor returns) rather than inside Run. This way wg.Add strictly happens-before any wg.Wait in Close, which is what sync.WaitGroup requires when its counter starts at zero.

func (*EventReloader) Close added in v0.2.1

func (r *EventReloader) Close() error

Close stops Run and the underlying notifier.

In the normal (non-nil target & notifier) configuration Close blocks until Run has actually returned, so on success the caller is guaranteed that no further Rebuild call or timer-driven flush can happen. Calling Close before Run was started would deadlock, so callers MUST start Run first; this matches the EventReloader lifecycle documented on Run / NewEventReloader.

When target or notifier is nil, Close is a no-op and may be called at any time.

func (*EventReloader) Run added in v0.2.1

func (r *EventReloader) Run(ctx context.Context) error

Run blocks until ctx is cancelled or Close is called. Run MUST be called at most once. When target or notifier is nil, Run returns immediately as a no-op.

type Hit added in v0.2.1

type Hit struct {
	DatasetID  string
	DocName    string
	Layer      Layer
	Content    string
	Score      float64
	ChunkIndex int            // -1 for layer hits
	Sig        DerivedSig     // freshness traceability
	Metadata   map[string]any // backend-passthrough metadata
}

Hit is one ranked search result.

func FuseHits added in v0.2.1

func FuseHits(perRetriever [][]Hit, k int) []Hit

FuseHits is a free-function form of RRF fusion, useful for tooling that already has per-retriever Hit lists. It is the building block used by RRFRanker.Rank.

type Layer added in v0.2.1

type Layer string

Layer indicates the granularity of a search result.

The pre-v0.3.0 name was ContextLayer; that name remains exported as a type alias in model.go so callers using either spelling keep compiling.

const (
	LayerAbstract Layer = "L0" // ~100 token one-sentence summary
	LayerOverview Layer = "L1" // ~1k token structured overview
	LayerDetail   Layer = "L2" // full chunk content
)

type LayerQuery added in v0.2.1

type LayerQuery struct {
	DatasetIDs []string
	Layer      Layer
	Text       string
	Vector     []float32
	Mode       Mode
	TopK       int
}

LayerQuery is the recall input for layer-tier searches.

type LayerRepo added in v0.2.1

type LayerRepo interface {
	Put(ctx context.Context, layer DerivedLayer) error
	Get(ctx context.Context, datasetID, docName string, layer Layer) (*DerivedLayer, error)
	DeleteByDoc(ctx context.Context, datasetID, docName string) error
	DeleteByDataset(ctx context.Context, datasetID string) error
	Search(ctx context.Context, q LayerQuery) ([]Candidate, error)
}

LayerRepo persists DerivedLayers and supports layer-scoped recall.

type LayerRetriever added in v0.2.1

type LayerRetriever struct {
	Layers   LayerRepo
	Embedder Embedder
}

LayerRetriever queries LayerRepo for L0/L1 hits. It activates only when Query.Layer is LayerAbstract or LayerOverview; LayerDetail queries are routed to chunk-tier retrievers instead.

Embedder is consulted only for vector lanes (ModeVector / ModeHybrid).

func NewLayerRetriever added in v0.2.1

func NewLayerRetriever(l LayerRepo, e Embedder) *LayerRetriever

NewLayerRetriever constructs a LayerRetriever; pass a nil embedder to disable the vector lane.

func (*LayerRetriever) Name added in v0.2.1

func (r *LayerRetriever) Name() string

Name implements Retriever.

func (*LayerRetriever) Recall added in v0.2.1

func (r *LayerRetriever) Recall(ctx context.Context, q Query) ([]Candidate, error)

Recall implements Retriever. Mode handling:

  • ModeBM25: BM25-only.
  • ModeVector: vector-only. Returns nil when Embedder is missing or q.Text is empty — matches BM25Retriever / VectorRetriever's "mode I cannot serve → nil" contract instead of silently downgrading to BM25 (issue #156). A misconfigured deployment gets a loud empty result rather than a different lane's answer masquerading as vector.
  • ModeHybrid: hybrid when Embedder is wired; documented graceful degrade to BM25-only when Embedder is nil.

type Mode added in v0.2.1

type Mode = SearchMode

Mode is an alias for SearchMode, kept so newer call-sites can read "knowledge.Mode" without the redundant Search prefix while older "knowledge.SearchMode" call sites keep working unchanged.

func ResolveMode added in v0.2.1

func ResolveMode(m Mode) Mode

ResolveMode normalises zero values to a canonical Mode.

  • "" -> ModeBM25 (legacy default)

Any other recognised mode is returned unchanged.

type Query added in v0.2.1

type Query struct {
	DatasetID string
	Scope     Scope
	Text      string
	Mode      Mode
	Layer     Layer
	TopK      int
	Threshold float64
	// contains filtered or unexported fields
}

Query is the canonical search input.

Validation rules (enforced by Service.Search):

  • Layer must be valid; defaults to LayerDetail when zero.
  • Mode must be valid; defaults to ModeBM25 when zero.
  • Scope=ScopeSingleDataset requires DatasetID to be non-empty.

type RRFRanker added in v0.2.1

type RRFRanker struct {
	K int
}

RRFRanker performs reciprocal-rank fusion across candidates grouped by Candidate.Source. K defaults to DefaultRRFK when zero.

func NewRRFRanker added in v0.2.1

func NewRRFRanker() *RRFRanker

NewRRFRanker constructs an RRFRanker with sensible defaults.

func (*RRFRanker) Rank added in v0.2.1

func (r *RRFRanker) Rank(candidates []Candidate, q Query) []Hit

Rank fuses candidates and truncates to q.TopK (zero means "no limit").

Threshold filtering is applied AFTER fusion using the fused score: candidates whose fused score is below q.Threshold are dropped. When q.Threshold is zero, no filtering is applied.

type Ranker added in v0.2.1

type Ranker interface {
	Rank(candidates []Candidate, q Query) []Hit
}

Ranker fuses candidates from multiple Retrievers into ordered Hits.

type RebuildScope added in v0.2.1

type RebuildScope struct {
	DatasetID string // "" means all datasets
	DocName   string // "" means all documents in the dataset
}

RebuildScope narrows what Rebuilder.Rebuild touches. Zero value means "everything".

type Rebuilder added in v0.2.1

type Rebuilder interface {
	Rebuild(ctx context.Context, scope RebuildScope) error
}

Rebuilder is the consumer side of the change-driven reload pipeline. Service satisfies this interface; EventReloader invokes Rebuild on the trailing edge of a debounce window over an EventNotifier stream.

type ReloaderOptions

type ReloaderOptions struct {
	Debounce       time.Duration
	RebuildTimeout time.Duration
}

ReloaderOptions configures EventReloader.

  • Debounce controls the trailing-edge window.
  • RebuildTimeout caps each rebuild call.

type Result added in v0.2.1

type Result struct {
	Hits []Hit
}

Result wraps the ordered hit list returned by Service.Search.

type Retriever added in v0.2.1

type Retriever interface {
	// Name returns a stable identifier used by Rankers (e.g. "bm25").
	Name() string
	// Recall fetches up to q.TopK candidates for q.
	Recall(ctx context.Context, q Query) ([]Candidate, error)
}

Retriever produces a candidate set for a Query. Implementations are stateless with respect to the Query; all state lives in the underlying repos.

type Scope added in v0.2.1

type Scope int

Scope expresses the dataset breadth of a query.

const (
	// ScopeSingleDataset restricts the search to Query.DatasetID.
	ScopeSingleDataset Scope = iota
	// ScopeAllDatasets searches across every known dataset.
	ScopeAllDatasets
)

type SearchEngine added in v0.2.1

type SearchEngine struct {
	Chunk []Retriever // chunk-tier retrievers (LayerDetail)
	Layer []Retriever // layer-tier retrievers (LayerAbstract / LayerOverview)
	Rank  Ranker
}

SearchEngine routes a Query through Retrievers, then a Ranker.

Behaviour:

  • LayerDetail queries fan out to chunk-tier Retrievers (BM25/Vector).
  • Layer{Abstract,Overview} queries fan out to layer-tier Retrievers.
  • ModeHybrid runs both BM25 and Vector recall paths and lets the Ranker fuse them (RRF by default).

func NewSearchEngine added in v0.2.1

func NewSearchEngine(chunk, layer []Retriever, ranker Ranker) *SearchEngine

NewSearchEngine assembles a SearchEngine. ranker may be nil; the Service will substitute a default RRFRanker.

func (*SearchEngine) Search added in v0.2.1

func (e *SearchEngine) Search(ctx context.Context, q Query) (*Result, error)

Search runs the engine for one Query. Validation is the caller's job (Service.Search performs it before delegating).

type SearchMode

type SearchMode string

SearchMode chooses the retrieval algorithm. Legacy callers that pass the empty string are normalised to ModeBM25 by ResolveMode().

const (
	ModeBM25   SearchMode = "bm25"
	ModeVector SearchMode = "vector"
	ModeHybrid SearchMode = "hybrid"
)

type Service added in v0.2.1

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

Service orchestrates document lifecycle, derived-data persistence and search. All public Knowledge entry points (graph node, tool adapters) route through Service so contract guarantees live in one place.

func NewService added in v0.2.1

func NewService(
	docs DocumentRepo,
	chunks ChunkRepo,
	layers LayerRepo,
	engine *SearchEngine,
	opts ServiceOptions,
) *Service

NewService constructs a Service from explicit repositories.

Most callers should use NewLocalService / NewRetrievalService instead; this entry point exists so out-of-tree backends can be wired the same way the built-ins are.

func (*Service) DatasetLayer added in v0.2.1

func (s *Service) DatasetLayer(ctx context.Context, datasetID string, layer Layer) (string, error)

DatasetLayer reads a dataset-level layer; returns "" without error when missing.

func (*Service) DeleteDocument added in v0.2.1

func (s *Service) DeleteDocument(ctx context.Context, datasetID, name string) error

DeleteDocument removes the document and all its derived data (chunks + layers). Errors from chunk/layer cleanup are returned after a best-effort attempt so a single failure does not leave the document orphaned in DocumentRepo.

func (*Service) GetDocument added in v0.2.1

func (s *Service) GetDocument(ctx context.Context, datasetID, name string) (*SourceDocument, error)

GetDocument returns the lossless SourceDocument (contract #4).

func (*Service) Layer added in v0.2.1

func (s *Service) Layer(ctx context.Context, datasetID, name string, layer Layer) (string, error)

Layer reads a document-level layer; returns "" without error when missing (contract: callers should treat absence as "not yet generated").

func (*Service) ListDatasets added in v0.2.1

func (s *Service) ListDatasets(ctx context.Context) ([]string, error)

ListDatasets enumerates every known dataset id.

func (*Service) ListDocuments added in v0.2.1

func (s *Service) ListDocuments(ctx context.Context, datasetID string) ([]SourceDocument, error)

ListDocuments returns SourceDocuments in the dataset. Implementations MAY omit Content for performance; FSDocumentRepo currently returns it.

func (*Service) PutDatasetLayer added in v0.2.1

func (s *Service) PutDatasetLayer(ctx context.Context, datasetID string, layer Layer, content string) error

PutDatasetLayer persists a dataset-level layer (DocName == "").

func (*Service) PutDocument added in v0.2.1

func (s *Service) PutDocument(ctx context.Context, datasetID, name, raw string) error

PutDocument writes raw content under (datasetID, name).

Behaviour:

  • SourceDocument.Version is incremented atomically by DocumentRepo.
  • DerivedChunks are recomputed and ChunkRepo.Replace is called.
  • DerivedLayers are NOT touched (layer generation is explicit; see PutDocumentLayer / PutDatasetLayer).

Atomicity model: chunk replacement happens AFTER the document write succeeds. A failure between the two leaves the document persisted but chunks stale; the next PutDocument or Rebuild restores consistency. Backends with native transactions can override this by composing repos that share a transaction.

func (*Service) PutDocumentLayer added in v0.2.1

func (s *Service) PutDocumentLayer(ctx context.Context, datasetID, name string, layer Layer, content string) error

PutDocumentLayer persists an LLM-derived layer for one document. Caller is expected to have produced content via GenerateDocumentContext.

func (*Service) Rebuild added in v0.2.1

func (s *Service) Rebuild(ctx context.Context, scope RebuildScope) error

Rebuild re-derives chunks for every document in the requested scope.

Freshness gate (issue #152): when the configured ChunkRepo implements ChunkSigReader, each doc's on-disk DerivedSig is compared against the current (SourceVer, ChunkerSig, EmbedSig) via DerivedSig.IsStale; up-to-date docs are skipped, stale docs are re-chunked and (if an embedder is configured) re-embedded. Backends that do NOT implement ChunkSigReader fall through to the unconditional re-chunk + re-embed path, so operators wanting the embedding-budget optimisation must wire a sig-aware ChunkRepo backend.

Layers are not regenerated automatically; callers drive layer rebuilds explicitly via PutDocumentLayer / PutDatasetLayer.

func (*Service) Search added in v0.2.1

func (s *Service) Search(ctx context.Context, q Query) (*Result, error)

Search executes the query through the configured SearchEngine.

Validation (the only place these checks live, contract #2):

  • q.Layer defaults to LayerDetail when zero; rejected otherwise.
  • q.Mode defaults to ModeBM25 when zero; rejected otherwise.
  • q.Scope=ScopeSingleDataset requires q.DatasetID to be non-empty.

For ScopeAllDatasets the dataset list is resolved once via DocumentRepo.ListDatasets and pushed down to retrievers via the unexported resolvedDatasets field.

func (*Service) SearchDocuments added in v0.3.10

func (s *Service) SearchDocuments(ctx context.Context, q Query) (*Result, error)

SearchDocuments executes the query at DOC granularity: each Hit represents one matching docName scored over the entire document rather than per chunk. Use this when the consumer (e.g. eval/beir or any IR benchmark with doc-level qrels) needs an apples-to-apples doc-level ranking; for RAG-style top-K-chunks-to-LLM workflows keep using Search.

Requires the underlying ChunkRepo to implement DocLevelSearcher. Backends that do not — returning ErrDocLevelSearchUnsupported — are explicitly NOT folded back to a chunk-level + collapse fallback; that fallback is exactly what landing this API is meant to retire. See #126.

The returned Hits have ChunkIndex = -1 and Layer = "" (doc-level results have no specific chunk). Validation matches Search except the Layer field is ignored (always doc-granular).

type ServiceOptions added in v0.2.1

type ServiceOptions struct {
	// Chunker overrides the default chunker; nil means "use
	// NewDefaultChunker(DefaultChunkConfig())".
	Chunker Chunker
	// Embedder enables vector indexing and semantic search; nil
	// disables vector lanes.
	Embedder Embedder
	// EmbedSig is stamped onto every DerivedSig produced while this
	// Service runs. Embedder doesn't expose a model identifier, so
	// callers (typically the Factory) supply one. Empty string means
	// "use the embedder's Go type name", which is good enough for
	// freshness checks within a single binary but not stable across
	// processes — production wiring should set it explicitly.
	EmbedSig string
	// Now overrides the clock; nil means time.Now (unit-test hook).
	Now func() time.Time
}

ServiceOptions configures a Service. Nil-friendly: every field is optional and falls back to a sensible default.

type SimpleTokenizer

type SimpleTokenizer = textsearch.SimpleTokenizer

Type and function aliases re-exported from sdk/textsearch for backward compatibility. Internal code and tests can use these without changing import paths.

type SourceDocument added in v0.2.1

type SourceDocument struct {
	DatasetID string
	Name      string
	Content   string
	Metadata  map[string]string

	// Version is monotonically incremented on every successful Put.
	// Derived data uses it as a freshness key.
	Version   uint64
	UpdatedAt time.Time
}

SourceDocument is the canonical, lossless representation of user input.

It is the single source of truth for a document; every DerivedChunk and DerivedLayer carries a DerivedSig that points back to a particular SourceDocument.Version, so derived data can be detected as stale and recomputed deterministically.

type Tokenizer

type Tokenizer = textsearch.Tokenizer

Type and function aliases re-exported from sdk/textsearch for backward compatibility. Internal code and tests can use these without changing import paths.

type VectorRetriever added in v0.2.1

type VectorRetriever struct {
	Chunks   ChunkRepo
	Embedder Embedder
}

VectorRetriever queries ChunkRepo with Mode=ModeVector. Embedder is invoked lazily; nil disables the retriever (Recall returns nil).

func NewVectorRetriever added in v0.2.1

func NewVectorRetriever(c ChunkRepo, e Embedder) *VectorRetriever

NewVectorRetriever constructs a VectorRetriever; pass a nil embedder to disable the lane (Recall short-circuits).

func (*VectorRetriever) Name added in v0.2.1

func (r *VectorRetriever) Name() string

Name implements Retriever.

func (*VectorRetriever) Recall added in v0.2.1

func (r *VectorRetriever) Recall(ctx context.Context, q Query) ([]Candidate, error)

Recall implements Retriever. ModeBM25 queries are skipped; ModeHybrid produces both lanes and the Ranker fuses them.

Directories

Path Synopsis
backend
fs
Package fs implements knowledge.DocumentRepo, knowledge.ChunkRepo and knowledge.LayerRepo on top of any workspace.Workspace.
Package fs implements knowledge.DocumentRepo, knowledge.ChunkRepo and knowledge.LayerRepo on top of any workspace.Workspace.
retrieval
Package retrieval implements knowledge.ChunkRepo and knowledge.LayerRepo on top of any retrieval.Index.
Package retrieval implements knowledge.ChunkRepo and knowledge.LayerRepo on top of any retrieval.Index.
Package factory wires the canonical knowledge.Service stacks.
Package factory wires the canonical knowledge.Service stacks.

Jump to

Keyboard shortcuts

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