Files
majordomo/embeddings/embeddings.go
T
steve cacce61ddc feat: musicgen + embeddings/rerank surfaces (ADR-0021, ADR-0022)
- NEW musicgen leaf package: blocking Generate over ACE-Step's async job
  queue (release_task -> poll query_result -> fetch file, all via
  /upstream); tolerant envelope parsing, double-encoded result handled
- NEW embeddings leaf package: EmbedModel + RerankModel as separate mints
  (two server instances on the host, llama.cpp #20085); InstructedQuery
  helper for Qwen3-style query/document asymmetry
- provider/llamaswap: /v1/embeddings + /v1/rerank clients with strict
  validation (index-ordered vectors, count mismatch and out-of-range
  index are hard errors; rerank sorted descending, minimal parser)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 00:50:36 -04:00

167 lines
5.5 KiB
Go

// Package embeddings is majordomo's canonical text-embedding and reranking
// surface (ADR-0022, following the ADR-0016→0021 leaf-contract lineage).
// Two small halves, split like audio's Speech/Transcription so backends can
// implement either:
//
// - EmbedModel turns texts into dense vectors (/v1/embeddings-style).
// - RerankModel scores documents against a query with a cross-encoder
// (/v1/rerank-style) — usually a DIFFERENT backend model than the
// embedder, hence a separate mint.
//
// Instruction-aware embedders (Qwen3-Embedding et al.) want queries wrapped
// as "Instruct: {task}\nQuery: {query}" while documents go in bare;
// InstructedQuery encodes that so callers don't hand-roll (and silently
// degrade retrieval) at each site.
package embeddings
import "context"
// EmbedRequest is a batch embedding request. Inputs are embedded
// independently; the result vector order matches the input order.
type EmbedRequest struct {
// Inputs are the texts to embed. Required (at least one).
Inputs []string
}
// EmbedResult is the canonical embedding result.
type EmbedResult struct {
// Vectors holds one embedding per input, in input order. Backends
// normalize per their own convention (llama-server: Euclidean-normalized).
Vectors [][]float32
// Raw is the provider-native response object. May be nil.
Raw any
}
// EmbedOption mutates an EmbedRequest before it is sent. Reserved: the
// request shape is deliberately minimal today.
type EmbedOption func(*EmbedRequest)
// Apply returns a copy of the request with all options applied.
func (r EmbedRequest) Apply(opts ...EmbedOption) EmbedRequest {
for _, opt := range opts {
opt(&r)
}
return r
}
// InstructedQuery wraps a retrieval QUERY for instruction-aware embedding
// models. Documents must NOT be wrapped — the asymmetry is the point, and
// getting it wrong silently costs retrieval quality. An empty task uses the
// generic web-search instruction the reference model was trained with.
func InstructedQuery(task, query string) string {
if task == "" {
task = "Given a web search query, retrieve relevant passages that answer the query"
}
return "Instruct: " + task + "\nQuery: " + query
}
// EmbedModel embeds texts as dense vectors.
type EmbedModel interface {
// Embed returns one vector per input, in input order.
Embed(ctx context.Context, req EmbedRequest, opts ...EmbedOption) (*EmbedResult, error)
}
// EmbedModelOption configures an EmbedModel at construction time. Reserved
// for future per-model settings.
type EmbedModelOption func(*EmbedModelConfig)
// EmbedModelConfig carries per-model construction settings.
type EmbedModelConfig struct{}
// ApplyEmbedModelOptions folds options into a config.
func ApplyEmbedModelOptions(opts []EmbedModelOption) EmbedModelConfig {
var cfg EmbedModelConfig
for _, opt := range opts {
opt(&cfg)
}
return cfg
}
// EmbedProvider mints embedding models bound to one backend.
type EmbedProvider interface {
// Name is the registry identifier for the provider.
Name() string
// EmbedModel returns an EmbedModel bound to the given id (passed through
// to the backend verbatim; no catalog validation).
EmbedModel(id string, opts ...EmbedModelOption) (EmbedModel, error)
}
// RerankRequest scores documents against a query.
type RerankRequest struct {
// Query is the search query. Required.
Query string
// Documents are the candidate texts to score. Required (at least one).
Documents []string
// TopN limits how many results the backend returns; 0 = all.
TopN int
}
// RerankItem is one scored document.
type RerankItem struct {
// Index is the document's position in the request's Documents slice.
Index int
// Score is the backend's relevance score (higher = more relevant).
// Scales are model-specific — compare within one response only.
Score float64
}
// RerankResult is the canonical rerank result, sorted by descending Score.
type RerankResult struct {
// Results are the scored documents (top-N when the request bounded it).
Results []RerankItem
// Raw is the provider-native response object. May be nil.
Raw any
}
// RerankOption mutates a RerankRequest before it is sent.
type RerankOption func(*RerankRequest)
// WithTopN limits how many results the backend returns.
func WithTopN(n int) RerankOption { return func(r *RerankRequest) { r.TopN = n } }
// Apply returns a copy of the request with all options applied.
func (r RerankRequest) Apply(opts ...RerankOption) RerankRequest {
for _, opt := range opts {
opt(&r)
}
return r
}
// RerankModel scores documents against a query with a cross-encoder.
type RerankModel interface {
// Rerank returns scored documents sorted by descending relevance.
Rerank(ctx context.Context, req RerankRequest, opts ...RerankOption) (*RerankResult, error)
}
// RerankModelOption configures a RerankModel at construction time. Reserved
// for future per-model settings.
type RerankModelOption func(*RerankModelConfig)
// RerankModelConfig carries per-model construction settings.
type RerankModelConfig struct{}
// ApplyRerankModelOptions folds options into a config.
func ApplyRerankModelOptions(opts []RerankModelOption) RerankModelConfig {
var cfg RerankModelConfig
for _, opt := range opts {
opt(&cfg)
}
return cfg
}
// RerankProvider mints rerank models bound to one backend.
type RerankProvider interface {
// Name is the registry identifier for the provider.
Name() string
// RerankModel returns a RerankModel bound to the given id (passed
// through to the backend verbatim; no catalog validation).
RerankModel(id string, opts ...RerankModelOption) (RerankModel, error)
}