Documentation
¶
Index ¶
- func DecodeEmbedding(s string) ([]float32, error)
- func EncodeEmbedding(vec []float32) string
- func TruncateNormalize(vec []float32, dims int) []float32
- func WithTimingSink(ctx context.Context, fn func(SearchTiming)) context.Context
- type CellNeighborhood
- type CellSearchResult
- type CellStore
- func (s *CellStore) CollectionSize() int64
- func (s *CellStore) Delete(_ context.Context, id string) error
- func (s *CellStore) GetRecentBySubject(_ context.Context, partition, service string, topN int, since time.Time) ([]IndexEntry, error)
- func (s *CellStore) Index(ctx context.Context, doc IndexEntry) ([]float32, error)
- func (s *CellStore) Neighborhood(id string, topK, edgesPerNeighbor int, keep func(IndexEntry) bool) (*CellNeighborhood, bool)
- func (s *CellStore) QueryEmbedCacheEntries() int
- func (s *CellStore) QueryEmbedCacheStats() QueryEmbedCacheStats
- func (s *CellStore) Search(ctx context.Context, query string, topK int, kindFilter string) ([]CellSearchResult, error)
- func (s *CellStore) SearchByEmbedding(_ context.Context, partition string, embedding []float32, since time.Time, ...) (*CellSearchResult, error)
- func (s *CellStore) SearchExhaustive(ctx context.Context, query, subject string, kindFilter string) ([]CellSearchResult, error)
- func (s *CellStore) SearchExhaustiveTimed(ctx context.Context, query, kind string, keep func(IndexEntry) bool) ([]CellSearchResult, SearchTiming, error)
- func (s *CellStore) SearchSince(ctx context.Context, query string, topK int, kind string, since time.Time) ([]CellSearchResult, error)
- func (s *CellStore) SearchSubject(ctx context.Context, query, subject string, topK int, kindFilter string) ([]CellSearchResult, error)
- func (s *CellStore) SearchTimed(ctx context.Context, query string, topK int, kindFilter string) ([]CellSearchResult, SearchTiming, error)
- func (s *CellStore) SeedFromEmbedding(doc IndexEntry, embedding []float32) bool
- func (s *CellStore) SeedOrEmbed(ctx context.Context, cell *memoryv1.MemoryCell, doc IndexEntry) (embedded string, err error)
- func (s *CellStore) SetQueryEmbedCounter(fn func(hit bool))
- func (s *CellStore) SetScoreGap(float32)deprecated
- type EmbedMetrics
- type Embedder
- func (e *Embedder) EmbedBatch(ctx context.Context, texts []string) ([][]float32, error)
- func (e *Embedder) EmbedDocument(ctx context.Context, text string) ([]float32, error)
- func (e *Embedder) EmbedQuery(ctx context.Context, text string) ([]float32, error)
- func (e *Embedder) Metrics() EmbedMetrics
- func (e *Embedder) Model() string
- func (e *Embedder) OutputDims() int
- func (e *Embedder) SetErrorSink(fn ErrorFunc)
- func (e *Embedder) SetUsageSink(fn UsageFunc)
- type ErrorFunc
- type IndexEntry
- type NeighborEdge
- type QueryEmbedCacheStats
- type SearchTiming
- type UsageFunc
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func DecodeEmbedding ¶
DecodeEmbedding deserializes a base64 string produced by EncodeEmbedding back to a float32 vector. Returns nil for an empty string.
func EncodeEmbedding ¶
EncodeEmbedding serializes a float32 vector to a base64 string (little-endian). Returns an empty string for a nil or empty slice.
func TruncateNormalize ¶
TruncateNormalize applies the Matryoshka reduction: keep the first dims components, then L2-renormalize. dims <= 0 or dims >= len(vec) returns the vector unchanged (still renormalized when truncated). Renormalization is required — a raw slice of a unit vector is not itself a unit vector, so cosine distances would be skewed without it.
func WithTimingSink ¶
func WithTimingSink(ctx context.Context, fn func(SearchTiming)) context.Context
WithTimingSink asks Search, SearchSubject and SearchExhaustive to report their phase split to fn.
Types ¶
type CellNeighborhood ¶
type CellNeighborhood struct {
Center IndexEntry
Neighbors []CellSearchResult
Edges []NeighborEdge
}
CellNeighborhood is the semantic neighbourhood of one cell: its nearest neighbours and the links among them that mark sub-clusters.
type CellSearchResult ¶
type CellSearchResult struct {
Document IndexEntry
Score float32
}
CellSearchResult is a memory cell returned by CellStore.Search.
func CutAtGap ¶
func CutAtGap(results []CellSearchResult, gap float32) []CellSearchResult
CutAtGap drops the tail of results (sorted by Score, best first) that falls more than gap below the top score. A zero or negative gap keeps everything.
func CutAtLargestGap ¶
func CutAtLargestGap(results []CellSearchResult) []CellSearchResult
CutAtLargestGap drops the tail of results (sorted by Score, best first) at their steepest score drop. On a corpus whose documents resemble each other, every document scores something against every query, so a query with one right answer still fills the window with near-ties a few points below it. The signal is not the absolute score, which moves with the query, but where the list falls off a cliff; keeping everything above the largest single drop adapts to how many answers the query has, which a fixed threshold cannot. It trades recall for precision, so it is for a caller that wants it.
type CellStore ¶
type CellStore struct {
// contains filtered or unexported fields
}
CellStore holds a per-subject HNSW index. Each subject's cells are isolated so cross-subject interference is impossible.
func NewCellStore ¶
NewCellStore creates an empty CellStore.
func (*CellStore) CollectionSize ¶
CollectionSize returns total indexed cells across all subjects.
func (*CellStore) Delete ¶
Delete removes a memory cell by ID across all subject indexes. The graph is rebuilt lazily before the next search (see subjectIndex.dirty), so a sweep that deletes hundreds of cells costs one rebuild.
func (*CellStore) GetRecentBySubject ¶
func (s *CellStore) GetRecentBySubject(_ context.Context, partition, service string, topN int, since time.Time) ([]IndexEntry, error)
GetRecentBySubject returns up to topN cells of one service, oldest first, inside the "since" window, keeping the newest when topN cuts. Only cells that carry a Disposition are returned. A cell is visible when its Partition equals partition or is empty; one naming another partition is never returned, so a same-named service in two partitions never shares history.
func (*CellStore) Neighborhood ¶
func (s *CellStore) Neighborhood(id string, topK, edgesPerNeighbor int, keep func(IndexEntry) bool) (*CellNeighborhood, bool)
Neighborhood returns the semantic neighbourhood of the cell with the given id, searched in the index of the subject that holds it: subjects never share a graph. The second result is false when the id has no embedding in any index (never seeded, private, or since deleted), so a caller can tell "not indexed" from "no neighbours". keep is an optional per-cell filter; nil keeps all.
Neighbour-to-neighbour edges follow a scale-free rule, not an absolute similarity threshold: an edge survives only when the two cells are more similar to each other than either is to the centre, and then only if it is among each endpoint's edgesPerNeighbor strongest such links (symmetrised, the union, so a hub may end up with a higher degree). Embeddings of same-kind cells crowd into a narrow high band, so any fixed threshold either admits the complete graph or is retuned per corpus and per embedder. "Tighter to each other than to the centre" is what sub-cluster means and holds at any scale. A neighbourhood with no sub-structure returns no edges: a star.
func (*CellStore) QueryEmbedCacheEntries ¶
QueryEmbedCacheEntries is how many queries the query-embedding cache holds; 0 when the store has no embedder.
func (*CellStore) QueryEmbedCacheStats ¶
func (s *CellStore) QueryEmbedCacheStats() QueryEmbedCacheStats
QueryEmbedCacheStats reads the query-embedding cache counters; zero when the store has no cache.
func (*CellStore) Search ¶
func (s *CellStore) Search(ctx context.Context, query string, topK int, kindFilter string) ([]CellSearchResult, error)
Search returns the top-k results across every subject's index. An optional kindFilter (e.g. "stable_fact") restricts them to one kind. Subject-scoped search is SearchSubject.
func (*CellStore) SearchByEmbedding ¶
func (s *CellStore) SearchByEmbedding(_ context.Context, partition string, embedding []float32, since time.Time, minSimilarity float32, source string) (*CellSearchResult, error)
SearchByEmbedding returns the single most similar cell above minSimilarity (cosine) among the cells that carry a Disposition, are visible to partition (their Partition equals it or is empty), fall inside the "since" window and, when source is not empty, name exactly that Source.
The source guard is a correctness guard: templated texts embed almost identically across sources, so the nearest vector alone can belong to another source whose recorded outcome does not apply. The partition rule is the one GetRecentBySubject applies.
func (*CellStore) SearchExhaustive ¶
func (s *CellStore) SearchExhaustive(ctx context.Context, query, subject string, kindFilter string) ([]CellSearchResult, error)
SearchExhaustive ranks every entry the caller may see by exact cosine, with no graph and no fetch window: the subject's own cells and the global ones, or every subject's when subject is empty. It exists for one caller. A graph search returns its top-K window, and a caller that drops hits afterwards (a cell deleted or retired on another replica is still indexed here) can see the whole window rejected although other cells would have qualified. This hands back the full ranked population so the caller's filter gets every candidate. No score gap is applied.
func (*CellStore) SearchExhaustiveTimed ¶
func (s *CellStore) SearchExhaustiveTimed(ctx context.Context, query, kind string, keep func(IndexEntry) bool) ([]CellSearchResult, SearchTiming, error)
SearchExhaustiveTimed ranks every entry of every subject against the query by exact cosine, with no graph and no fetch window, optionally restricted to a kind and narrowed by keep before scoring. A graph search returns its top-K window, and a caller that rejects hits afterwards (visibility, state) can see the whole window rejected although other cells would have qualified; this hands back the full ranked population. keep is a pre-filter for a caller that can already tell from the entry (its tags, say) that it would reject a cell. No score gap is applied.
func (*CellStore) SearchSince ¶
func (s *CellStore) SearchSince(ctx context.Context, query string, topK int, kind string, since time.Time) ([]CellSearchResult, error)
SearchSince returns the top-k cells of one kind most similar to the query text that carry a Timestamp inside the "since" window (a zero since keeps the whole kind). A caller names its own kinds; the index knows none.
func (*CellStore) SearchSubject ¶
func (s *CellStore) SearchSubject(ctx context.Context, query, subject string, topK int, kindFilter string) ([]CellSearchResult, error)
SearchSubject searches within the given subject's index and merges results from the global (empty subject) index, returning top-k by score.
func (*CellStore) SearchTimed ¶
func (s *CellStore) SearchTimed(ctx context.Context, query string, topK int, kindFilter string) ([]CellSearchResult, SearchTiming, error)
SearchTimed is Search that also returns the embed-versus-index split.
func (*CellStore) SeedFromEmbedding ¶
func (s *CellStore) SeedFromEmbedding(doc IndexEntry, embedding []float32) bool
SeedFromEmbedding inserts a pre-embedded cell without calling the embedding API. It returns false, inserting nothing, when the vector's length disagrees with the store dimension (a cell embedded by another model): the caller should re-embed it. An empty embedding has nothing to seed and returns true.
The dimension is the embedder's configured output dimension when known, so a stale wrong-dimension cell seeded first cannot lock the store to a wrong shape; a seeded vector only sets it when it is not known.
func (*CellStore) SeedOrEmbed ¶
func (s *CellStore) SeedOrEmbed(ctx context.Context, cell *memoryv1.MemoryCell, doc IndexEntry) (embedded string, err error)
SeedOrEmbed makes sure a cell is in the index. A cell that carries an embedding which decodes and has the index's dimension is seeded from it, and nothing needs writing: the returned string is empty. A cell with none, or with one that cannot be decoded or has the wrong dimension (it was embedded by another model), is embedded now, and the returned string is the new embedding for the caller to persist on the cell. doc is the cell's index entry.
func (*CellStore) SetQueryEmbedCounter ¶
SetQueryEmbedCounter sets where the query-embedding cache reports each lookup's outcome, true for a hit. Call before the store is searched.
func (*CellStore) SetScoreGap
deprecated
SetScoreGap is kept for callers that still set a cutoff. The store no longer cuts its results: a cutoff applied here, before the caller has filtered what it may see and fused the semantic channel with the lexical one, starves a caller whose answers sit a few points below a hit it cannot read. A caller that wants a cutoff applies CutAtGap or CutAtLargestGap to the results it kept.
Deprecated: it has no effect.
type EmbedMetrics ¶
type EmbedMetrics struct {
CacheHits int64 `json:"cacheHits"`
EmbedCalls int64 `json:"embedCalls"`
}
EmbedMetrics holds cache and API call counters.
type Embedder ¶
type Embedder struct {
// contains filtered or unexported fields
}
Embedder generates vector embeddings. It has two modes:
Vertex AI (genai != nil): uses google.golang.org/genai SDK with BackendVertexAI, authenticated via Workload Identity (ADC). Created when endpoint == "".
Gateway (endpoint != ""): routes through an OpenAI-compatible /v1/embeddings endpoint.
func NewEmbedder ¶
func NewEmbedder(ctx context.Context, endpoint, model, project, location string, outputDims, maxInputChars int, logger zerolog.Logger) (*Embedder, error)
NewEmbedder creates an Embedder. When endpoint == "", uses Vertex AI via Workload Identity (ADC); project and location are required. When endpoint != "", routes through the OpenAI-compatible gateway; project and location are unused. outputDims reduces the output vector dimension (0 = model default). Applied to Vertex AI natively, and to the gateway via the OpenAI-compatible "dimensions" param (e.g. Matryoshka models like nomic-embed-text truncate cleanly to 128/256/512). maxInputChars hard-caps input length before the embed call (0 = defaultMaxInputChars); set it below the model's token window × ~4 to avoid the gateway rejecting oversized input.
func (*Embedder) EmbedBatch ¶
EmbedBatch returns embeddings for multiple texts, preserving input order. It issues up to embedBatchConcurrency requests concurrently (see the const) and fails fast on the first error, cancelling the rest.
func (*Embedder) EmbedDocument ¶
EmbedDocument returns the embedding vector for a document to be indexed.
func (*Embedder) EmbedQuery ¶
EmbedQuery returns the embedding vector for a search query.
func (*Embedder) Metrics ¶
func (e *Embedder) Metrics() EmbedMetrics
Metrics returns a snapshot of cache hit and API call counters.
func (*Embedder) OutputDims ¶
OutputDims returns the configured Matryoshka output dimension. 0 means the model's own size, with no truncation.
func (*Embedder) SetErrorSink ¶
SetErrorSink reports every failed provider request this embedder makes. Set it before the embedder is shared.
func (*Embedder) SetUsageSink ¶
SetUsageSink accounts every provider request this embedder makes. The sink may be called from several goroutines (EmbedBatch). Set it before the embedder is shared.
type ErrorFunc ¶
ErrorFunc receives every failed provider request: the model, the endpoint path it went to ("/v1/embeddings" for a gateway, "vertex" for Vertex AI) and the error. ctx is the caller's, so a receiver can tell a cancelled caller from a failing endpoint.
type IndexEntry ¶
type IndexEntry struct {
ID string
Kind string
Scope string
Subject string
Title string
Content string
Confidence string
Status string
Tags []string
EventDate string // RFC3339, only set for kind=event
// Timestamp is when the underlying occurrence happened; the "since" window of
// GetRecentBySubject and SearchByEmbedding filters on it.
Timestamp time.Time
// Source is where the occurrence came from. SearchByEmbedding can require an
// exact match, so a near-identical text from another source is not a hit.
Source string
// Service is the key GetRecentBySubject filters on.
Service string
// Disposition is the outcome recorded for the occurrence. Empty while it is
// still in flight; SearchByEmbedding and GetRecentBySubject skip such cells.
Disposition string
// Partition is the partition the cell belongs to. Empty means visible to
// every partition; a cell naming another partition is never returned.
Partition string
// GroupID ties the cell to a caller-side group (may be empty).
GroupID string
// Type, Message, Severity and Classification are free labels the caller
// reads back from a hit.
Type, Message, Severity, Classification string
}
IndexEntry is the per-cell record indexed in CellStore: the searchable metadata projection of a memory cell (Title+Content is what gets embedded; the other fields drive filtering). Not a document/file.
type NeighborEdge ¶
NeighborEdge is one neighbour-to-neighbour similarity link. Score uses the same [0,1] mapping as CellSearchResult.Score, so an edge and a neighbour score compare directly.
type QueryEmbedCacheStats ¶
QueryEmbedCacheStats is a point-in-time counter readout for observability.
type SearchTiming ¶
SearchTiming is the embed-versus-index split of one search: an embedder latency spike and an in-memory scan spike are indistinguishable from the total.
type UsageFunc ¶
type UsageFunc func(ctx context.Context, model string, latency time.Duration, inputTokens int64, failed bool)
UsageFunc receives every provider request the embedder makes: the model, how long it took, its input tokens (the provider's count, or an estimate of 4 characters per token when it gives none) and whether it failed. A request answered from the embedder's cache is not reported. ctx is the caller's, so a receiver can attribute the call.