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 <[email protected]>
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
// Package musicgen is majordomo's canonical music-generation surface (full
|
||||
// songs from a text prompt, optionally with lyrics). Like imagegen/audio/
|
||||
// videogen/meshgen, it is a deliberately separate leaf contract from the llm
|
||||
// package (ADR-0021, following the ADR-0016→0020 lineage: functional
|
||||
// options, zero values = backend default, bytes-only I/O, Raw escape hatch).
|
||||
//
|
||||
// The first implementation is provider/llamaswap, which targets an
|
||||
// ACE-Step-1.5-style async job API reached through the /upstream
|
||||
// passthrough; Generate blocks (polling internally) so callers get the
|
||||
// imagegen-style one-call contract — bound it with a context deadline.
|
||||
package musicgen
|
||||
|
||||
import "context"
|
||||
|
||||
// Audio is one generated piece: raw encoded bytes plus a MIME type.
|
||||
type Audio struct {
|
||||
// Data is the encoded audio container.
|
||||
Data []byte
|
||||
|
||||
// MIME is the audio MIME type, e.g. "audio/mpeg".
|
||||
MIME string
|
||||
}
|
||||
|
||||
// Request is a music-generation request. Zero values mean "backend default".
|
||||
type Request struct {
|
||||
// Prompt describes the music (genre, mood, instrumentation, tempo...).
|
||||
Prompt string
|
||||
|
||||
// Lyrics are optional song lyrics for models that sing; "" =
|
||||
// instrumental or model-written lyrics, per backend behavior.
|
||||
Lyrics string
|
||||
|
||||
// DurationSeconds is the requested clip length; 0 = backend default.
|
||||
DurationSeconds int
|
||||
|
||||
// Format is the audio container ("mp3", "wav", "flac", "opus", "aac");
|
||||
// "" = backend default (mp3 on ACE-Step).
|
||||
Format string
|
||||
|
||||
// Steps is the number of inference steps; nil = backend default.
|
||||
Steps *int
|
||||
|
||||
// Seed fixes the RNG seed for reproducible output; nil = backend
|
||||
// default (random).
|
||||
Seed *int64
|
||||
}
|
||||
|
||||
// Result is the canonical music-generation result.
|
||||
type Result struct {
|
||||
// Audio is the generated piece.
|
||||
Audio Audio
|
||||
|
||||
// Raw is the provider-native response object (e.g. the job result with
|
||||
// bpm/keyscale metadata). May be nil.
|
||||
Raw any
|
||||
}
|
||||
|
||||
// Option mutates a Request before it is sent. Options passed to Generate are
|
||||
// applied to a copy of the request, so a Request value can be reused.
|
||||
type Option func(*Request)
|
||||
|
||||
// WithLyrics sets song lyrics.
|
||||
func WithLyrics(l string) Option { return func(r *Request) { r.Lyrics = l } }
|
||||
|
||||
// WithDuration sets the requested clip length in seconds.
|
||||
func WithDuration(seconds int) Option {
|
||||
return func(r *Request) { r.DurationSeconds = seconds }
|
||||
}
|
||||
|
||||
// WithFormat sets the audio container format.
|
||||
func WithFormat(f string) Option { return func(r *Request) { r.Format = f } }
|
||||
|
||||
// WithSteps overrides the number of inference steps.
|
||||
func WithSteps(n int) Option { return func(r *Request) { r.Steps = &n } }
|
||||
|
||||
// WithSeed fixes the RNG seed.
|
||||
func WithSeed(seed int64) Option { return func(r *Request) { r.Seed = &seed } }
|
||||
|
||||
// Apply returns a copy of the request with all options applied. Providers
|
||||
// call this once at the top of Generate.
|
||||
func (r Request) Apply(opts ...Option) Request {
|
||||
for _, opt := range opts {
|
||||
opt(&r)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Model generates music from text.
|
||||
type Model interface {
|
||||
// Generate renders the request as one audio clip. It blocks until the
|
||||
// backend finishes (or ctx expires) even when the backend is an async
|
||||
// job queue — polling is the provider's business, not the caller's.
|
||||
Generate(ctx context.Context, req Request, opts ...Option) (*Result, error)
|
||||
}
|
||||
|
||||
// ModelOption configures a Model at construction time. Reserved for future
|
||||
// per-model settings.
|
||||
type ModelOption func(*ModelConfig)
|
||||
|
||||
// ModelConfig carries per-model construction settings.
|
||||
type ModelConfig struct{}
|
||||
|
||||
// ApplyModelOptions folds options into a config.
|
||||
func ApplyModelOptions(opts []ModelOption) ModelConfig {
|
||||
var cfg ModelConfig
|
||||
for _, opt := range opts {
|
||||
opt(&cfg)
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
// Provider mints music models bound to one backend.
|
||||
type Provider interface {
|
||||
// Name is the registry identifier for the provider.
|
||||
Name() string
|
||||
|
||||
// MusicModel returns a Model bound to the given id (passed through to
|
||||
// the backend verbatim; no catalog validation).
|
||||
MusicModel(id string, opts ...ModelOption) (Model, error)
|
||||
}
|
||||
Reference in New Issue
Block a user