search

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Jun 29, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package search implements V0 retrieval over the SQLite FTS5 index. It exposes a Retriever seam (bm25 now, hybrid later) and an FTS5-backed engine that MATCHes a sanitized query, ranks with bm25() column weights, applies small deterministic Go-side boosts, and enforces exact document filters.

Index

Constants

This section is empty.

Variables

View Source
var ErrEmptyQuery = errors.New("search: empty query after sanitization")

ErrEmptyQuery is returned when a query reduces to no searchable terms after sanitization (e.g. it was empty or pure punctuation).

Functions

func Reindex

func Reindex(ctx context.Context, db *sql.DB, emb embed.Embedder, logger *slog.Logger) (int, error)

Reindex (re)computes and stores a vector for every chunk in db using emb. It embeds in batches and commits every reindexCommitEvery chunks so the single SQLite writer is not held for the whole run; the ON DELETE CASCADE on embeddings means re-ingesting a document already drops its stale vectors, so Reindex only needs to (re)write current chunks. It returns the number of vectors written.

Types

type Engine

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

Engine is the FTS5-backed Retriever. It MATCHes a sanitized query against chunks_fts, ranks with bm25() column weights, then applies a small deterministic heading boost and re-sorts so higher score = better.

func NewEngine

func NewEngine(db *sql.DB, logger *slog.Logger) *Engine

NewEngine builds an FTS5 Engine over db. A nil logger is replaced with a discard logger so the engine never panics on a missing dependency.

func (*Engine) Search

func (e *Engine) Search(ctx context.Context, q Query) ([]model.Result, error)

Search implements Retriever. It sanitizes q.Text into a safe FTS5 MATCH expression, runs the ranked join with q's exact filters applied, converts the negative bm25 rank into a positive score, layers the heading boost, and returns results sorted best-first. An empty query (after sanitization) is a friendly ErrEmptyQuery rather than an FTS5 syntax error.

type HybridRetriever

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

HybridRetriever fuses a lexical (bm25) Retriever and a vector Retriever with Reciprocal Rank Fusion. It implements Retriever, so MCP/CLI callers are unchanged. When the vector side yields nothing (no embeddings, or a no-op embedder), fusion of a single non-empty list reproduces the lexical ranking — so hybrid degrades gracefully to bm25-only.

func NewHybridRetriever

func NewHybridRetriever(lexical, vector Retriever, logger *slog.Logger) *HybridRetriever

NewHybridRetriever builds a HybridRetriever from a lexical and a vector Retriever. A nil logger is replaced with a discard logger.

func (*HybridRetriever) Search

func (h *HybridRetriever) Search(ctx context.Context, q Query) ([]model.Result, error)

Search implements Retriever. It runs both retrievers at a widened candidate depth, fuses them with RRF, and truncates to q.Limit. A lexical query that sanitizes to empty is tolerated when the vector side still returns hits; ErrEmptyQuery surfaces only when neither side can produce results.

type Query

type Query struct {
	// Text is the raw user query string. It may contain arbitrary punctuation;
	// the engine sanitizes it before handing it to FTS5 MATCH.
	Text string
	// Collection, when set, restricts results to documents.collection = it.
	Collection string
	// PathPrefix, when set, restricts results to documents whose uri starts
	// with it (LIKE prefix match).
	PathPrefix string
	// FileType, when set, restricts results to documents whose uri ends with a
	// matching extension (e.g. "md" or ".md").
	FileType string
	// ModifiedSince, when set, restricts results to documents.modified_at >= it.
	// It is compared lexically, so an RFC3339 timestamp sorts correctly.
	ModifiedSince string
	// Limit caps the result count. The caller supplies the configured default
	// when it is not overridden on the command line.
	Limit int
}

Query is a single retrieval request. Text is the raw user query (sanitized into FTS5 barewords by the engine). The remaining fields are exact document filters compiled into the SQL WHERE clause; the zero value of each means "no constraint". Limit caps the number of returned results.

type Retriever

type Retriever interface {
	// Search runs q against the index and returns ranked results.
	Search(ctx context.Context, q Query) ([]model.Result, error)
}

Retriever is the retrieval seam: a query in, ranked results out. The FTS5 engine implements it now; a hybrid (vector + bm25) retriever can implement the same interface later without touching callers.

type VectorRetriever

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

VectorRetriever implements Retriever via a linear cosine scan over the stored embeddings. It embeds the query with an Embedder, then walks every vector for the embedder's model (cosine = dot, because vectors are L2-normalized) and returns the top-K. When the embedder is the no-op (default build) it degrades to returning no results so a HybridRetriever falls back to lexical-only.

func NewVectorRetriever

func NewVectorRetriever(db *sql.DB, emb embed.Embedder, logger *slog.Logger) *VectorRetriever

NewVectorRetriever builds a VectorRetriever over db using emb. A nil logger is replaced with a discard logger.

func (*VectorRetriever) Search

func (v *VectorRetriever) Search(ctx context.Context, q Query) ([]model.Result, error)

Search implements Retriever. It returns the top-K chunks by cosine similarity to the embedded query, applying the same exact document filters as the lexical engine. A query the embedder cannot handle (no-op build) yields no results and no error, so semantic search degrades gracefully to lexical-only.

Jump to

Keyboard shortcuts

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