Add vibe-aware playlists with CLAP text embeddings

Blend taste profile with text-embedded mood descriptions (e.g. "chill
ambient lo-fi") using pre-blended vector search against the existing
HNSW index. New optional `vibe` and `alpha` params on playlist generate
and recommendations endpoints. Backward compatible — no vibe = pure
taste profile (alpha=1.0).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-22 13:14:28 -06:00
parent 23fd0e9804
commit 1b739fbd20
9 changed files with 146 additions and 12 deletions

View File

@@ -1,5 +1,5 @@
from fastapi import APIRouter, Depends
from pydantic import BaseModel
from pydantic import BaseModel, Field
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
@@ -17,15 +17,31 @@ class GenerateRequest(BaseModel):
name: str | None = None
speaker_entity: str | None = None
auto_play: bool = False
vibe: str | None = None
alpha: float = Field(default=0.5, ge=0.0, le=1.0)
@router.post("/generate")
async def generate(req: GenerateRequest, session: AsyncSession = Depends(get_session)):
# Compute text embedding for vibe description
vibe_embedding = None
if req.vibe:
from haunt_fm.services.embedding import embed_text, is_model_loaded, load_model
if not is_model_loaded():
load_model()
vibe_embedding = embed_text(req.vibe)
# Force pure taste when no vibe provided (preserves current behavior)
alpha = req.alpha if req.vibe else 1.0
playlist = await generate_playlist(
session,
total_tracks=req.total_tracks,
known_pct=req.known_pct,
name=req.name,
vibe_embedding=vibe_embedding,
alpha=alpha,
vibe_text=req.vibe,
)
# Load playlist tracks with track info
@@ -58,6 +74,8 @@ async def generate(req: GenerateRequest, session: AsyncSession = Depends(get_ses
"name": playlist.name,
"total_tracks": playlist.total_tracks,
"known_pct": playlist.known_pct,
"vibe": playlist.vibe,
"alpha": playlist.alpha,
"tracks": track_list,
"auto_played": req.auto_play and req.speaker_entity is not None,
}