cacce61ddc
- 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>
167 lines
5.5 KiB
Go
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)
|
|
}
|