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
- Variables
- func ChunkConfigSig(c ChunkConfig) string
- func CosineSimilarity(a, b []float32) float64
- func IsValidLayer(l Layer) bool
- func IsValidMode(m Mode) bool
- type BM25Retriever
- type CJKTokenizer
- type Candidate
- type ChangeEvent
- type ChunkConfig
- type ChunkQuery
- type ChunkRepo
- type ChunkSigReader
- type ChunkSpec
- type Chunker
- type ContextLayer
- type CorpusStats
- type DatasetContext
- type DerivedChunk
- type DerivedLayer
- type DerivedSig
- type DocLevelSearcher
- type DocVectorSearcher
- type DocumentContext
- type DocumentRepo
- type DocumentSummary
- type Embedder
- type EventKind
- type EventNotifier
- type EventReloader
- type Hit
- type Layer
- type LayerQuery
- type LayerRepo
- type LayerRetriever
- type Mode
- type Query
- type RRFRanker
- type Ranker
- type RebuildScope
- type Rebuilder
- type ReloaderOptions
- type Result
- type Retriever
- type Scope
- type SearchEngine
- type SearchMode
- type Service
- func (s *Service) DatasetLayer(ctx context.Context, datasetID string, layer Layer) (string, error)
- func (s *Service) DeleteDocument(ctx context.Context, datasetID, name string) error
- func (s *Service) GetDocument(ctx context.Context, datasetID, name string) (*SourceDocument, error)
- func (s *Service) Layer(ctx context.Context, datasetID, name string, layer Layer) (string, error)
- func (s *Service) ListDatasets(ctx context.Context) ([]string, error)
- func (s *Service) ListDocuments(ctx context.Context, datasetID string) ([]SourceDocument, error)
- func (s *Service) PutDatasetLayer(ctx context.Context, datasetID string, layer Layer, content string) error
- func (s *Service) PutDocument(ctx context.Context, datasetID, name, raw string) error
- func (s *Service) PutDocumentLayer(ctx context.Context, datasetID, name string, layer Layer, content string) error
- func (s *Service) Rebuild(ctx context.Context, scope RebuildScope) error
- func (s *Service) Search(ctx context.Context, q Query) (*Result, error)
- func (s *Service) SearchDocuments(ctx context.Context, q Query) (*Result, error)
- type ServiceOptions
- type SimpleTokenizer
- type SourceDocument
- type Tokenizer
- type VectorRetriever
Constants ¶
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.
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.
const DefaultRRFK = 60
DefaultRRFK is the conventional fusion constant for reciprocal-rank fusion. Smaller K weights the head of each ranked list more heavily.
const DefaultThreshold = 0.1
DefaultThreshold is the BM25-scale relevance floor used by legacy search paths. Service-driven Search uses Query.Threshold instead.
Variables ¶
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
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
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
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.
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
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
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
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
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
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
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 ¶
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 ¶
DocumentSummary pairs a document name with its L0 abstract, used as input to GenerateDatasetContext.
type 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.
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.
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.
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.
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
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
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
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.
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 ¶
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 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.
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
DatasetLayer reads a dataset-level layer; returns "" without error when missing.
func (*Service) DeleteDocument ¶ added in v0.2.1
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
GetDocument returns the lossless SourceDocument (contract #4).
func (*Service) Layer ¶ added in v0.2.1
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
ListDatasets enumerates every known dataset id.
func (*Service) ListDocuments ¶ added in v0.2.1
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
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
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
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
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.
Source Files
¶
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. |