feedback

package
v0.3.2 Latest Latest
Warning

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

Go to latest
Published: Sep 2, 2026 License: MIT Imports: 14 Imported by: 0

README

feedback

Relevance feedback (query reformulation from user feedback) — the interactive loop of the retrieval cycle.

Components

  • Label (LabelUnlabeled, relevant/irrelevant) and Feedback (NewFeedback(query, labels)) — one query plus per-chunk relevance labels.
  • Collector — accumulates feedback across a session; ErrNoFeedback when nothing usable has been collected.
  • RelevanceFeedback (NewRelevanceFeedback(searcher, getter, embedder)) — turns collected feedback into a reformulated query.

Algorithms

  • Rocchio (vector): Rocchio(query, relevant, irrelevant, params) — shifts the query vector toward relevant and away from irrelevant chunk embeddings (RocchioParams / DefaultRocchioParams).
  • Rocchio (terms): RocchioTerms(...) — adjusts the keyword side (TermRocchioParams), returning the reformulated query string and the term weights.
  • BoostRelevant(results, relevant) — reorders existing results to lift feedback-positive chunks.
  • Helpers: CosineSimilarity, L2Normalize, MeanVectors.

Interfaces VectorSearcher / ChunkGetter are defined so the package stays decoupled from store.

Documentation

Overview

Package feedback provides relevance feedback and query expansion for improving retrieval quality. It implements the classic Rocchio algorithm in both vector and lexical forms, and an iterative "expand-and-retrieve" loop.

All types are thread-safe and dependency-free. Feedback can be accumulated in a Collector and serialized to chunk metadata for future training.

Index

Constants

This section is empty.

Variables

View Source
var ErrNoFeedback = errors.New("feedback: no usable relevance feedback")

ErrNoFeedback is returned when a feedback round contains no usable relevance judgments or the referenced chunks cannot be resolved to embeddings.

Functions

func BoostRelevant

func BoostRelevant(results []index.SearchResult, relevant []string) []index.SearchResult

BoostRelevant stably partitions results so those whose chunk ID is in relevant come first, preserving the original order within each group.

func CosineSimilarity

func CosineSimilarity(a, b []float32) float64

CosineSimilarity returns the cosine similarity between two vectors. The shorter vector is zero-padded. Returns 0 for any zero vector.

func L2Normalize

func L2Normalize(v []float32) []float32

L2Normalize returns a unit-length copy of v. A zero vector is returned as-is.

func MeanVectors

func MeanVectors(vectors [][]float32) []float32

MeanVectors returns the element-wise mean of a set of vectors. Vectors of differing lengths are handled using the length of the first vector; shorter vectors contribute zero for their missing dimensions. Empty input returns an empty slice.

func Rocchio

func Rocchio(query []float32, relevant, irrelevant [][]float32, p RocchioParams) []float32

Rocchio applies the Rocchio algorithm in embedding space. It returns a new query vector shifted toward the centroid of relevant embeddings and away from the centroid of not-relevant embeddings.

All vectors are expected to have the same dimension as query. If relevant or irrelevant is empty, that term is simply omitted. If the input query is empty, the dimension is inferred from the feedback vectors; if none are supplied, an empty slice is returned.

func RocchioTerms

func RocchioTerms(query string, relevantTexts, irrelevantTexts []string, p TermRocchioParams) (string, map[string]float64)

RocchioTerms applies the Rocchio algorithm in term space. It returns an expanded query string (top terms by adjusted weight) plus the full term-weight map for inspection.

The adjusted weight of a term t is:

w(t) = freq_query(t) + Beta*meanFreq_relevant(t) - Gamma*meanFreq_irrelevant(t)

Negative weights are clamped to zero and zero-weight terms are dropped.

Types

type ChunkGetter

type ChunkGetter interface {
	// GetChunk returns a chunk by its ID.
	GetChunk(id string) (*core.Chunk, bool)
}

ChunkGetter returns a stored chunk by ID. It is satisfied by store.Store.

type Collector

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

Collector is a thread-safe in-memory store of feedback rounds. It acts as the "training store" for relevance feedback: accumulate human judgments here, then aggregate them to drive Rocchio expansion or offline model training.

func NewCollector

func NewCollector() *Collector

NewCollector creates an empty Collector.

func (*Collector) Add

func (c *Collector) Add(f *Feedback)

Add records a feedback round.

func (*Collector) All

func (c *Collector) All() []*Feedback

All returns a copy of every feedback round in insertion order.

func (*Collector) ByQuery

func (c *Collector) ByQuery(query string) []*Feedback

ByQuery returns all feedback rounds for a query, in insertion order.

func (*Collector) Count

func (c *Collector) Count() int

Count returns the number of recorded feedback rounds.

func (*Collector) IrrelevantFor

func (c *Collector) IrrelevantFor(query string) []string

IrrelevantFor returns the sorted union of not-relevant chunk IDs across all feedback rounds for a query.

func (*Collector) RelevantFor

func (c *Collector) RelevantFor(query string) []string

RelevantFor returns the sorted union of relevant chunk IDs across all feedback rounds for a query.

type Feedback

type Feedback struct {
	// ID is a unique identifier for this feedback round.
	ID string

	// Query is the original query text this feedback applies to.
	Query string

	// Time is when the feedback was recorded.
	Time time.Time

	// Comment is an optional free-form annotation from the reviewer.
	Comment string

	// Labels maps chunk ID to relevance label.
	Labels map[string]Label
}

Feedback captures one round of relevance feedback for a query: which retrieved chunks a human judged relevant or not relevant.

func NewFeedback

func NewFeedback(query string, labels map[string]Label) *Feedback

NewFeedback creates a Feedback with a generated ID, timestamp, and labels. A nil labels map is replaced with an empty one.

func (*Feedback) HasJudgment

func (f *Feedback) HasJudgment() bool

HasJudgment reports whether at least one relevant or not-relevant label exists.

func (*Feedback) Irrelevant

func (f *Feedback) Irrelevant() []string

Irrelevant returns the chunk IDs labeled as not relevant, sorted.

func (*Feedback) Relevant

func (f *Feedback) Relevant() []string

Relevant returns the chunk IDs labeled as relevant, sorted for determinism.

func (*Feedback) ToMetadata

func (f *Feedback) ToMetadata() map[string]core.Value

ToMetadata serializes the feedback into chunk-metadata-friendly values so it can be persisted on chunks or documents for future training or audit.

type Label

type Label int

Label is a human relevance judgment for a retrieved chunk.

const (
	// LabelUnlabeled means no judgment was recorded for the chunk.
	LabelUnlabeled Label = iota
	// LabelRelevant marks the chunk as relevant to the query.
	LabelRelevant
	// LabelNotRelevant marks the chunk as not relevant to the query.
	LabelNotRelevant
)

func (Label) String

func (l Label) String() string

String returns a human-readable label name.

type RelevanceFeedback

type RelevanceFeedback struct {
	// Searcher retrieves chunks by query embedding.
	Searcher VectorSearcher

	// Getter resolves chunk IDs to chunks (for their embeddings/content).
	Getter ChunkGetter

	// Embedder embeds query text.
	Embedder embedder.Embedder

	// Params configures Rocchio (vector form).
	Params RocchioParams

	// TermParams configures Rocchio (lexical form) for AdjustText.
	TermParams TermRocchioParams

	// BoostRelevant, when true, moves chunks the user marked relevant to the
	// front of the re-ranked results.
	BoostRelevant bool
}

RelevanceFeedback adjusts queries based on user relevance feedback using the Rocchio algorithm. It combines a vector search capability (e.g., an index.Index), chunk access (e.g., a store.Store), and an embedder so the full retrieve → feedback → re-rank loop can run.

func NewRelevanceFeedback

func NewRelevanceFeedback(searcher VectorSearcher, getter ChunkGetter, e embedder.Embedder) *RelevanceFeedback

NewRelevanceFeedback creates a RelevanceFeedback with standard Rocchio parameters and relevant-boosting enabled.

func (*RelevanceFeedback) AdjustText

func (r *RelevanceFeedback) AdjustText(query string, relevantTexts, irrelevantTexts []string) string

AdjustText returns a textually expanded query for keyword-based retrieval, using lexical Rocchio over the query and the relevant/not-relevant chunk texts.

func (*RelevanceFeedback) AdjustVector

func (r *RelevanceFeedback) AdjustVector(query []float32, relevant, irrelevant [][]float32) []float32

AdjustVector returns a Rocchio-adjusted query embedding given the original query embedding and the embeddings of the relevant and not-relevant chunks.

func (*RelevanceFeedback) ExpandAndRetrieve

func (r *RelevanceFeedback) ExpandAndRetrieve(ctx context.Context, query string, fb *Feedback, topK int) ([]index.SearchResult, []float32, error)

ExpandAndRetrieve performs one round of iterative relevance feedback:

  1. embed the query,
  2. gather the embeddings of the chunks judged relevant / not relevant,
  3. apply Rocchio to obtain an adjusted query vector,
  4. re-retrieve with the adjusted vector,
  5. re-rank so user-marked relevant chunks come first (when BoostRelevant).

It returns the re-ranked results and the adjusted query vector.

type RocchioParams

type RocchioParams struct {
	// Alpha is the weight of the original query vector. 0 keeps the query from
	// contributing.
	Alpha float64

	// Beta is the weight of the mean relevant-document vector. 0 disables the
	// positive shift.
	Beta float64

	// Gamma is the weight of the mean not-relevant-document vector (subtracted).
	// 0 disables the negative shift.
	Gamma float64

	// Normalize L2-normalizes the resulting vector so it stays comparable with
	// stored embeddings under cosine similarity.
	Normalize bool
}

RocchioParams configures the classic Rocchio query expansion algorithm.

Rocchio:  Q' = Alpha*Q + Beta*mean(relevant) - Gamma*mean(irrelevant)

Defaults (via DefaultRocchioParams): Alpha=1, Beta=0.5, Gamma=0.3, Normalize=true.

func DefaultRocchioParams

func DefaultRocchioParams() RocchioParams

DefaultRocchioParams returns the standard Rocchio weights.

type TermRocchioParams

type TermRocchioParams struct {
	// Beta is the weight of terms from relevant documents.
	Beta float64

	// Gamma is the weight of terms from not-relevant documents (subtracted).
	Gamma float64

	// MaxTerms limits how many terms are kept in the expanded query. 0 keeps
	// every term with a positive weight.
	MaxTerms int
}

TermRocchioParams configures lexical (term-frequency) Rocchio expansion.

func DefaultTermRocchioParams

func DefaultTermRocchioParams() TermRocchioParams

DefaultTermRocchioParams returns standard lexical Rocchio weights.

type VectorSearcher

type VectorSearcher interface {
	// Search finds the most similar chunks to the given query embedding.
	Search(ctx context.Context, query []float32, opts index.SearchOptions) ([]index.SearchResult, error)
}

VectorSearcher searches a chunk index by embedding vector. It is satisfied by index.Index.

Jump to

Keyboard shortcuts

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