Music Streaming: Playlist Generation
Typed co-listen edges plus MMR diversity over a graph frontier
What if cross-genre bridges came from real co-listen edges, and diversity came from MMR over that frontier?
Good playlists balance two things: songs you will love and songs you did not know you would love. The first is relevance. The second is diversity. SwarnDB gives you both from one store, by separating where the structure comes from from how the ranking is shaped.
The structure is explicit. Cross-genre bridges are typed edges you author: PLAYLIST_WITH and CO_LISTEN, derived from real editorial playlists and listening sessions, where a jazz track and an electronic track sit side by side because people actually played them together. You bulk-import these from your playlist and session logs, and each edge carries provenance back to the source. This is the graph; it is authored, not inferred from audio similarity.
The shaping is vector math. MMR (Maximal Marginal Relevance) runs server-side over a candidate set and trades relevance against diversity so a playlist does not collapse into twenty near-identical tracks. On a hybrid collection, MMR runs over the frontier a graph query produced: seed by audio or lyric similarity, traverse the co-listen edges across genres, then diversify with MMR. Structure decides who is reachable; MMR decides the mix.
The Traditional Approach
The fragmented stack most teams cobble together today.
- Co-listen edges live in one store, audio embeddings in another, joined in app code
- Two separate ML models (collaborative + content-based) to train and maintain
- Diversity logic is custom code, fragile, and hard to tune
- Cross-genre bridges need a graph engine bolted onto the audio similarity results
- No single audit trail tying a co-listen edge back to the sessions that produced it
- Keeping the playlist graph in sync with the catalog is a standing ETL job
The SwarnDB Approach
One database. Every capability built in.
Typed Co-Listen Edges
PLAYLIST_WITH and CO_LISTEN are explicit, typed edges derived from editorial playlists and listening sessions. A jazz-to-electronic bridge is an edge you authored because people played those tracks together, not a similarity score over audio features.
MMR Diversity (Vector Math)
Maximal Marginal Relevance runs server-side and balances relevance against diversity. The lambda parameter dials the mix. On a hybrid collection, MMR runs over the frontier a graph query produced.
Graph Traversal
Walk the typed co-listen edges to reach tracks genuinely played alongside the seed, across genres. Single-hop, k-hop, and shortest path over the edges you authored.
Centroid Computation
Find the center of gravity of a playlist or taste profile. Use centroid vectors to understand what defines a listener's taste and track how it evolves over time.
- Cross-genre bridges are authored co-listen edges, not audio-similarity guesses
- Co-listen edges and audio vectors in one store, one object per track
- Diversity from MMR over a graph-built frontier, in one composable query
- Bulk-import playlist and session edges from CSV or JSONL with provenance
- Taste evolution tracked via centroids and drift, server-side
- No second graph store and no ETL job to keep the playlist graph aligned
01. Structure, Then Diversity
A great playlist comes from two decisions made in order: who is reachable, and how to mix them. SwarnDB makes the first decision with the graph and the second with vector math.
Reachability is the typed co-listen graph. Seed a candidate set by audio or lyric similarity, then traverse the PLAYLIST_WITH and CO_LISTEN edges to reach tracks genuinely played alongside the seed, including across genres. This is the cross-genre bridge, and it is authored: the edges come from real playlists and listening sessions, with provenance, not from an audio-similarity threshold.
The mix is MMR. Over the frontier the graph produced, MMR (Maximal Marginal Relevance) selects tracks that are relevant to the seed yet different from each other, so the playlist does not collapse into twenty near-identical songs. The lambda parameter dials the balance: higher lambda leans toward relevance, lower lambda leans toward variety. Structure decides who is in the room; MMR decides the lineup.
Key insight:The graph decides who is reachable through real co-listen edges; MMR decides the mix. Structure first, diversity second.
# Structure first: reach co-listened tracks via typed edges.
frontier = (
client.graph.query("songs")
.vector_similar(seed_track_embedding, k=50)
.traverse("CO_LISTEN", direction="outgoing")
.vector_rank(seed_track_embedding, k=40)
.return_nodes()
)
# Then shape the mix with MMR over that frontier (vector math).
playlist = client.math.mmr("songs",
query_vector=seed_track_embedding,
k=20,
lambda_param=0.7 # Relevance vs diversity
)
# Cross-genre because the co-listen edges are; varied because MMR is.02. Cross-Genre Bridges
Genre labels are human conventions, and listeners cross them constantly. A jazz track with heavy synthesizer textures gets played in the same session as ambient electronic; a classical arrangement of a pop song sits in playlists next to both. Those crossings are real events, and they are exactly what you want to recommend across.
In SwarnDB the crossings are typed edges you author. Roll up your editorial playlists and listening sessions into PLAYLIST_WITH and CO_LISTEN edges: a jazz track linked to an electronic track because people genuinely played them together, with provenance back to the session or playlist that produced the edge. This is the bridge, and it is grounded in behavior, not in an audio-feature similarity score.
Traversal makes the bridges actionable. From a jazz seed, walk the co-listen edges one hop to reach tracks played alongside it, another hop to reach their co-listened tracks, ranking the surviving frontier by relevance at the end. Each step follows a concrete, authored edge, so every cross-genre suggestion is explainable: you can show the listening sessions that connect the jazz seed to the ambient track it surfaced.
Key insight:A jazz-to-electronic bridge is an authored co-listen edge, grounded in real sessions, not an audio-similarity guess. Every bridge is explainable.
# Cross-genre bridges from authored co-listen edges
# Edges derived from playlists and sessions, then bulk-imported:
# client.graph.bulk_import_edges("songs", colisten_rows, format="csv")
bridges = (
client.graph.query("songs")
.vector_similar(jazz_track_embedding, k=30)
.traverse("CO_LISTEN", direction="outgoing")
.vector_rank(jazz_track_embedding, k=20)
.return_nodes()
)
# Results span genres because real listeners crossed them.
# Every bridge is explainable from the sessions behind its edge.
for node in bridges.nodes:
print(node.id, node.label)03. The Lambda Dial
Lambda is the most powerful parameter in shaping a playlist. It controls the tradeoff between comfort and discovery once the candidate set is fixed, and it does so with mathematical precision rather than ad-hoc rules. MMR runs server-side, so the dial is a single argument, not a custom diversity service.
At lambda=1.0, MMR reduces to pure relevance ranking. You get the most similar tracks to the seed. If the seed is a jazz piano trio, you get jazz piano trios. Technically tight, musically flat.
At lambda=0.0, MMR maximizes variety at the expense of relevance: each pick is chosen to differ as much as possible from what is already selected. From a jazz piano seed you might get tracks with nothing in common, diverse but incoherent as a playlist.
At lambda=0.7, the balance works. Early picks sit close to the seed and set the mood; as the playlist grows, MMR reaches further, pulling in tracks that share some musical character but bring variety. Different contexts call for different lambdas: a focus playlist wants high lambda (mostly similar, calming), a discovery playlist wants a lower lambda (more variety). One parameter, run over whatever candidate set you give it, including a graph-built frontier.
Key insight:Lambda is the relevance-versus-variety dial, applied to whatever candidate set you give MMR, including a graph-built frontier. One parameter, server-side.
# Lambda = 1.0: relevance only
same_vibe = client.math.mmr("songs",
query_vector=seed_embedding,
k=20, lambda_param=1.0
)
# Tightly similar tracks. Coherent but flat.
# Lambda = 0.7: the balanced mix
diverse = client.math.mmr("songs",
query_vector=seed_embedding,
k=20, lambda_param=0.7
)
# Relevant to the seed, but varied. A journey, not a loop.
# Lambda = 0.5: lean into variety
explorer = client.math.mmr("songs",
query_vector=seed_embedding,
k=20, lambda_param=0.5
)
# Context-specific lambdas
focus_playlist = client.math.mmr("songs",
query_vector=calm_seed, k=30, lambda_param=0.9
) # High relevance, calming
party_mix = client.math.mmr("songs",
query_vector=upbeat_seed, k=30, lambda_param=0.6
) # More variety, energetic04. Playlist Center of Gravity
Every playlist has a center of gravity, an abstract point in musical space that represents the playlist's overall character. If you could find a single song that best represents a playlist, what would it be? SwarnDB's centroid computation answers this question mathematically.
The centroid of a set of vectors is their average position in high-dimensional space. For a playlist, this means the centroid embedding captures the "average" musical characteristics of all songs in the playlist. Search for the nearest real song to this centroid, and you've found the most representative track, the song that best captures what this playlist "is about."
This has practical applications beyond novelty. Centroid-based comparison lets you measure how similar two playlists are. Compare a user's "Monday morning" playlist centroid to their "Friday night" playlist centroid, and you've quantified their musical range. Track the centroid of a user's recent listening over time, and you can detect taste drift, gradual shifts in musical preference that happen over weeks and months.
SwarnDB's drift detection formalizes this. Compare a user's current listening centroid to their historical centroid, and the drift score tells you how much their taste has changed. A high drift score might trigger a "Your taste is evolving, here are some new genres to explore" notification. A low drift score might suggest introducing more diversity. This kind of insight typically requires a dedicated analytics pipeline. In SwarnDB, it's a math operation.
Key insight:The centroid of a playlist is its musical identity. Track it over time and you can see taste evolving. No analytics pipeline needed.
# Find the center of gravity of a playlist
centroid = client.math.centroid("songs",
vector_ids=playlist_track_ids
)
# centroid.vector = average position in musical space
# Find the most representative track
representative = client.search.query("songs",
vector=centroid.vector, k=1
)
# The single song that best captures the playlist's vibe
# Track taste evolution over time
current_centroid = client.math.centroid("songs",
vector_ids=recent_listening_ids
)
historical_centroid = client.math.centroid("songs",
vector_ids=all_time_listening_ids
)
# Drift detection: how much has taste changed?
drift = client.math.drift("songs",
vector_id_a=current_centroid.id,
vector_id_b=historical_centroid.id
)
# drift.score > 0.3 = significant taste evolution
# drift.score < 0.1 = stable preferencesSwarnDB vs Traditional Stack
A side-by-side look at the traditional approach versus SwarnDB.
| Capability | Traditional Stack | SwarnDB |
|---|---|---|
| Diversity | Custom diversity service | MMR over a graph frontier, one call |
| Cross-Genre | Edges in a separate graph store | Typed CO_LISTEN edges, same engine |
| Edge source | Opaque model output | Authored from sessions, with provenance |
| Co-listen + audio | Two engines joined in app code | One hybrid query |
| Taste Evolution | Separate analytics pipeline | Built-in centroid and drift |
Key Metrics
The Code
Everything above, in a few lines of Python.
from swarndb import SwarnDBClient
client = SwarnDBClient(host="localhost", port=50051)
# Hybrid mode: audio/lyric vectors and a typed co-listen graph in one store.
client.collections.create(
"songs", dimension=512, distance_metric="cosine", mode="hybrid"
)
# Cross-genre bridges from authored co-listen edges (bulk-imported).
client.graph.bulk_import_edges("songs", colisten_rows, format="csv")
# Structure first: reach co-listened tracks via the typed graph.
frontier = (
client.graph.query("songs")
.vector_similar(seed_track_embedding, k=50)
.traverse("CO_LISTEN", direction="outgoing")
.vector_rank(seed_track_embedding, k=40)
.return_nodes()
)
# Then shape the mix with MMR (vector math).
playlist = client.math.mmr("songs",
query_vector=seed_track_embedding, k=20, lambda_param=0.7
)
# Track taste evolution: playlist centroid and drift.
centroid = client.math.centroid("songs", vector_ids=playlist_track_ids)
drift = client.math.drift("songs",
vector_id_a=current_centroid_id, vector_id_b=historical_centroid_id
)